Ruby解析Windows PE檔案
阿新 • • 發佈:2019-02-14
純屬業餘分析一些應用程式的需要,特意去了解了下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