from filetypes.base import *
from filetypes.ordinals import OrdinalTranslator
from filetypes.P7X import parse_der_certificate
import malcat
import datetime
import struct

def align(val, what, down=False):
    if not what:
        return val
    if val % what:
        if down:
            val -= val % what
        else:
            val += what - (val % what)
    return val

MAX_DYNAMIC_STRUCTURE_LENGTH = 4096


class MZ(Struct):

    def parse(self):
        yield String(2, name="Signature", comment="MZ signature")
        yield UInt16(name="LastSize", comment="size of last page of file")
        yield UInt16(name="NumberOfBlocks", comment="number of pages in file")
        yield UInt16(name="NumberOfRelocs", comment="number of relocations")
        yield UInt16(name="HeaderSize", comment="size of header (in 16 bytes paragraphs)")
        yield UInt16(name="MinimumAllocation", comment="minimum extra paragraphs needed")
        yield UInt16(name="MaximumAllocation", comment="maximum extra paragraphs needed")
        yield UInt16(name="InitialSS", comment="initial ss value (relative)")
        yield UInt16(name="InitialSP", comment="inital sp value")
        yield UInt16(name="Checksum", comment="file checksum")
        yield UInt16(name="InitialIP", comment="inital ip value")
        yield UInt16(name="InitialCS", comment="inital cs value")
        yield UInt16(name="RelocPosition", comment="file offset to relocation table")
        yield UInt16(name="OverlayNumber", comment="overlay number")
        yield Unused(8, name="Reserved", comment="reserved")
        yield UInt16(name="OEMid", comment="OEM identifier")
        yield UInt16(name="OEMinfos", comment="OEM-specific information")
        yield Unused(20, name="Reserved2", comment="reserved")
        yield UInt32(name="AddressOfPE", comment="file offset to PE header")
            
RICH_TOOL_ID = [
('Unknown',0x0000), ('Import',0x0001), ('Linker',0x0002), ('Cvtomf',0x0003), ('Linker',0x0004), ('Cvtomf',0x0005), ('Cvtres',0x0006), ('Basic',0x0007), ('C',0x0008), ('Basic',0x0009), 
('C',0x000a), ('CPP',0x000b), ('AliasObj',0x000c), ('VisualBasic',0x000d), ('Masm',0x000e), ('Masm',0x000f), ('Linker',0x0010), ('Cvtomf',0x0011), ('Masm',0x0012), ('Linker',0x0013), 
('Cvtomf',0x0014), ('C_Std',0x0015), ('CPP_Std',0x0016), ('C_Book',0x0017), ('CPP_Book',0x0018), ('Implib',0x0019), ('Cvtomf',0x001a), ('Basic',0x001b), ('C',0x001c), ('CPP',0x001d), 
('Linker',0x001e), ('Cvtomf',0x001f), ('Linker',0x0020), ('Cvtomf',0x0021), ('Basic',0x0022), ('C',0x0023), ('CPP',0x0024), ('Linker',0x0025), ('Cvtomf',0x0026), ('AliasObj',0x0027), 
('Linker',0x0028), ('Cvtomf',0x0029), ('Masm',0x002a), ('LTCG_C',0x002b), ('LTCG_CPP',0x002c), ('Masm',0x002d), ('ILAsm',0x002e), ('Basic',0x002f), ('C',0x0030), ('CPP',0x0031), 
('C_Std',0x0032), ('CPP_Std',0x0033), ('C_Book',0x0034), ('CPP_Book',0x0035), ('Implib',0x0036), ('Cvtomf',0x0037), ('Cvtres',0x0038), ('C_Std',0x0039), ('CPP_Std',0x003a), ('Cvtpgd',0x003b), 
('Linker',0x003c), ('Linker',0x003d), ('Export',0x003e), ('Export',0x003f), ('Masm',0x0040), ('POGO_I_C',0x0041), ('POGO_I_CPP',0x0042), ('POGO_O_C',0x0043), ('POGO_O_CPP',0x0044), ('Cvtres',0x0045), 
('Cvtres',0x0046), ('Linker',0x0047), ('Cvtomf',0x0048), ('Export',0x0049), ('Implib',0x004a), ('Masm',0x004b), ('C',0x004c), ('CPP',0x004d), ('C_Std',0x004e), ('CPP_Std',0x004f), 
('LTCG_C',0x0050), ('LTCG_CPP',0x0051), ('POGO_I_C',0x0052), ('POGO_I_CPP',0x0053), ('POGO_O_C',0x0054), ('POGO_O_CPP',0x0055), ('Linker',0x0056), ('Cvtomf',0x0057), ('Export',0x0058), ('Implib',0x0059), 
('Linker',0x005a), ('Cvtomf',0x005b), ('Export',0x005c), ('Implib',0x005d), ('Cvtres',0x005e), ('C',0x005f), ('CPP',0x0060), ('C_Std',0x0061), ('CPP_Std',0x0062), ('LTCG_C',0x0063), 
('LTCG_CPP',0x0064), ('POGO_I_C',0x0065), ('POGO_I_CPP',0x0066), ('POGO_O_C',0x0067), ('POGO_O_CPP',0x0068), ('AliasObj',0x0069), ('AliasObj',0x006a), ('Cvtpgd',0x006b), ('Cvtpgd',0x006c), ('C',0x006d), 
('CPP',0x006e), ('C_Std',0x006f), ('CPP_Std',0x0070), ('LTCG_C',0x0071), ('LTCG_CPP',0x0072), ('POGO_I_C',0x0073), ('POGO_I_CPP',0x0074), ('POGO_O_C',0x0075), ('POGO_O_CPP',0x0076), ('Cvtpgd',0x0077), 
('Linker',0x0078), ('Cvtomf',0x0079), ('Export',0x007a), ('Implib',0x007b), ('Cvtres',0x007c), ('Masm',0x007d), ('AliasObj',0x007e), ('CIL_C',0x0080), ('CIL_CPP',0x0081), ('LTCG_MSIL',0x0082), 
('C',0x0083), ('CPP',0x0084), ('C_Std',0x0085), ('CPP_Std',0x0086), ('CIL_C',0x0087), ('CIL_CPP',0x0088), ('LTCG_C',0x0089), ('LTCG_CPP',0x008a), ('LTCG_MSIL',0x008b), ('POGO_I_C',0x008c), 
('POGO_I_CPP',0x008d), ('POGO_O_C',0x008e), ('POGO_O_CPP',0x008f), ('Cvtpgd',0x0090), ('Linker',0x0091), ('Export',0x0092), ('Implib',0x0093), ('Cvtres',0x0094), ('Masm',0x0095), ('AliasObj',0x0096), 
('Resource',0x0097), ('AliasObj',0x0098), ('Cvtpgd',0x0099), ('Cvtres',0x009a), ('Export',0x009b), ('Implib',0x009c), ('Linker',0x009d), ('Masm',0x009e), ('C',0x009f), ('CPP',0x00a0), 
('CIL_C',0x00a1), ('CIL_CPP',0x00a2), ('LTCG_C',0x00a3), ('LTCG_CPP',0x00a4), ('LTCG_MSIL',0x00a5), ('POGO_I_C',0x00a6), ('POGO_I_CPP',0x00a7), ('POGO_O_C',0x00a8), ('POGO_O_CPP',0x00a9), ('C',0x00aa), 
('CPP',0x00ab), ('CIL_C',0x00ac), ('CIL_CPP',0x00ad), ('LTCG_C',0x00ae), ('LTCG_CPP',0x00af), ('LTCG_MSIL',0x00b0), ('POGO_I_C',0x00b1), ('POGO_I_CPP',0x00b2), ('POGO_O_C',0x00b3), ('POGO_O_CPP',0x00b4), 
('AliasObj',0x00b5), ('Cvtpgd',0x00b6), ('Cvtres',0x00b7), ('Export',0x00b8), ('Implib',0x00b9), ('Linker',0x00ba), ('Masm',0x00bb), ('C',0x00bc), ('CPP',0x00bd), ('CIL_C',0x00be), 
('CIL_CPP',0x00bf), ('LTCG_C',0x00c0), ('LTCG_CPP',0x00c1), ('LTCG_MSIL',0x00c2), ('POGO_I_C',0x00c3), ('POGO_I_CPP',0x00c4), ('POGO_O_C',0x00c5), ('POGO_O_CPP',0x00c6), ('AliasObj',0x00c7), ('Cvtpgd',0x00c8), 
('Cvtres',0x00c9), ('Export',0x00ca), ('Implib',0x00cb), ('Linker',0x00cc), ('Masm',0x00cd), ('C',0x00ce), ('CPP',0x00cf), ('CIL_C',0x00d0), ('CIL_CPP',0x00d1), ('LTCG_C',0x00d2), 
('LTCG_CPP',0x00d3), ('LTCG_MSIL',0x00d4), ('POGO_I_C',0x00d5), ('POGO_I_CPP',0x00d6), ('POGO_O_C',0x00d7), ('POGO_O_CPP',0x00d8), ('AliasObj',0x00d9), ('Cvtpgd',0x00da), ('Cvtres',0x00db), ('Export',0x00dc), 
('Implib',0x00dd), ('Linker',0x00de), ('Masm',0x00df), ('C',0x00e0), ('CPP',0x00e1), ('CIL_C',0x00e2), ('CIL_CPP',0x00d3), ('LTCG_C',0x00e4), ('LTCG_CPP',0x00e5), ('LTCG_MSIL',0x00e6), 
('POGO_I_C',0x00e7), ('POGO_I_CPP',0x00e8), ('POGO_O_C',0x00e9), ('POGO_O_CPP',0x00ea), ('AliasObj',0x00eb), ('Cvtpgd',0x00ec), ('Cvtres',0x00ed), ('Export',0x00ee), ('Implib',0x00ef), ('Linker',0x00f0), 
('Masm',0x00f1), ('C',0x00f2), ('CPP',0x00f3), ('CIL_C',0x00f4), ('CIL_CPP',0x00f5), ('LTCG_C',0x00f6), ('LTCG_CPP',0x00f7), ('LTCG_MSIL',0x00f8), ('POGO_I_C',0x00f9), ('POGO_I_CPP',0x00fa), 
('POGO_O_C',0x00fb), ('POGO_O_CPP',0x00fc), ('AliasObj',0x00fd), ('Cvtpgd',0x00fe), ('Cvtres',0x00ff), ('Export',0x0100), ('Implib',0x0101), ('Linker',0x0102), ('Masm',0x0103), ('C',0x0104), 
('CPP',0x0105), ('CIL_C',0x0106), ('CIL_CPP',0x0107), ('LTCG_C',0x0108), ('LTCG_CPP',0x0109), ('LTCG_MSIL',0x010a), ('POGO_I_C',0x010b), ('POGO_I_CPP',0x010c), ('POGO_O_C',0x010d), ('POGO_O_CPP',0x010e), 
        ] 

class RichEntry(Struct): 
    
    def __init__(self, xorkey, **kwargs): 
        Struct.__init__(self, **kwargs) 
        self.xorkey = xorkey 
        
    def parse(self):
        key32 = self.xorkey
        key16_1 = key32 & 0xFFFF
        key16_2 = key32 >> 16
        yield UInt16Xor(key16_1, name="Version", comment="Internal build ID of tool")
        yield UInt16Xor(key16_2, name="Tool", comment="Id of tool", values=RICH_TOOL_ID)
        yield UInt32Xor(key32, name="NumberOfUses", comment="Number of uses of the tool")


class RichHeader(Struct):

    def __init__(self, xorkey, size, **kwargs):
        Struct.__init__(self, **kwargs)
        self.xorkey = xorkey
        self.size = size

    def parse(self):
        sig = yield UInt32Xor(self.xorkey, name="Signature", comment="DanS")
        yield Unused(12, name="Padding")
        left = self.size - (len(self) + 8)
        if left > 0:
            yield Array(left//8, RichEntry(self.xorkey), name="ToolsList")
        yield String(4, zero_terminated=False, name="Rich", comment="Rich signature")
        yield UInt32(name="XorKey", comment="xor key")


class PE(Struct):

    def parse(self):
        sig = yield String(4, name="Signature", comment="PE signature")
        if sig != "PE\x00\x00":
            raise FatalError("Invalid PE signature")
        yield UInt16(name="Machine", comment="supported architecture", values=[
            ("IMAGE_FILE_MACHINE_UNKNOWN", 0x0),
            ("IMAGE_FILE_MACHINE_AM33", 0x1d3),
            ("IMAGE_FILE_MACHINE_AMD64", 0x8664),
            ("IMAGE_FILE_MACHINE_ARM", 0x1c0),
            ("IMAGE_FILE_MACHINE_ARM64", 0xaa64),
            ("IMAGE_FILE_MACHINE_ARMNT", 0x1c4),
            ("IMAGE_FILE_MACHINE_EBC", 0xebc),
            ("IMAGE_FILE_MACHINE_I386", 0x14c),
            ("IMAGE_FILE_MACHINE_IA64", 0x200),
            ("IMAGE_FILE_MACHINE_M32R", 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_RISCV32", 0x5032),
            ("IMAGE_FILE_MACHINE_RISCV64", 0x5064),
            ("IMAGE_FILE_MACHINE_RISCV128", 0x5128),
            ("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),
            ])
        yield UInt16(name="NumberOfSections", comment="number of sections")
        yield Timestamp(name="TimeDateStamp", comment="file creation time")
        yield Rva(name="PointerToSymbolTable", comment="pointer to symbol table")
        yield UInt32(name="NumberOfSymbols", comment="number of symbols")
        yield UInt16(name="SizeOfOptionalHeader", comment="size of optional header")
        yield BitsField(
            Bit(name="RelocsStripped", comment="the file does not contain base relocations and must therefore be loaded at its preferred base"),
            Bit(name="ExecutableImage", comment="the image file is valid and can be run"),
            Bit(name="LineNumbersStripped", comment="COFF line numbers have been removed. This flag is deprecated and should be zero"),
            Bit(name="LocalSymbolsStripped", comment="COFF symbol table entries for local symbols have been removed. This flag is deprecated and should be zero"),
            Bit(name="AggresiveWsTrim", comment="aggressively trim working set. This flag is deprecated for Windows 2000 and later and must be zero."),
            Bit(name="LargeAddressAware", comment="application can handle > 2-GB addresses."),
            NullBits(1),
            Bit(name="BytesReversedLo", comment="the least significant bit (LSB) precedes the most significant bit (MSB) in memory. This flag is deprecated and should be zero"),
            Bit(name="32BitsMachine", comment="based on a 32-bit-word architecture"),
            Bit(name="DebugStripped", comment="debugging information is removed from the image file"),
            Bit(name="RemovableRunFromSwap", comment="if the image is on removable media, fully load it and copy it to the swap file"),
            Bit(name="NetRunFromSwap", comment="if the image is on network media, fully load it and copy it to the swap file"),
            Bit(name="SytemFile", comment="the image file is a system file, not a user program"),
            Bit(name="Dll", comment="image file is a dynamic-link library (DLL)."),
            Bit(name="SystemOnly", comment="file should be run only on a uniprocessor machine"),
            Bit(name="BytesReversedHi", comment="the MSB precedes the LSB in memory. This flag is deprecated and should be zero"),
            name="Characteristics", comment="file characteristics")


class OptionalHeader32(Struct):

    def parse(self):
        magic = yield UInt16(name="Magic", comment="magic", values=[
            ("PE32", 0x10b),
            ("ROM", 0x107),
            ("PE32+", 0x20b),
            ])
        if magic != 0x010B and magic != 0x0107:
            raise FatalError("Invalid magic value: {:x}".format(magic))
        yield UInt8(name="MajorLinkerVersion", comment="linker version (major)")
        yield UInt8(name="MinorLinkerVersion", comment="linker version (minor)")
        yield UInt32(name="SizeOfCode", comment="cumulated size of all code sections")
        yield UInt32(name="SizeOfInitializedData", comment="cumulated size of all initialized data sections")
        yield UInt32(name="SizeOfUninitializedData", comment="cumulated size of all uninitialized data sections")
        yield Rva(name="AddressOfEntryPoint", zero_is_invalid=True, comment="rva of module entry point")
        yield Rva(name="BaseOfCode", zero_is_invalid=True, comment="rva of first code section")
        yield Rva(name="BaseOfData", zero_is_invalid=True, comment="rva of first data section")
        yield Va32(name="ImageBase", comment="preferred loading address")
        yield UInt32(name="SectionAlignment", comment="alignment of sections in memory")
        yield UInt32(name="FileAlignment", comment="alignment of sections on the disk")
        yield UInt16(name="MajorOperatingSystemVersion", comment="required OS version (major)")
        yield UInt16(name="MinorOperatingSystemVersion", comment="required OS version (minor)")
        yield UInt16(name="MajorImageVersion", comment="image version (major)")
        yield UInt16(name="MinorImageVersion", comment="image version (minor)")
        yield UInt16(name="MajorSubsystemVersion", comment="subsystem version (major)")
        yield UInt16(name="MinorSubsystemVersion", comment="subsystem version (minor)")
        yield UInt32(name="Win32VersionValue", comment="???")
        yield UInt32(name="SizeOfImage", comment="size of module once loaded in memory")
        yield UInt32(name="SizeOfHeaders", comment="cumulated size of all headers")
        yield UInt32(name="Checksum", comment="checksum")
        yield UInt16(name="Subsystem", comment="subsystem", values=[
            ("IMAGE_SUBSYSTEM_UNKNOWN", 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),
            ])
        yield BitsField(
            NullBits(5),
            Bit(name="HighEntropyVA", comment="image can handle a high entropy 64-bit virtual address space"),
            Bit(name="DynamicBase", comment="DLL can be relocated at load time"),
            Bit(name="ForceIntegrity", comment="code Integrity checks are enforced"),
            Bit(name="NXcompat", comment="image is NX compatible"),
            Bit(name="NoIsolation", comment="isolation aware, but do not isolate the image"),
            Bit(name="NoSEH", comment="does not use Structured Exception Handling"),
            Bit(name="NoBind", comment="do not bind the image"),
            Bit(name="AppContainer", comment="image must execute in an AppContainer"),
            Bit(name="WDMDriver", comment="image is a WDM driver"),
            Bit(name="ControlFlowGuard", comment="image supports control flow guard"),
            Bit(name="TerminalServerAware", comment="terminal server aware"),
            name="DllCharacteristics", comment="pe characteristics")
        yield UInt32(name="SizeOfStackReserve", comment="maximum size of stack")
        yield UInt32(name="SizeOfStackCommit", comment="initial size of stack")
        yield UInt32(name="SizeOfHeapReserve", comment="maximum size of heap")
        yield UInt32(name="SizeOfHeapCommit", comment="initial size of heap")
        yield UInt32(name="LoaderFlags", comment="reserved, must be zero")
        num_dd = yield UInt32(name="NumberOfRvaAndSizes", comment="number of entries in datadirectory")
        if self.parser["PE"]["SizeOfOptionalHeader"]:
            if not num_dd:
                num_dd = (self.parser["PE"]["SizeOfOptionalHeader"] - len(self)) // 8
            else:
                num_dd = min(num_dd, (self.parser["PE"]["SizeOfOptionalHeader"] - len(self)) // 8)
        if num_dd <= 0:
            num_dd = 1
        yield DataDirectory(num_dd)

class OptionalHeader64(Struct):

    def parse(self):
        magic = yield UInt16(name="Magic", comment="magic", values=[
            ("PE32", 0x10b),
            ("ROM", 0x107),
            ("PE32+", 0x20b),
            ])
        if magic != 0x020B:
            raise FatalError("Invalid magic value: {:x}".format(magic))
        yield UInt8(name="MajorLinkerVersion", comment="linker version (major)")
        yield UInt8(name="MinorLinkerVersion", comment="linker version (minor)")
        yield UInt32(name="SizeOfCode", comment="cumulated size of all code sections")
        yield UInt32(name="SizeOfInitializedData", comment="cumulated size of all initialized data sections")
        yield UInt32(name="SizeOfUninitializedData", comment="cumulated size of all uninitialized data sections")
        yield Rva(name="AddressOfEntryPoint", zero_is_invalid=True, comment="rva of module entry point")
        yield Rva(name="BaseOfCode", zero_is_invalid=True, comment="rva of first code section")
        yield Va64(name="ImageBase", comment="preferred loading address")
        yield UInt32(name="SectionAlignment", comment="alignment of sections in memory")
        yield UInt32(name="FileAlignment", comment="alignment of sections on the disk")
        yield UInt16(name="MajorOperatingSystemVersion", comment="required OS version (major)")
        yield UInt16(name="MinorOperatingSystemVersion", comment="required OS version (minor)")
        yield UInt16(name="MajorImageVersion", comment="image version (major)")
        yield UInt16(name="MinorImageVersion", comment="image version (minor)")
        yield UInt16(name="MajorSubsystemVersion", comment="subsystem version (major)")
        yield UInt16(name="MinorSubsystemVersion", comment="subsystem version (minor)")
        yield UInt32(name="Win32VersionValue", comment="???")
        yield UInt32(name="SizeOfImage", comment="size of module once loaded in memory")
        yield UInt32(name="SizeOfHeaders", comment="cumulated size of all headers")
        yield UInt32(name="Checksum", comment="checksum")
        yield UInt16(name="Subsystem", comment="subsystem", values=[
            ("IMAGE_SUBSYSTEM_UNKNOWN", 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),
            ])
        yield BitsField(
            NullBits(5),
            Bit(name="HighEntropyVA", comment="image can handle a high entropy 64-bit virtual address space"),
            Bit(name="DynamicBase", comment="DLL can be relocated at load time"),
            Bit(name="ForceIntegrity", comment="code Integrity checks are enforced"),
            Bit(name="NXcompat", comment="image is NX compatible"),
            Bit(name="NoIsolation", comment="isolation aware, but do not isolate the image"),
            Bit(name="NoSEH", comment="does not use Structured Exception Handling"),
            Bit(name="NoBind", comment="do not bind the image"),
            Bit(name="AppContainer", comment="image must execute in an AppContainer"),
            Bit(name="WDMDriver", comment="image is a WDM driver"),
            Bit(name="ControlFlowGuard", comment="image supports control flow guard"),
            Bit(name="TerminalServerAware", comment="terminal server aware"),
            name="DllCharacteristics", comment="pe characteristics")
        yield UInt64(name="SizeOfStackReserve", comment="maximum size of stack")
        yield UInt64(name="SizeOfStackCommit", comment="initial size of stack")
        yield UInt64(name="SizeOfHeapReserve", comment="maximum size of heap")
        yield UInt64(name="SizeOfHeapCommit", comment="initial size of heap")
        yield UInt32(name="LoaderFlags", comment="???")
        num_dd = yield UInt32(name="NumberOfRvaAndSizes", comment="number of entries in datadirectory")
        if num_dd == 0:
            raise FatalError("invalid NumberOfRvaAndSize: {}".format(num_dd))
        num_dd = min(num_dd, (self.parser["PE"]["SizeOfOptionalHeader"] - len(self)) // 8)
        if num_dd <= 0:
            num_dd = 1
        yield DataDirectory(num_dd)


class DataDirectory(Struct):

    def __init__(self, num_dd, **kwargs):
        Struct.__init__(self, **kwargs)
        self.num_dd = num_dd

    def parse(self):
        for name, typ in [
            ("ExportTable", InfoRvaDataDirectory),
            ("ImportTable", InfoRvaDataDirectory),
            ("ResourceDirectory", InfoRvaDataDirectory),
            ("ExceptionTable", InfoRvaDataDirectory),
            ("CertificateTable", InfoOffsetDataDirectory),
            ("RelocationTable", InfoRvaDataDirectory),
            ("DebugData", InfoRvaDataDirectory),
            ("Architecture", InfoRvaDataDirectory),
            ("GlobalPointer", InfoRvaDataDirectory),
            ("ThreadLocalStorage", InfoRvaDataDirectory),
            ("LoadConfiguration", InfoRvaDataDirectory),
            ("BoundImportTable", InfoOffsetDataDirectory),
            ("ImportAddressTable", InfoRvaDataDirectory),
            ("DelayImportDescriptor", InfoRvaDataDirectory),
            ("DotnetHeader", InfoRvaDataDirectory),
            ("Unused", InfoRvaDataDirectory),
            ]:
            yield typ(name=name)

class InfoRvaDataDirectory(StaticStruct):

    @classmethod
    def parse(cls):
        yield Rva(name="Rva")
        yield UInt32(name="Size")


class InfoOffsetDataDirectory(StaticStruct):

    @classmethod
    def parse(cls):
        yield Offset32(name="Offset")
        yield UInt32(name="Size")


class Section(StaticStruct):

    @classmethod
    def parse(cls):
        yield String(8, name="Name", comment="section name")
        yield UInt32(name="VirtualSize", comment="section size in memory")
        yield Rva(name="VirtualAddress", comment="section rva")
        yield UInt32(name="SizeOfRawData", comment="section size on disk")
        yield Offset32(name="PointerToRawData", comment="section file offset")
        yield Offset32(name="PointerToRelocations", zero_is_invalid=True, comment="")
        yield Offset32(name="PointerToLinenumbers", zero_is_invalid=True, comment="")
        yield UInt16(name="NumberOfRelocations", comment="")
        yield UInt16(name="NumberOfLinenumbers", comment="")
        yield BitsField(
            NullBits(3),
            Bit(name="SctTypeNoPad", comment="section should not be padded to the next boundary (this flag is obsolete and is replaced by ScnAlign1)"),
            NullBits(1),
            Bit(name="SctCntCode", comment="section contains executable code"),
            Bit(name="SctCntInitializedData", comment="section contains initialized data"),
            Bit(name="SctCntUnititializedData", comment="section contains uninitialized data"),
            Bit(name="SctLnkOther", comment="for future use"),
            Bit(name="ScnLnkInfo", comment="section contains comments or other information, valid only for object files"),
            NullBits(1),
            Bit(name="ScnLnkRemove", comment="section will not become part of the image, valid only for object files"),
            Bit(name="ScnLnkComdat", comment="section contains COMDAT data, valid only for object files"),
            NullBits(2),
            Bit(name="ScnGprel", comment="section contains data referenced through the global pointer (GP)"),
            Bit(name="MemPurgeable", comment="for future use"),
            Bit(name="Mem16bit", comment="for future use"),
            Bit(name="MemLocked", comment="for future use"),
            Bit(name="MemPreload", comment="for future use"),
            Bit(name="ScnAlign1", comment="align data on an 1-byte boundary, valid only for object files"),
            Bit(name="ScnAlign2", comment="align data on an 2-byte boundary, valid only for object files"),
            Bit(name="ScnAlign8", comment="align data on an 8-byte boundary, valid only for object files"),
            Bit(name="ScnAlign128", comment="align data on an 128-byte boundary, valid only for object files"),
            Bit(name="LinkRelocOverflow ", comment="section contains extended relocations"),
            Bit(name="MemDiscardable", comment="section can be discarded as needed"),
            Bit(name="MemNotCached", comment="section cannot be cached"),
            Bit(name="MemNotPaged", comment="section is not pageable"),
            Bit(name="MemShared", comment="section can be shared in memory"),
            Bit(name="MemExecute", comment="section can be executed as code"),
            Bit(name="MemRead", comment="section can be read"),
            Bit(name="MemWrite", comment="section can be written to"),
            name="Characteristics", comment="section characteristics")


class ExportDirectory(Struct):        
    def parse(self):
        yield UInt32(name="Flags", comment="reserved, must be 0")
        yield Timestamp(name="TimeDateStamp", comment="timedatestamp")
        yield UInt16(name="MajorVersion", comment="major version number")
        yield UInt16(name="MinorVersion", comment="minor version number")
        yield Rva(name="Name", hint=String(0, True), comment="rva to dll name")
        yield UInt32(name="OrdinalBase", comment="specifies the starting ordinal number for the export address table, usually set to 1")
        yield UInt32(name="AddressTableEntries", comment="number of entries in the export address table")
        yield UInt32(name="NameTableEntries", comment="number of entries in the name pointer table")
        yield Rva(name="AddressTable", zero_is_invalid=True, comment="export address table rva")
        yield Rva(name="NameTable", zero_is_invalid=True, comment="export address table rva")
        yield Rva(name="OrdinalTable", zero_is_invalid=True, comment="export ordinal table rva")


class ExceptionEntryX64(StaticStruct):

    @classmethod
    def parse(cls):
        yield Rva(name="Begin", comment="start of function")
        yield Rva(name="End", comment="end of function")
        yield Rva(name="Information", comment="unwind information")


class BoundImportTable(Struct):

    def __init__(self, max_size, **kwargs):
        Struct.__init__(self, **kwargs)
        self.max_size = max_size

    def parse(self):
        while (len(self) < self.max_size):
            next_8 = self.look_ahead(8)
            if next_8 == b"\x00" * 8:
                yield Unused(8, name="Terminator")
                break
            else:
                yield BoundImportDescriptor(self.offset, self.max_size - len(self))

class BoundImportDescriptor(Struct):

    def __init__(self, base, max_size, **kwargs):
        Struct.__init__(self, **kwargs)
        self.base = base
        self.max_size = max_size

    def parse(self):
        yield Timestamp(name="Date", comment="time date stamp of the imported DLL")
        yield Offset16(name="OffsetModuleName", base=self.base, hint=String(0, True), comment="offset to a string with the name of the imported DLL. It’s an offset from the first IMAGE_BOUND_IMPORT_DESCRIPTOR")
        num_refs = yield UInt16(name="NumberOfModuleForwarderRefs", comment="number of the IMAGE_BOUND_FORWARDER_REF structures that immediately follow this structure")
        num_refs = min(num_refs, (self.max_size - len(self)) // 8)
        if num_refs > 0:
            yield Array(num_refs, BoundImportReference(self.base), name="References")


class BoundImportReference(Struct):

    def __init__(self, base, **kwargs):
        Struct.__init__(self, **kwargs)
        self.base = base

    def parse(self):
        yield Timestamp(name="Date", comment="time date stamp of the imported DLL")
        yield Offset16(name="OffsetModuleName", base=self.base, hint=String(0, True), comment="offset to a string with the name of the imported DLL. It’s an offset from the first IMAGE_BOUND_IMPORT_DESCRIPTOR")
        yield Unused(2)


class DelayImportDescriptor(Struct):
    def __init__(self, use_rva=False, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.use_rva = use_rva

    def parse(self):
        if self.use_rva:
            ptr = Rva
        else:
            ptr = Va32
        yield BitsField(
            Bit(name="UseRva", comment="new format used, pointers are rvas"),
            NullBits(31),
            name="Attributes")
        yield ptr(name="Name", hint=String(0, True), comment="rva to dll name")
        yield Rva(name="ModuleHandle",comment="RVA of the module handle (in the data section of the image) of the DLL to be delay-loaded. It is used for storage by the routine that is supplied to manage delay-loading")
        yield Rva(name="AddressTable",comment="RVA of the delay-load import address table")
        yield ptr(name="NameTable",comment="RVA of the delay-load name table, which contains the names of the imports that might need to be loaded. This matches the layout of the import name table")
        yield ptr(name="BoundTable",comment="RVA of the bound delay-load address table, if it exists")
        yield ptr(name="UnloadAddressTable",comment="RVA of the unload delay-load address table, if it exists. This is an exact copy of the delay import address table. If the caller unloads the DLL, this table should be copied back over the delay import address table so that subsequent calls to the DLL continue to use the thunking mechanism correctly")
        yield Timestamp(name="TimeDateStamp", comment="timedatestamp of bound dll")


class ImageImportDescriptor(StaticStruct):

    @classmethod
    def parse(cls):
        yield Rva(name="OriginalFirstThunk", zero_is_invalid=True, comment="rva to original list of api names")
        yield Timestamp(name="TimeDateStamp", comment="timedatestamp of bound dll")
        yield UInt32(name="ForwarderChain", comment="-1 if not forwarded")
        yield Rva(name="Name", hint=String(0, True), comment="rva to dll name")
        yield Rva(name="FirstThunk", zero_is_invalid=True, comment="rva to import address table")


class ImageResourceDirectory(Struct):

    def __init__(self, entries, **kwargs):
        Struct.__init__(self, **kwargs)
        self.entries = entries

    def parse(self):
        yield UInt32(name="Characteristics", comment="resource flags (always 0)")
        yield Timestamp(name="TimeDateStamp", comment="creation time of the resource")
        yield UInt16(name="MajorVersion", comment="major version number of resource")
        yield UInt16(name="MinorVersion", comment="minor version number of resource")
        num_named = yield UInt16(name="NumberOfNamedEntries", comment="number of named entries in dir")
        num_id = yield UInt16(name="NumberOfIdEntries", comment="number of ID entries in dir")
        if num_named + num_id > MAX_DYNAMIC_STRUCTURE_LENGTH:
            raise FatalError("Resources too big")
        yield Array(num_id + num_named, self.entries(), name="DirectoryEntries")




class ImageResourceDirectoryEntry2(StaticStruct):

    @classmethod
    def parse(cls):
        yield UInt32(name="Name", comment="ID or offset (relative to start of resources) to name (if high bit is set)")
        yield UInt32(name="OffsetToData", comment="offset (relative to start of resources) to ImageResourceDataEntry")

class ImageResourceDirectoryEntry1(StaticStruct):

    @classmethod
    def parse(cls):
        yield UInt32(name="Type", comment="ID or offset (relative to start of resources) to resource type (if high bit is set)", values=RSRC_ID)
        yield UInt32(name="OffsetToData", comment="offset (relative to start of resources) to ImageResourceDirectory")        

class ImageResourceDirectoryEntry3(StaticStruct):

    @classmethod
    def parse(cls):
        yield LanguageId(name="Language", comment="resource language")
        yield UInt32(name="OffsetToData", comment="offset (relative to start of resources) to ImageResourceDirectory ")        


class ImageResourceDirString(Struct):

    def parse(self):
        num_cars= yield UInt16(name="Length", comment="number of unicode characters")
        yield StringUtf16le(num_cars, zero_terminated=False, name="NameString")

class ImageResourceDataEntry(Struct):

    def parse(self):
        rva = yield Rva(name="OffsetToData", comment="rva to resource data")
        yield UInt32(name="Size", comment="size of resource data")
        yield UInt32(name="CodePage", comment="codepage")
        # Some compilers make the resource starts at Reserved in order to save 4 bytes. 
        # If we don't want structures to overlap, we just omit Reserved then
        off = self.parser.rva2off(rva)
        if off is None or off != self.parser.tell():
            yield UInt32(name="Reserved", comment="")


class Relocations(Struct):

    def parse(self):
        while True:
            rc = yield RelocChunk(name="Chunk")
            if rc["PageRva"] == 0 or rc["BlockSize"] == 0:
                break

class RelocChunk(Struct):

    def parse(self):
        yield Rva(name="PageRva", comment="base rva of relocs")
        bs = yield UInt32(name="BlockSize", comment="size of reloc chunk")
        if bs < 8:
            raise FatalError("Invalid reloc chunk size")
        yield Array((bs - 8) // 2, UInt16(), name="Relocs", comment="list of relocations")


class Region:

    def __init__(self, imagebase, rva, vsize, foff, fsize, section):
        self.va = imagebase + rva
        self.rva = rva
        self.vsize = vsize
        self.foff = foff
        self.fsize = fsize
        self.section = section

    @property
    def x(self):
        return self.section["Characteristics"]["MemExecute"]

    @property
    def w(self):
        return self.section["Characteristics"]["MemWrite"]

    @property
    def r(self):
        return self.section["Characteristics"]["MemRead"]

    @property
    def name(self):
        sname = self.section["Name"]
        si = sname.find("\x00")
        if si >= 0:
            sname = sname[:si]
        return sname


class VersionInfo(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionInfo structure")
        yield UInt16(name="ValueLength", comment="size of Value member")
        yield UInt16(name="Type", comment="type of data in the version resource")
        key = yield CStringUtf16le(name="Key", comment="the unicode string VS_VERSION_INFO")
        if key != "VS_VERSION_INFO":
            raise FatalError("Invalid VersionInfo key {}".format(repr(key)))
        # some malware got the alignment wrong
        yield Align(4)
        yield FixedFileInfo()
        yield Align(4)
        # check for stringfileinfo
        while len(self) < length:
            if len(self) + 6 + 14*2 < length and self.look_ahead(6 + 14*2)[6:] == "StringFileInfo".encode("utf-16-le"):
                try:
                    yield VersionStringFileInfo()
                except ParsingError:
                    pass
            # check for varfileinfo
            elif len(self) + 6 + 11*2 < length and self.look_ahead(6 + 11*2)[6:] == "VarFileInfo".encode("utf-16-le"):
                try:
                    yield VersionVarFileInfo()
                except ParsingError:
                    pass
            else:
                break


class VersionString(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionString structure")
        value_length = yield UInt16(name="ValueLength", comment="size, in words, of the Value member")
        yield UInt16(name="Type", comment="1 if the version resource contains text data and 0 if the version resource contains binary data")
        key = yield CStringUtf16le(name="Key", comment="string name")
        yield Align(4)
        len_words = len(self) + value_length * 2
        len_bytes = len(self) + value_length
        if value_length > 1 and len_words - length > 3 and len_bytes <= length and value_length > 3:
            # some compilers fuck this up and write the length in bytes instead of words
            value_length = value_length // 2
        data = self.look_ahead(value_length * 2)[-2:]
        if data != b"\x00\x00" and len_words < length:
            value = yield CStringUtf16le(name="Value", comment="string value")
        else:
            value = yield StringUtf16le(value_length, name="Value", zero_terminated=True, comment="string value")
        key = key.encode("ascii", errors="ignore").decode("ascii")
        if not key in self.parser.version_metadata_key_seen:
            self.parser.add_metadata(key, value, category="VersionInfo")
            self.parser.version_metadata_key_seen.add(key)
        if length - len(self) > 0:
            yield Unused(length - len(self), name="Overlay", comment="this should not be there")
        yield Align(4)



class VersionStringTable(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionStringTable structure")
        yield UInt16(name="ValueLength", comment="always equal to zero")
        yield UInt16(name="Type", comment="1 if the version resource contains text data and 0 if the version resource contains binary data")
        key = yield StringUtf16le(9, name="Key", zero_terminated=True, comment="8-digit hexadecimal number stored as a Unicode string. The four most significant digits represent the language identifier. The four least significant digits represent the code page for which the data is formatted")
        yield Align(4)
        while len(self) < length:
            yield VersionString(name="String")


class VersionStringFileInfo(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionStringFileInfo structure")
        yield UInt16(name="ValueLength", comment="always equal to zero")
        tp = yield UInt16(name="Type", comment="1 if the version resource contains text data and 0 if the version resource contains binary data")
        key = yield CStringUtf16le(name="Key", comment="the unicode string StringFileInfo")
        if key != "StringFileInfo":
            print("Invalid StringFileInfo key {}".format(repr(key)))
            # still continue
        yield Align(4)
        while len(self) < length:
            yield VersionStringTable(name="VersionStringTable")

class VersionVarFileInfo(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionVarFileInfo structure")
        yield UInt16(name="ValueLength", comment="always equal to zero")
        yield UInt16(name="Type", comment="1 if the version resource contains text data and 0 if the version resource contains binary data")
        key = yield CStringUtf16le(name="Key", comment="the unicode string VarFileInfo")
        if key != "VarFileInfo":
            print("Invalid VarFileInfo key {}".format(repr(key)))
            # still continue
        yield Align(4)
        while len(self) < length:
            yield VersionVar(name="VersionVar")


class VersionVar(Struct):

    def parse(self):
        length = yield UInt16(name="Length", comment="size of VersionVar structure")
        value_length = yield UInt16(name="ValueLength", comment="length, in bytes, of the Value member")
        type = yield UInt16(name="Type", comment="1 if the version resource contains text data and 0 if the version resource contains binary data")
        key = yield CStringUtf16le(name="Key", comment="var name")
        yield Align(4)
        if value_length:
            if type == 1:
                len_words = len(self) + value_length * 2
                len_bytes = len(self) + value_length
                if value_length > 1 and len_words > length and len_bytes <= length:
                    # some compilers fuck this up and write the length in bytes instead of words
                    value_length = value_length // 2
                data = self.look_ahead(value_length * 2)[-2:]
                if data != b"\x00\x00" and len_words < length:
                    value = yield CStringUtf16le(name="Value", comment="var value")
                else:
                    value = yield StringUtf16le(value_length, name="Value", zero_terminated=True, comment="var value")
            else:
                yield Bytes(value_length, name="Value", comment="var value")
                value = None
            if value:
                key = key.encode("ascii", errors="ignore").decode("ascii")
                if not key in self.parser.version_metadata_key_seen:
                    self.parser.version_metadata_key_seen.add(key)
                    self.parser.add_metadata(key, value, category="VersionInfo")
            yield Align(4)


class FixedFileInfo(Struct):

    def parse(self):
        sig = yield UInt32(name="Signature", comment="the value 0xFEEF04BD")
        if sig is not None and sig != 0xFEEF04BD:
            raise FatalError("Invalid fileinfo signature {:x}".format(sig))
        yield UInt16(name="StrucVersionMinor", comment="binary version number of this structure (minor)")
        yield UInt16(name="StrucVersionMajor", comment="binary version number of this structure (major)")
        yield UInt32(name="FileVersionMS", comment="most significant 32 bits of the file's binary version number")
        yield UInt32(name="FileVersionLS", comment="least significant 32 bits of the file's binary version number")
        yield UInt32(name="ProductVersionMS", comment="most significant 32 bits of the binary version number of the product with which this file was distributed")
        yield UInt32(name="ProductVersionLS", comment="least significant 32 bits of the binary version number of the product with which this file was distributed")
        yield UInt32(name="FileFlagsMask", comment="bitmask that specifies the valid bits in FileFlags")
        yield BitsField(
            Bit(name="VS_FF_DEBUG", comment="file contains debugging information or is compiled with debugging features enabled"),
            Bit(name="VS_FF_PRERELEASE", comment="file is a development version, not a commercially released product"),
            Bit(name="VS_FF_PATCHED", comment="file has been modified and is not identical to the original shipping file of the same version number"),
            Bit(name="VS_FF_PRIVATEBUILD", comment="file was not built using standard release procedures. If this flag is set, the StringFileInfo structure should contain a PrivateBuild entry"),
            Bit(name="VS_FF_INFOINFERRED", comment="file's version structure was created dynamically; therefore, some of the members in this structure may be empty or incorrect. This flag should never be set in a file's VS_VERSIONINFO data"),
            Bit(name="VS_FF_SPECIALBUILD", comment="file was built by the original company using standard release procedures but is a variation of the normal file of the same version number. If this flag is set, the StringFileInfo structure should contain a SpecialBuild entry"),
            NullBits(26), name="FileFlags", comment="bitmask that specifies the Boolean attributes of the file"
        )
        yield UInt32(name="FileOS", comment="operating system for which this file was designed", values=[
            ("VOS_DOS", 0x00010000),
            ("VOS_NT", 0x00040000),
            ("VOS_WINDOWS16", 0x00000001),
            ("VOS_WINDOWS32", 0x00000004),
            ("VOS_OS216", 0x00020000),
            ("VOS_OS232", 0x00030000),
            ("VOS_PM16", 0x00000002),
            ("VOS_PM32", 0x00000003),
            ("VOS_UNKNOWN", 0x00000000),
            ("VOS_DOS_WINDOWS16", 0x00010001),
            ("VOS_DOS_WINDOWS32", 0x00010004),
            ("VOS_NT_WINDOWS32", 0x00040004),
            ("VOS_OS216_PM16", 0x00020002),
            ("VOS_OS232_PM32", 0x00030003),
        ])
        yield UInt32(name="FileType", comment="general type of file", values=[
            ("VFT_UNKNOWN", 0x00000000),
            ("VFT_APP", 0x00000001),
            ("VFT_DLL", 0x00000002),
            ("VFT_DRV", 0x00000003),
            ("VFT_FONT", 0x00000004),
            ("VFT_VXD", 0x00000005),
            ("VFT_STATIC_LIB", 0x00000007),
        ])
        yield UInt32(name="FileSubtype", comment="function of the file. The possible values depend on the value of FileType", values = [
            ("VFT2_DRV_COMM", 0x0000000A), 
            ("VFT2_DRV_DISPLAY", 0x00000004), 
            ("VFT2_DRV_INSTALLABLE", 0x00000008), 
            ("VFT2_DRV_KEYBOARD", 0x00000002), 
            ("VFT2_DRV_LANGUAGE", 0x00000003), 
            ("VFT2_DRV_MOUSE", 0x00000005), 
            ("VFT2_DRV_NETWORK", 0x00000006), 
            ("VFT2_DRV_PRINTER", 0x00000001), 
            ("VFT2_DRV_SOUND", 0x00000009), 
            ("VFT2_DRV_SYSTEM", 0x00000007), 
            ("VFT2_DRV_VERSIONED_PRINTER", 0x0000000C), 
            ("VFT2_UNKNOWN", 0x00000000), 
        ])
        yield UInt32(name="FileDateMS", comment="most significant 32 bits of the file's 64-bit binary creation date and time stamp")
        yield UInt32(name="FileDateLS", comment="least significant 32 bits of the file's 64-bit binary creation date and time stamp")


class CertificateEntry(Struct):

    def parse(self):
        length = yield UInt32(name="Length", comment="size of certifcate entry")
        yield UInt16(name="Revision", comment="certificate version number", values=[
            ("WIN_CERT_REVISION_1_0", 0x100),
            ("WIN_CERT_REVISION_2_0", 0x200),
            ])
        yield UInt16(name="CertificateType", comment="certificate type", values=[
            ("WIN_CERT_TYPE_X509", 1),
            ("WIN_CERT_TYPE_PKCS_SIGNED_DATA", 2),
            ("WIN_CERT_TYPE_RESERVED", 3),
            ("WIN_CERT_TYPE_TS_STACK_SIGNED", 4),
            ])
        todo = length - len(self)
        if todo < 0:
            raise FatalError("Invalid certificate size")
        yield Bytes(todo, name="Data", comment="certificate data")
        try:
            yield Align(8)
        except OutOfBoundError: pass


class DebugDirectory(StaticStruct):

    @classmethod
    def parse(cls):
        yield UInt32(name="Characteristics", comment="reserved, must be zero")
        yield Timestamp(name="TimeDateStamp", comment="time and date that the debug data was created")
        yield UInt16(name="MajorVersion", comment="major version of the debug data format")
        yield UInt16(name="MinorVersion", comment="minor version of the debug data format")
        yield UInt32(name="Type", comment="certificate type", values=[
            ("IMAGE_DEBUG_TYPE_UNKNOWN", 0),
            ("IMAGE_DEBUG_TYPE_COFF", 1),
            ("IMAGE_DEBUG_TYPE_CODEVIEW", 2),
            ("IMAGE_DEBUG_TYPE_FPO", 3),
            ("IMAGE_DEBUG_TYPE_MISC", 4),
            ("IMAGE_DEBUG_TYPE_EXCEPTION", 5),
            ("IMAGE_DEBUG_TYPE_FIXUP", 6),
            ("IMAGE_DEBUG_TYPE_OMAP_TO_SRC", 7),
            ("IMAGE_DEBUG_TYPE_OMAP_FROM_SRC", 8),
            ("IMAGE_DEBUG_TYPE_BORLAND", 9),
            ("IMAGE_DEBUG_TYPE_RESERVED10", 10),
            ("IMAGE_DEBUG_TYPE_CLSID", 11),
            ("IMAGE_DEBUG_TYPE_VC_FEATURE", 12),
            ("IMAGE_DEBUG_TYPE_POGO", 13),
            ("IMAGE_DEBUG_TYPE_ILTCG", 14),
            ("IMAGE_DEBUG_TYPE_MPX", 15),
            ("IMAGE_DEBUG_TYPE_REPRO", 16),
            ("IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS", 20),
            ])
        yield UInt32(name="SizeOfData", comment="size of the debug data (not including the debug directory itself)")
        yield Rva(name="AddressOfRawData", comment="address of the debug data when loaded, relative to the image base")
        yield Offset32(name="PointerToRawData", comment="file pointer to the debug data")


class CVInfoPdb20(Struct):

    def parse(self):
        yield String(4, name="CvSignature", zero_terminated=False)
        yield UInt32(name="Offset", comment="should be zero")
        yield Timestamp(name="Signature", comment="pdb time")
        yield UInt32(name="Age", comment="always incrementing value")
        yield CString(name="Path", comment="PDB path")


class CVInfoPdb70(Struct):

    def parse(self):
        yield String(4, name="CvSignature", zero_terminated=False)
        yield GUID(name="Signature", comment="unique signature")
        yield UInt32(name="Age", comment="always incrementing value")
        yield CString(name="Path", comment="PDB path")


class TlsDirectory(Struct):

    def parse(self):
        if self.parser.is64:
            ptr = Va64
        else:
            ptr = Va32
        yield ptr(name="RawDataStart", comment="starting address of the TLS template")
        yield ptr(name="RawDataEnd", comment="end address of the TLS template")
        yield ptr(name="TlsIndexAddress", comment="location to receive the TLS index")
        yield ptr(name="CallbacksAddress", comment="pointer to an array of TLS callback functions")
        yield UInt32(name="SizeOfZeroFill", comment="size in bytes of the template, beyond the initialized data delimited by the Raw Data Start VA and Raw Data End VA fields")
        yield BitsField(
            NullBits(19),
            Bit(name="ScnAlign1", comment="align data on an 1-byte boundary, valid only for object files"),
            Bit(name="ScnAlign2", comment="align data on an 2-byte boundary, valid only for object files"),
            Bit(name="ScnAlign8", comment="align data on an 8-byte boundary, valid only for object files"),
            Bit(name="ScnAlign128", comment="align data on an 128-byte boundary, valid only for object files"),
            NullBits(9),
            name="Characteristics", comment="TLS array characteristics")


class LoadConfigurationTable(Struct):

    def parse(self):
        if self.parser.is64:
            va = Va64
            sz = UInt64
        else:
            va = Va32
            sz = UInt32
        chars = yield UInt32(name="SizeOfStructure", comment="size of structure")
        yield Timestamp(name="TimeDateStamp")
        yield UInt16(name="MajorVersion")
        yield UInt16(name="MinorVersion")
        yield UInt32(name="GlobalFlagsClear", comment="global loader flags to clear for this process as the loader starts the process")
        yield UInt32(name="GlobalFlagsSet", comment="global loader flags to set for this process as the loader starts the process")
        yield UInt32(name="CriticalSectionDefaultTimeout", comment="default timeout value to use for this process's critical sections that are abandoned")
        yield sz(name="DeCommitFreeBlockThreshold", comment="memory that must be freed before it is returned to the system, in bytes")
        yield sz(name="DeCommitTotalFreeThreshold", comment="total amount of free memory, in bytes")
        yield va(name="LockPrefixTable", comment="VA of a list of addresses where the LOCK prefix is used so that they can be replaced with NOP on single processor machines")
        yield sz(name="MaximumAllocationSize", comment="total amount of free memory, in bytes")
        yield sz(name="VirtualMemoryThreshold", comment="maximum virtual memory size, in bytes")
        yield sz(name="ProcessAffinityMask", comment="setting this field to a non-zero value is equivalent to calling SetProcessAffinityMask with this value during process startup (.exe only)")
        yield BitsField(
                Bit(name="HEAP_NO_SERIALIZE", comment="serialized access will not be used for this allocation"),
                NullBits(1),
                Bit(name="HEAP_GENERATE_EXCEPTIONS", comment="system will raise an exception to indicate a function failure, such as an out-of-memory condition, instead of returning NULL"),
                Bit(name="HEAP_ZERO_MEMORY", comment="allocated memory will be initialized to zero"),
                NullBits(14),
                Bit(name="HEAP_CREATE_ENABLE_EXECUTE", comment="blocks that are allocated from this heap allow code execution"),
                NullBits(13),
                name="ProcessHeapFlags", comment="heap flags that correspond to the first argument of the HeapCreate function. These flags apply to the process heap that is created during process startup")
        yield UInt16(name="CSDVersion", comment="Service Pack identifier")
        yield UInt16(name="Reserved", comment="must be zero")
        yield sz(name="EditList", comment="for use by the system")
        yield va(name="SecurityCookie", comment="pointer to a cookie that is used by Visual C++ or GS implementation")
        yield va(name="SEHandlerTable", comment="VA of the sorted table of RVAs of each valid, unique SE handler in the image")
        yield sz(name="SEHandlerCount", comment="count of unique handlers in the SEHandlerTable")
        if len(self) < chars:
            yield va(name="GuardCFCheckFunctionPointer", comment="VA where Control Flow Guard check-function pointer is stored")
            yield va(name="GuardCFDispatchFunctionPointer", comment="VA where Control Flow Guard dispatch-function pointer is stored")
            yield va(name="GuardCFFunctionTable", comment="VA of the sorted table of RVAs of each Control Flow Guard function in the image")
            yield sz(name="GuardCFFunctionCount", comment="count of unique RVAs in the GuardCFFunctionTable")
            yield BitsField(
                    NullBits(8),
                    Bit(name="CF_INSTRUMENTED", comment="performs control flow integrity checks using system-supplied support"),
                    Bit(name="CFW_INSTRUMENTED", comment="performs control flow and write integrity checks"),
                    Bit(name="CF_FUNCTION_TABLE_PRESENT", comment="contains valid control flow target metadata"),
                    Bit(name="SECURITY_COOKIE_UNUSED", comment="does not make use of the /GS security cookie"),
                    Bit(name="PROTECT_DELAYLOAD_IAT", comment="supports read only delay load IAT"),
                    Bit(name="DELAYLOAD_IAT_IN_ITS_OWN_SECTION", comment="Delayload import table in its own .didat section (with nothing else in it) that can be freely reprotected"),
                    Bit(name="CF_EXPORT_SUPPRESSION_INFO_PRESENT", comment="contains suppressed export information. This also infers that the address taken IAT table is also present in the load config"),
                    Bit(name="CF_ENABLE_EXPORT_SUPPRESSION", comment="enables suppression of exports"),
                    Bit(name="CF_LONGJUMP_TABLE_PRESENT", comment="contains longjmp target information"),
                    NullBits(15),
                    name="GuardFlags", comment="Control Flow Guard related flags")
        if len(self) < chars:
            yield Bytes(12, name="CodeIntegrity", comment="code integrity information")
        if len(self) < chars:
            yield va(name="GuardAddressTakenIatEntryTable", comment="VA where Control Flow Guard address taken IAT table is stored")
            yield sz(name="GuardAddressTakenIatEntryCount", comment="count of unique RVAs in GuardAddressTakenIatEntryTable")
        if len(self) < chars:
            yield va(name="GuardLongJumpTargetTable", comment="VA where Control Flow Guard long jump target table is stored")
            yield sz(name="GuardLongJumpTargetCount", comment="count of unique RVAs in GuardLongJumpTargetTable")


class UnitInfo(Struct):

    def __init__(self, sz, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.sz = sz

    def parse(self):
        if self.sz < 2:
            raise FatalError
        info = yield UInt16(name="Info")
        if info and self.sz > 2:
            yield CString(name="Name")


class PackageInfo(Struct):

    def __init__(self, sz, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.sz = sz


    def parse(self):
        yield BitsField(
            Bit(name="NeverBuild"),
            Bit(name="DesignTimeBuild"),
            Bit(name="RunTimeBuild"),
            Bit(name="IgnoreDup", comment="don't check for duplicate units"),
            NullBits(26),
            Bit(name="PackageDll"),
            Bit(name="LibraryDll"),
            name="BuildFlags", comment="Delphi build flags")
        yield Bytes(8, name="Unknown")
        try:
            while len(self) < self.sz:
                ui = yield UnitInfo(self.sz - len(self))
                if not ui["Info"]:
                    break
        except FatalError:
            pass
    


RSRC_ID = [
    ("CUR",1 ),
    ("BMP",2 ),
    ("ICO",3 ),
    ("MENU",4 ),
    ("DLG",5 ),
    ("STR",6 ),
    ("FNTDIR",7 ),
    ("FNT",8 ),
    ("ACC",9 ),
    ("RCDATA",10 ),
    ("MSG",11 ),
    ("GRPCUR",12 ),
    ("GRPICO",14 ),
    ("VER",16 ),
    ("DLGINC",17 ),
    ("PNP",19 ),
    ("VXD",20 ),
    ("ANICUR",21 ),
    ("ANIICO",22 ),
    ("HTML",23 ),
    ("MANIF",24 ),
]



class Resource:
    def __init__(self, type, name, lang, rva, size):
        self.type = type
        self.name = name
        self.lang = lang
        self.rva = rva
        self.size = size


class PEAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.PROGRAM
    name = "PE"
    regexp = r"MZ.{58}+(?!\x00\x00\x00)...\x00"


    def __init__(self):
        FileTypeAnalyzer.__init__(self)
        self.regions = []                       # regions != sections, we may adjust the mapping for memory-mapped PEs
        self.is64 = False
        self.ordinal_translator = OrdinalTranslator()
        self.resources = {}
        self.raw_imports = []                   # only used for imphash
        self.version_metadata_key_seen = set()
        self.aot_header = None
        self.has_DotNetRuntimeInfo = False

    
    def rva2region(self, rva):
        for s in self.regions:
            if rva >= s.rva and rva < s.rva + s.vsize:
                return s

    def rva2off(self, rva):
        if not rva:
            return None
        for s in self.regions:
            if rva >= s.rva and rva < s.rva + s.vsize:
                delta = rva - s.rva
                if delta < s.fsize:
                    return s.foff + delta
        return None

    def off2rva(self, off):
        for s in self.regions:
            if off >= s.foff and off < s.foff + s.fsize:
                delta = off - s.foff
                if delta < s.fsize:
                    return s.rva + delta
        return None

    def va2off(self, va):
        if va < self.imagebase:
            return None
        return self.rva2off(va - self.imagebase)

    def off2va(self, off):
        rva = self.off2rva(off)
        if rva is None:
            return None
        return self.imagebase + rva


    def parse_exports(self, et_foff):
        self.jump(et_foff)
        expdir = yield ExportDirectory(name="ExportDirectory", category=Type.HEADER)
        name_rva = expdir["Name"]
        name_off = self.rva2off(name_rva)
        names_locations = []
        if name_off:
            name = self.read_cstring_ascii(name_off)
            if name:
                self.add_metadata("Module name", name, category="Exports")
                names_locations.append((name_off, name_off + len(name) + 1))
        if int(expdir["TimeDateStamp"].timestamp()) not in (0, 0xffffffff):
            self.add_metadata("Exports date", expdir["TimeDateStamp"].strftime("%Y-%m-%d %H:%M:%S"), category="Exports")
        nametable_off = self.rva2off(expdir["NameTable"])
        ordinaltable_off = self.rva2off(expdir["OrdinalTable"])
        addresstable_off = self.rva2off(expdir["AddressTable"])
        ordinal_base = expdir["OrdinalBase"]
        number_of_names = expdir["NameTableEntries"]
        number_of_addresses = expdir["AddressTableEntries"]
        if number_of_addresses > 100000:
            raise FatalError("Too many addresses in export directory")
        elif number_of_names > 100000:
            raise FatalError("Too many names in export directory")
        names = []
        ordinals = []
        addresses = []
        dd = self["OptionalHeader"]["DataDirectory"]
        if nametable_off and number_of_names:
            self.jump(nametable_off)
            names = yield Array(number_of_names, Rva(hint=String(0, True)), name="ExportNameTable", category=Type.HEADER, parent=expdir)
        if ordinaltable_off and number_of_names:
            self.jump(ordinaltable_off)
            ordinals = yield Array(number_of_names, UInt16(), name="OrdinalNameTable", category=Type.HEADER, parent=expdir)
        if addresstable_off and number_of_addresses:
            self.jump(addresstable_off)
            addresses = yield Array(number_of_addresses, Rva(), name="ExportAddressTable", category=Type.HEADER, parent=expdir)
        ordinal_to_name = {}
        for i, name_rva in enumerate(names):
            name_rva = name_rva.value
            name_off = self.rva2off(name_rva)
            name = None
            if name_off:
                name = self.read_cstring_ascii(name_off, 256)
                if name:
                    names_locations.append((name_off, name_off + len(name) + 1))
            if not name:
                name = f"invalid_name_{name_rva:x}"
            if (not ordinals) or i >= ordinals.count:
                raise FatalError("Ordinal table smaller than export name table")
            ordinal = ordinals[i]
            if (not addresses) or ordinal >= addresses.count:
                raise FatalError("Address table smaller than ordinal table")
            ordinal_to_name[ordinal] = name
        for i, address in enumerate(addresses):
            address = address.value
            if not address:
                pass
            elif address > dd[0]["Rva"] and address < dd[0]["Rva"] + dd[0]["Size"]:
                # forward
                dll_name_off = self.rva2off(address)
                if dll_name_off:
                    dll_name = self.read_cstring_ascii(dll_name_off)
                    if dll_name:
                        names_locations.append((dll_name_off, dll_name_off + len(dll_name) + 1))
                    self.add_symbol(self.imagebase + address, "{}->{}".format(name, dll_name), malcat.FileSymbol.EXPORT)
            else:
                if i in ordinal_to_name:
                    name = ordinal_to_name[i]
                else:
                    name = f"Ordinal#{ordinal_base + i}"
                # add symbol
                self.add_symbol(self.imagebase + address, name, malcat.FileSymbol.EXPORT)
                if name == "DotNetRuntimeDebugHeader":
                    self.aot_header = self.rva2off(address)
                elif name == "DotNetRuntimeInfo":
                    self.has_DotNetRuntimeInfo = True
        # tag api names
        names_locations.sort()
        if names_locations:
            reported = None
            for i, location in enumerate(names_locations):
                start, end = location
                if i + 1 <len(names_locations) and abs(end - names_locations[i+1][0]) < 4:
                    if reported is None:
                        reported = start
                else:
                    if reported is not None:
                        start = reported
                        reported = None
                    self.jump(start)
                    yield Bytes(end - start, name="ExportNames", category=Type.DATA, comment="API names", parent=expdir)



    def parse_imports(self, iat_foff):
        self.jump(iat_foff)
        import_table = yield DynamicArray(lambda iid, count: iid["Name"] == 0 or iid["FirstThunk"] == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, ImageImportDescriptor(), name="ImportTable", category=Type.HEADER)
        if self.is64:
            rvaclass = Rva64
            ptrsize = 8
        else:
            rvaclass = Rva
            ptrsize = 4

        names_locations = []
        for iid in import_table:
            dll_name_foff = self.rva2off(iid["Name"])
            if dll_name_foff is not None:
                dll_name_raw = self.read_cstring_ascii(dll_name_foff, 256).lower()
                names_locations.append((dll_name_foff, dll_name_foff + len(dll_name_raw) + 1))
            else:
                continue
            dll_name = dll_name_raw.split(".")[0].lower()
            if not dll_name: 
                continue
            oft_foff = self.rva2off(iid["OriginalFirstThunk"])
            ft_foff = self.rva2off(iid["FirstThunk"])
            namearray = None
            ptrarray = None
            if iid["OriginalFirstThunk"] and oft_foff is not None:
                self.jump(oft_foff)
                namearray = yield DynamicArray(lambda rva, count: rva == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, rvaclass(), name="{}.OFT".format(dll_name), category=Type.HEADER, parent=import_table)
                ptrarray = namearray
            if ft_foff is not None and ft_foff != oft_foff:
                self.jump(ft_foff)
                if namearray is not None:
                    max_elements = namearray.count
                else: 
                    max_elements = MAX_DYNAMIC_STRUCTURE_LENGTH
                try:
                    ptrarray = yield DynamicArray(lambda rva, count: rva == 0 or count >= MAX_DYNAMIC_STRUCTURE_LENGTH or count >= max_elements, rvaclass(), name="{}.FT".format(dll_name), category=Type.HEADER, parent=import_table)
                except SuperposingError: pass
                if namearray is None:
                    namearray = ptrarray
            # build symbols
            if namearray is not None:
                for k, rva in enumerate(namearray):
                    rva = rva.value
                    if rva == 0:
                        break
                    if self.is64:
                        is_ordinal = (rva & 0x8000000000000000) != 0
                    else:
                        is_ordinal = (rva & 0x80000000) != 0
                    if rva and is_ordinal:
                        ordinal = rva & 0xffff
                        self.raw_imports.append((dll_name_raw, ordinal))
                        translation = self.ordinal_translator.translate(dll_name, ordinal)
                        if translation:
                            name = "{}.{}".format(dll_name, translation)
                        else:
                            name = "{}.ord{}".format(dll_name, ordinal)
                    else:
                        off = self.rva2off(rva)
                        if off is None:
                            continue
                        api_name = self.read_cstring_ascii(off + 2, 256)
                        self.raw_imports.append((dll_name_raw, api_name))
                        names_locations.append((off, off + 2 + len(api_name) + 1))
                        name = "{}.{}".format(dll_name, api_name)
                    self.add_symbol(self.imagebase + iid["FirstThunk"] + k * ptrsize, name, malcat.FileSymbol.IMPORT)

        # tag api names
        names_locations.sort()
        if names_locations:
            reported = None
            for i, location in enumerate(names_locations):
                start, end = location
                if i + 1 <len(names_locations) and abs(end - names_locations[i+1][0]) < 4:
                    if reported is None:
                        reported = start
                else:
                    if reported is not None:
                        start = reported
                        reported = None
                    self.jump(start)
                    yield Bytes(end - start, name="ImportNames", category=Type.DATA, comment="API names", parent=import_table)





    def parse_version(self, version_off, version_size, parent=None):
        self.jump(version_off)
        start = version_off
        yield VersionInfo(category=Type.META, parent=parent)
        if self.tell() < start + version_size:
            yield Unused(start + version_size - self.tell(), name="VersionInfo.Overlay", category=Type.ANOMALY, parent=parent)

    def open_rsrc(self, file, password=None):
        rsrc = self.resources[file.path]
        return self.read(self.rva2off(rsrc.rva), rsrc.size)

    def open_rsrc_net(self, file, password=None):
        rsrc = self.resources_net["/".join(file.path.split("/")[1:])]
        return self.read(self.rva2off(rsrc.rva), rsrc.size)

    def parse_resources_dir(self, rsrc_foff, lvl=0, rtype="", name="", lang="", parent=None, seen=None):
        if seen is None:
            seen = set()
        if self.tell() in seen:
            return
        # avoid some tricky recrusive resource dirs
        seen.add(self.tell())
        if lvl == 0:
            elem = yield ImageResourceDirectory(ImageResourceDirectoryEntry1, name="Resources", category=Type.HEADER, parent=parent)
        elif lvl == 1:
            elem = yield ImageResourceDirectory(ImageResourceDirectoryEntry2, name="Resources.{}".format(rtype), category=Type.HEADER, parent=parent)
        elif lvl == 2:
            elem = yield ImageResourceDirectory(ImageResourceDirectoryEntry3, name="Resources.{}.{}".format(rtype, name), category=Type.HEADER, parent=parent)
        elif lvl == 3:
            elem = yield ImageResourceDataEntry(name="Resources.{}.{}.{}".format(rtype, name, lang), category=Type.HEADER, parent=parent)
        else:
            return
        if lvl == 3:
            roff = self.rva2off(elem["OffsetToData"])
            rsize = min(elem["Size"], self.size() - (roff or 0))
            dataname = "Resources.{}.{}.{}.Data".format(rtype, name, lang)
            def pp(s):
                if type(s) == int:
                    return "#{:d}".format(s)
                else:
                    return s.replace("/", "\\")
            filename = "/".join(map(pp, [rtype, name, lang]))
            self.resources[filename] = Resource(rtype, name, lang, elem["OffsetToData"], elem["Size"])
            if rsize > 0 and roff is not None and roff + rsize <= self.size():
                self.jump(roff)
                before = self.tell()
                try:
                    if rtype == "VER":
                        yield from self.parse_version(roff, rsize, parent=elem)
                    elif rtype == "MANIF":
                        yield Bytes(rsize, name="Manifest", category=Type.META, parent=elem)
                    elif rtype == "RCDATA" and name == "PACKAGEINFO":
                        pinfo = yield PackageInfo(rsize, name="PackageInfo", category=Type.META, parent=elem)
                        if "UnitInfo" in pinfo:
                            first_unit = pinfo[2]
                            if "Name" in first_unit:
                                self.add_metadata("ProjectName", first_unit["Name"], category="Delphi")
                    else:
                        yield Bytes(rsize, name=dataname, category=Type.RESOURCE, parent=elem)
                except SuperposingError: 
                    pass  # some resources are references in more than one directory 
                except ParsingError as e:
                    print(e)
                    if self.tell() - before < rsize:
                        yield Bytes(rsize - (self.tell() - before), name=dataname, category=Type.RESOURCE, parent=elem)
                if self.tell() > before:
                    self.add_file(filename, self.tell() - before, "open_rsrc")
            else:
                pass
        else:
            for sub in elem["DirectoryEntries"]:
                try:
                    if lvl == 0:
                        field = sub.Type
                    elif lvl == 1:
                        field = sub.Name
                    else:
                        field = sub.Language
                    rid = field.value
                    if (rid & 0x80000000) != 0:
                        noff = rsrc_foff + (rid & 0x7fffffff)
                        try:
                            self.jump(noff)
                        except OutOfBoundError:
                            print("Invalid resource name offset: {:x} for {}".format(noff, elem.name))
                            continue
                        irds = yield ImageResourceDirString(name="ResourceName", category=Type.HEADER, parent=elem)
                        sub_name = irds["NameString"]
                    else:
                        if field.has_enum and field.enum:
                            sub_name = field.enum
                        else:
                            sub_name = str(field.value)
                    if lvl == 0:
                        rtype = sub_name
                    elif lvl == 1:
                        name = sub_name
                    elif lvl == 2:
                        lang = sub_name
                    data = sub["OffsetToData"]
                    is_subdir = (data & 0x80000000) != 0
                    dataoff = rsrc_foff + (data & 0x7fffffff)
                    if dataoff and dataoff < self.size():
                        self.jump(dataoff)
                        #if is_subdir:
                        yield from self.parse_resources_dir(rsrc_foff, lvl+1, rtype, name, lang, elem, seen)
                    else:
                        print("Invalid resource entry offset: {:x} .. skipping".format(dataoff))
                except SuperposingError:
                    continue


    def parse_certificates(self, cert_foff, cert_size):
        certs_meta = []
        self.jump(cert_foff)
        while self.tell() < cert_foff + cert_size:
            # prevalidate, check against corrupted certs
            cert_size, cert_revision, cert_type = struct.unpack("<IHH", self.read(self.tell(), 8))
            if cert_size == 0 or cert_revision not in (0x100, 0x200) or cert_type not in (1,2,3,4):
                raise FatalError("Invalid certificate header")
            cert = yield CertificateEntry(name="Certificate", category=Type.META)
            if cert["CertificateType"] == 2:
                parsed, meta = parse_der_certificate(cert["Data"])
                if meta:
                    certs_meta.append(meta)
        if certs_meta:
            if len(certs_meta) == 1:
                for k, v in certs_meta[0].items():
                    self.add_metadata(k, v, category="Certificate")
            else:
                for i, meta in enumerate(certs_meta):
                    for k,v in meta.items():
                        self.add_metadata("Cert[{}].{}".format(i, k), v, category="Certificates")
        if self.tell() > cert_foff + cert_size:
            raise FatalError("Invalide certificates size")


    def parse_debug(self, dbg_foff, dbg_size):
        self.jump(dbg_foff)
        dbg_meta = {}
        dds = yield DynamicArray(lambda dd, _: dd.offset + dd.size >= dbg_foff + dbg_size, DebugDirectory(), name="DebugDirectory", category=Type.DEBUG)
        for dd in dds:
            if dd.Type.has_enum:
                name = "Debug.{}".format("".join(map(str.capitalize, dd.Type.enum.split("_")[3:])))
            else:
                name = "Debug.Unknown_{:x}".format(dd["Type"])
            if dd["TimeDateStamp"].timestamp() > 0 and dd["TimeDateStamp"].timestamp() < 0xffffffff:
                dbg_meta["Date.{}".format(name)] = dd["TimeDateStamp"].strftime("%Y-%m-%d %H:%M:%S")
            address = None
            if "AddressOfRawData" in dd and dd["AddressOfRawData"]:
                address = self.rva2off(dd["AddressOfRawData"])
            if address is None:
                address = dd["PointerToRawData"]
            if dd["Type"] == 2:
                self.jump(address)
                sig = self.read(address, 4)
                if sig == b"RSDS":
                    # CVINFO_PDB70_CVSIGNATURE
                    cv = yield CVInfoPdb70(category=Type.DEBUG, name=name, parent=dds)
                    dbg_meta["Path"] = cv["Path"]
                elif sig == b"NB10":
                    # CVINFO_PDB20_CVSIGNATURE
                    cv = yield CVInfoPdb20(category=Type.DEBUG, name=name, parent=dds)
                    dbg_meta["Path"] = cv["Path"]
            elif address and dd["SizeOfData"]:
                self.jump(address)
                yield Bytes(dd["SizeOfData"], name=name, category=Type.DEBUG, parent=dds)
            #elif dd["Type"] == 0x10 and dd["PointerToRawData"]:
        if dbg_meta:
            for k, v in dbg_meta.items():
                self.add_metadata(k, v, category="Debug")

    def parse_load_config(self, lc_foff, lc_size):
        self.jump(lc_foff)
        lct = yield LoadConfigurationTable()
        off = self.va2off(lct["SecurityCookie"])
        if off:
            self.jump(off)
            if self.is64:
                yield UInt64(name="SecurityCookie", parent=lct, category=Type.DEBUG)
            else:
                yield UInt32(name="SecurityCookie", parent=lct, category=Type.DEBUG)
        if lct["SEHandlerCount"]:
            off = self.va2off(lct["SEHandlerTable"])
            if off:
                self.jump(off)
                handlers = yield Array(lct["SEHandlerCount"], Rva(), name="SEHandlers", parent=lct, category=Type.HEADER)
                for i, handler in enumerate(filter(None, handlers)):
                    self.add_symbol(self.imagebase + handler.value, "SEH.{}".format(i), malcat.FileSymbol.FUNCTION)
        for ptr in ("GuardCFCheckFunctionPointer", "GuardCFDispatchFunctionPointer"):
            if ptr in lct and lct[ptr]:
                off = self.va2off(lct[ptr])
                if off:
                    self.jump(off)
                    self.add_symbol(lct[ptr], ptr, malcat.FileSymbol.DATA)
                    if self.is64:
                        va = yield Va64(name=ptr, parent=lct, category=Type.HEADER)
                    else:
                        va = yield Va32(name=ptr, parent=lct, category=Type.HEADER)
                    if va:
                        self.add_symbol(va, ptr.replace("Pointer", ""), malcat.FileSymbol.FUNCTION)





    def parse_tls(self, tls_foff, tls_size):
        self.jump(tls_foff)
        dd = yield TlsDirectory(category=Type.HEADER)
        arrayoff = self.va2off(dd["RawDataStart"])
        arraysize = dd["RawDataEnd"] - dd["RawDataStart"]
        if arraysize > 0 and arrayoff:
            self.jump(arrayoff)
            yield Bytes(arraysize, name="TLSInitArray", category=Type.FIXUP, parent=dd)
        if dd["CallbacksAddress"]:
            cb_off = self.va2off(dd["CallbacksAddress"])
            if cb_off:
                self.jump(cb_off)
                if self.is64:
                    ptr = Va64
                else:
                    ptr = Va32
                cbs = yield DynamicArray(lambda p, count: p == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, ptr(), name="TlsCallbacks", category=Type.FIXUP, parent=dd)
                for i, v in enumerate(cbs[:-1]):
                    if v.value:
                        self.add_symbol(v.value, "TLS.{}".format(i), malcat.FileSymbol.ENTRY)
                       


    def parse_exception(self, exc_foff, exc_size):
        self.jump(exc_foff)
        # todo: check for other arch
        etype = ExceptionEntryX64
        esize = 12
        exception_table = yield Array(exc_size // esize, etype(), name="ExceptionTable", category=Type.DEBUG)
    

    def parse_delay(self, delay_foff, delay_size):
        self.jump(delay_foff)
        use_rva = (self.read(delay_foff, 1)[0] & 1) != 0    # best effort for the GUI: assume all entries have same attributes
        delay_table = yield DynamicArray(lambda iid, count: iid["Name"] == 0 or iid["AddressTable"] == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, DelayImportDescriptor(use_rva), name="DelayImportTable", category=Type.HEADER)
        for delay in delay_table:
            use_rva = delay["Attributes"]["UseRva"]
            name = delay["Name"]
            if self.is64:
                ptr = Va64
                rva = Rva64
            else:
                ptr = Va32
                rva = Rva
            if not name:
                continue
            if use_rva:
                dll_name_foff = self.rva2off(name)
            else:
                dll_name_foff = self.va2off(name)
            if dll_name_foff is not None:
                dll_name = self.read_cstring_ascii(dll_name_foff, 256).lower()
            else:
                dll_name = "???"
            dll_name = dll_name.split(".")[0].lower()
            address_table = self.rva2off(delay["AddressTable"])
            if use_rva:
                name_table = self.rva2off(delay["NameTable"])
            else:
                name_table = self.va2off(delay["NameTable"])
            if name_table:
                self.jump(name_table)
                names = yield DynamicArray(lambda x, count: x == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, rva(), name="{}.Names".format(dll_name), parent=delay_table, category=Type.HEADER)
            else:
                names = None
            if address_table:
                self.jump(address_table)
                if names is not None:
                    addresses = yield Array(names.count - 1, ptr(), name="{}.Addresses".format(dll_name), parent=delay_table, category=Type.HEADER)
                else:
                    addresses = yield DynamicArray(lambda x, count: x == 0 or count > MAX_DYNAMIC_STRUCTURE_LENGTH, ptr(), name="{}.Addresses".format(dll_name), parent=delay_table, category=Type.HEADER)
            if name_table and address_table:
                for i, pointer in enumerate(addresses):
                    pointer = pointer.value
                    if i >= names.count or pointer == 0 or pointer == 0xffffffff:
                        continue
                    if names[i] & 0x80000000:
                        ordinal = names[i] & 0xffff
                        translation = self.ordinal_translator.translate(dll_name, ordinal)
                        if translation:
                            apiname = translation
                        else:
                            apiname = "#{}".format(ordinal)
                    else:
                        off = self.rva2off(names[i])
                        if off is None:
                            apiname = "delay#{}".format(i)
                        else:
                            apiname = self.read_cstring_ascii(off + 2, 256)
                    if pointer:
                        self.add_symbol(pointer, "{}.{} (delaystub)".format(dll_name, apiname), malcat.FileSymbol.FUNCTION)
                    va = self.off2va(addresses.at(i).offset)
                    if va:
                        self.add_symbol(va, "{}.{} (delayed)".format(dll_name, apiname), malcat.FileSymbol.IMPORT)

    def parse_bound(self, bound_off, size):
        self.jump(bound_off)
        bit = yield BoundImportTable(size)
        if self.tell() < bound_off + size:
            yield Bytes(bound_off + size - self.tell(), name="BoundImportNames", parent=bit, category=Type.DATA)

    def parse_rich(self):
        off = self["PE"].offset - 8
        rich_size = 0
        rich_off = None
        while off > 0 and rich_size == 0:
            rich = self.read(off, 4)
            xor, = struct.unpack("<I", self.read(off + 4, 4))
            if rich == b"Rich":
                rich_off = off - 16
                # find "Dans"
                while rich_off > 0 and rich_size == 0:
                    dans, = struct.unpack("<I", self.read(rich_off, 4))
                    dans = dans ^ xor
                    if dans == 0x536e6144:
                        rich_size = off + 8 - rich_off
                        break
                    rich_off -= 8
            off -= 4
        if rich_size:
            self.jump(rich_off)
            yield RichHeader(xor, rich_size, category=Type.META)

    def is_import_table_valid_looking(self):
        # heuristik
        if len(self["OptionalHeader"]["DataDirectory"]) >= 2:
            off = self.rva2off(self["OptionalHeader"]["DataDirectory"][1]["Rva"])
            if off is None:
                return False
            i = 0
            ends_with_dll = 0
            while i < 512:
                oft, tds, fc, name, ft = struct.unpack("<IIIII", self.read(off, 0x14))
                off += 0x14
                if oft == 0 and ft == 0 and name == 0:
                    break
                if oft and self.rva2off(oft) is None:
                    return False
                if ft and self.rva2off(ft) is None:
                    return False
                name_off = self.rva2off(name)
                if name_off is None:
                    return False
                dll_name = self.read_cstring_ascii(name_off)
                if dll_name and dll_name.lower().endswith(".dll"):
                    ends_with_dll += 1
                i += 1
            if not ends_with_dll:
                return False
        return True

    def parse(self, hint):
        # some packers put the PE header inside the MZ header. Since malcat does not 
        # support intricated structure, we just skip the MZ header in this case
        pe_offset, = struct.unpack("<I", self.read(0x3c, 4))
        if pe_offset >= 0x40:
            yield MZ(category=Type.HEADER)
        self.jump(pe_offset)
        pe = yield PE(category=Type.HEADER)
        self.add_metadata("Compile date", pe["TimeDateStamp"].strftime("%Y-%m-%d %H:%M:%S"))
        magic, = struct.unpack("<H", self.look_ahead(2))
        self.is64 = magic == 0x020B

        machine = pe.Machine.value
        if machine == 0x14c:
            self.set_architecture(malcat.Architecture.X86)
        elif machine == 0x8664:
            self.set_architecture(malcat.Architecture.X64)
        elif machine == 0x1c0:
            self.set_architecture(malcat.Architecture.ARMV7)
        elif machine == 0x1c2 or machine == 0x1c4:
            self.set_architecture(malcat.Architecture.ARMV7T)
        elif machine == 0xaa64:
            self.set_architecture(malcat.Architecture.AARCH64)
        elif machine in (0x0162, 0x166, 0x168, 0x169, 0x266, 0x366, 0x466):
            if self.is64:
                self.set_architecture(malcat.Architecture.MIPS64)
            else:
                self.set_architecture(malcat.Architecture.MIPS32)
        if self.is64:
            optheader = yield OptionalHeader64(name="OptionalHeader", category=Type.HEADER)
        else:
            optheader = yield OptionalHeader32(name="OptionalHeader", category=Type.HEADER)

        self.set_imagebase(optheader["ImageBase"])
        if optheader["AddressOfEntryPoint"]:
            # handle engative rva done by some memory dumpers
            ep, = struct.unpack("<i", struct.pack("<I",  optheader["AddressOfEntryPoint"]))
            if ep > 0:
                self.add_symbol(self.imagebase + ep, "EntryPoint", malcat.FileSymbol.ENTRY)
        section_table_offset = max(pe_offset + pe["SizeOfOptionalHeader"] + 0x18, self.tell())
        self.jump(section_table_offset)
        sections = yield Array(pe["NumberOfSections"], Section(), name="Sections", category=Type.HEADER)

        file_align = optheader["FileAlignment"]
        mem_align = max(optheader["SectionAlignment"], 512)
        imagesize = optheader["SizeOfImage"]

        eof_header = align(self.tell(), mem_align)

        # map regions
        for s in sections:
            phys_start = align(s["PointerToRawData"], min(512, file_align), down=True)
            phys_size = align(s["SizeOfRawData"], file_align)
            rva_start = align(s["VirtualAddress"], mem_align, down=True)
            virt_size = align(s["VirtualSize"], mem_align)
            if (phys_size or virt_size) and phys_start < self.size():
                # handle negative rvas, some memory dumper do this
                srva, = struct.unpack("<i", struct.pack("<I", rva_start))
                r = Region(self.imagebase, srva, virt_size, phys_start, phys_size, s)
                self.regions.append(r)
                
        # handle overlapping
        self.regions = sorted(self.regions, key=lambda x: x.foff)
        for i in range(len(self.regions)):
            cur = self.regions[i]
            if cur.foff + cur.fsize > self.size():
                cur.fsize = self.size() - cur.foff
            if i and cur.fsize:
                prev = self.regions[i-1]
                if prev.foff + prev.fsize > cur.foff:
                    prev.fsize = cur.foff - prev.foff
        self.regions = sorted(self.regions, key=lambda x: x.rva)
        for i in range(len(self.regions)):
            cur = self.regions[i]
            if cur.rva < imagesize and cur.rva + cur.vsize > imagesize:
                cur.vsize = imagesize - cur.rva
            if i:
                prev = self.regions[i-1]
                if prev.rva + prev.vsize > cur.rva:
                    prev.vsize = cur.rva - prev.rva
        self.regions = [x for x in self.regions if x.fsize or x.vsize]

        # check if memory-mapped PE (stupid heuristic)
        try:
            old_regions = self.regions
            if not self.is_import_table_valid_looking() and optheader["DataDirectory"][1]["Rva"]:
                # try mapping virtual->physical
                old_regions = self.regions
                self.regions = []
                for r in old_regions:
                    self.regions.append(Region(self.imagebase, r.rva, r.vsize, r.rva, r.vsize, r.section))
                if not self.is_import_table_valid_looking():
                    self.regions = old_regions
                else:
                    print("Memory-mapped PE")
        except: 
            self.regions = old_regions

        # map regions
        for r in self.regions:
            sname = r.section["Name"]
            si = sname.find("\x00")
            if si >= 0:
                sname = sname[:si]
            self.add_section(sname, r.foff, r.fsize, max(0, self.imagebase + r.rva), r.vsize,
                r = r.section["Characteristics"]["MemRead"],
                w = r.section["Characteristics"]["MemWrite"],
                x = r.section["Characteristics"]["MemExecute"],
                discardable = r.section["Characteristics"]["MemDiscardable"],
            )
        if sections.count == 0:
            self.set_imagebase(0)
            raise FatalError("No section")

        self.confirm()
        
        dd = optheader["DataDirectory"]
        # certificates
        if "CertificateTable" in dd:
            cert_foff = dd["CertificateTable"]["Offset"]
            cert_size = dd["CertificateTable"]["Size"]
            if cert_foff and cert_size:
                try:
                    yield from self.parse_certificates(cert_foff, cert_size)
                except ParsingError as e: print(e)
        # the certificate is the last known structure than can be in overlay, so we 
        # have an estimate of the file's size by now
        last_physical_offset = self.tell()
        for r in self.regions:
            if r.fsize and r.foff + r.fsize > last_physical_offset:
                last_physical_offset = r.foff + r.fsize
        #self.set_eof(last_physical_offset)

        # rsrc
        if "ResourceDirectory" in dd:
            rsrc_foff = self.rva2off(dd["ResourceDirectory"]["Rva"])
            if rsrc_foff is not None:
                self.jump(rsrc_foff)
                try:
                    yield from self.parse_resources_dir(rsrc_foff)
                except ParsingError as e: print(e)
        # reloc
        if "RelocationTable" in dd:
            reloc_foff = self.rva2off(dd["RelocationTable"]["Rva"])
            if reloc_foff is not None:
                self.jump(reloc_foff)
                try:
                    yield Relocations(category=Type.FIXUP)
                except ParsingError: pass
        # imports
        if "ImportTable" in dd:
            iat_foff = self.rva2off(dd["ImportTable"]["Rva"])
            if iat_foff is not None:
                try:
                    yield from self.parse_imports(iat_foff)
                except ParsingError as e: print(e)
        # exports
        if "ExportTable" in dd:
            et_foff = self.rva2off(dd["ExportTable"]["Rva"])
            if et_foff is not None:
                try:
                    yield from self.parse_exports(et_foff)
                except ParsingError: pass

        # debug
        if "DebugData" in dd:
            dbg_foff = self.rva2off(dd["DebugData"]["Rva"])
            if dbg_foff is not None:
                try:
                    yield from self.parse_debug(dbg_foff, dd["DebugData"]["Size"])
                except ParsingError: pass

        # architecture / copyright
        if "Architecture" in dd:
            cr_foff = self.rva2off(dd["Architecture"]["Rva"])
            cr_size = dd["Architecture"]["Size"]
            if cr_foff is not None and cr_size and cr_foff + cr_size < self.size():
                self.jump(cr_foff)
                s = yield String(cr_size, zero_terminated=True, name="Copyright", category=Type.META)
                if s:
                    self.add_metadata("Copyright", s)

        # tls
        if "ThreadLocalStorage" in dd:
            tls_foff = self.rva2off(dd["ThreadLocalStorage"]["Rva"])
            if tls_foff is not None:
                try:
                    yield from self.parse_tls(tls_foff, dd["ThreadLocalStorage"]["Size"])
                except ParsingError: pass

        # load config
        if "LoadConfiguration" in dd:
            lc_foff = self.rva2off(dd["LoadConfiguration"]["Rva"])
            if lc_foff is not None:
                try:
                    yield from self.parse_load_config(lc_foff, dd["LoadConfiguration"]["Size"])
                except IOError: pass                

        # delay
        if "DelayImportDescriptor" in dd:
            delay_foff = self.rva2off(dd["DelayImportDescriptor"]["Rva"])
            if delay_foff is not None:
                try:
                    yield from self.parse_delay(delay_foff, dd["DelayImportDescriptor"]["Size"])
                except ParsingError: pass

        # exceptions
        if "ExceptionTable" in dd:
            exc_foff = self.rva2off(dd["ExceptionTable"]["Rva"])
            if exc_foff is not None:
                try:
                    yield from self.parse_exception(exc_foff, dd["ExceptionTable"]["Size"])
                except ParsingError: pass

        # bound imports
        if "BoundImportTable" in dd:
            bound_foff = dd["BoundImportTable"]["Offset"]
            if bound_foff is not None:
                try:
                    yield from self.parse_bound(bound_foff, dd["BoundImportTable"]["Size"])
                except ParsingError: pass

        # rich header
        try:
            yield from self.parse_rich()
        except ParsingError: pass

        self.confirm()
        # vb parsing
        try:
            from filetypes.PE_vb import parse_vb
            yield from parse_vb(self)
        except ParsingError as e: print(e)

        # net parsing
        try:
            from filetypes.PE_net import parse_net
            yield from parse_net(self)
        except ParsingError as e: print(e)

        # net Singlefile bundle parsing
        if self.has_DotNetRuntimeInfo:
            try:
                from filetypes.PE_net import parse_single_file_bundle
                yield from parse_single_file_bundle(self)
            except ParsingError as e: print(e)

        # net AOT parsing
        if self.aot_header:
            try:
                from filetypes.PE_net import parse_aot
                yield from parse_aot(self, self.aot_header)
            except ParsingError as e: print(e)

        # golang parsing
        try:
            from filetypes.PE_golang import parse_golang
            yield from parse_golang(self)
        except ParsingError as e: print(e)

        # bat2exe parsing
        try:
            from filetypes.PE_bat2exe import parse_bat2exe
            parse_bat2exe(self) # vfiles parsing
        except ParsingError as e: print(e)

        # nuitka onefile parsing
        try:
            from filetypes.PE_nuitka import parse_nuitka
            parse_nuitka(self) # vfiles parsing
        except ParsingError as e: print(e)




