#!/usr/bin/env ruby # -*- coding: binary -*- # $Id$ require 'rex/peparsey/exceptions' require 'rex/struct2' module Rex module PeParsey class PeBase # #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ IMAGE_DOS_SIGNATURE = 0x5a4d # # typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header # 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 # } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; # IMAGE_DOS_HEADER_SIZE = 64 IMAGE_DOS_HEADER = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'e_magic', IMAGE_DOS_SIGNATURE ], [ 'uint16v', 'e_cblp', 0 ], [ 'uint16v', 'e_cp', 0 ], [ 'uint16v', 'e_crlc', 0 ], [ 'uint16v', 'e_cparhdr', 0 ], [ 'uint16v', 'e_minalloc', 0 ], [ 'uint16v', 'e_maxalloc', 0 ], [ 'uint16v', 'e_ss', 0 ], [ 'uint16v', 'e_sp', 0 ], [ 'uint16v', 'e_csum', 0 ], [ 'uint16v', 'e_ip', 0 ], [ 'uint16v', 'e_cs', 0 ], [ 'uint16v', 'e_lfarlc', 0 ], [ 'uint16v', 'e_ovno', 0 ], [ 'template', 'e_res', Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'e_res_0', 0 ], [ 'uint16v', 'e_res_1', 0 ], [ 'uint16v', 'e_res_2', 0 ], [ 'uint16v', 'e_res_3', 0 ] )], [ 'uint16v', 'e_oemid', 0 ], [ 'uint16v', 'e_oeminfo', 0 ], [ 'template', 'e_res2', Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'e_res2_0', 0 ], [ 'uint16v', 'e_res2_1', 0 ], [ 'uint16v', 'e_res2_2', 0 ], [ 'uint16v', 'e_res2_3', 0 ], [ 'uint16v', 'e_res2_4', 0 ], [ 'uint16v', 'e_res2_5', 0 ], [ 'uint16v', 'e_res2_6', 0 ], [ 'uint16v', 'e_res2_7', 0 ], [ 'uint16v', 'e_res2_8', 0 ], [ 'uint16v', 'e_res2_9', 0 ] )], [ 'uint32v', 'e_lfanew', 0 ] ) class HeaderAccessor attr_accessor :dos, :file, :opt, :sections, :config, :exceptions, :tls def initialize end end class GenericStruct attr_accessor :struct def initialize(_struct) self.struct = _struct end # The following methods are just pass-throughs for struct # Access a value def v struct.v end # Access a value by array def [](*args) struct[*args] end # Obtain an array of all fields def keys struct.keys end def method_missing(meth, *args) v[meth.to_s] || (raise NoMethodError.new, meth) end end class GenericHeader < GenericStruct end class DosHeader < GenericHeader def initialize(rawdata) dos_header = IMAGE_DOS_HEADER.make_struct if !dos_header.from_s(rawdata) raise DosHeaderError, "Couldn't parse IMAGE_DOS_HEADER", caller end if dos_header.v['e_magic'] != IMAGE_DOS_SIGNATURE raise DosHeaderError, "Couldn't find DOS e_magic", caller end self.struct = dos_header end def e_lfanew v['e_lfanew'] end end def self._parse_dos_header(rawdata) return DosHeader.new(rawdata) end # # typedef struct _IMAGE_FILE_HEADER { # WORD Machine; # WORD NumberOfSections; # DWORD TimeDateStamp; # DWORD PointerToSymbolTable; # DWORD NumberOfSymbols; # WORD SizeOfOptionalHeader; # WORD Characteristics; # } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; # # #define IMAGE_NT_SIGNATURE 0x00004550 // PE00 # #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. # #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 # #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 # #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) # #define IMAGE_SIZEOF_FILE_HEADER 20 # IMAGE_NT_SIGNATURE = 0x00004550 IMAGE_FILE_MACHINE_I386 = 0x014c IMAGE_FILE_MACHINE_IA64 = 0x0200 IMAGE_FILE_MACHINE_ALPHA64 = 0x0284 IMAGE_FILE_MACHINE_AMD64 = 0x8664 IMAGE_FILE_HEADER_SIZE = 20+4 # because we include the signature IMAGE_FILE_HEADER = Rex::Struct2::CStructTemplate.new( # not really in the header, but easier for us this way [ 'uint32v', 'NtSignature', 0 ], [ 'uint16v', 'Machine', 0 ], [ 'uint16v', 'NumberOfSections', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint32v', 'PointerToSymbolTable', 0 ], [ 'uint32v', 'NumberOfSymbols', 0 ], [ 'uint16v', 'SizeOfOptionalHeader', 0 ], [ 'uint16v', 'Characteristics', 0 ] ) SUPPORTED_MACHINES = [ IMAGE_FILE_MACHINE_I386, IMAGE_FILE_MACHINE_IA64, IMAGE_FILE_MACHINE_ALPHA64, IMAGE_FILE_MACHINE_AMD64 ] class FileHeader < GenericHeader def initialize(rawdata) file_header = IMAGE_FILE_HEADER.make_struct if !file_header.from_s(rawdata) raise FileHeaderError, "Couldn't parse IMAGE_FILE_HEADER", caller end if file_header.v['NtSignature'] != IMAGE_NT_SIGNATURE raise FileHeaderError, "Couldn't find the PE magic!" end if SUPPORTED_MACHINES.include?(file_header.v['Machine']) == false raise FileHeaderError, "Unsupported machine type: #{file_header.v['Machine']}", caller end self.struct = file_header end def Machine v['Machine'] end def SizeOfOptionalHeader v['SizeOfOptionalHeader'] end def NumberOfSections v['NumberOfSections'] end end def self._parse_file_header(rawdata) return FileHeader.new(rawdata) end # # typedef struct _IMAGE_IMPORT_DESCRIPTOR { # union { # DWORD Characteristics; // 0 for terminating null import descriptor # DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) # }; # DWORD TimeDateStamp; // 0 if not bound, # // -1 if bound, and real date\time stamp # // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) # // O.W. date/time stamp of DLL bound to (Old BIND) # # DWORD ForwarderChain; // -1 if no forwarders # DWORD Name; # DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) # } IMAGE_IMPORT_DESCRIPTOR; # IMAGE_ORDINAL_FLAG32 = 0x80000000 IMAGE_IMPORT_DESCRIPTOR_SIZE = 20 IMAGE_IMPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'OriginalFirstThunk', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint32v', 'ForwarderChain', 0 ], [ 'uint32v', 'Name', 0 ], [ 'uint32v', 'FirstThunk', 0 ] ) # # typedef struct _IMAGE_IMPORT_BY_NAME { # WORD Hint; # BYTE Name[1]; # } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; # class ImportDescriptor attr_accessor :name, :entries def initialize(_name, _entries) self.name = _name self.entries = _entries end end class ImportEntry attr_accessor :name, :ordinal def initialize(_name, _ordinal) self.name = _name self.ordinal = _ordinal end end # # typedef struct _IMAGE_EXPORT_DIRECTORY { # DWORD Characteristics; # DWORD TimeDateStamp; # WORD MajorVersion; # WORD MinorVersion; # DWORD Name; # DWORD Base; # DWORD NumberOfFunctions; # DWORD NumberOfNames; # DWORD AddressOfFunctions; // RVA from base of image # DWORD AddressOfNames; // RVA from base of image # DWORD AddressOfNameOrdinals; // RVA from base of image # } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; # IMAGE_EXPORT_DESCRIPTOR_SIZE = 40 IMAGE_EXPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Characteristics', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint16v', 'MajorVersion', 0 ], [ 'uint16v', 'MinorVersion', 0 ], [ 'uint32v', 'Name', 0 ], [ 'uint32v', 'Base', 0 ], [ 'uint32v', 'NumberOfFunctions', 0 ], [ 'uint32v', 'NumberOfNames', 0 ], [ 'uint32v', 'AddressOfFunctions', 0 ], [ 'uint32v', 'AddressOfNames', 0 ], [ 'uint32v', 'AddressOfNameOrdinals', 0 ] ) class ExportDirectory attr_accessor :name, :entries, :base def initialize(_name, _entries, _base) self.name = _name self.entries = _entries self.base = _base end end class ExportEntry attr_accessor :name, :ordinal, :rva def initialize(_name, _ordinal, _rva) self.name = _name self.ordinal = _ordinal self.rva = _rva end end # # typedef struct _IMAGE_DATA_DIRECTORY { # DWORD VirtualAddress; # DWORD Size; # } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; # IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 IMAGE_DATA_DIRECTORY_SIZE = 8 IMAGE_DIRECTORY_ENTRY_EXPORT = 0 IMAGE_DIRECTORY_ENTRY_IMPORT = 1 IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 IMAGE_DIRECTORY_ENTRY_SECURITY = 4 IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 IMAGE_DIRECTORY_ENTRY_DEBUG = 6 IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 IMAGE_DIRECTORY_ENTRY_TLS = 9 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 IMAGE_DIRECTORY_ENTRY_IAT = 12 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 IMAGE_DATA_DIRECTORY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'Size', 0 ] ) # # typedef struct _IMAGE_OPTIONAL_HEADER { # // # // Standard fields. # // # # 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]; # } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; # # #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b # #define IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224 # IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b IMAGE_SIZEOF_NT_OPTIONAL32_HEADER = 224 IMAGE_OPTIONAL_HEADER32 = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'Magic', 0 ], [ 'uint8', 'MajorLinkerVersion', 0 ], [ 'uint8', 'MinorLinkerVersion', 0 ], [ 'uint32v', 'SizeOfCode', 0 ], [ 'uint32v', 'SizeOfInitializeData', 0 ], [ 'uint32v', 'SizeOfUninitializeData', 0 ], [ 'uint32v', 'AddressOfEntryPoint', 0 ], [ 'uint32v', 'BaseOfCode', 0 ], [ 'uint32v', 'BaseOfData', 0 ], [ 'uint32v', 'ImageBase', 0 ], [ 'uint32v', 'SectionAlignment', 0 ], [ 'uint32v', 'FileAlignment', 0 ], [ 'uint16v', 'MajorOperatingSystemVersion', 0 ], [ 'uint16v', 'MinorOperatingSystemVersion', 0 ], [ 'uint16v', 'MajorImageVersion', 0 ], [ 'uint16v', 'MinorImageVersion', 0 ], [ 'uint16v', 'MajorSubsystemVersion', 0 ], [ 'uint16v', 'MinorSubsystemVersion', 0 ], [ 'uint32v', 'Win32VersionValue', 0 ], [ 'uint32v', 'SizeOfImage', 0 ], [ 'uint32v', 'SizeOfHeaders', 0 ], [ 'uint32v', 'CheckSum', 0 ], [ 'uint16v', 'Subsystem', 0 ], [ 'uint16v', 'DllCharacteristics', 0 ], [ 'uint32v', 'SizeOfStackReserve', 0 ], [ 'uint32v', 'SizeOfStackCommit', 0 ], [ 'uint32v', 'SizeOfHeapReserve', 0 ], [ 'uint32v', 'SizeOfHeapCommit', 0 ], [ 'uint32v', 'LoaderFlags', 0 ], [ 'uint32v', 'NumberOfRvaAndSizes', 0 ], [ 'template', 'DataDirectory', Rex::Struct2::CStructTemplate.new( [ 'template', 'DataDirectoryEntry_0', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_1', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_2', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_3', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_4', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_5', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_6', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_7', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_8', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_9', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_10', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_11', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_12', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_13', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_14', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_15', IMAGE_DATA_DIRECTORY ] )] ) # # typedef struct _IMAGE_OPTIONAL_HEADER64 { # USHORT Magic; # UCHAR MajorLinkerVersion; # UCHAR MinorLinkerVersion; # ULONG SizeOfCode; # ULONG SizeOfInitializedData; # ULONG SizeOfUninitializedData; # ULONG AddressOfEntryPoint; # ULONG BaseOfCode; # ULONGLONG ImageBase; # ULONG SectionAlignment; # ULONG FileAlignment; # USHORT MajorOperatingSystemVersion; # USHORT MinorOperatingSystemVersion; # USHORT MajorImageVersion; # USHORT MinorImageVersion; # USHORT MajorSubsystemVersion; # USHORT MinorSubsystemVersion; # ULONG Win32VersionValue; # ULONG SizeOfImage; # ULONG SizeOfHeaders; # ULONG CheckSum; # USHORT Subsystem; # USHORT DllCharacteristics; # ULONGLONG SizeOfStackReserve; # ULONGLONG SizeOfStackCommit; # ULONGLONG SizeOfHeapReserve; # ULONGLONG SizeOfHeapCommit; # ULONG LoaderFlags; # ULONG NumberOfRvaAndSizes; # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; # } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; # # #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b # #define IMAGE_SIZEOF_NT_OPTIONAL64_HEADER 240 # IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b IMAGE_SIZEOF_NT_OPTIONAL64_HEADER = 240 IMAGE_OPTIONAL_HEADER64 = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'Magic', 0 ], [ 'uint8', 'MajorLinkerVersion', 0 ], [ 'uint8', 'MinorLinkerVersion', 0 ], [ 'uint32v', 'SizeOfCode', 0 ], [ 'uint32v', 'SizeOfInitializeData', 0 ], [ 'uint32v', 'SizeOfUninitializeData', 0 ], [ 'uint32v', 'AddressOfEntryPoint', 0 ], [ 'uint32v', 'BaseOfCode', 0 ], [ 'uint64v', 'ImageBase', 0 ], [ 'uint32v', 'SectionAlignment', 0 ], [ 'uint32v', 'FileAlignment', 0 ], [ 'uint16v', 'MajorOperatingsystemVersion', 0 ], [ 'uint16v', 'MinorOperatingsystemVersion', 0 ], [ 'uint16v', 'MajorImageVersion', 0 ], [ 'uint16v', 'MinorImageVersion', 0 ], [ 'uint16v', 'MajorSubsystemVersion', 0 ], [ 'uint16v', 'MinorSubsystemVersion', 0 ], [ 'uint32v', 'Win32VersionValue', 0 ], [ 'uint32v', 'SizeOfImage', 0 ], [ 'uint32v', 'SizeOfHeaders', 0 ], [ 'uint32v', 'CheckSum', 0 ], [ 'uint16v', 'Subsystem', 0 ], [ 'uint16v', 'DllCharacteristics', 0 ], [ 'uint64v', 'SizeOfStackReserve', 0 ], [ 'uint64v', 'SizeOfStackCommit', 0 ], [ 'uint64v', 'SizeOfHeapReserve', 0 ], [ 'uint64v', 'SizeOfHeapCommit', 0 ], [ 'uint32v', 'LoaderFlags', 0 ], [ 'uint32v', 'NumberOfRvaAndSizes', 0 ], [ 'template', 'DataDirectory', Rex::Struct2::CStructTemplate.new( [ 'template', 'DataDirectoryEntry_0', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_1', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_2', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_3', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_4', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_5', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_6', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_7', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_8', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_9', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_10', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_11', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_12', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_13', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_14', IMAGE_DATA_DIRECTORY ], [ 'template', 'DataDirectoryEntry_15', IMAGE_DATA_DIRECTORY ] )] ) class OptionalHeader < GenericHeader def ImageBase v['ImageBase'] end def FileAlignment v['FileAlignment'] end end class OptionalHeader32 < OptionalHeader def initialize(rawdata) optional_header = IMAGE_OPTIONAL_HEADER32.make_struct if !optional_header.from_s(rawdata) raise OptionalHeaderError, "Couldn't parse IMAGE_OPTIONAL_HEADER32", caller end if optional_header.v['Magic'] != IMAGE_NT_OPTIONAL_HDR32_MAGIC raise OptionalHeaderError, "Magic did not match!", caller() end self.struct = optional_header end end class OptionalHeader64 < OptionalHeader def initialize(rawdata) optional_header = IMAGE_OPTIONAL_HEADER64.make_struct if !optional_header.from_s(rawdata) raise OptionalHeaderError, "Couldn't parse IMAGE_OPTIONAL_HEADER64", caller end if optional_header.v['Magic'] != IMAGE_NT_OPTIONAL_HDR64_MAGIC raise OptionalHeaderError, "Magic did not match!", caller() end self.struct = optional_header end end def self._parse_optional_header(rawdata) case rawdata.length # no optional header when 0 return nil # good, good when IMAGE_SIZEOF_NT_OPTIONAL32_HEADER return OptionalHeader32.new(rawdata) when IMAGE_SIZEOF_NT_OPTIONAL64_HEADER return OptionalHeader64.new(rawdata) # bad, bad else raise OptionalHeaderError, "I don't know this header size, #{rawdata.length}", caller end end # # typedef struct _IMAGE_SECTION_HEADER { # BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; # union { # DWORD PhysicalAddress; # DWORD VirtualSize; # } Misc; # DWORD VirtualAddress; # DWORD SizeOfRawData; # DWORD PointerToRawData; # DWORD PointerToRelocations; # DWORD PointerToLinenumbers; # WORD NumberOfRelocations; # WORD NumberOfLinenumbers; # DWORD Characteristics; # } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; # # #define IMAGE_SIZEOF_SECTION_HEADER 40 # IMAGE_SIZEOF_SECTION_HEADER = 40 IMAGE_SECTION_HEADER = Rex::Struct2::CStructTemplate.new( [ 'string', 'Name', 8, '' ], [ 'uint32v', 'Misc', 0 ], [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'SizeOfRawData', 0 ], [ 'uint32v', 'PointerToRawData', 0 ], [ 'uint32v', 'PointerToRelocations', 0 ], [ 'uint32v', 'NumberOfRelocations', 0 ], [ 'uint32v', 'NumberOfLineNumbers', 0 ], [ 'uint32v', 'Characteristics', 0 ] ) class SectionHeader < GenericHeader def initialize(rawdata) section_header = IMAGE_SECTION_HEADER.make_struct if !section_header.from_s(rawdata) raise SectionHeaderError, "Could not parse header", caller end self.struct = section_header end def VirtualAddress v['VirtualAddress'] end def SizeOfRawData v['SizeOfRawData'] end def PointerToRawData v['PointerToRawData'] end end def self._parse_section_headers(rawdata) section_headers = [ ] size = IMAGE_SIZEOF_SECTION_HEADER numsections = rawdata.length / size numsections.times do |i| data = rawdata[i * size, size] section_headers << SectionHeader.new(data) end return section_headers end # # typedef struct _IMAGE_BASE_RELOCATION { # DWORD VirtualAddress; # DWORD SizeOfBlock; # // WORD TypeOffset[1]; # } IMAGE_BASE_RELOCATION; # typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; # # #define IMAGE_SIZEOF_BASE_RELOCATION 8 # IMAGE_SIZEOF_BASE_RELOCATION = 8 IMAGE_BASE_RELOCATION = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'SizeOfBlock', 0 ] ) IMAGE_BASE_RELOCATION_TYPE_OFFSET = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'TypeOffset', 0 ] ) class RelocationDirectory attr_accessor :entries, :rva def initialize(rva, entries) self.rva = rva self.entries = entries self.name = name self.characteristics = chars self.timedate = timedate self.version = version self.entries = [] end end class RelocationEntry attr_accessor :rva, :reltype def initialize(_rva, _type) self.rva = _rva self.reltype = _type end end class ResourceDirectory attr_accessor :entries, :name def initialize(name, entries) self.name = name self.entries = entries end end class ResourceEntry attr_accessor :path, :lang, :code, :rva, :size, :pe, :file def initialize(pe, path, lang, code, rva, size, file) self.pe = pe self.path = path self.lang = lang self.code = code self.rva = rva self.size = size self.file = file.to_s end def data pe._isource.read(pe.rva_to_file_offset(rva), size) end end # # typedef struct { # DWORD Size; # DWORD TimeDateStamp; # WORD MajorVersion; # WORD MinorVersion; # DWORD GlobalFlagsClear; # DWORD GlobalFlagsSet; # DWORD CriticalSectionDefaultTimeout; # DWORD DeCommitFreeBlockThreshold; # DWORD DeCommitTotalFreeThreshold; # DWORD LockPrefixTable; // VA # DWORD MaximumAllocationSize; # DWORD VirtualMemoryThreshold; # DWORD ProcessHeapFlags; # DWORD ProcessAffinityMask; # WORD CSDVersion; # WORD Reserved1; # DWORD EditList; // VA # DWORD SecurityCookie; // VA # DWORD SEHandlerTable; // VA # DWORD SEHandlerCount; # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; # IMAGE_LOAD_CONFIG_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint16v', 'MajorVersion', 0 ], [ 'uint16v', 'MinorVersion', 0 ], [ 'uint32v', 'GlobalFlagsClear', 0 ], [ 'uint32v', 'GlobalFlagsSet', 0 ], [ 'uint32v', 'CriticalSectionDefaultTimeout', 0 ], [ 'uint32v', 'DeCommitFreeBlockThreshold', 0 ], [ 'uint32v', 'DeCommitTotalFreeThreshold', 0 ], [ 'uint32v', 'LockPrefixTable', 0 ], [ 'uint32v', 'MaximumAllocationSize', 0 ], [ 'uint32v', 'VirtualMemoryThreshold', 0 ], [ 'uint32v', 'ProcessHeapFlags', 0 ], [ 'uint32v', 'ProcessAffinityMask', 0 ], [ 'uint16v', 'CSDVersion', 0 ], [ 'uint16v', 'Reserved1', 0 ], [ 'uint32v', 'EditList', 0 ], [ 'uint32v', 'SecurityCookie', 0 ], [ 'uint32v', 'SEHandlerTable', 0 ], [ 'uint32v', 'SEHandlerCount', 0 ] ) # # typedef struct { # ULONG Size; # ULONG TimeDateStamp; # USHORT MajorVersion; # USHORT MinorVersion; # ULONG GlobalFlagsClear; # ULONG GlobalFlagsSet; # ULONG CriticalSectionDefaultTimeout; # ULONGLONG DeCommitFreeBlockThreshold; # ULONGLONG DeCommitTotalFreeThreshold; # ULONGLONG LockPrefixTable; // VA # ULONGLONG MaximumAllocationSize; # ULONGLONG VirtualMemoryThreshold; # ULONGLONG ProcessAffinityMask; # ULONG ProcessHeapFlags; # USHORT CSDVersion; # USHORT Reserved1; # ULONGLONG EditList; // VA # ULONGLONG SecurityCookie; // VA # ULONGLONG SEHandlerTable; // VA # ULONGLONG SEHandlerCount; # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; # IMAGE_LOAD_CONFIG_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint16v', 'MajorVersion', 0 ], [ 'uint16v', 'MinorVersion', 0 ], [ 'uint32v', 'GlobalFlagsClear', 0 ], [ 'uint32v', 'GlobalFlagsSet', 0 ], [ 'uint32v', 'CriticalSectionDefaultTimeout', 0 ], [ 'uint64v', 'DeCommitFreeBlockThreshold', 0 ], [ 'uint64v', 'DeCommitTotalFreeThreshold', 0 ], [ 'uint64v', 'LockPrefixTable', 0 ], [ 'uint64v', 'MaximumAllocationSize', 0 ], [ 'uint64v', 'VirtualMemoryThreshold', 0 ], [ 'uint64v', 'ProcessAffinityMask', 0 ], [ 'uint32v', 'ProcessHeapFlags', 0 ], [ 'uint16v', 'CSDVersion', 0 ], [ 'uint16v', 'Reserved1', 0 ], [ 'uint64v', 'EditList', 0 ], [ 'uint64v', 'SecurityCookie', 0 ], [ 'uint64v', 'SEHandlerTable', 0 ], [ 'uint64v', 'SEHandlerCount', 0 ] ) class ConfigHeader < GenericHeader end # doesn't seem to be used -- not compatible with 64-bit #def self._parse_config_header(rawdata) # header = IMAGE_LOAD_CONFIG_DIRECTORY32.make_struct # header.from_s(rawdata) # ConfigHeader.new(header) #end def _parse_config_header # # Get the data directory entry, size, etc # exports_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG] rva = exports_entry.v['VirtualAddress'] size = exports_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # dirdata = _isource.read(rva_to_file_offset(rva), size) klass = (ptr_64?) ? IMAGE_LOAD_CONFIG_DIRECTORY64 : IMAGE_LOAD_CONFIG_DIRECTORY32 header = klass.make_struct header.from_s(dirdata) @config = ConfigHeader.new(header) end def config _parse_config_header if @config.nil? @config end # # TLS Directory # # # typedef struct { # DWORD Size; # DWORD TimeDateStamp; # WORD MajorVersion; # WORD MinorVersion; # DWORD GlobalFlagsClear; # DWORD GlobalFlagsSet; # DWORD CriticalSectionDefaultTimeout; # DWORD DeCommitFreeBlockThreshold; # DWORD DeCommitTotalFreeThreshold; # DWORD LockPrefixTable; // VA # DWORD MaximumAllocationSize; # DWORD VirtualMemoryThreshold; # DWORD ProcessHeapFlags; # DWORD ProcessAffinityMask; # WORD CSDVersion; # WORD Reserved1; # DWORD EditList; // VA # DWORD SecurityCookie; // VA # DWORD SEHandlerTable; // VA # DWORD SEHandlerCount; # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; # IMAGE_LOAD_TLS_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint16v', 'MajorVersion', 0 ], [ 'uint16v', 'MinorVersion', 0 ], [ 'uint32v', 'GlobalFlagsClear', 0 ], [ 'uint32v', 'GlobalFlagsSet', 0 ], [ 'uint32v', 'CriticalSectionDefaultTimeout', 0 ], [ 'uint32v', 'DeCommitFreeBlockThreshold', 0 ], [ 'uint32v', 'DeCommitTotalFreeThreshold', 0 ], [ 'uint32v', 'LockPrefixTable', 0 ], [ 'uint32v', 'MaximumAllocationSize', 0 ], [ 'uint32v', 'VirtualMemoryThreshold', 0 ], [ 'uint32v', 'ProcessHeapFlags', 0 ], [ 'uint32v', 'ProcessAffinityMask', 0 ], [ 'uint16v', 'CSDVersion', 0 ], [ 'uint16v', 'Reserved1', 0 ], [ 'uint32v', 'EditList', 0 ], [ 'uint32v', 'SecurityCookie', 0 ], [ 'uint32v', 'SEHandlerTable', 0 ], [ 'uint32v', 'SEHandlerCount', 0 ] ) # # typedef struct { # ULONG Size; # ULONG TimeDateStamp; # USHORT MajorVersion; # USHORT MinorVersion; # ULONG GlobalFlagsClear; # ULONG GlobalFlagsSet; # ULONG CriticalSectionDefaultTimeout; # ULONGLONG DeCommitFreeBlockThreshold; # ULONGLONG DeCommitTotalFreeThreshold; # ULONGLONG LockPrefixTable; // VA # ULONGLONG MaximumAllocationSize; # ULONGLONG VirtualMemoryThreshold; # ULONGLONG ProcessAffinityMask; # ULONG ProcessHeapFlags; # USHORT CSDVersion; # USHORT Reserved1; # ULONGLONG EditList; // VA # ULONGLONG SecurityCookie; // VA # ULONGLONG SEHandlerTable; // VA # ULONGLONG SEHandlerCount; # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; # IMAGE_LOAD_TLS_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], [ 'uint16v', 'MajorVersion', 0 ], [ 'uint16v', 'MinorVersion', 0 ], [ 'uint32v', 'GlobalFlagsClear', 0 ], [ 'uint32v', 'GlobalFlagsSet', 0 ], [ 'uint32v', 'CriticalSectionDefaultTimeout', 0 ], [ 'uint64v', 'DeCommitFreeBlockThreshold', 0 ], [ 'uint64v', 'DeCommitTotalFreeThreshold', 0 ], [ 'uint64v', 'LockPrefixTable', 0 ], [ 'uint64v', 'MaximumAllocationSize', 0 ], [ 'uint64v', 'VirtualMemoryThreshold', 0 ], [ 'uint64v', 'ProcessAffinityMask', 0 ], [ 'uint32v', 'ProcessHeapFlags', 0 ], [ 'uint16v', 'CSDVersion', 0 ], [ 'uint16v', 'Reserved1', 0 ], [ 'uint64v', 'EditList', 0 ], [ 'uint64v', 'SecurityCookie', 0 ], [ 'uint64v', 'SEHandlerTable', 0 ], [ 'uint64v', 'SEHandlerCount', 0 ] ) class TLSHeader < GenericHeader end def _parse_tls_header # # Get the data directory entry, size, etc # exports_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_TLS] rva = exports_entry.v['VirtualAddress'] size = exports_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # dirdata = _isource.read(rva_to_file_offset(rva), size) klass = (ptr_64?) ? IMAGE_LOAD_TLS_DIRECTORY64 : IMAGE_LOAD_TLS_DIRECTORY32 header = klass.make_struct header.from_s(dirdata) @tls = TLSHeader.new(header) end def tls _parse_config_header if @tls.nil? @tls end ## # # Exception directory # ## # # typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { # DWORD BeginAddress; # DWORD EndAddress; # DWORD UnwindInfoAddress; # } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; # IMAGE_RUNTIME_FUNCTION_ENTRY_SZ = 12 IMAGE_RUNTIME_FUNCTION_ENTRY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'BeginAddress', 0 ], [ 'uint32v', 'EndAddress', 0 ], [ 'uint32v', 'UnwindInfoAddress', 0 ] ) UNWIND_INFO_HEADER_SZ = 4 UNWIND_INFO_HEADER = Rex::Struct2::CStructTemplate.new( [ 'uint8', 'VersionFlags', 0 ], [ 'uint8', 'SizeOfProlog', 0 ], [ 'uint8', 'CountOfCodes', 0 ], [ 'uint8', 'FrameRegisterAndOffset', 0 ] ) UWOP_PUSH_NONVOL = 0 # 1 node UWOP_ALLOC_LARGE = 1 # 2 or 3 nodes UWOP_ALLOC_SMALL = 2 # 1 node UWOP_SET_FPREG = 3 # 1 node UWOP_SAVE_NONVOL = 4 # 2 nodes UWOP_SAVE_NONVOL_FAR = 5 # 3 nodes UWOP_SAVE_XMM128 = 8 # 2 nodes UWOP_SAVE_XMM128_FAR = 9 # 3 nodes UWOP_PUSH_MACHFRAME = 10 # 1 node UNW_FLAG_EHANDLER = 1 UNW_FLAG_UHANDLER = 2 UNW_FLAG_CHAININFO = 4 class UnwindCode def initialize(data) self.code_offset = data[0].to_i self.unwind_op = data[1].to_i & 0xf self.op_info = data[1].to_i >> 4 self.frame_offset = data[2..3].unpack("v")[0] data.slice!(0, 4) end attr_reader :code_offset, :unwind_op, :op_info, :frame_offset attr_writer :code_offset, :unwind_op, :op_info, :frame_offset end class UnwindInfo def initialize(pe, unwind_rva) data = pe.read_rva(unwind_rva, UNWIND_INFO_HEADER_SZ) unwind = UNWIND_INFO_HEADER.make_struct unwind.from_s(data) @version = unwind.v['VersionFlags'] & 0x7 @flags = unwind.v['VersionFlags'] >> 3 @size_of_prolog = unwind.v['SizeOfProlog'] @count_of_codes = unwind.v['CountOfCodes'] @frame_register = unwind.v['FrameRegisterAndOffset'] & 0xf @frame_register_offset = unwind.v['FrameRegisterAndOffset'] >> 4 # Parse unwind codes clist = pe.read_rva(unwind_rva + UNWIND_INFO_HEADER_SZ, count_of_codes * 4) @unwind_codes = [] while clist.length > 0 @unwind_codes << UnwindCode.new(clist) end end attr_reader :version, :flags, :size_of_prolog, :count_of_codes attr_reader :frame_register, :frame_register_offset def unwind_codes @unwind_codes end end class RuntimeFunctionEntry def initialize(pe, data) @pe = pe @begin_address, @end_address, @unwind_info_address = data.unpack("VVV"); self.unwind_info = UnwindInfo.new(pe, unwind_info_address) end attr_reader :begin_address, :end_address, :unwind_info_address attr_reader :unwind_info attr_writer :unwind_info end def _load_exception_directory @exception = [] exception_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_EXCEPTION] rva = exception_entry.v['VirtualAddress'] size = exception_entry.v['Size'] return if (rva == 0) data = _isource.read(rva_to_file_offset(rva), size) case hdr.file.Machine when IMAGE_FILE_MACHINE_AMD64 count = data.length / IMAGE_RUNTIME_FUNCTION_ENTRY_SZ count.times { |current| @exception << RuntimeFunctionEntry.new(self, data.slice!(0, IMAGE_RUNTIME_FUNCTION_ENTRY_SZ)) } else end return @exception end def exception _load_exception_directory if @exception.nil? @exception end # # Just a stupid routine to round an offset up to it's alignment. # # For example, you're going to want this for FileAlignment and # SectionAlignment, etc... # def self._align_offset(offset, alignment) offset += alignment - 1 offset -= offset % alignment return offset end # # instance stuff # attr_accessor :_isource attr_accessor :_dos_header, :_file_header, :_optional_header, :_section_headers, :_config_header, :_tls_header, :_exception_header attr_accessor :sections, :header_section, :image_base attr_accessor :_imports_cache, :_imports_cached attr_accessor :_exports_cache, :_exports_cached attr_accessor :_relocations_cache, :_relocations_cached attr_accessor :_resources_cache, :_resources_cached attr_accessor :hdr def self.new_from_file(filename, disk_backed = false) file = ::File.new(filename) file.binmode # windows... :\ if disk_backed return self.new(ImageSource::Disk.new(file)) else obj = new_from_string(file.read) file.close return obj end end def self.new_from_string(data) return self.new(ImageSource::Memory.new(data)) end def close _isource.close end # # # Random rva, vma, file offset, section offset, etc # conversion routines... # # def rva_to_vma(rva) return rva + image_base end def vma_to_rva(vma) return vma - image_base end def rva_to_file_offset(rva) all_sections.each do |section| if section.contains_rva?(rva) return section.rva_to_file_offset(rva) end end raise WtfError, "wtf!", caller end def vma_to_file_offset(vma) return rva_to_file_offset(vma_to_rva(vma)) end def file_offset_to_rva(foffset) if foffset < 0 raise WtfError, "lame", caller end all_sections.each do |section| if section.contains_file_offset?(foffset) return section.file_offset_to_rva(foffset) end end raise WtfError, "wtf! #{foffset}", caller end def file_offset_to_vma(foffset) return rva_to_vma(file_offset_to_rva(foffset)) end # # # Some routines to find which section something belongs # to. These will search all_sections (so including # our fake header section, etc... # # # # Find a section by an RVA # def _find_section_by_rva(rva) all_sections.each do |section| if section.contains_rva?(rva) return section end end return nil end def find_section_by_rva(rva) section = _find_section_by_rva(rva) if !section raise WtfError, "Cannot find rva! #{rva}", caller end return section end # # Find a section by a VMA # def find_section_by_vma(vma) return find_section_by_rva(vma_to_rva(vma)) end def valid_rva?(rva) _find_section_by_rva(rva) != nil end def valid_vma?(vma) _find_section_by_rva(vma_to_rva(vma)) != nil end # # # Some convenient methods to read a vma/rva without having # the section... (inefficent though I suppose...) # # def read_rva(rva, length) return find_section_by_rva(rva).read_rva(rva, length) end def read_vma(vma, length) return read_rva(vma_to_rva(vma), length) end def read_asciiz_rva(rva) return find_section_by_rva(rva).read_asciiz_rva(rva) end def read_asciiz_vma(vma) return read_asciiz_rva(vma_to_rva(vma)) end # # # Imports, exports, and other stuff! # # # # We lazily parse the imports, and then cache it # def imports if !_imports_cached self._imports_cache = _load_imports self._imports_cached = true end return _imports_cache end def _load_imports # # Get the data directory entry, size, etc # imports_entry = _optional_header['DataDirectory'][1] rva = imports_entry.v['VirtualAddress'] size = imports_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # imports = [ ] descriptors_data = _isource.read(rva_to_file_offset(rva), size) while descriptors_data.length >= IMAGE_IMPORT_DESCRIPTOR_SIZE descriptor = IMAGE_IMPORT_DESCRIPTOR.make_struct descriptor.from_s(descriptors_data) descriptors_data = descriptor.leftover othunk = descriptor.v['OriginalFirstThunk'] fthunk = descriptor.v['FirstThunk'] break if fthunk == 0 dllname = _isource.read_asciiz(rva_to_file_offset(descriptor.v['Name'])) import = ImportDescriptor.new(dllname, [ ]) # we prefer the Characteristics/OriginalFirstThunk... thunk_off = rva_to_file_offset(othunk == 0 ? fthunk : othunk) while (orgrva = _isource.read(thunk_off, 4).unpack('V')[0]) != 0 hint = nil name = nil if (orgrva & IMAGE_ORDINAL_FLAG32) != 0 hint = orgrva & 0xffff else foff = rva_to_file_offset(orgrva) hint = _isource.read(foff, 2).unpack('v')[0] name = _isource.read_asciiz(foff + 2) end import.entries << ImportEntry.new(name, hint) thunk_off += 4 end imports << import end return imports end # # We lazily parse the exports, and then cache it # def exports if !_exports_cached self._exports_cache = _load_exports self._exports_cached = true end return _exports_cache end def _load_exports # # Get the data directory entry, size, etc # exports_entry = _optional_header['DataDirectory'][0] rva = exports_entry.v['VirtualAddress'] size = exports_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # directory = IMAGE_EXPORT_DESCRIPTOR.make_struct directory.from_s(_isource.read(rva_to_file_offset(rva), IMAGE_EXPORT_DESCRIPTOR_SIZE)) # # We can have nameless exports, so we need to do the whole # NumberOfFunctions NumberOfNames foo # num_functions = directory.v['NumberOfFunctions'] num_names = directory.v['NumberOfNames'] dllname_rva = directory.v['Name'] dllname = _isource.read_asciiz(rva_to_file_offset(dllname_rva)) # FIXME Base, etc fun_off = rva_to_file_offset(directory.v['AddressOfFunctions']) name_off = rva_to_file_offset(directory.v['AddressOfNames']) ord_off = rva_to_file_offset(directory.v['AddressOfNameOrdinals']) base = directory.v['Base'] # Allocate the list of names names = Array.new(num_functions) # # Iterate the names and name/ordinal list, getting the names # and storing them in the name list... # num_names.times do |i| name_rva = _isource.read(name_off + (i * 4), 4).unpack('V')[0] ordinal = _isource.read(ord_off + (i * 2), 2).unpack('v')[0] name = _isource.read_asciiz(rva_to_file_offset(name_rva)) # store the exported name in the name list names[ordinal] = name end exports = ExportDirectory.new(dllname, [ ], base) # # Now just iterate the functions (rvas) list.. # num_functions.times do |i| rva = _isource.read(fun_off + (i * 4), 4).unpack('V')[0] # ExportEntry.new(name, ordinal, rva) exports.entries << ExportEntry.new(names[i], i + base, rva) end return exports end # # Base relocations in the hizzy # def relocations if !_relocations_cached self._relocations_cache = _load_relocations self._relocations_cached = true end return _relocations_cache end def _load_relocations # # Get the data directory entry, size, etc # exports_entry = _optional_header['DataDirectory'][5] rva = exports_entry.v['VirtualAddress'] size = exports_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # dirdata = _isource.read(rva_to_file_offset(rva), size) relocdirs = [ ] while dirdata.length >= IMAGE_SIZEOF_BASE_RELOCATION header = IMAGE_BASE_RELOCATION.make_struct header.from_s(dirdata) dirdata = header.leftover numrelocs = (header.v['SizeOfBlock'] - IMAGE_SIZEOF_BASE_RELOCATION) / 2 relocbase = header.v['VirtualAddress'] relocdir = RelocationDirectory.new(relocbase, [ ]) numrelocs.times do reloc = IMAGE_BASE_RELOCATION_TYPE_OFFSET.make_struct reloc.from_s(dirdata) dirdata = reloc.leftover typeoffset = reloc.v['TypeOffset'] relocrva = relocbase + (typeoffset & 0xfff) reloctype = (typeoffset >> 12) & 0xf relocdir.entries << RelocationEntry.new(relocrva, reloctype) end relocdirs << relocdir end return relocdirs end # # We lazily parse the resources, and then cache them # def resources if !_resources_cached _load_resources self._resources_cached = true end return self._resources_cache end def _load_resources # # Get the data directory entry, size, etc # rsrc_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_RESOURCE] rva = rsrc_entry.v['VirtualAddress'] size = rsrc_entry.v['Size'] return nil if size == 0 # # Ok, so we have the data directory, now lets parse it # data = _isource.read(rva_to_file_offset(rva), size) self._resources_cache = {} _parse_resource_directory(data) end def _parse_resource_directory(data, rname=0, rvalue=0x80000000, path='0', pname=nil) pname = _parse_resource_name(data, rname) if (path.scan('/').length == 1) if (pname !~ /^\d+/) path = "/" + pname else path = "/" + _resource_lookup( (rname & ~0x80000000).to_s) end end rvalue &= ~0x80000000 vals = data[rvalue, 16].unpack('VVvvvv') chars = vals[0] tdate = vals[1] vers = "#{vals[2]}#{vals[3]}" count = vals[4] + vals[5] 0.upto(count-1) do |i| ename, evalue = data[rvalue + 16 + ( i * 8), 8].unpack('VV') epath = path + '/' + i.to_s if (ename & 0x80000000 != 0) pname = _parse_resource_name(data, ename) end if (evalue & 0x80000000 != 0) # This is a subdirectory _parse_resource_directory(data, ename, evalue, epath, pname) else # This is an entry _parse_resource_entry(data, ename, evalue, epath, pname) end end end def _resource_lookup(i) tbl = { '1' => 'RT_CURSOR', '2' => 'RT_BITMAP', '3' => 'RT_ICON', '4' => 'RT_MENU', '5' => 'RT_DIALOG', '6' => 'RT_STRING', '7' => 'RT_FONTDIR', '8' => 'RT_FONT', '9' => 'RT_ACCELERATORS', '10' => 'RT_RCDATA', '11' => 'RT_MESSAGETABLE', '12' => 'RT_GROUP_CURSOR', '14' => 'RT_GROUP_ICON', '16' => 'RT_VERSION', '17' => 'RT_DLGINCLUDE', '19' => 'RT_PLUGPLAY', '20' => 'RT_VXD', '21' => 'RT_ANICURSOR', '22' => 'RT_ANIICON', '23' => 'RT_HTML', '24' => 'RT_MANIFEST', '32767' => 'RT_ERROR', '8192' => 'RT_NEWRESOURCE', '8194' => 'RT_NEWBITMAP', '8196' => 'RT_NEWMENU', '8197' => 'RT_NEWDIALOG' } tbl[i] || i end def _parse_resource_entry(data, rname, rvalue, path, pname) rva, size, code = data[rvalue, 12].unpack('VVV') lang = _parse_resource_name(data, rname) ent = ResourceEntry.new( self, path, lang, code, rva, size, pname ) self._resources_cache[path] = ent end def _parse_resource_name(data, rname) if (rname & 0x80000000 != 0) rname &= ~0x80000000 unistr = data[rname+2, 2 * data[rname,2].unpack('v')[0] ] unistr, trash = unistr.split(/\x00\x00/, 2) return unistr ? unistr.gsub(/\x00/, '') : nil end rname.to_s end def update_checksum off = _dos_header.e_lfanew + IMAGE_FILE_HEADER_SIZE + 0x40 _isource.rawdata[off, 4] = [0].pack('V') rem = _isource.size % 4 sum_me = '' sum_me << _isource.rawdata sum_me << "\x00" * (4 - rem) if rem > 0 cksum = 0 sum_me.unpack('V*').each { |el| cksum = (cksum & 0xffffffff) + (cksum >> 32) + el if cksum > 2**32 cksum = (cksum & 0xffffffff) + (cksum >> 32) end } cksum = (cksum & 0xffff) + (cksum >> 16) cksum += (cksum >> 16) cksum &= 0xffff cksum += _isource.size _isource.rawdata[off, 4] = [cksum].pack('V') end end end end