This page deals with the PE format, or more specifically, x86/x64 Windows (from XP to W7) binaries (ie, not other OSes or systems, not OBJ format, etc...)
It deals with the reality of the loader, not the theory of the format itself, as specified in Microsoft PE and COFF Specification. Many malwares or packers actually run without a problem, while they theoretically shouldn't.
All proof of concepts are included with source:
They're all working and clean.
They're all generated in asssembly, by hand, from scratch, so no superfluous information is included (they might be wrongly detected as suspicious for that).
a standard (high aligments, sections, imports) PE such as normal can be defined with only the following structure elements:
at IMAGE_DOS_HEADER.e_magic, db 'MZ'
at IMAGE_DOS_HEADER.e_lfanew, dd NT_Signature - IMAGEBASE
...
at IMAGE_NT_HEADERS.Signature, db 'PE', 0, 0
...
at IMAGE_FILE_HEADER.Machine, dw IMAGE_FILE_MACHINE_I386
at IMAGE_FILE_HEADER.NumberOfSections, dw NUMBEROFSECTIONS
at IMAGE_FILE_HEADER.SizeOfOptionalHeader, dw SIZEOFOPTIONALHEADER
at IMAGE_FILE_HEADER.Characteristics, dw IMAGE_FILE_EXECUTABLE_IMAGE
...
istruc IMAGE_OPTIONAL_HEADER32
at IMAGE_OPTIONAL_HEADER32.Magic, dw IMAGE_NT_OPTIONAL_HDR32_MAGIC
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd EntryPoint - IMAGEBASE
at IMAGE_OPTIONAL_HEADER32.ImageBase, dd IMAGEBASE
at IMAGE_OPTIONAL_HEADER32.SectionAlignment, dd SECTIONALIGN
at IMAGE_OPTIONAL_HEADER32.FileAlignment, dd FILEALIGN
at IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion, dw 4
at IMAGE_OPTIONAL_HEADER32.SizeOfImage, dd 2 * SECTIONALIGN
at IMAGE_OPTIONAL_HEADER32.SizeOfHeaders, dd SIZEOFHEADERS
at IMAGE_OPTIONAL_HEADER32.Subsystem, dw IMAGE_SUBSYSTEM_WINDOWS_CUI
at IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSizes, dd 16
...
at IMAGE_DATA_DIRECTORY_16.ImportsVA, dd Import_Descriptor - IMAGEBASE
...
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.Characteristics, dd IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_WRITE
so everything else is not required.
a more extreme PE can rely on even less elements:
mini is a working PE with the minimum amount of defined elements.
at IMAGE_NT_HEADERS.Signature, db 'PE', 0, 0
...
at IMAGE_FILE_HEADER.Machine, dw IMAGE_FILE_MACHINE_I386
at IMAGE_FILE_HEADER.Characteristics, dw IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_32BIT_MACHINE
...
at IMAGE_OPTIONAL_HEADER32.Magic, dw IMAGE_NT_OPTIONAL_HDR32_MAGIC
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd EntryPoint - IMAGEBASE ; not strictly required
at IMAGE_OPTIONAL_HEADER32.ImageBase, dd IMAGEBASE ; not required under XP
at IMAGE_OPTIONAL_HEADER32.SectionAlignment, dd SECTIONALIGN
at IMAGE_OPTIONAL_HEADER32.FileAlignment, dd FILEALIGN
at IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion, dw 4
at IMAGE_OPTIONAL_HEADER32.SizeOfImage, dd SIZEOFIMAGE
at IMAGE_OPTIONAL_HEADER32.SizeOfHeaders, dd SIZEOFIMAGE - 1 ; required for XP
at IMAGE_OPTIONAL_HEADER32.Subsystem, dw IMAGE_SUBSYSTEM_WINDOWS_CUI
as most elements are actually unused, they can be used for other reasons.
hdrcode contains a maximum of executed code in its header, and calculate fibonacci numbers via FPU.
DOS_HEADER:
...
.e_cparhdr dw (dos_stub - DOS_HEADER) >> 4 ; defines MZ stub entry point
...
align 010h, db 0
dos_stub:
bits 16
push cs
pop ds
mov dx, dos_msg - dos_stub
mov ah, 9
int 21h
mov ax, 4c01h
int 21h
dos_msg
db 'This program cannot be run in DOS mode.', 0dh, 0dh, 0ah, '$'
While it's usually just printing a string and terminating, the dos stub can do everything: open, modify files, and even executes PE.
exe2pe
this file is a broken 32b PE that is fixed (on disk), then launched by its (16b) dos stub. What's the most surprising is that a 16b process can launch the 32b part of a PE: if you were in a DOS environment, the 16b stub would be executed.
it can be ZM on an (non-PE) EXE. These executables still work under XP via ntvdm.
dosZMXP is a non-PE EXE with a ZM signature
at IMAGE_DOS_HEADER.e_magic, db 'ZM'
e_cparhrd
points to the dos stub, shifted by 4 (count of paragraphs)
e_lfanew
is the only required element (besides the signature) of the DOS HEADER to turn the EXE into a PE.
is a relative offset to the NT Headers.
can't be null (signatures would overlap)
can be 4 at minimum
tiny has a e_lfanew of 4, which means the NT Headers is overlapping the DOS Header.
DOS_HEADER:
.e_magic dw 'MZ'
align 4, db 0
istruc IMAGE_NT_HEADERS
at IMAGE_NT_HEADERS.Signature, db 'PE',0,0
...
at IMAGE_OPTIONAL_HEADER32.SectionAlignment, dd 4 ; also sets e_lfanew
at IMAGE_OPTIONAL_HEADER32.FileAlignment, dd 4
appendedhdr and apphdrW7 have both a e_lfanew of 400h, which puts the NT Headers in appended data. However, it's required under XP that the Header is extended till there via SizeOfHeaders.
Rich header
is an unofficial structure generated by compilers.
has no consequence on PE execution
is not documented officially, but documented by lifewire and Daniel Pistelli.
this is the smallest requirement for a valid PE. see
has to be dword-aligned.
Machine
specifies the used CPU
014c for 32b, which officially correspond to 'Intel 386 or later'. 014d/014e are unofficially corresponding to 486 and Pentium, however such Machine types are rejected for execution by Windows.
8664 for 64b (AMD64 only, not Itaniums, with 0200)
65535sects.exe has 65535 sections, that are all virtually executed.
TimeDateStamp
has a different meaning whether it's a Borland or a Microsoft compiler
is used for bound imports check
PointerToSymbolTable/NumberOfSymbols
no importance whatsoever for the loader
SizeOfOptionalHeader
is not the size of the optional header, but the delta between the top of the Optional header and the start of the section table.
Thus, it can be null (the section table will overlap the Optional Header, or can be null when no sections are present), or bigger than the file (the section table will be in virtual space, full of zeroes), but can't be negative.
nullSOH-XP is a PE with a null SizeOfHeaders. the section table is thus overlapping the optional header. (XP only).
...
SECTIONALIGN equ 4
FILEALIGN equ 4
...
OptionalHeader: ; Section Table
istruc IMAGE_OPTIONAL_HEADER32
at IMAGE_OPTIONAL_HEADER32.Magic, dw IMAGE_NT_OPTIONAL_HDR32_MAGIC
at IMAGE_OPTIONAL_HEADER32.SizeOfInitializedData, dd EntryPoint - IMAGEBASE ; VirtualSize (duplicate value to accept loading)
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd EntryPoint - IMAGEBASE ; SizeOfRawData
...
virtsectblXP is a PE with its 82 sections of empty information, with its section table in virtual space.
...
at IMAGE_FILE_HEADER.NumberOfSections, dw 82 ; 0 <= NumberOfSections <= 82 (varies with SizeOfOptionalHeader)
...
SIZEOFOPTIONALHEADER equ 10h + $ - IMAGEBASE ; bigger than the file itself !
; section table starts here...
<EOF>
standard value: E0
Characteristics
0x2 IMAGE_FILE_EXECUTABLE_IMAGE is required when code is executed.
0x2000 / IMAGE_FILE_DLL is required in for DLLs, in most cases, except:
if it's not set, the DLLMain will not be called, but the DLL is loaded and exports are usable. if it was dynamically called, the imports of the DLL won't be resolved
dllnomain.dll is staticly-loaded, has no valid DllMain, but its export is executed.
...
at IMAGE_FILE_HEADER.Characteristics, dw IMAGE_FILE_EXECUTABLE_IMAGE
...
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd 31415926h
...
dllnomain2.dll is an import-less dynamically loaded dll with no DllMain
IMAGE_FILE_DLL should NOT be set for a non-DLL PE.
maxvalues.exe has all its characteristics set excepted IMAGE_FILE_DLL.
0x100 / IMAGE_FILE_32BIT_MACHINE is not required, even in 32b.
it can be set for a 64b PE32+, and causes no problem.
nothing else is required
Optional Header
is anything but optional, as soon as execution is required.
Magic
specifies the exact format of OptionalHeader
10b for 32b
20b for 64b
MajorLinkerVersion/MinorLinkerVersion
no particular importance whatsoever
if LinkerVersion < 2.5, Microsoft AppLocker might wrongly report is not a valid Win32 application. (Exception from HRESULT: 0x800700C1) for no apparent reason. Changing these fields might fix the problem.
nullEP.exe has a null EntryPoint: Execution starts at ImageBase, executing 'MZ' as 'dec ebp/pop edx'
EntryPoint:
istruc IMAGE_DOS_HEADER
at IMAGE_DOS_HEADER.e_magic, db 'MZ'
push Msg
call [__imp__printf]
add esp, 1 * 4
push 0
call [__imp__ExitProcess]
at IMAGE_DOS_HEADER.e_lfanew, dd NT_Signature - IMAGEBASE
can be absent/bypassed (see TLS)
can be negative
dllextep-ld has an external EntryPoint that is located in dllextep.dll (a dll with no relocations)
dllext-ep:
IMAGEBASE equ 1000000h
dllextep-ld:
IMAGEBASE equ 33000000h
...
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd 1001008h - IMAGEBASE
can be virtual
virtEP.exe code starts one byte before the start of the section. This virtual space will be full of 0 on loading, so the first opcode will be made from one virtual byte and one physical byte.
at IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint, dd EntryPoint - IMAGEBASE - 1 ; -1 to start in virtual space
...
; actual EntryPoint starts here...
; there will be a virtual 00 before, so 00 C0 will be executed as `add al, al`
EntryPoint:
db 0c0h
...
tls_virtEP has an 'invalid' EntryPoint, but the TLS allocates the memory space and generates some codes, so the EntryPoint works fine.
is not used when a staticly loaded DLL doesn't have IMAGE_FILE_DLL set as IMAGE_FILE_HEADER.Characteristics
when a static DLL's DllMain is executed, the context of the not-executed-yet PE is available via lpvReserved. Thus, a static DLL can freely modify the value of the future EntryPoint to be executed
ctxt and ctxt-ld are an example of such context modification via lpvReserved.
BaseOfCode/BaseOfData
no particular importance whatsoever
ImageBase
is a multiple of 10000h
can be null, under XP. In this case, the binary will be relocated to 10000h
ibnullXP.exe has a null imagebase, and relocations
can be any value as long as ImageBase + 'SizeOfImage' < 80000000h
bigib.exe has an ImageBase of 7ffd0000h, and no relocations
ImageBase can't collide with ntdll.dll or kernel32, even if relocations are present (in the loaded PE), because they can't be relocated.
in this case, it gives a unique error message, under Windows 7: the subsystem needed to support the image type is not present. even if it has nothing to do with the subsystem.
if the ImageBase is bigger than that, the binary will be relocated to 10000h
ibkernel.exe has an ImageBase of 0FFFF0000h and relocations.
SectionAlignment/FileAlignment
both are power of 2 (4, 8, 16...)
standard mode: 200 <= FileAlignment <= SectionAlignment and 1000 <= SectionAlignment
normal.exe has aligments of 1000/200
bigalign.exe has alignments of 20000000/10000
in low alignment: 1 <= FileAlignment = SectionAlignment <= 800
maxvals has a maximum of PE structures set to FF, including its NumberOfRvaAndSizes which is set to FFFF
can be 0
no_dd has no data directory (see imports)
.Net loaders ignores this value, even if it requires relocations and a COM data directory, and will parse them. Thus, a .Net PE can work with a NumberOfRvaAndSizes of 2.
data directories
exports
like many data directories, Exports' size are not necessary, except for forwarding (see below).
Characteristics, TimeDateStamp, MajorVersion and MinorVersion are not necessary.
Base is only used for ordinal imports
dll.dll has a small export table, for one named entry:
AddressOfNames is lexicographically-ordered (like a dictionary): the pointer to a name starting with A shouldn't be after a name starting with B, and so on...
export_order has some exports wrongly ordered, which makes them unusable with the official loader.
export names can have any value (even null or more than 65536 characters long, with unprintable characters), just null terminated.
dllemptyexp-ld.exe loads dllemptyexp.dll with a null export
dllweirdexp-ld.exe loads dllweirdexp.dll with a 131102-long non-Ascii export.
an .EXE can have exports (no need of relocation nor DLL flag), and can use them normally
ownexports.exe uses its own exports
or they can be not used for execution, but for documenting the internal code
PE_exports_doc has internal exports used as address symbols
or they can have external or virtual addresses (but no address of 0, (un?)surprisingly)
ownexports2 has a -1 export, to which it jumps (after adding 1). It also has an export in virtual space, which is executed.
exports at fixed value can be used to encode data, when their values are filled in the Import Address Table (their actual value doesn't matter as long as they are not executed, it's just a copied dword)
exportsdata.exe's code is stored as dodgy exports RVAs, and is restored when imports are resolved
fake exports values can disrupt proper disassembly
exportobf.exe contains fake exports to make disassembly harder
ordinals-only exports can make the structure even smaller (no NumberOfFunctions/NumberOfNames/AddressOfNames/AddressOfNameOrdinals). Fake entries can be also present in exports as long as Base + Ordinal matches the wanted export.
impbyord.exe calls its own imports by ordinals
dllord-ld.exe is importing export #314 from dllord.dll
dllord-ld.exe
dll.dll_iat:
__imp__export:
dd 1 << 31 | 314h ; highest bit set to indicate imports by ordinal
dllord.dll
Exports_Directory:
...
Base dd 313h
...
AddressOfFunctions dd address_of_functions - IMAGEBASE
...
address_of_functions:
dd -1 ; bogus entry that will be 313h
dd __exp__Export - IMAGEBASE
exports can be used just to forward imports. in this case, the forwarded import is written as dll.API and must be within exportsVA and exportsVA+Size.
dllfw.dll forwards imports from msvcrt
address_of_functions:
dd amsvcrt_printf - IMAGEBASE
...
address_of_names:
dd a__exp__Export - IMAGEBASE
...
amsvcrt_printf db "msvcrt.printf", 0 ; forwarding string can only be within the official export directory bounds
a__exp__Export db 'ExitProcess', 0
EXPORTS_SIZE equ $ - Exports_Directory
exports can also forward each other and create loops
dllfwloop.dll forwards its own imports until the right API is called.
because it can be null, it can be put in virtual space, like the next 2 dwords (timestamp, forwarderchain)
imports_virtdesc has an import descriptor starting in virtual space over its 3 first dwords
if the IAT is empty, then the DLL is not loaded, and the file is loaded even with an invalid dll name.
imports_nothunk has a bogus descriptor with an empty IAT, and a bogus DLL name, between real descriptors.
the imports directory is not compulsory (XP or later). either don't call any API (ex, resource placeholders), or locate Kernel32 and its exports by hand (see below, no_dd)
the imports descriptor terminator just need to have null values. So, it can be placed in virtual space.
imports_vterm.exe has a virtual terminator
;terminator is partially in virtual space
dd msvcrt.dll_hintnames - IMAGEBASE
dd 0, 0
<EOF>
the terminator of imports descriptor only needs to have its Name offset null, or its IAT. Then, the other values are not important.
imports_badterm has a bad terminator, that looks almost like a valid one:
under Vista and Windows 7, the dll names can be redirected from API-MS-* to standard system DLLs by c:\windows\system32\apisecschema.dll, as documented by deroko
imports_apimsW7 imports ExitProcess via API-MS-Win-Core-Localization-L1-1-0.dll which is redirected during loading to kernel32.dll
as most fields of a descriptor are not necessary, it's possible to squeeze an overlapping IAT in it.
imports_iatindesc.exe has IATs in descriptors
Import_Descriptor:
;kernel32.dll_DESCRIPTOR:
dd 0 ; can't put the IAT over this one
msvcrt.dll_iat:
__imp__printf:
dd hnprintf - IMAGEBASE
dd 0
dd kernel32.dll - IMAGEBASE
dd kernel32.dll_iat - IMAGEBASE
;msvcrt.dll_DESCRIPTOR:
dd 0
kernel32.dll_iat:
__imp__ExitProcess:
dd hnExitProcess - IMAGEBASE
dd 0
dd msvcrt.dll - IMAGEBASE
dd msvcrt.dll_iat - IMAGEBASE
imports_tiny combines a lot of these techniques with ordinals, to make a working imports structure as small as possible (40 bytes for 2 imports from 2 dlls)
imports' Data Directory is not required to load imports: by locating kernel32 manually then parsing its exports manually, it's possible to resolve LoadLibraryA, then it's possible to manually load any DLL and resolve any of its exports (manually or via GetProcAddress).
no_dd has no data directory, and loads its imports manually.
.Net requires an import to mscoree.dll._CoreExeMain, and an import size to be at least 0x28.
It doesn't accept to import to mscoree (without extension).
resources
directory-like structure.
...
at IMAGE_DATA_DIRECTORY_16.ResourceVA, dd Directory_Entry_Resource - IMAGEBASE
...
push SOME_TYPE ; lpType
push SOME_NAME ; lpName
push 0 ; hModule
call [__imp__FindResourceA]
...
Directory_Entry_Resource:
; root directory
istruc IMAGE_RESOURCE_DIRECTORY
at IMAGE_RESOURCE_DIRECTORY.NumberOfIdEntries, dw 1
iend
istruc IMAGE_RESOURCE_DIRECTORY_ENTRY
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_TYPE ; .. resource type of that directory
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd (1<<31) | (resource_directory_type - Directory_Entry_Resource)
iend
resource_directory_type:
istruc IMAGE_RESOURCE_DIRECTORY
at IMAGE_RESOURCE_DIRECTORY.NumberOfIdEntries, dw 1
iend
istruc IMAGE_RESOURCE_DIRECTORY_ENTRY
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_NAME ; name of the underneath resource
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd (1<<31) | (resource_directory_language - Directory_Entry_Resource)
iend
resource_directory_language:
istruc IMAGE_RESOURCE_DIRECTORY
at IMAGE_RESOURCE_DIRECTORY.NumberOfIdEntries, dw 1
iend
istruc IMAGE_RESOURCE_DIRECTORY_ENTRY
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd resource_entry - Directory_Entry_Resource
iend
resource_entry:
istruc IMAGE_RESOURCE_DATA_ENTRY
at IMAGE_RESOURCE_DATA_ENTRY.OffsetToData, dd resource_data - IMAGEBASE
at IMAGE_RESOURCE_DATA_ENTRY.Size1, dd RESOURCE_SIZE
iend
resource_data:
Msg db " * message stored in resources", 0ah, 0
RESOURCE_SIZE equ $ - resource_data
in standard, 3 levels: Root/Type/Name/Language but anything else is possible.
loops are possible
resourceloop.exe contains several loops between resource directories
resource_directory_type:
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd (1<<31) | (resource_directory_loop - Directory_Entry_Resource)
...
resource_directory_loop:
...
; double level recursivity
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd (1<<31) | (resource_directory_type - Directory_Entry_Resource)
...
; direct recursivity
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData, dd (1<<31) | (resource_directory_loop - Directory_Entry_Resource)
Names and Types of a resource can be used:
immediate integers (aka IDs) - like resource.exe
SOME_TYPE equ 315h
SOME_NAME equ 7354h
...
push SOME_TYPE ; lpType
push SOME_NAME ; lpName
push 0 ; hModule
call [__imp__FindResourceA]
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_TYPE ; .. resource type of that directory
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_NAME ; name of the underneath resource
immediate integers (aka IDs) converted as string for loading - like resource2.exe
push atype ; lpType
push ares ; lpName
push 0 ; hModule
call [__imp__FindResourceA]
...
atype db '#315', 0
ares db "#7354", 0
...
SOME_TYPE equ 315
SOME_NAME equ 7354
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_TYPE ; .. resource type of that directory
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd SOME_NAME ; name of the underneath resource
...
strings (aka Names). the Name in resource directory is with the format length + widestring - like namedresource.exe
the resource structures are relative to the start of the Data Directory, but the resource data can be anywhere in the file (RVA)
reshdr.exe has its resource data in the PE header
at IMAGE_DOS_HEADER.e_magic, db 'MZ'
at IMAGE_DOS_HEADER.e_lfanew, dd NT_Signature - IMAGEBASE
...
resource_data:
Msg db " * resource stored in header and shuffled resource structure", 0ah, 0
RESOURCE_SIZE equ $ - resource_data
resource strings have their own awkward format, in which 16 strings are stored in each block, and a null string is equivalent to no string, as all strings of the same block are stored consecutively as <length16><string>.
resource_string stores and access its message via resource string
Version information
version information is a resource of type RT_VERSION 0x10 (16)
thus, parsing version informations requires Data directories and resource parsing, which is not-trivial in extreme cases (folded headers, resource loops...). It might be preferable to just check the FILE_HEADER's TimeDateStamp (which is straightforward, see hard_imports) if you just require some minor version check.
under XP, the resource DataDirectory has to start at the beginning of its own section, or the properties dialog fails to parse the resources.
the VS_FIXEDFILEINFO structure is required as well as it's signature, even if not used.
version_cust contains minimal version informations, which generates the version tab but doesn't show any information
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd RT_VERSION ; .. resource type
...
resource_data:
VS_VERSION_INFO:
.wLength dw VERSIONLENGTH
.wValueLength dw VALUELENGTH
.wType dw 0 ; 0 = bin, 1 = text
WIDE 'VS_VERSION_INFO'
align 4, db 0
Value:
istruc VS_FIXEDFILEINFO
at VS_FIXEDFILEINFO.dwSignature, dd 0FEEF04BDh
iend
VALUELENGTH equ $ - Value
align 4, db 0
; no children
VERSIONLENGTH equ $ - VS_VERSION_INFO
RESOURCE_SIZE equ $ - resource_data
version_mini implements a children StringFileInfo structure, which actually displays some minimal version information (and crashes Windows XP explorer).
; children
StringFileInfo:
dw STRINGFILEINFOLEN
dw 0 ; no value
dw 0 ; type
WIDE 'StringFileInfo'
align 4, db 0
; children
StringTable:
dw STRINGTABLELEN
dw 0 ; no value
dw 0
WIDE '040904b0' ; required correct
align 4, db 0
;children
; required or won't be displayed by explorer
__string 'FileVersion', ''
STRINGTABLELEN equ $ - StringTable
STRINGFILEINFOLEN equ $ - StringFileInfo
the FixedFileInfo structure can contain any value besides its signature. version_std contains only FFs in it.
istruc VS_FIXEDFILEINFO
at VS_FIXEDFILEINFO.dwSignature, dd 0FEEF04BDh
times 6 dd 0ffffffffh
iend
the StringFileInfo structure accepts standard values, but also unknown strings, empty strings and duplicates.
Manifest are XML resource that store informations about the executable.
Manifest presence can be checked by kernel32.CreateActCtxA
the minimal Manifest is <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'/>
XP SP2 used to BSOD with a simple manifest PE
a PE with an incorrect manifest resource won't run
a manifest with an invalid type will be ignored, thus the file will run.
manifest_broken has an invalid manifest with an invalid type, so it's ignored altogether
...
MYMAN equ 3 ; incorrect - this file wouldn't run if it's set to 1 or 2
...
at IMAGE_RESOURCE_DIRECTORY_ENTRY.NameID, dd MYMAN
...
resource_data:
db "<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>" ; broken end
RESOURCE_SIZE equ $ - resource_data
...
exception
used by 64b binaries for structured exception handling.
exceptions is a minimalist 64b PE that use exceptions.
the OS relies on the DataDirectory itself in memory.
seh_change64 alterates the handler in memory, which is taken into account by the OS.
security
defines the offset and size of the digital certificate blob (as it can be appended after appended data itself).
the area covered by this data directory is not included in the signature checking. Thus, increasing the size of a certificate and appending some data that won't be checked is possible without breaking the signature. This leads to security problems if the file is an installer with a ZIP in appended data / last section, as ZIPs are parsed bottom-up.
moreover, it was possible to move the certificate so that its starts overlaps IMAGE_DOS_HEADER.e_lfanew, which could lead to an alternate IMAGE_NT_HEADERS being used - thus, a different PE - while the signature is still valid.
relocations
relocations are used when the PE is loaded at a different ImageBase. Hardcoded Addresses such as push <addr> and call [<addr>] have to be adjusted.
at least 1 relocation is required and used for .Net files, on the EntryPoint jump FF25 xxxxxxxx: jmp [_CorExeMain]
relocations are not used (even with corrupted ones) if the PE can be loaded at the expected address.
forcing the binary to relocate to a known place (ex, with a null or kernel ImageBase) makes it possible for the relocations to have predictable behavior. Thus, they can be used to decrypt or clear some values in memory. Also, relocations may be used to modify themselves.
reloccrypt.exe has a 0ffff0000h ImageBase, and its relocations are, first, modifying themselves, using uncommon relocation types, then decrypting the code to be executed.
applying manually an extra relocations on the ImageBase field itself doesn't change the memory mapping, as the used value was read from disk, and relocations are done in memory. However, once the PE is mapped, relocations are performed, altering the ImageBase in memory, and thus influencing the value of the EntryPoint (for it to work, a writeable header is required, thus low alignments)
ibreloc.exe is a low alignment PE with a altered EntryPoint and an extra relocation on the ImageBase field.
The callbacks are VAs, not RVAs (ImageBase is included).
each callback is executed until an error happens or a null dword is next in the list. then, no matter what happened (error or not) the EntryPoint is executed:
a TLS doesn't need to return cleanly if it knows it's the last one
an incorrect entry in the list doesn't trigger a visible error
tls_obfuscation.exe has many fake TLS callback entries to disrupt disassembly
like the entrypoint value, a callback VA is blindly called. It can be:
outside the PE, in a known in advance address
pointing an Import Address Table entry, which means an API will be called with ImageBase as parameter.
tls_import.exe executes mz.exe via a call to WinExec through a TLS callback in its IAT.
under XP, TLS are only executed with staticly loaded DLL, not dynamicly loaded ones.
on XP, TLS are executed twice, on process start and process termination. Thus, code is executed even after a call to ExitProcess.
This is true even under Windows 7, however libraries such as user32.dll might be already unloaded, preventing code using it to work normally.
TLS callbacks are not executed on thread start if no DLL importing kernel32 is imported. Thus, only execution on thread stop if kernel32 is the only import.
tls_k32 only has imports to kernel32, and a TLS that is ignored on thread start and used on thread stop, while the EntryPoint code is meaningless.
this behavior might be altered if the OS or debugger is loading an extra DLL.
TLS callbacks' list is updated at each callback execution. If a TLS or the EntryPoint code add or remove an entry, it will be taken into consideration
tls_onthefly's first TLS adds a second one on the list that will be executed directly after the first one is over
TLS AddressOfIndex is cleared on loading. Thus, it can be used to modify code execution.
tls_aoi patches the operand of a looping jump by pointing AddressOfIndex to it.
AddressOfIndex equ $ + 1
jmp long $
like DllMain, after TLS execution, only ESI needs to be correct. the rest doesn't matter, including ESP, which could easily crash an emulator.
fakeregs corrupts all registers except ESI, on DllMain and TLS execution.
Load config
store various OS related flags, including the SafeSEH structure, which is handled by extra code added in the PE by VisualStudio.
Bound imports
are a shortcut structure to hardcode some imports values in advance, to make import values faster
all the loader does is take a filename, compare the timestamp of the file and the one included in the bound imports table, then use the VA directly as import if they match.
dllbound-ld.exe loads and execute 'dllbound.dll' via bound imports.
at IMAGE_DATA_DIRECTORY_16.BoundImportsVA, dd BoundImports - IMAGEBASE
...
BoundImports:
dd 31415925h ; timestamp of the bound DLL
dw bounddll - BoundImports ; it's a WORD relative offset :(
dw 0
...
bounddll db 'dllbound.dll', 0 ; we have to duplicate locally this string... :(
...
dll.dll_iat:
__imp__export:
dd 01001008h ;VA of the export of the loaded DLL
thus, replacing the RVA in the bound import table is an easy way to redirect imports.
dllbound-redirld.exe will load the wrong import of dllbound.dll because one RVA has been changed.
dll.dll_iat:
__imp__export:
dd 01001018h ; corrupted VA of the import
under XP only, it's even possible to put a different filename and timestamp. a completely different DLL will be used no matter what the standard import table says.
dllbound-redirldXP.exe will load the wrong dll dllbound2.dll, as the name and timestamp have been modified.
BoundImports:
dd 27182818h ; timestamp of the hijacking DLL
dw bounddll - BoundImports
dw 0
;terminator
dd 0, 0
bounddll db 'dllbound2.dll', 0 ; hijacking DLL name
Import table
the RVA and the Size required to be set on a low alignment PE to make the import table writeable, under XP.
nosectionXP.exe needs an IAT to make its imports writeable
delay imports
is a rip-off, a fake structure.
delay_corrupt.exe contains completely corrupted delay imports, which doesn't alterate the file loading and running.
is just a trampoline added by the compiler to load imports and DLL on request,
with a 'frontend' in the data directories, with a structure similar to standard imports (adress table + name table) so that external tools can still indicate that imports calls are present.
delayimports.exe has working delay imports (displayed by other tools):
...
at IMAGE_DATA_DIRECTORY_16.DelayImportsVA, dd delay_imports - IMAGEBASE
at IMAGE_DATA_DIRECTORY_16.DelayImportsSize, dd DELAY_IMPORTS_SIZE
...
delay_imports:
istruc _IMAGE_DELAY_IMPORT_DESCRIPTOR
at _IMAGE_DELAY_IMPORT_DESCRIPTOR.rvaDLLName, dd msvcrt.dll
at _IMAGE_DELAY_IMPORT_DESCRIPTOR.rvaIAT, dd delay_iat - IMAGEBASE
at _IMAGE_DELAY_IMPORT_DESCRIPTOR.rvaINT, dd msvcrt_int
iend
istruc _IMAGE_DELAY_IMPORT_DESCRIPTOR
iend
delay_iat:
__imp__printf:
dd __delay__printf
dd 0
the information in the header is not required to make delay import works, are they are extra code added in the file by the compiler.
Erasing the data directory VA from a standard file with delay imports will not disturb its execution
delaycorrupt.exe has the same structure as delayimports.exe, but the descriptors are empty.
sections have to be in increasing order, virtually.
A section can start before the previous one ends. Which means that offset-wise, the address is not constantly increasing.
secinsec.exe has a 1x-sized section inside the previous 3x-sized section
SectionHeader:
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 3 * FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1 * FILEALIGN
iend
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 2 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 2 * FILEALIGN
iend
sections don't have to be virtually contiguous
virtgap introduces a gap of 10000000h between 2 sections.
SizeOfRawData
with standard alignments, sections can be physically empty.
the last section doesn't have to be physically aligned in size, cf truncatedlast.exe
if bigger than virtual size, then virtual size is taken.
bigSoRD.exe has an inflated section
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 1 * FILEALIGN + 0ffff0000h
PointerToRawData
sections can be physically overlapping.
dupsec has 2 identical sections (besides the VirtualAddress)
SectionHeader:
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.Characteristics, dd IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_WRITE
iend
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 2 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1 * FILEALIGN
at IMAGE_SECTION_HEADER.Characteristics, dd IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_WRITE
iend
if a section starts at offset 0, it's invalid.
if a section's physical start is lower than 200h (the lower limit for standard alignment), it is rounded down to 0. Thus, it's a legitimate way to map the header.
duphead.exe maps the header in a section via rounding down it's physical start:
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1ffh ; upper limit of the down-rounding trick
sections can be in wrong order physically
shuffledsect.exe has sections in a wrong physical order.
SectionHeader:
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 2 * FILEALIGN
...
iend
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd 1 * SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd 2 * SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 1 * FILEALIGN
sections can leave a physically unused space in the PE
slackspace.exe has 2 section leaving an unused physical space in between
...
SectionHeader:
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd SECTIONALIGN
at IMAGE_SECTION_HEADER.SizeOfRawData, dd FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd FILEALIGN
...
iend
istruc IMAGE_SECTION_HEADER
at IMAGE_SECTION_HEADER.VirtualSize, dd SECTIONALIGN
at IMAGE_SECTION_HEADER.VirtualAddress, dd SECTIONALIGN * 2
at IMAGE_SECTION_HEADER.SizeOfRawData, dd FILEALIGN
at IMAGE_SECTION_HEADER.PointerToRawData, dd 3 * FILEALIGN
...
the physically-last section defines the appended data. However, it's easy to 'hide' appended data by either:
adding a fake extra section (even physically one byte), such as in hiddenappdata1 (creating some slackspace)
enlarging the physically-last section, such as in hiddenappdata2
a 64b PE (PE32+) is like a 32b PE, except that the FILE HEADER's Machine and the OPTIONAL HEADER's Magic have AMD specific values, and the Imports's INT, as well as the ImageBase, and the Stack and Heap info are QWORD (which drops a few fields from the Optional header as a consequence
252 bytes, under Vista/Windows 7 (the same as under XP, with null padding): tinyW7
268 bytes, under Vista/Windows 7 (same again, just null bytes to get the required size, but the same elements as tinyXP)
in 32b, tinyW7_3264
in 64b, tinyW7x64
so, 268 bytes is the smallest size for a universal tiny PE.
specific cases
folded header
found out by Reversing Labs as 'Dual PE Header'
the first checks of the PE are done on file. then the file is loaded in memory. then imports are resolved, from the image in memory.
by extending the header until the first SectionAlignment, it's possible to have the first section overlapping the header partially.
thus, the actual data directories used for imports resolving will not be the contiguous ones on the disk.
...
section progbits vstart=IMAGEBASE + SECTIONALIGN align=FILEALIGN
;---------------------------------------------- CUT and FOLD here ----------------------------------
; ok here we're at RVA 1000h (start of 1st section),
; so this data will overwrite the PE headers originally loaded from offset F80h, physically further in the file.
dd Import_Descriptor - IMAGEBASE, 0
...
times 0f80h - FILEALIGN * 2 db 0 ; to make the header overlap on address offset/rva 1000h
NT_Signature:
istruc IMAGE_NT_HEADERS
at IMAGE_NT_HEADERS.Signature, db 'PE', 0, 0
iend
...
istruc IMAGE_DATA_DIRECTORY_16
dd 88660001h,010009988h
;---------------------------------------------- CUT and FOLD here ----------------------------------
; we cut the header here, and we're right at offset 1000h
;---------------------------------------------- CUT and FOLD here ----------------------------------
dd 86600010h,001000998h
dd 66000100h,000100099h
dd 6000100Fh,0F0010009h
...
PE + PDF + ZIP
PDF in/and ZIP:
PDF documents don't need to have their signature starting at offset 0
in a ZIP archive with no compression, the file is stored in its original form
So it's possible to make a ZIP containing a PDF that also works as the PDF itself.
ZIP archives also don't need to start at offset 0
so a PE can contain such a ZIP, and work as both a PE and a ZIP
moving the 'PE' headers far enough via increasing e_lfanew enables to store any size of file in the PE
so it's possible to make a PE containing a ZIP containing a uncompressed PDF, that works as both a document (PDF), an archive (ZIP), and an executable (PE), like pdf_zip_pe.exe.
File formats should have enforce their signature at offset 0.
this W7-only binary use the TLS AoI trick to clean its imports. On disk, the import table is full of bogus descriptors, which will be ignored on loading
by using a similar trick as in quine to make a printable DOS header, and relocating the NT HEADERS far enough so that e_lfanew contains no null char, no0code contains no null byte before the code start.
db 'MZ'
align 3bh, db 90h
dd nt_header - IMAGEBASE
db 90h
EntryPoint:
bits 32
...
times 01010000h db ' '
...
nt_header:
istruc IMAGE_NT_HEADERS
at IMAGE_NT_HEADERS.Signature, db 'PE',0,0
however, the NT HEADERS magic contains necessarily 2 null bytes, thus it's impossible to have a working PE with no null byte at all.
data PEs
some specific cases require PE files with less elements than otherwise mentioned
Data Files
loading a file via LoadLibraryEx with LOAD_LIBRARY_AS_DATAFILE needs a PE file with only a very few defined elements: not even the Subsystem or the Machine needs to be defined for such a ''library''.
d_tiny.dll is a 61 bytes PE with only 3 defined elements
yet even if nothing is defined, its code can still be ran. This allows us to make a non-null PE with code.
d_nonnull.dll is a data PE with executed code and no null character
resources
A standard use for code-less PEs is to store resources. In this case, more fields are required (Machine, SizeOfOptionalHeader, SizeOfHeaders), but most fields can contain bogus values.
d_resource.dll is a PE with a lot of corrupted fields yet correct and usable resources
conclusion
a PE executing code can have either no sections, no entrypoint, no data directories - but not at least one of the 3 is required - which breaks the typical model.
many fields are not relevant, and can have completely bogus values (even important-looking ones such as BaseOfCode, SizeOfCode...)
many fields are taken into account only once the PE is mapped in memory, which requires a different way of thinking (allocated areas, mapped sections...)
most fields are not checked for boundaries, even important ones such as EntryPoint, TLS Callbacks, imports, exports...
several fields enable loader-based decryption (relocations, exports)
TLS is arguably the most f*cked up part of the PE format.