# -*- coding: utf-8 -*-
# This code is part of Amoco
# based on elf.py, improving pefile to work out corkami's CoST.exe.
# Copyright (C) 2014 Axel Tillequin (bdcht3@gmail.com)
# published under GPLv2 license
"""
system/pe.py
============
The system pe module implements the PE class which support both 32 and 64 bits
executable formats.
"""
from amoco.system.core import BinFormat
from amoco.system.structs import struct, Consts, StructFormatter, StructDefine
from amoco.system.structs import token_datetime_fmt, StructureError
from amoco.ui.render import Token, highlight
from amoco.logger import Log
logger = Log(__name__)
logger.debug("loading module")
[docs]class PEError(Exception):
"""
PEError is raised whenever PE object instance fails
to decode required structures.
"""
def __init__(self, message):
self.message = message
def __str__(self):
return str(self.message)
# ------------------------------------------------------------------------------
[docs]class PE(BinFormat):
"""
This class takes a DataIO object (ie an opened file of BytesIO instance)
and decodes all PE structures found in it.
Attributes:
data (DataIO): a reference to the input data file/bytes object.
entrypoints (list of int): list of entrypoint addresses.
filename (str): binary file name.
DOS (DOSHdr,optional): the DOS Header (only if present.)
NT (COFFHdr): the PE header.
Opt (OptionalHdr): the Optional Header
basemap (int): base address for this ELF image.
sections (list of SectionHdr): list of PE sections.
functions (list): a list of function names gathered from internal
definitions (if not stripped) and import names.
variables (list): a list of global variables' names (if found.)
tls (TlsTable): the Thead local Storage table (or None.)
"""
is_PE = True
@property
def entrypoints(self):
l = [self.Opt.AddressOfEntryPoint + self.basemap]
if self.tls:
l.extend(list(set(self.tls.callbacks)))
return l
@property
def filename(self):
return self.data.name
@property
def dataio(self):
return self.data
@property
def header(self):
return self.NT
def __init__(self, data):
self.data = data
# parse DOS header:
try:
self.DOS = DOSHdr(data)
except Exception:
raise PEError("not a DOSHdr")
# parse PE header:
self.NT = COFFHdr(data, self.DOS.e_lfanew)
# parse Optional Header:
self.Opt = OptionalHdr(data, self.DOS.e_lfanew + len(self.NT))
self.basemap = self.Opt.ImageBase
if self.NT.SizeOfOptionalHeader != len(self.Opt):
logger.warning("Optional header size mismatch")
# read Sections:
self.sections = []
offset = self.DOS.e_lfanew + len(self.NT) + self.NT.SizeOfOptionalHeader
for i in range(self.NT.NumberOfSections):
s = SectionHdr(data, offset)
self.sections.append(s)
offset += len(s)
self.functions = self.__functions()
self.variables = self.__variables()
self.tls = self.__tls()
def checksec(self):
R = {}
dllc = self.Opt.DllCharacteristics
dynamic_base = dllc & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
R['ASLR'] = "DYNAMIC_BASE" if dynamic_base else False
he = dllc & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
if he:
R['ASLR'] = "HIGH_ENTROPY_VA"
R['CFG'] = (dllc & IMAGE_DLLCHARACTERISTICS_GUARD_CF)!=0
R['DEP'] = (dllc & IMAGE_DLLCHARACTERISTICS_NX_COMPAT)!=0
R['Isolation'] = (dllc & IMAGE_DLLCHARACTERISTICS_NO_ISOLATION)==0
R['SEH'] = (dllc & IMAGE_DLLCHARACTERISTICS_NO_SEH)==0
cth = self.__LoadConfigTable()
if cth:
if cth.SEHandlerCount>0:
R['Safe-SEH'] = True
if (cth.GuardFlags & IMAGE_GUARD_RF_INSTRUMENTED) and\
((cth.GuardFlags & IMAGE_GUARD_RF_ENABLE) or\
(cth.GuardFlags & IMAGE_GUARD_RF_STRICT)):
R['RFG'] = True
else:
R['Safe-SEH'] = False
R['RFG'] = False
return R
[docs] def locate(self, addr, absolute=False):
"""
returns a tuple with:
- the section that holds addr (rva or absolute), or 0 or None.
- the offset within the section (or addr or 0).
Note:
If returned section is 0, then addr is within SizeOfImage,
but is not found within any sections. Then offset is addr.
If returned section is None, then addr is not mapped at all,
and offset is set to 0.
"""
if absolute:
addr = addr - self.basemap
# now we have addr so we can see in which section/segment it is...
# sections are smaller than segments so we try first with Shdr
# but this may lead to errors because what really matters are segments
# loaded by the kernel binfmt_elf.c loader.
for s in self.sections:
if s.Characteristics == IMAGE_SCN_LNK_REMOVE:
continue
if s.RVA <= addr < s.RVA + s.VirtualSize:
return s, addr - s.RVA
if 0 <= addr < self.Opt.SizeOfImage:
return 0, addr
return None, 0
[docs] def getdata(self, addr, absolute=False):
"get section bytes from given virtual address to end of mapped section."
s, offset = self.locate(addr, absolute)
if s is None:
logger.debug("address 0x%08x not mapped"%addr)
raise ValueError
return self.loadsegment(s, raw=True)[offset:]
[docs] def loadsegment(self, S, pagesize=0, raw=False):
"""
returns a dict {base: bytes} (or only bytes if optional arg raw is True,)
indicating that section S data bytes (padded and extended to pagesize bounds)
need to be mapped at virtual base address.
Note:
If S is 0, returns base=0 and the first Opt.SizeOfHeaders bytes.
"""
if S and not S.Characteristics == IMAGE_SCN_LNK_REMOVE:
addr = self.basemap + S.RVA
if addr % self.Opt.SectionAlignment:
logger.warning("bad alignment for section %s" % S.Name)
sta = S.PointerToRawData
if sta % self.Opt.FileAlignment:
logger.warning("bad file alignment for section %s" % S.Name)
sto = sta + S.SizeOfRawData
bytes_ = self.data[sta:sto].ljust(S.VirtualSize)
if pagesize:
# note: bytes are not truncated, only extended if needed...
bytes_ = bytes_.ljust(pagesize, b"\x00")
if raw:
return bytes_
else:
return {addr: bytes_}
elif S == 0:
bytes_ = self.data[0 : self.Opt.SizeOfHeaders]
if raw:
return bytes_
else:
return {self.basemap: bytes_}
return None
[docs] def getfileoffset(self, addr):
"converts given address back to offset in file"
s, offset = self.locate(addr, absolute=True)
return s.PointerToRawData + offset
def __functions(self):
D = {}
imports = self.Opt.DataDirectories.get("ImportTable", None)
if imports is not None:
data = b""
while len(data) < imports.Size:
try:
nextdata = self.getdata(imports.RVA + len(data))
if len(nextdata) == 0:
break
data += nextdata
except ValueError:
return D
if len(data) < imports.Size:
logger.warning("ImportTable length mismatch")
data = data[: imports.Size]
self.ImportTable = ImportTable(data)
for e in self.ImportTable.dlls:
try:
dllname = self.getdata(e.NameRVA)
e.Name = str(dllname.partition(b"\0")[0].decode())
except Exception:
logger.warning("invalid dll name RVA in ImportTable")
try:
if e.ImportLookupTableRVA != 0:
data = self.getdata(e.ImportLookupTableRVA)
else:
data = self.getdata(e.ImportAddressTableRVA)
except ValueError:
logger.warning("invalid ImportLookupTable RVA")
else:
e.ImportLookupTable = ImportLookupTable(data, self.Opt.Magic)
e.ImportAddressTable = []
vaddr = e.ImportAddressTableRVA + self.basemap
for x in e.ImportLookupTable.imports:
if x[0] == 0:
ref = NameTableEntry(self.getdata(x[1]))
else:
ref = "#%s" % str(x[1]) # ordinal case
e.ImportAddressTable.append((vaddr, ref))
vaddr += e.ImportLookupTable.elsize
D.update(e.ImportAddressTable)
for vaddr, ref in e.ImportAddressTable:
if isinstance(ref, str):
symbol = ref
else:
symbol = ref.symbol
D[vaddr] = "%s::%s" % (e.Name, symbol)
return D
def __tls(self):
tls = self.Opt.DataDirectories.get("TLSTable", None)
if tls is not None and tls.RVA != 0:
try:
data = self.getdata(tls.RVA)
except ValueError:
logger.warning("invalid TLS RVA")
else:
tls = TLSTable(data, self.Opt.Magic)
try:
cbtable = self.getdata(tls.AddressOfCallbacks, absolute=True)
except ValueError:
tls.callbacks = []
else:
tls.readcallbacks(cbtable)
return tls
return None
def __LoadConfigTable(self):
lct = self.Opt.DataDirectories.get("LoadConfigTable", None)
if lct is not None and lct.RVA != 0:
try:
data = self.getdata(lct.RVA)
except ValueError:
logger.warning("invalid LoadLConfigTable RVA")
else:
lct = LoadConfigTable(data, self.Opt.Magic)
return lct
return None
def __variables(self):
D = {}
return D
def __str__(self):
ss = ["DOS header:"]
tmp = self.DOS.pfx
self.DOS.pfx = "\t"
ss.append(str(self.DOS))
self.DOS.pfx = tmp
ss += ["\nPE header:"]
tmp = self.NT.pfx
self.NT.pfx = "\t"
ss.append(str(self.NT))
self.NT.pfx = tmp
ss += ["\nOptional header:"]
tmp = self.Opt.pfx
self.Opt.pfx = "\t"
ss.append(str(self.Opt))
self.Opt.pfx = tmp
ss += ["\nSections:"]
for s in self.sections:
tmp = s.pfx
s.pfx = "\t"
ss.append(str(s))
ss.append("---")
s.pfx = tmp
return "\n".join(ss)
# ------------------------------------------------------------------------------
[docs]@StructDefine(
"""
c*2 : e_magic
x*58: unused
I : e_lfanew
"""
)
class DOSHdr(StructFormatter):
def __init__(self, data=None):
if data:
self.unpack(data)
if self.e_magic != b"MZ":
raise PEError("no DOS Header found")
IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
IMAGE_ORDINAL_FLAG = 0x80000000
IMAGE_ORDINAL_FLAG64 = 0x8000000000000000
[docs]@StructDefine(
"""
I : Signature
H : Machine
H : NumberOfSections
I : TimeDateStamp
I : PointerToSymbolTable
I : NumberOfSymbols
H : SizeOfOptionalHeader
H : Characteristics
"""
)
class COFFHdr(StructFormatter):
def __init__(self, data=None, offset=0):
self.name_formatter("Signature", "Machine")
self.flag_formatter("Characteristics")
self.func_formatter(TimeDateStamp=token_datetime_fmt)
if data:
self.unpack(data, offset)
if self.Signature != IMAGE_NT_SIGNATURE:
raise PEError("no PE header found")
with Consts("Signature"):
IMAGE_DOS_SIGNATURE = 0x5A4D
IMAGE_OS2_SIGNATURE = 0x454E
IMAGE_OS2_SIGNATURE_LE = 0x454C
IMAGE_VXD_SIGNATURE = 0x454C
IMAGE_NT_SIGNATURE = 0x00004550
with Consts("Machine"):
IMAGE_FILE_MACHINE_UNKNOWN = 0
IMAGE_FILE_MACHINE_AM33 = 0x1D3
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_ARM = 0x1C0
IMAGE_FILE_MACHINE_EBC = 0xEBC
IMAGE_FILE_MACHINE_I386 = 0x14C
IMAGE_FILE_MACHINE_IA64 = 0x200
IMAGE_FILE_MACHINE_MR32 = 0x9041
IMAGE_FILE_MACHINE_MIPS16 = 0x266
IMAGE_FILE_MACHINE_MIPSFPU = 0x366
IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466
IMAGE_FILE_MACHINE_POWERPC = 0x1F0
IMAGE_FILE_MACHINE_POWERPCFP = 0x1F1
IMAGE_FILE_MACHINE_R4000 = 0x166
IMAGE_FILE_MACHINE_SH3 = 0x1A2
IMAGE_FILE_MACHINE_SH3DSP = 0x1A3
IMAGE_FILE_MACHINE_SH4 = 0x1A6
IMAGE_FILE_MACHINE_SH5 = 0x1A8
IMAGE_FILE_MACHINE_THUMB = 0x1C2
IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169
with Consts("COFFHdr.Characteristics"):
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002
IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004
IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008
IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010
IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020
IMAGE_FILE_16BIT_MACHINE = 0x0040
IMAGE_FILE_BYTES_REVERSED_LO = 0x0080
IMAGE_FILE_32BIT_MACHINE = 0x0100
IMAGE_FILE_DEBUG_STRIPPED = 0x0200
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400
IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800
IMAGE_FILE_SYSTEM = 0x1000
IMAGE_FILE_DLL = 0x2000
IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000
IMAGE_FILE_BYTES_REVERSED_HI = 0x8000
[docs]@StructDefine(
"""
H : Magic
B : MajorLinkerVersion
B : MinorLinkerVersion
I : SizeOfCode
I : SizeOfInitializedData
I : SizeOfUninitializedData
I : AddressOfEntryPoint
I : BaseOfCode
I : BaseOfData
I : ImageBase
I : SectionAlignment
I : FileAlignment
H : MajorOperatingSystemVersion
H : MinorOperatingSystemVersion
H : MajorImageVersion
H : MinorImageVersion
H : MajorSubsystemVersion
H : MinorSubsystemVersion
I : Win32VersionValue
I : SizeOfImage
I : SizeOfHeaders
I : CheckSum
H : Subsystem
H : DllCharacteristics
I : SizeOfStackReserve
I : SizeOfStackCommit
I : SizeOfHeapReserve
I : SizeOfHeapCommit
I : LoaderFlags
I : NumberOfRvaAndSizes
"""
)
class OptionalHdr(StructFormatter):
def __init__(self, data=None, offset=0):
self.name_formatter("Magic")
self.name_formatter("Subsystem")
self.address_formatter(
"AddressOfEntryPoint", "BaseOfCode", "BaseOfData", "ImageBase"
)
self.address_formatter("Checksum")
self.flag_formatter("DllCharacteristics", "LoaderFlags")
if data:
self.unpack(data, offset)
def unpack(self, data, offset=0):
magic = data[offset : offset + 2]
if magic == b"\x0b\x01":
logger.verbose("PE32 Magic found")
elif magic == b"\x0b\x02":
logger.verbose("PE32+ Magic found")
f = self.fields
f.pop(8)
for x in (8, 23, 24, 25, 26):
f[x].typename = "Q"
elif magic == b"\x07\x01":
logger.info("ROM Magic found (unsupported)")
else:
logger.error("unknown Magic")
# parse structure
self.DataDirectories = {}
StructFormatter.unpack(self, data, offset)
l = offset + len(self)
dnames = (
"ExportTable",
"ImportTable",
"ResourceTable",
"ExceptionTable",
"CertificateTable",
"BaseRelocationTable",
"Debug",
"Architecture",
"GlobalPtr",
"TLSTable",
"LoadConfigTable",
"BoundImport",
"IAT",
"DelayImportDescriptor",
"CLRRuntimeHeader",
"Reserved",
)
for dn in range(min(self.NumberOfRvaAndSizes, len(dnames))):
d = DataDirectory(data, offset=l)
self.DataDirectories[dnames[dn]] = d
l += len(d)
def __len__(self):
baselen = StructFormatter.__len__(self)
dirslen = sum(map(len, self.DataDirectories.values()))
return baselen + dirslen
with Consts("Magic"):
OPTIONAL_HEADER_MAGIC_PE = 0x10B
OPTIONAL_HEADER_MAGIC_PE_PLUS = 0x20B
with Consts("Subsystem"):
IMAGE_SUBSYSTEM_UNKOWN = 0
IMAGE_SUBSYSTEM_NATIVE = 1
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
IMAGE_SUBSYSTEM_OS2_CUI = 5
IMAGE_SUBSYSTEM_POSIX_CUI = 7
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9
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
with Consts("DllCharacteristics"):
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800
IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000
IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
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_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_DIRECTORY_ENTRY_RESERVED = 15
[docs]@StructDefine(
"""
I : RVA
I : Size
"""
)
class DataDirectory(StructFormatter):
def __init__(self, data=None, offset=0):
self.address_formatter("RVA")
if data:
self.unpack(data, offset)
# ------------------------------------------------------------------------------
# PE Sections
[docs]@StructDefine(
"""
s*8 : Name
I : VirtualSize
I : RVA
I : SizeOfRawData
I : PointerToRawData
I : PointerToRelocations
I : PointerToLineNumbers
H : NumberOfRelocations
H : NumberOfLineNumbers
I : Characteristics
"""
)
class SectionHdr(StructFormatter):
def __init__(self, data=None, offset=0):
self.name_formatter("Name")
self.address_formatter("RVA")
self.address_formatter("PointerToRawData")
self.flag_formatter("Characteristics")
if data:
self.unpack(data, offset)
def group(self):
return self.Name.partition("$")
with Consts("SectionHdr.Characteristics"):
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
IMAGE_SCN_LNK_OTHER = 0x00000100
IMAGE_SCN_LNK_INFO = 0x00000200
IMAGE_SCN_LNK_REMOVE = 0x00000800
IMAGE_SCN_LNK_COMDAT = 0x00001000
IMAGE_SCN_MEM_FARDATA = 0x00008000
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
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
IMAGE_SCN_ALIGN_MASK = 0x00F00000
IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
IMAGE_SCN_MEM_NOT_CACHED = 0x04000000
IMAGE_SCN_MEM_NOT_PAGED = 0x08000000
IMAGE_SCN_MEM_SHARED = 0x10000000
IMAGE_SCN_MEM_EXECUTE = 0x20000000
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000
# COFF Relocations
# ------------------------------------------------------------------------------
[docs]@StructDefine(
"""
I : RVA
I : SymboleTableIndex
H : Type
"""
)
class COFFRelocation(StructFormatter):
def __init__(self, data=None, offset=0):
self.address_formatter("RVA")
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
I : Type
H : LineNumber
"""
)
class COFFLineNumber(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
@property
def SymbolTableIndex(self):
if self.LineNumber == 0:
return self.Type
else:
logger.warning("invalid COFF Line Number entry")
IMAGE_SYM_UNDEF = 0
IMAGE_SYM_ABSOLUTE = -1
IMAGE_SYM_DEBUG = -2
# COFF Symbol table
# ------------------------------------------------------------------------------
class COFFSymbolTable(object):
def __init__(self, data=None):
self.symbols = []
if data is None:
data = ""
if len(data) > 0:
self.build(data)
def build(self, data):
q, r = divmod(len(data), 18)
assert r == 0
while len(data) > 0:
self.symbols.append(StdSymbolRecord(data))
data = data[len(self.symbols[-1]) :]
assert len(self.symbols) == q
def __len__(self):
return sum(map(len, self.symbols))
[docs]@StructDefine(
"""
s*8 : _Name
i : Value
H : SectionNumber
H : Type
B : StorageClass
B : NumberOfAuxSymbols
"""
)
class StdSymbolRecord(StructFormatter):
def __init__(self, data=None, offset=0):
self.func_formatter(
_Name=lambda k, x, cls: highlight([(Token.Name, self.Name)])
)
self.func_formatter(
StorageClass=lambda k, x, cls: highlight([(Token.Name, self.classname())])
)
self.AuxSymbols = []
if data:
self.unpack(data, offset)
offset += len(self)
if (
self.Type >> 8 == 0x20
and self.StorageClass == 2
and self.SectionNumber > IMAGE_SYM_UNDEF
):
auxclass = AuxFunctionDefinition
elif self.StorageClass == 101 and (
self.Name == ".bf" or self.Name == ".ef"
):
auxclass = Aux_bf_ef
elif (
self.StorageClass == 2
and self.SectionNumber == IMAGE_SYM_UNDEF
and Self.Value == 0
):
auxclass = AuxWeakExternal
elif self.StorageClass == 103:
assert self.Name == ".file"
auxclass = AuxFile
elif self.StorageClass == 3:
auxclass = AuxSectionDefinition
else:
auxclass = AuxSymbolRecord
for x in range(self.NumberOfAuxSymbols):
s = auxclass(data, offset)
self.AuxSymbols.append(s)
offset += len(s)
def __len__(self):
baselen = StructFormatter.__len__(self)
auxtlen = sum([len(s) for s in self.AuxSymbols], 0)
return baselen + auxtlen
@property
def Name(self):
if self._Name.startswith("\0" * 4):
index = struct.unpack("I", self._Name[4:8])[0]
return index.strip("\0")
else:
try:
return self._Name.decode("utf-8").strip("\0")
except UnicodeDecodeError:
logger.info("StdSymbolHdr: Name decode error %s" % repr(self._Name))
return self._Name
def typename(self):
try:
t1 = [
"NULL",
"VOID",
"CHAR",
"SHORT",
"INT",
"LONG",
"FLOAT",
"DOUBLE",
"STRUCT",
"UNION",
"ENUM",
"MOE",
"BYTE",
"WORD",
"UINT",
"DWORD",
][self.Type & 0xFF]
t2 = ["NULL", "POINTER", "FUNC", "ARRAY"][self.Type >> 8]
return (t1, t2)
except IndexError:
logger.warning("invalid Type field (was: %d)" % self.Type)
def classname(self):
c = {
0xFF: "END_OF_FUNCTION",
0: "NULL",
1: "AUTOMATIC",
2: "EXTERNAL",
3: "STATIC",
4: "REGISTER",
5: "EXTERNAL_DEF",
6: "LABEL",
7: "UNDEFINED_LABEL",
8: "MEMBER_OF_STRUCT",
9: "ARGUMENT",
10: "STRUCT_TAG",
11: "MEMBER_OF_UNION",
12: "UNION_TAG",
13: "TYPE_DEF",
14: "UNDEFINED_STATIC",
15: "ENUM_TAG",
16: "MEMBER_OF_ENUM",
17: "REGISTER_PARAM",
18: "BITFIELD",
100: "BLOCK",
101: "FUNCTION",
102: "END_OF_STRUCT",
103: "FILE",
104: "SECTION",
105: "WEAK_EXTERNAL",
107: "CLR_TOKEN",
}.get(self.StorageClass, None)
return c
[docs]@StructDefine(
"""
s*18 : data
"""
)
class AuxSymbolRecord(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
I : TagIndex
I : TotalSize
I : PointerToLineNumber
I : PointerToNextFunction
x*2: unused
"""
)
class AuxFunctionDefinition(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
x*4: unused
H*6: LineNumber
x : unused
I : PointerToNextFunction
x*2: unused
"""
)
class Aux_bf_ef(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
I : TagIndex
I : Characteristics
x*10: unused
"""
)
class AuxWeakExternal(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
s*18 : Filename
"""
)
class AuxFile(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
[docs]@StructDefine(
"""
I : length
H : NumberOfRelocations
H : NumberOfLineNumbers
I : Checksum
H : Number
B : Selection
x*3 : unused
"""
)
class AuxSectionDefinition(StructFormatter):
def __init__(self, data=None, offset=0):
if data:
self.unpack(data, offset)
# COFF String table
# ------------------------------------------------------------------------------
class COFFStringTable(object):
def __init__(self, data=None):
if data is None:
data = struct.pack("I", 4)
self.length = struct.unpack("I", data[:4])
self.strings = data[4 : self.length].split("\0")
self.strings.pop()
# ------------------------------------------------------------------------------
class AttributeCertificateTable(object):
NotImplementedError
[docs]class AttributeCertificate(StructFormatter):
pass
# ------------------------------------------------------------------------------
class DelayLoadImportTable(object):
def __init__(self, data):
raise NotImplementedError
[docs]@StructDefine(
"""
I : Attributes
I : Name
I : ModuleHandle
I : DelayImportAddressTable
I : DelayImportNameTable
I : BoundDelayImportTable
I : UnloadDelayImportTable
I : TimeStamp
"""
)
class DelayLoadDirectoryTable(StructFormatter):
def __init__(self, data=None, offset=0):
self.func_formatter(TimeStamp=token_datetime_fmt)
if data:
self.unpack(data, offset)
# ------------------------------------------------------------------------------
[docs]@StructDefine(
"""
I : Flags
I : TimeStamp
H : MajorVersion
H : MinorVersion
I : NameRVA
I : OrdinalBase
I : AddressTableEntries
I : NumberOfNamePointers
I : ExportAddressTableRVA
I : NamePointerRVA
I : OrdinalTableRVA
"""
)
class ExportTable(StructFormatter):
def __init__(self, data=None, offset=0):
self.func_formatter(TimeStamp=token_datetime_fmt)
self.address_formatter("OrdinalBase", "NameRVA")
self.address_formatter("ExportAddressTableRVA", "NamePointerRVA")
self.address_formatter("OrdinalTableRVA")
if data:
self.unpack(data, offset)
# ------------------------------------------------------------------------------
class ImportTable(object):
def __init__(self, data, offset=0):
self.dlls = []
e = None
while len(data) > 0:
try:
e = ImportTableEntry(data, offset)
except StructureError:
logger.error("bad ImportTableEntry at offset=%d"%offset)
return
else:
if e.isNULL():
return
self.dlls.append(e)
offset += len(e)
logger.warning("NULL Import entry not found")
[docs]@StructDefine(
"""
I : ImportLookupTableRVA
I : TimeStamp
I : ForwarderChain
I : NameRVA
I : ImportAddressTableRVA
"""
)
class ImportTableEntry(StructFormatter):
def __init__(self, data=None, offset=0):
self.address_formatter("ImportLookupTableRVA", "NameRVA")
self.address_formatter("ImportAddressTableRVA")
self.func_formatter(TimeStamp=token_datetime_fmt)
if data:
self.unpack(data, offset)
def isNULL(self):
res = 0
for k in self.fields:
res = res | getattr(self, k.name)
return res == 0
class ImportLookupTable(object):
def __init__(self, data, magic):
size = {0x20B: 64, 0x10B: 32}[magic]
self.elsize = size // 8
self.fmt = "Q" if size == 64 else "I"
self.readimports(data)
def readimports(self, data):
self.imports = []
fshift = (self.elsize * 8) - 1
while len(data) >= self.elsize:
v = struct.unpack(self.fmt, data[: self.elsize])[0]
if v == 0:
return
flag = v >> fshift
if flag == 1:
self.imports.append([flag, v & 0xFFFF])
elif flag == 0:
self.imports.append([flag, v & 0x7FFFFFFF])
data = data[self.elsize :]
class NameTableEntry(object):
def __init__(self, data):
hint = struct.unpack("H", data[:2])
s, _, _ = data[2:].partition(b"\0")
self.hint = hint
self.symbol = str(s.decode())
# ------------------------------------------------------------------------------
[docs]@StructDefine(
"""
I : RawDataStartVA
I : RawDataEndVA
I : AddressOfIndex
I : AddressOfCallbacks
I : SizeOfZeroFill
I : Characteristics
"""
)
class TLSTable(StructFormatter):
def __init__(self, data, magic):
self.address_formatter("RawDataStartVA", "RawDataEndVA")
size = {0x20B: 64, 0x10B: 32}[magic]
self.elsize = size // 8
if magic == 0x20B:
for f in self.fields:
f.typename = "Q"
if data:
self.unpack(data)
def readcallbacks(self, data):
self.callbacks = []
while len(data) >= self.elsize:
v = struct.unpack(self.fields[0].typename, data[: self.elsize])[0]
if v == 0:
return
self.callbacks.append(v)
data = data[self.elsize :]
# ------------------------------------------------------------------------------
with Consts("GuardFlags"):
IMAGE_GUARD_CF_INSTRUMENTED = 0x00000100
IMAGE_GUARD_CFW_INSTRUMENTED = 0x00000200
IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT = 0x00000400
IMAGE_GUARD_SECURITY_COOKIE_UNUSED = 0x00000800
IMAGE_GUARD_PROTECT_DELAYLOAD_IAT = 0x00001000
IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x00002000
IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x00004000
IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION = 0x00008000
IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT = 0x00010000
IMAGE_GUARD_RF_INSTRUMENTED = 0x00020000
IMAGE_GUARD_RF_ENABLE = 0x00040000
IMAGE_GUARD_RF_STRICT = 0x00080000
IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000
IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT = 28
[docs]@StructDefine(
"""
I : Characteristics
I : TimeDateStamp
H : MajorVersion
H : MinorVersion
I : GlobalFlagsClear
I : GlobalFlagsSet
I : CriticalSectionDefaultTimeout
I : DeCommitFreeBlockThreshold
I : DeCommitTotalFreeThreshold
I : LockPrefixTable
I : MaximumAllocationSize
I : VirtualMemoryThreshold
I : ProcessAffinityMask
I : ProcessHeapFlags
H : CSDVersion
H : reserved
I : EditList
I : SecurityCookie
I : SEHandlerTable
I : SEHandlerCount
I : GuardCFCheckFunctionPointer
I : GuardCFDispatchFunctionPointer
I : GuardCFFunctionTable
I : GuardCFFunctionCount
I : GuardFlags
I*3: CodeIntegrity
I : GuardAddressTakenIatEntryTable
I : GuardAddressTakenIatEntryCount
I : GuardLongJumpTargetTable
I : GuardLongJumpTargetCount
"""
)
class LoadConfigTable(StructFormatter):
def __init__(self, data, magic):
size = {0x20B: 64, 0x10B: 32}[magic]
self.elsize = size // 8
if magic == 0x20B:
for i in (7,8,9,10,11,12,16,17,18,19,20,21,22,23,
26,27,28,29):
self.fields[i].typename = "Q"
self.flag_formatter("Characteristics",
"ProcessHeapFlags",
"GuardFlags",
)
if data:
self.unpack(data)