Ruby解析Windows PE文件

纯属业余分析一些应用程序的需要,特意去了解了下Windows下的PE文件格式,相比MSDN的其它内容,PE文件规范文件的部分描述算是相当的晦涩了。好记性不如烂笔头,搭好框架后就此记录,以便日后抄袭。代码解决了导入和导出的部分,其余部分日后再作添加和完善,可以继续利用Ruby动态语言的优势、魔幻般的语法糖和强悍的元编程能力构造更便利的代码。

#encoding:gbk

require "delegate"

raise RuntimeError,"This program cannot be run in platform #{RUBY_PLATFORM}" if /mswin|mingw/ !~ RUBY_PLATFORM

#Add convenient method to buildin classes
class Fixnum
	#Byte
	def to_sb
		s = StringIO.new
		s.printf('%02x',self)
		s.string
	end
	#Word
	def to_sw
		s = StringIO.new
		s.printf('%04x',self)
		s.string
	end
	#Double Word
	def to_sd
		s = StringIO.new
		s.printf('%08x',self)
		s.string
	end	
	#Test specified bit field
	def test_b(n)
		self & 2 ** n != 0
	end
	#Masked data
	def part_b(a)
		result = 0
		if a.class == Array or a.class == Range
			a.each do |item|
				result |= self & 2 ** item
			end
		end
		result
	end
	#Read little endian data
	def Fixnum.read(n,bytes)
		bytes.slice(0,n).reverse.collect{|item| item.to_sb}.join('').hex
	end
	#Read little endian data from fixed location
	def Fixnum.readn(ind_s,nlen,bytes)
		tmpbytes = bytes.slice(ind_s,nlen)
		Fixnum.read(nlen,tmpbytes)
	end
end

class Bignum
	#Byte
	def to_sb
		s = StringIO.new
		s.printf('%02x',self)
		s.string
	end
	#Word
	def to_sw
		s = StringIO.new
		s.printf('%04x',self)
		s.string
	end
	#Double Word
	def to_sd
		s = StringIO.new
		s.printf('%08x',self)
		s.string
	end
	#Double Long Long
	def to_ll
		s = StringIO.new
		s.printf('%016x',self)
		s.string
	end
	#Test specified bit field
	def test_b(n)
		self & 2 ** n != 0
	end
	#Masked data
	def part_b(a)
		result = 0
		if a.class == Array or a.class == Range
			a.each do |item|
				result |= self & 2 ** item
			end
		end
		result
	end
end

class Array
	def to_hex
		s = StringIO.new
		self.each do |item|
			s.print item.to_sb
		end
		s.string
	end

	def to_num
		to_hex.hex
	end

	def to_lenum
		s = StringIO.new
		self.reverse.each do |item|
			s.print item.to_sb
		end
		s.string.hex
	end

	def to_repl
		self.select{|c| c != 0}.pack('C*')
	end
end

module PE
	#Image signature
	IMAGE_DOS_SIGNATURE = 0x4D5A      #MZ
	IMAGE_NT_SIGNATURE = 0x50450000  #PE00

	#Machine types
	IMAGE_FILE_MACHINE_UNKNOWN	=           0
	IMAGE_FILE_MACHINE_I386	=              0x014c  # Intel 386.
	IMAGE_FILE_MACHINE_R3000	=             0x0162  # MIPS little-endian, 0x160 big-endian
	IMAGE_FILE_MACHINE_R4000	=             0x0166  # MIPS little-endian
	IMAGE_FILE_MACHINE_R10000	=            0x0168  # MIPS little-endian
	IMAGE_FILE_MACHINE_WCEMIPSV2	=         0x0169  # MIPS little-endian WCE v2
	IMAGE_FILE_MACHINE_ALPHA	=             0x0184  # Alpha_AXP
	IMAGE_FILE_MACHINE_SH3	=               0x01a2  # SH3 little-endian
	IMAGE_FILE_MACHINE_SH3DSP	=            0x01a3
	IMAGE_FILE_MACHINE_SH3E	=              0x01a4  # SH3E little-endian
	IMAGE_FILE_MACHINE_SH4	=               0x01a6  # SH4 little-endian
	IMAGE_FILE_MACHINE_SH5	=               0x01a8  # SH5
	IMAGE_FILE_MACHINE_ARM	=               0x01c0  # ARM Little-Endian
	IMAGE_FILE_MACHINE_THUMB	=             0x01c2  # ARM Thumb/Thumb-2 Little-Endian
	IMAGE_FILE_MACHINE_ARMNT	=             0x01c4  # ARM Thumb-2 Little-Endian
	IMAGE_FILE_MACHINE_AM33	=              0x01d3
	IMAGE_FILE_MACHINE_POWERPC	=           0x01F0  # IBM PowerPC Little-Endian
	IMAGE_FILE_MACHINE_POWERPCFP	=         0x01f1
	IMAGE_FILE_MACHINE_IA64	=              0x0200  # Intel 64
	IMAGE_FILE_MACHINE_MIPS16	=            0x0266  # MIPS
	IMAGE_FILE_MACHINE_ALPHA64	=           0x0284  # ALPHA64
	IMAGE_FILE_MACHINE_MIPSFPU	=           0x0366  # MIPS
	IMAGE_FILE_MACHINE_MIPSFPU16	=         0x0466  # MIPS
	IMAGE_FILE_MACHINE_AXP64	=             IMAGE_FILE_MACHINE_ALPHA64
	IMAGE_FILE_MACHINE_TRICORE	=           0x0520  # Infineon
	IMAGE_FILE_MACHINE_CEF	=               0x0CEF
	IMAGE_FILE_MACHINE_EBC	=               0x0EBC  # EFI Byte Code
	IMAGE_FILE_MACHINE_AMD64	=             0x8664  # AMD64 (K8)
	IMAGE_FILE_MACHINE_M32R	=              0x9041  # M32R little-endian
	IMAGE_FILE_MACHINE_CEE	=               0xC0EE

	#Characteristics
	IMAGE_FILE_RELOCS_STRIPPED	=           0x0001  # Relocation info stripped from file.
	IMAGE_FILE_EXECUTABLE_IMAGE	=          0x0002  # File is executable  (i.e. no unresolved external references).
	IMAGE_FILE_LINE_NUMS_STRIPPED	=        0x0004  # Line nunbers stripped from file.
	IMAGE_FILE_LOCAL_SYMS_STRIPPED	=       0x0008  # Local symbols stripped from file.
	IMAGE_FILE_AGGRESIVE_WS_TRIM	=         0x0010  # Aggressively trim working set
	IMAGE_FILE_LARGE_ADDRESS_AWARE	=       0x0020  # App can handle >2gb addresses
	IMAGE_FILE_BYTES_REVERSED_LO	=         0x0080  # Bytes of machine word are reversed.
	IMAGE_FILE_32BIT_MACHINE	=             0x0100  # 32 bit word machine.
	IMAGE_FILE_DEBUG_STRIPPED	=            0x0200  # Debugging info stripped from file in .DBG file
	IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP	=   0x0400  # If Image is on removable media, copy and run from the swap file.
	IMAGE_FILE_NET_RUN_FROM_SWAP	=         0x0800  # If Image is on Net, copy and run from the swap file.
	IMAGE_FILE_SYSTEM	=                    0x1000  # System File.
	IMAGE_FILE_DLL	=                       0x2000  # File is a DLL.
	IMAGE_FILE_UP_SYSTEM_ONLY	=            0x4000  # File should only be run on a UP machine
	IMAGE_FILE_BYTES_REVERSED_HI	=         0x8000  # Bytes of machine word are reversed.

	# Subsystem Values
	IMAGE_SUBSYSTEM_UNKNOWN       =       0   # Unknown subsystem.
	IMAGE_SUBSYSTEM_NATIVE        =       1   # Image doesn't require a subsystem.
	IMAGE_SUBSYSTEM_WINDOWS_GUI   =       2   # Image runs in the Windows GUI subsystem.
	IMAGE_SUBSYSTEM_WINDOWS_CUI   =       3   # Image runs in the Windows character subsystem.
	IMAGE_SUBSYSTEM_OS2_CUI       =       5   # image runs in the OS/2 character subsystem.
	IMAGE_SUBSYSTEM_POSIX_CUI     =       7   # image runs in the Posix character subsystem.
	IMAGE_SUBSYSTEM_NATIVE_WINDOWS   =    8   # image is a native Win9x driver.
	IMAGE_SUBSYSTEM_WINDOWS_CE_GUI   =    9   # Image runs in the Windows CE subsystem.
	IMAGE_SUBSYSTEM_EFI_APPLICATION   =   10  #
	IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11   #
	IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER  = 12  #
	IMAGE_SUBSYSTEM_EFI_ROM       =       13
	IMAGE_SUBSYSTEM_XBOX          =       14
	IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16

	# DllCharacteristics Entries
	#IMAGE_LIBRARY_PROCESS_INIT      =      0x0001     # Reserved.
	#IMAGE_LIBRARY_PROCESS_TERM      =      0x0002     # Reserved.
	#IMAGE_LIBRARY_THREAD_INIT       =      0x0004     # Reserved.
	#IMAGE_LIBRARY_THREAD_TERM       =      0x0008     # Reserved.
	IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA  =  0x0020  # Image can handle a high entropy 64-bit virtual address space.
	IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040     # DLL can move.
	IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY  =  0x0080     # Code Integrity Image
	IMAGE_DLLCHARACTERISTICS_NX_COMPAT  =  0x0100     # Image is NX compatible
	IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200     # Image understands isolation and doesn't want it
	IMAGE_DLLCHARACTERISTICS_NO_SEH   =    0x0400     # Image does not use SEH.  No SE handler may reside in this image
	IMAGE_DLLCHARACTERISTICS_NO_BIND  =    0x0800     # Do not bind this image.
	IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000     # Image should execute in an AppContainer
	IMAGE_DLLCHARACTERISTICS_WDM_DRIVER =  0x2000     # Driver uses WDM model
	IMAGE_DLLCHARACTERISTICS_GUARD_CF   =  0x4000     # Image supports Control Flow Guard.
	IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE  =   0x8000

	# Directory Entries
	IMAGE_DIRECTORY_ENTRY_EXPORT	=	0	# Export Directory
	IMAGE_DIRECTORY_ENTRY_IMPORT	=	1	# Import Directory
	IMAGE_DIRECTORY_ENTRY_RESOURCE	=	2	# Resource Directory
	IMAGE_DIRECTORY_ENTRY_EXCEPTION	=	3	# Exception Directory
	IMAGE_DIRECTORY_ENTRY_SECURITY	=	4	# Security Directory
	IMAGE_DIRECTORY_ENTRY_BASERELOC	=	5	# Base Relocation Table
	IMAGE_DIRECTORY_ENTRY_DEBUG	=	6	# Debug Directory
	#IMAGE_DIRECTORY_ENTRY_COPYRIGHT	=	7	# (X86 usage)
	IMAGE_DIRECTORY_ENTRY_ARCHITECTURE	=	7	# Architecture Specific Data
	IMAGE_DIRECTORY_ENTRY_GLOBALPTR	=	8	# RVA of GP
	IMAGE_DIRECTORY_ENTRY_TLS	=	9	# TLS Directory
	IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG	=	10	# Load Configuration Directory
	IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT	=	11	# Bound Import Directory in headers
	IMAGE_DIRECTORY_ENTRY_IAT	=	12	# Import Address Table
	IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT	=	13	# Delay Load Import Descriptors
	IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR	=	14	# COM Runtime descriptor

	# Section characteristics.
	#
	#      IMAGE_SCN_TYPE_REG	=	0x00000000	# Reserved.
	#      IMAGE_SCN_TYPE_DSECT	=	0x00000001	# Reserved.
	#      IMAGE_SCN_TYPE_NOLOAD	=	0x00000002	# Reserved.
	#      IMAGE_SCN_TYPE_GROUP	=	0x00000004	# Reserved.
	IMAGE_SCN_TYPE_NO_PAD	=	0x00000008	# Reserved.
	#      IMAGE_SCN_TYPE_COPY	=	0x00000010	# Reserved.

	IMAGE_SCN_CNT_CODE	=	0x00000020	# Section contains code.
	IMAGE_SCN_CNT_INITIALIZED_DATA	=	0x00000040	# Section contains initialized data.
	IMAGE_SCN_CNT_UNINITIALIZED_DATA	=	0x00000080	# Section contains uninitialized data.

	IMAGE_SCN_LNK_OTHER	=	0x00000100	# Reserved.
	IMAGE_SCN_LNK_INFO	=	0x00000200	# Section contains comments or some other type of information.
	#      IMAGE_SCN_TYPE_OVER	=	0x00000400	# Reserved.
	IMAGE_SCN_LNK_REMOVE	=	0x00000800	# Section contents will not become part of image.
	IMAGE_SCN_LNK_COMDAT	=	0x00001000	# Section contents comdat.
	#	=	0x00002000	# Reserved.
	#      IMAGE_SCN_MEM_PROTECTED - Obsolete	=	0x00004000	
	IMAGE_SCN_NO_DEFER_SPEC_EXC	=	0x00004000	# Reset speculative exceptions handling bits in the TLB entries for this section.
	IMAGE_SCN_GPREL	=	0x00008000	# Section content can be accessed relative to GP
	IMAGE_SCN_MEM_FARDATA	=	0x00008000	
	#      IMAGE_SCN_MEM_SYSHEAP  - Obsolete	=	0x00010000	
	IMAGE_SCN_MEM_PURGEABLE	=	0x00020000	
	IMAGE_SCN_MEM_16BIT	=	0x00020000	
	IMAGE_SCN_MEM_LOCKED	=	0x00040000	
	IMAGE_SCN_MEM_PRELOAD	=	0x00080000	

	IMAGE_SCN_ALIGN_1BYTES	=	0x00100000	#
	IMAGE_SCN_ALIGN_2BYTES	=	0x00200000	#
	IMAGE_SCN_ALIGN_4BYTES	=	0x00300000	#
	IMAGE_SCN_ALIGN_8BYTES	=	0x00400000	#
	IMAGE_SCN_ALIGN_16BYTES	=	0x00500000	# Default alignment if no others are specified.
	IMAGE_SCN_ALIGN_32BYTES	=	0x00600000	#
	IMAGE_SCN_ALIGN_64BYTES	=	0x00700000	#
	IMAGE_SCN_ALIGN_128BYTES	=	0x00800000	#
	IMAGE_SCN_ALIGN_256BYTES	=	0x00900000	#
	IMAGE_SCN_ALIGN_512BYTES	=	0x00A00000	#
	IMAGE_SCN_ALIGN_1024BYTES	=	0x00B00000	#
	IMAGE_SCN_ALIGN_2048BYTES	=	0x00C00000	#
	IMAGE_SCN_ALIGN_4096BYTES	=	0x00D00000	#
	IMAGE_SCN_ALIGN_8192BYTES	=	0x00E00000	#
	# Unused	=	0x00F00000	
	IMAGE_SCN_ALIGN_MASK	=	0x00F00000	

	IMAGE_SCN_LNK_NRELOC_OVFL	=	0x01000000	# Section contains extended relocations.
	IMAGE_SCN_MEM_DISCARDABLE	=	0x02000000	# Section can be discarded.
	IMAGE_SCN_MEM_NOT_CACHED	=	0x04000000	# Section is not cachable.
	IMAGE_SCN_MEM_NOT_PAGED	=	0x08000000	# Section is not pageable.
	IMAGE_SCN_MEM_SHARED	=	0x10000000	# Section is shareable.
	IMAGE_SCN_MEM_EXECUTE	=	0x20000000	# Section is executable.
	IMAGE_SCN_MEM_READ	=	0x40000000	# Section is readable.
	IMAGE_SCN_MEM_WRITE	=	0x80000000	# Section is writeable.

	#
	# TLS Characteristic Flags
	#
	IMAGE_SCN_SCALE_INDEX	=	0x00000001	# Tls index is scaled

	#Optional magic number
	IMAGE_NT_OPTIONAL_HDR32_MAGIC   =   0x10b
	IMAGE_NT_OPTIONAL_HDR64_MAGIC   =   0x20b
	IMAGE_ROM_OPTIONAL_HDR_MAGIC    =   0x107

	#Max size of directory entries
	IMAGE_NUMBEROF_DIRECTORY_ENTRIES  =  16

	#Section header short name max length
	IMAGE_SIZEOF_SHORT_NAME       =       8

	$TYPE_CLASS_INFORMATION = Hash.new
	$UN_REGISTER_FLAG = true

	class KeyAddress < DelegateClass(Hash)
		def initialize(init = nil)
			content = Hash.new
			fixed = false
			if init.class == Hash
				content.merge!(init)
				fixed = true
			end
			yield content,fixed if block_given?
			super(content)
			self.keys.each do |k|
				add_method(k)
			end
		end

		def add_item(k,v)
			self[k] = v
			add_method(k)
		end

		def add_method(k)
			KeyAddress.class_eval <<-EOF
				def #{k.downcase}
					self['#{k}']
				end
			EOF
		end
	end

	class TypeInfo
		attr_accessor :type,:cls,:nsize,:scheme
		def initialize(type,cls,scheme = nil,nsize = 0)
			@type = type
			@cls = cls
			@scheme = scheme
			if scheme.nil?
				@nsize = nsize
			else
				nsize = 0
				@scheme.each do |n,v|
					if v =~ /\[[0-9]+\]/
						pattern = Regexp.new(/(.*)\[([0-9]+)\]/)
						m = pattern.match(v)
						if not $TYPE_CLASS_INFORMATION.has_key?(m[1])
							raise RuntimeError,"Type [#{m[1]}] not found in list of registered."
						end
						nsize += $TYPE_CLASS_INFORMATION[m[1]].nsize * instance_eval(m[2])
					else
						if not $TYPE_CLASS_INFORMATION.has_key?(v)
							raise RuntimeError,"Type [#{v}] not found in list of registered."
						end
						nsize += $TYPE_CLASS_INFORMATION[v].nsize
					end
				end
				@nsize = nsize
			end
			$TYPE_CLASS_INFORMATION[@type] = self
		end

		def TypeInfo.fixup(obj,bytes)
			nseek = 0
			if obj.class == String
				nsize = $TYPE_CLASS_INFORMATION[obj].nsize
				obj = Fixnum.read(nsize,bytes)
				nseek += nsize
			else
				obj.scheme.each do |k,v|
					if v =~ /\[[0-9]+\]/
						pattern = Regexp.new(/(.*)\[([0-9]+)\]/)
						m = pattern.match(v)
						n = obj.instance_eval(m[2])
						obj[k] = Array.new
						ti = $TYPE_CLASS_INFORMATION[m[1]]
						n.times do
							o = nil
							if ti.cls.respond_to?(:new)
								o = ti.cls.new
							else
								o = ti.type
							end
							o,new_nseek = TypeInfo.fixup(o,bytes.slice(nseek,ti.nsize))
							nseek += new_nseek
							obj[k] << o
						end
					else
						ti = $TYPE_CLASS_INFORMATION[v]
						o = nil
						if ti.cls.respond_to?(:new)
							o = ti.cls.new
						else
							o = ti.type
						end
						o,new_nseek = TypeInfo.fixup(o,bytes.slice(nseek,ti.nsize))
						nseek += new_nseek
						obj[k] = o
					end
				end
			end
			return obj,nseek
		end
	end

	class UglyParser
		attr_accessor :ntbs
		def initialize(code)
			t = StringIO.new(code)
			state = false
			pattern = Regexp.new(/[ \t]*([A-Za-z0-9_]+)[ \t]+([A-Za-z0-9_\[\]]+);.*/)
			@ntbs = Hash.new
			t.readlines.each do |line|
				next if line.strip.empty? or line.lstrip.start_with?('//')
				if line.strip == '<<TAG_BEGIN>>'
					state = true
				else
					break if line.strip == '<<TAG_END>>'
				end
				if state
					m = pattern.match(line)
					@ntbs[m[2]] = m[1] if m and m.size == 3
				end
			end
			fine_tbs = Hash.new
			@ntbs.each do |k,v|
				if k =~ /\[[0-9]+\]/
					pattern = Regexp.new(/(.*)(\[[0-9]+\])/)
					m = pattern.match(k)
					fine_tbs[m[1]] = v + m[2]
				else
					fine_tbs[k] = v
				end
			end
			@ntbs = fine_tbs
			if block_given?
				yield self
			end
		end
	end

	class Image	< DelegateClass(Array)
		attr_accessor :name,:bytes
		def initialize(input)
			case input
				#File path
				when String
					@name = :name
					@bytes = IO.read(input,mode:'rb').bytes
				#Loaded bytes
				when Array
					@bytes = input.clone
				#Image file object
				when File
					@name = input.to_path
					@bytes = input.read.bytes
				#Just prompt
				else
					raise RuntimeError,"Could not fetch image bytes from #{input.class}."
			end
			super(@bytes)
			#Invoke from block
			if block_given?
				yield self
			end
		end
	end

	#Delegate to buildin Hash
	class BaseStruct < DelegateClass(Hash)
		attr_accessor :bytes,:scheme,:nsize
		def initialize(defs,input,tn,cls)
			if $UN_REGISTER_FLAG
				$TYPE_CLASS_INFORMATION['BYTE'] = TypeInfo.new('BYTE',Fixnum,nil,1)
				$TYPE_CLASS_INFORMATION['WORD'] = TypeInfo.new('WORD',Fixnum,nil,2)
				$TYPE_CLASS_INFORMATION['DWORD'] = TypeInfo.new('DWORD',Fixnum,nil,4)
				$TYPE_CLASS_INFORMATION['LONG'] = TypeInfo.new('LONG',Fixnum,nil,4)
				$TYPE_CLASS_INFORMATION['ULONGLONG'] = TypeInfo.new('ULONGLONG',Fixnum,nil,8)
				$UN_REGISTER_FLAG = false
			end
			parser = UglyParser.new(defs)
			@scheme = parser.ntbs
			inner = Hash.new
			@scheme.keys.each do |k|
				inner[k] = nil
			end
			super(inner)
			ti = TypeInfo.new(tn,cls,@scheme)
			@nsize = ti.nsize
			TypeInfo.fixup(self,input) if not input.nil?
			@scheme.keys.each do |k|
				cls.class_eval <<-EOF
					def #{k.downcase}
						self['#{k}']
					end
				EOF
			end
		end
	end

	class DosStub < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
				<<TAG_BEGIN>>
				WORD   e_magic;                     // Magic number
				WORD   e_cblp;                      // Bytes on last page of file
				WORD   e_cp;                        // Pages in file
				WORD   e_crlc;                      // Relocations
				WORD   e_cparhdr;                   // Size of header in paragraphs
				WORD   e_minalloc;                  // Minimum extra paragraphs needed
				WORD   e_maxalloc;                  // Maximum extra paragraphs needed
				WORD   e_ss;                        // Initial (relative) SS value
				WORD   e_sp;                        // Initial SP value
				WORD   e_csum;                      // Checksum
				WORD   e_ip;                        // Initial IP value
				WORD   e_cs;                        // Initial (relative) CS value
				WORD   e_lfarlc;                    // File address of relocation table
				WORD   e_ovno;                      // Overlay number
				WORD   e_res[4];                    // Reserved words
				WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
				WORD   e_oeminfo;                   // OEM information; e_oemid specific
				WORD   e_res2[10];                  // Reserved words
				LONG   e_lfanew;                    // File address of new exe header
				<<TAG_END>>
			  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
			EOF
			super(defs,input,'IMAGE_DOS_HEADER',self.class)
		end
	end

	class CoffHeader < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_FILE_HEADER {
				<<TAG_BEGIN>>
				WORD    Machine;
				WORD    NumberOfSections;
				DWORD   TimeDateStamp;
				DWORD   PointerToSymbolTable;
				DWORD   NumberOfSymbols;
				WORD    SizeOfOptionalHeader;
				WORD    Characteristics;
				<<TAG_END>>
			} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
			EOF
			super(defs,input,'IMAGE_FILE_HEADER',self.class)
		end
	end

	class ImageDataDirectory < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_DATA_DIRECTORY {
				<<TAG_BEGIN>>
				DWORD   VirtualAddress;
				DWORD   Size;
				<<TAG_END>>
			} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
			EOF
			super(defs,input,'IMAGE_DATA_DIRECTORY',self.class)
		end
	end

	class OptionalHeader32 < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_OPTIONAL_HEADER {
				//
				// Standard fields.
				//
				<<TAG_BEGIN>>
				WORD    Magic;
				BYTE    MajorLinkerVersion;
				BYTE    MinorLinkerVersion;
				DWORD   SizeOfCode;
				DWORD   SizeOfInitializedData;
				DWORD   SizeOfUninitializedData;
				DWORD   AddressOfEntryPoint;
				DWORD   BaseOfCode;
				DWORD   BaseOfData;

				//
				// NT additional fields.
				//

				DWORD   ImageBase;
				DWORD   SectionAlignment;
				DWORD   FileAlignment;
				WORD    MajorOperatingSystemVersion;
				WORD    MinorOperatingSystemVersion;
				WORD    MajorImageVersion;
				WORD    MinorImageVersion;
				WORD    MajorSubsystemVersion;
				WORD    MinorSubsystemVersion;
				DWORD   Win32VersionValue;
				DWORD   SizeOfImage;
				DWORD   SizeOfHeaders;
				DWORD   CheckSum;
				WORD    Subsystem;
				WORD    DllCharacteristics;
				DWORD   SizeOfStackReserve;
				DWORD   SizeOfStackCommit;
				DWORD   SizeOfHeapReserve;
				DWORD   SizeOfHeapCommit;
				DWORD   LoaderFlags;
				DWORD   NumberOfRvaAndSizes;
				IMAGE_DATA_DIRECTORY DataDirectory[#{IMAGE_NUMBEROF_DIRECTORY_ENTRIES.to_s}];
				<<TAG_END>>
			} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
			EOF
			o = ImageDataDirectory.new
			super(defs,input,'IMAGE_OPTIONAL_HEADER32',self.class)
		end
	end

	class OptionalHeader64 < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_OPTIONAL_HEADER64 {
				<<TAG_BEGIN>>
				WORD        Magic;
				BYTE        MajorLinkerVersion;
				BYTE        MinorLinkerVersion;
				DWORD       SizeOfCode;
				DWORD       SizeOfInitializedData;
				DWORD       SizeOfUninitializedData;
				DWORD       AddressOfEntryPoint;
				DWORD       BaseOfCode;
				ULONGLONG   ImageBase;
				DWORD       SectionAlignment;
				DWORD       FileAlignment;
				WORD        MajorOperatingSystemVersion;
				WORD        MinorOperatingSystemVersion;
				WORD        MajorImageVersion;
				WORD        MinorImageVersion;
				WORD        MajorSubsystemVersion;
				WORD        MinorSubsystemVersion;
				DWORD       Win32VersionValue;
				DWORD       SizeOfImage;
				DWORD       SizeOfHeaders;
				DWORD       CheckSum;
				WORD        Subsystem;
				WORD        DllCharacteristics;
				ULONGLONG   SizeOfStackReserve;
				ULONGLONG   SizeOfStackCommit;
				ULONGLONG   SizeOfHeapReserve;
				ULONGLONG   SizeOfHeapCommit;
				DWORD       LoaderFlags;
				DWORD       NumberOfRvaAndSizes;
				IMAGE_DATA_DIRECTORY DataDirectory[#{IMAGE_NUMBEROF_DIRECTORY_ENTRIES.to_s}];
				<<TAG_END>>
			} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
			EOF
			o = ImageDataDirectory.new
			super(defs,input,'IMAGE_OPTIONAL_HEADER64',self.class)
		end
	end

	class RomOptionalHeader < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_ROM_OPTIONAL_HEADER {
				<<TAG_BEGIN>>
				WORD   Magic;
				BYTE   MajorLinkerVersion;
				BYTE   MinorLinkerVersion;
				DWORD  SizeOfCode;
				DWORD  SizeOfInitializedData;
				DWORD  SizeOfUninitializedData;
				DWORD  AddressOfEntryPoint;
				DWORD  BaseOfCode;
				DWORD  BaseOfData;
				DWORD  BaseOfBss;
				DWORD  GprMask;
				DWORD  CprMask[4];
				DWORD  GpValue;
				<<TAG_END>>
			} IMAGE_ROM_OPTIONAL_HEADER, *PIMAGE_ROM_OPTIONAL_HEADER;
			EOF
			super(defs,input,'IMAGE_ROM_OPTIONAL_HEADER',self.class)
		end
	end

	class ImageNTHeaders32 < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_NT_HEADERS {
				<<TAG_BEGIN>>
				DWORD Signature;
				IMAGE_FILE_HEADER FileHeader;
				IMAGE_OPTIONAL_HEADER32 OptionalHeader;
				<<TAG_END>>
			} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
			EOF
			o = CoffHeader.new
			o = OptionalHeader32.new
			super(defs,input,'IMAGE_NT_HEADERS32',self.class)
		end
	end

	class ImageNTHeaders64 < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_NT_HEADERS64 {
				<<TAG_BEGIN>>
				DWORD Signature;
				IMAGE_FILE_HEADER FileHeader;
				IMAGE_OPTIONAL_HEADER64 OptionalHeader;
				<<TAG_END>>
			} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
			EOF
			o = CoffHeader.new
			o = OptionalHeader64.new
			super(defs,input,'IMAGE_NT_HEADERS64',self.class)
		end
	end

	class SectionHeader < BaseStruct
		def initialize(input = nil)
			defs = <<-EOF
			//winnt.h
			typedef struct _IMAGE_SECTION_HEADER {
				<<TAG_BEGIN>>
				BYTE    Name[#{IMAGE_SIZEOF_SHORT_NAME.to_s}];
//				union {
//						DWORD   PhysicalAddress;
						DWORD   VirtualSize;
//				} Misc;
				DWORD   VirtualAddress;
				DWORD   SizeOfRawData;
				DWORD   PointerToRawData;
				DWORD   PointerToRelocations;
				DWORD   PointerToLinenumbers;
				WORD    NumberOfRelocations;
				WORD    NumberOfLinenumbers;
				DWORD   Characteristics;
				<<TAG_END>>
			} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
			EOF
			super(defs,input,'IMAGE_SECTION_HEADER',self.class)
		end
	end
	
	class ImportDirectoryTable
		attr_accessor :import_lookup_table_rva,:time_stamp,:forward_chain,:name_rav,:import_address_table_rav
		attr_accessor :image,:name,:hintnames
		def initialize(nstart,imagedesc)
			@image = imagedesc.image
			@import_lookup_table_rva = Fixnum.readn(nstart + 0,4,@image)
			@time_stamp = Fixnum.readn(nstart + 4,4,@image)
			@forward_chain = Fixnum.readn(nstart + 8,4,@image)
			@name_rav = Fixnum.readn(nstart + 12,4,@image)
			@import_address_table_rav = Fixnum.readn(nstart + 16,4,@image)
			if not null?				
				noffset = imagedesc.rav2offset(@name_rav)
				@name = imagedesc.read_mem_cstr(noffset)				
			end
			@hintnames = Array.new
		end

		def null?
			@import_lookup_table_rva == 0 and @time_stamp == 0 and @forward_chain == 0 and @name_rav == 0 and @import_address_table_rav == 0
		end
	end

	class ImportLookupTableArray
		attr_accessor :lookuptable
		def initialize(nstart,imagedesc)
			image = imagedesc.image
			arch = imagedesc.image_type
			@lookuptable = Array.new
			nsize = 0
			while true
				data = nil
				if arch == 'PE32'
					data = Fixnum.readn(nstart + nsize,4,image)
					nsize += 4
				elsif arch =='PE32+'
					data = Fixnum.readn(nstart + nsize,8,image)
					nsize += 8					
				end
				break if data == 0
				@lookuptable << data
			end
		end
	end

	class HintNameTable
		attr_accessor :image,:use_hint,:hint,:name
		def initialize(imagedesc,ilt_item)
			@image = imagedesc.image
			arch = imagedesc.image_type
			if arch == 'PE32'
				@use_hint = ilt_item.test_b(31)
			elsif arch =='PE32+'
				@use_hint = ilt_item.test_b(63)				
			end
			if @use_hint
				@hint = ilt_item.part_b(0..15)
				@name = nil
			else
				hntab_rva = ilt_item.part_b(0..30)
				@hint = nil
				noffset = imagedesc.rav2offset(hntab_rva)
				@name = imagedesc.read_mem_cstr(noffset + 2)
			end
		end
	end

	class ExportDirectoryTable
		attr_accessor :image,:name,:ordinal_list,:name_list
		attr_accessor :exportflags,:timestamp,:majorversion,:minorversion,:namerva,:ordinalbase,:addresstableentries,:numberofnamepointers,:exportaddresstablerva,:namepointerrva,:ordinaltablerva
		attr_accessor :imagedesc
		def initialize(nstart,imagedesc)
			@imagedesc = imagedesc
			@image = imagedesc.image
			@exportflags = Fixnum.readn(nstart + 0,4,@image)
			@timestamp = Fixnum.readn(nstart + 4,4,@image)
			@majorversion = Fixnum.readn(nstart + 8,2,@image)
			@minorversion = Fixnum.readn(nstart + 10,2,@image)
			@namerva = Fixnum.readn(nstart + 12,4,@image)
			@ordinalbase = Fixnum.readn(nstart + 16,4,@image)
			@addresstableentries = Fixnum.readn(nstart + 20,4,@image)
			@numberofnamepointers = Fixnum.readn(nstart + 24,4,@image)
			@exportaddresstablerva = Fixnum.readn(nstart + 28,4,@image)
			@namepointerrva = Fixnum.readn(nstart + 32,4,@image)
			@ordinaltablerva = Fixnum.readn(nstart + 36,4,@image)
			set_name
			set_ordinal_list
			set_name_list
		end

		def set_name
			noffset = @imagedesc.rav2offset(@namerva)
			@name = @imagedesc.read_mem_cstr(noffset)
		end

		def set_ordinal_list
			@ordinal_list = Array.new
			noffset = @imagedesc.rav2offset(@ordinaltablerva)
			nsize = 0
			@numberofnamepointers.times do
				o = Fixnum.readn(noffset + nsize,2,@image)
				@ordinal_list << o
				nsize += 2
			end
		end

		def set_name_list
			@name_list = Array.new
			noffset = @imagedesc.rav2offset(@namepointerrva)
			nsize = 0
			@numberofnamepointers.times do
				o = Fixnum.readn(noffset + nsize,4,@image)
				offset_o = @imagedesc.rav2offset(o)
				namestr = @imagedesc.read_mem_cstr(offset_o)
				@name_list << namestr
				nsize += 4
			end
		end
	end

	class ImageDesc
		attr_accessor :image,:dosheader,:ntheader,:sections,:image_type
		def initialize(image,dosheader,ntheader,sections,image_type)
			@image,@dosheader,@ntheader,@sections,@image_type = image,dosheader,ntheader,sections,image_type
		end		

		def res_idata
			raise RuntimeError,'No import data directory.' if @ntheader.fileheader.numberofsections < 2
			imports = Array.new
			imp_va = @ntheader.optionalheader.datadirectory[1].virtualaddress
			page_start = rav2offset(imp_va)
			nsize = 20
			while idt = ImportDirectoryTable.new(page_start,self)
				break if idt.null?
				noffset = rav2offset(idt.import_lookup_table_rva)
				lookuptable = ImportLookupTableArray.new(noffset,self).lookuptable
				lookuptable.each do |lt|
					hnt = HintNameTable.new(self,lt)
					idt.hintnames << hnt
				end
				imports << idt
				page_start += nsize
			end			
			imports
		end

		def res_edata
			raise RuntimeError,'No import data directory.' if @ntheader.fileheader.numberofsections < 1
			exp_va = @ntheader.optionalheader.datadirectory[0].virtualaddress
			page_start = rav2offset(exp_va)
			edt = ExportDirectoryTable.new(page_start,self)
		end

		def rav2offset(rav)
			hit_section = nil
			@sections.each do |section|
				if rav >= section.virtualaddress and rav <= section.virtualaddress + section.sizeofrawdata
					hit_section = section
					#puts "RAV[#{rav.to_sd}] hit #{section.name.to_repl} between [#{section.virtualaddress.to_sd} ~ #{(section.virtualaddress + section.sizeofrawdata).to_sd}],file offset at[#{(section.pointertorawdata +  (rav - hit_section.virtualaddress)).to_sd}] between [#{section.pointertorawdata.to_sd} ~ #{(section.virtualaddress + section.sizeofrawdata).to_sd}] "
					break
				end
			end
			raise RuntimeError,"RAV[#{rav.to_sd}] out of sections virtual address space." if hit_section.nil?
			hit_section.pointertorawdata +  (rav - hit_section.virtualaddress)
		end

		def read_mem_cstr(nstart)
			result = StringIO.new
			while @image[nstart] != 0
				result.putc @image[nstart]
				nstart += 1
			end
			result.string
		end
	end

	class PEParser
		attr_accessor :image,:keyaddr
		def initialize(src)
			@image = Image.new(src)
			@keyaddr = KeyAddress.new
		end

		def parse
			image_type = nil
			if @image.slice(0,2).to_num != IMAGE_DOS_SIGNATURE
				raise RuntimeError,'Not valid image file - test from IMAGE_DOS_SIGNATURE.'
			end
			@keyaddr.add_item('dos_start',0)
			dosstub = DosStub.new
			dosstub,nsize = TypeInfo.fixup(dosstub,@image.slice(0,dosstub.nsize))
			@keyaddr.add_item('pe_start',dosstub.e_lfanew)
			if @image.slice(@keyaddr.pe_start,4).to_num != IMAGE_NT_SIGNATURE
				raise RuntimeError,'Not valid image file - test from IMAGE_NT_SIGNATURE.'
			end
			@keyaddr.add_item('coff_start',@keyaddr.pe_start + 4)
			@keyaddr.add_item('optional_start',@keyaddr.coff_start + CoffHeader.new.nsize)
			pe_type = @image.slice(@keyaddr.optional_start,2).to_lenum
			ntheader,nsize = nil,0
			if pe_type == IMAGE_NT_OPTIONAL_HDR32_MAGIC
				image_type = 'PE32'
				ntheader = ImageNTHeaders32.new
				ntheader,nsize = TypeInfo.fixup(ntheader,@image.slice(@keyaddr.pe_start,ntheader.nsize))
			else 
				if pe_type == IMAGE_NT_OPTIONAL_HDR64_MAGIC
					image_type = 'PE32+'
					ntheader = ImageNTHeaders64.new
					ntheader,nsize = TypeInfo.fixup(ntheader,@image.slice(@keyaddr.pe_start,ntheader.nsize))				
				else
					puts 'MAGIC:' + pe_type.to_s(16)
					raise RuntimeError,'Never mind file type - test from IMAGE_NT_OPTIONAL_HDR**_MAGIC.'
				end
			end
			@keyaddr.add_item('nsections',ntheader.fileheader.numberofsections)
			@keyaddr.add_item('nsizeofoptionalheader',ntheader.fileheader.sizeofoptionalheader)
			@keyaddr.add_item('sections_start',@keyaddr.optional_start + @keyaddr.nsizeofoptionalheader)
			sections = Array.new
			@keyaddr.nsections.times do |i|
				s = SectionHeader.new
				s,nsize = TypeInfo.fixup(s,@image.slice(@keyaddr.sections_start + s.nsize * i,s.nsize))
				sections << s
			end
			ImageDesc.new(@image,dosstub,ntheader,sections,image_type)
		end
	end
end

这来一个简单的测试用例,输出动态库的import和export的简单信息。

pf = PE::PEParser.new('D:\\Bandicam\\bdcap32.dll')
pedec = pf.parse
imps = pedec.res_idata
$stderr.puts "-----imports-----"
imps.each do |imp|
	$stderr.puts "[#{imp.name}]"
	imp.hintnames.each do |hn|
		if hn.use_hint
			$stderr.puts "\t" + hn.hint.to_s
		else
			$stderr.puts "\t" + hn.name
		end
	end
end
edt = pedec.res_edata
$stderr.puts "-----exports-----"
$stderr.puts "[#{edt.name}]"
edt.name_list.each do |n|
	$stderr.puts "\t" + n
end


猜你喜欢

转载自blog.csdn.net/mscf/article/details/52939550