1663 lines
51 KiB
Ruby
1663 lines
51 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
require 'rex/peparsey/exceptions'
|
|
require 'rex/struct2'
|
|
|
|
module Rex
|
|
module PeParsey
|
|
class PeBase
|
|
|
|
|
|
# #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
|
|
IMAGE_DOS_SIGNATURE = 0x5a4d
|
|
|
|
IMAGE_DOS_HEADER_SIZE = 64
|
|
# Struct
|
|
# 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 = 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
|
|
|
|
# #define IMAGE_NT_SIGNATURE 0x00004550 // PE00
|
|
IMAGE_NT_SIGNATURE = 0x00004550
|
|
# #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
|
|
IMAGE_FILE_MACHINE_I386 = 0x014c
|
|
# #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
|
|
IMAGE_FILE_MACHINE_IA64 = 0x0200
|
|
# #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
|
|
IMAGE_FILE_MACHINE_ALPHA64 = 0x0284
|
|
# #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
|
|
IMAGE_FILE_MACHINE_AMD64 = 0x8664
|
|
# #define IMAGE_SIZEOF_FILE_HEADER 20
|
|
IMAGE_FILE_HEADER_SIZE = 20+4 # because we include the signature
|
|
|
|
# C struct defining the PE file header
|
|
# 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;
|
|
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
|
|
|
|
IMAGE_ORDINAL_FLAG32 = 0x80000000
|
|
IMAGE_IMPORT_DESCRIPTOR_SIZE = 20
|
|
# Struct
|
|
# 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_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
|
|
|
|
# sizeof(struct _IMAGE_EXPORT_DESCRIPTOR)
|
|
IMAGE_EXPORT_DESCRIPTOR_SIZE = 40
|
|
# Struct defining the export table
|
|
# 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 = 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
|
|
|
|
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
|
|
# Struct
|
|
# typedef struct _IMAGE_DATA_DIRECTORY {
|
|
# DWORD VirtualAddress;
|
|
# DWORD Size;
|
|
# } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
|
|
IMAGE_DATA_DIRECTORY = Rex::Struct2::CStructTemplate.new(
|
|
[ 'uint32v', 'VirtualAddress', 0 ],
|
|
[ 'uint32v', 'Size', 0 ]
|
|
)
|
|
|
|
# Struct
|
|
# 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 ]
|
|
)]
|
|
)
|
|
|
|
# #define IMAGE_SIZEOF_NT_OPTIONAL64_HEADER 240
|
|
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
|
|
# #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
|
|
IMAGE_SIZEOF_NT_OPTIONAL64_HEADER = 240
|
|
|
|
# Struct
|
|
# 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;
|
|
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
|
|
|
|
# #define IMAGE_SIZEOF_SECTION_HEADER 40
|
|
IMAGE_SIZEOF_SECTION_HEADER = 40
|
|
# Struct
|
|
# 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;
|
|
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
|
|
|
|
# #define IMAGE_SIZEOF_BASE_RELOCATION 8
|
|
IMAGE_SIZEOF_BASE_RELOCATION = 8
|
|
|
|
# Struct
|
|
# typedef struct _IMAGE_BASE_RELOCATION {
|
|
# DWORD VirtualAddress;
|
|
# DWORD SizeOfBlock;
|
|
# // WORD TypeOffset[1];
|
|
# } IMAGE_BASE_RELOCATION;
|
|
# typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
|
|
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
|
|
|
|
# Struct
|
|
# 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 ]
|
|
)
|
|
|
|
# Struct
|
|
# 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
|
|
#
|
|
|
|
# Struct
|
|
# 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 ]
|
|
)
|
|
|
|
# Struct
|
|
# 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
|
|
#
|
|
##
|
|
|
|
IMAGE_RUNTIME_FUNCTION_ENTRY_SZ = 12
|
|
# Struct
|
|
# typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
|
|
# DWORD BeginAddress;
|
|
# DWORD EndAddress;
|
|
# DWORD UnwindInfoAddress;
|
|
# } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;
|
|
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 PeParseyError, "No section contains RVA", 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 PeParseyError, "Offset should not be less than 0. The value is: #{foffset}", caller
|
|
end
|
|
|
|
all_sections.each do |section|
|
|
if section.contains_file_offset?(foffset)
|
|
return section.file_offset_to_rva(foffset)
|
|
end
|
|
end
|
|
|
|
raise PeParseyError, "No section contains file offset #{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 PeParseyError, "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/n, 2)
|
|
return unistr ? unistr.gsub(/\x00/n, '') : 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
|