#!/usr/bin/env ruby

# $Id: pe.rb 10036 2010-08-18 04:39:38Z jduck $

require 'rex/image_source'
require 'rex/peparsey/exceptions'
require 'rex/peparsey/pebase'
require 'rex/peparsey/section'
require 'rex/struct2'

module Rex
module PeParsey
class Pe < PeBase

	def initialize(isource)

		#
		# DOS Header
		#
		# Parse the initial dos header, starting at the file beginning
		#
		offset = 0
		dos_header = self.class._parse_dos_header(isource.read(offset, IMAGE_DOS_HEADER_SIZE))

		#
		# File Header
		#
		# If there is going to be a PE, the dos header tells us where to find it
		# So now we try to parse the file (pe) header
		#
		offset += dos_header.e_lfanew

		# most likely an invalid e_lfanew...
		if offset > isource.size
			raise FileHeaderError, "e_lfanew looks invalid", caller
		end

		file_header = self.class._parse_file_header(isource.read(offset, IMAGE_FILE_HEADER_SIZE))

		#
		# Optional Header
		#
		# After the file header, we find the optional header.  Right now
		# we require a optional header.  Despite it's name, all binaries
		# that we are interested in should have one.  We need this
		# header for a lot of stuff, so we die without it...
		#
		offset += IMAGE_FILE_HEADER_SIZE
		optional_header = self.class._parse_optional_header(
		  isource.read(offset, file_header.SizeOfOptionalHeader)
		)

		if !optional_header
			raise OptionalHeaderError, "No optional header!", caller
		end

		base = optional_header.ImageBase

		#
		# Section Headers
		#
		# After the optional header should be the section headers.
		# We know how many there should be from the file header...
		#
		offset += file_header.SizeOfOptionalHeader

		num_sections = file_header.NumberOfSections
		section_headers = self.class._parse_section_headers(
		  isource.read(offset, IMAGE_SIZEOF_SECTION_HEADER * num_sections)
		)

		#
		# End of Headers
		#
		# After the section headers (which are padded to FileAlignment)
		# we should find the section data, described by the section
		# headers...
		#
		# So this is the end of our header data, lets store this
		# in an image source for possible access later...
		#
		offset += IMAGE_SIZEOF_SECTION_HEADER * num_sections
		offset = self.class._align_offset(offset, optional_header.FileAlignment)

		header_section = Section.new(isource.subsource(0, offset), 0, nil)

		#
		# Sections
		#
		# So from here on out should be section data, and then any
		# trailing data (like authenticode and stuff I think)
		#

		sections = [ ]

		section_headers.each do |section_header|

			rva         = section_header.VirtualAddress
			size        = section_header.SizeOfRawData
			file_offset = section_header.PointerToRawData

			sections << Section.new(
			  isource.subsource(file_offset, size),
			  rva,
			  section_header
			)
		end



		#
		# Save the stuffs!
		#
		# We have parsed enough to load the file up here, now we just
		# save off all of the structures and data... We will
		# save our fake header section, the real sections, etc.
		#

		#
		# These should not be accessed directly
		#

		self._isource          = isource

		self._dos_header       = dos_header
		self._file_header      = file_header
		self._optional_header  = optional_header
		self._section_headers  = section_headers

		self.image_base        = base
		self.sections          = sections
		self.header_section    = header_section

		self._config_header    = _parse_config_header()
		self._tls_header       = _parse_tls_header()

		# These can be accessed directly
		self.hdr               = HeaderAccessor.new
		self.hdr.dos           = self._dos_header
		self.hdr.file          = self._file_header
		self.hdr.opt           = self._optional_header
		self.hdr.sections      = self._section_headers
		self.hdr.config        = self._config_header
		self.hdr.tls           = self._tls_header
		self.hdr.exceptions    = self._exception_header

		# We load the exception directory last as it relies on hdr.file to be created above.
		self._exception_header = _load_exception_directory()
	end

	#
	# Return everything that's going to be mapped in the process
	# and accessable.  This should include all of the sections
	# and our "fake" section for the header data...
	#
	def all_sections
		[ header_section ] + sections
	end

	#
	# Returns true if this binary is for a 64-bit architecture.
	#
	def ptr_64?
		[
			IMAGE_FILE_MACHINE_IA64,
			IMAGE_FILE_MACHINE_ALPHA64,
			IMAGE_FILE_MACHINE_AMD64
		].include?(self._file_header.Machine)
	end

	#
	# Returns true if this binary is for a 32-bit architecture.
	# This check does not take into account 16-bit binaries at the moment.
	#
	def ptr_32?
		ptr_64? == false
	end

	#
	# Converts a virtual address to a string representation based on the
	# underlying architecture.
	#
	def ptr_s(va)
		(ptr_32?) ? ("0x%.8x" % va) : ("0x%.16x" % va)
	end

	#
	# Converts a file offset into a virtual address
	#
	def file_offset_to_va(offset)
		image_base + file_offset_to_rva(offset)
	end

	#
	# Read raw bytes from the specified offset in the underlying file
	#
	# NOTE: You should pass raw file offsets into this, not offsets from
	# the beginning of the section. If you need to read from within a 
	# section, add section.file_offset prior to passing the offset in.
	#
	def read(offset, len)
		_isource.read(offset, len)
	end

	def size
		_isource.size
	end
	def length
		_isource.size
	end

end end end
