Introduction
PE32 is the format of programs and libraries on 32-bit
Microsoft Windows: Windows NT 3.1 from
1993, Windows 95, and every Windows since. 64-bit Windows still runs PE32 programs, so
the format is as common as ever. It is also the format of Windows CE programs, of
32-bit ARM Windows programs, of .NET assemblies built for any processor, and of some
UEFI firmware images. The same layout is used for programs (.exe),
libraries (.dll), drivers (.sys), control panel items
(.cpl), ActiveX controls (.ocx) and screen savers
(.scr).
PE stands for Portable Executable. The format is built on
COFF, the object file format of Microsoft’s
compilers: a PE file is an MS-DOS program, then the signature PE\0\0, a
COFF file header, an optional header that every image has, and a table of sections that
hold the code, the data and the tables Windows needs to load it. Microsoft publishes
the specification.
The 64-bit format, PE32+, has the same
structures, but its optional header is laid out differently and its addresses are 64
bits, so it is a separate format here. Everything added to PE32 since 1993 has only
added: new data directories, new flags, new machine values, and a load configuration
that grows at its end. So every PE32 file is one format. All the variations of
.exe are on the EXE page.
A program dropped on any page opens in the explorer. It shows the file’s structure, every header and table in the order of its bytes; its resources, listed by type, name and language; and its code, disassembled as x86 with an instruction decoded at every byte, laid out in columns, so that a jump into the middle of another instruction can be followed. The instructions are described in the x86 instruction reference.
Structure
All numbers are little-endian. Most addresses inside the file are relative virtual addresses (RVAs): offsets from the address the image is loaded at. The section table maps an RVA back to a file offset; see Section table and section data.
0x00MS-DOS headerMZ, withe_lfanewat0x3C64 bytes0x40DOS stub a small DOS program that prints “This program cannot be run in DOS mode.”varies…Rich header written by Microsoft’s linker: the tools that built the programvariese_lfanewPE signature50 45 00 004 bytes+4COFF file header machine, number of sections, time stamp, flags20 bytes+24Optional header magic0x10B, entry point, image base, alignments, subsystem, then the data directories224 bytes…Section table one entry for each section40 bytes each…Sections each section’s raw data, holding the code, the data and the tables the data directories point atvaries…After the image the certificate table, COFF symbols and any overlayvaries
The tables
The optional header ends with 16 data directories, each an RVA and a size. They locate the tables inside the sections:
| Directory | Table | Usually in |
|---|---|---|
| 0 | Exports | .edata or .rdata |
| 1, 12 | Imports and the import address tables | .idata or .rdata |
| 2 | Resources | .rsrc |
| 3 | Exception table (not on x86) | .pdata |
| 4 | Certificates (a file offset, not loaded) | after the last section |
| 5 | Base relocations | .reloc |
| 6 | Debug directory | .rdata |
| 9 | Thread-local storage | .rdata |
| 10 | Load configuration | .rdata |
| 11 | Bound imports | the headers |
| 13 | Delay-load imports | .rdata |
| 14 | CLR header (.NET) | .text |
Directories 7, 8 and 15 are reserved or unused; see Data directories.
The code
A section whose flags include CNT_CODE or MEM_EXECUTE holds
machine code. For machine 0x014C (Intel 386 and later) the explorer
disassembles each such section in 32-bit mode, from its raw data up to its
VirtualSize. The addresses it shows are those the code runs at: the image base plus
the RVA.
An instruction is decoded at every byte, not only where the one before ends, and the instructions are laid out in columns: the first column is the plain disassembly from the start of the section, and the others hold the instructions that start inside it. So when a jump lands in the middle of an instruction, the code it reaches is there to read. Every instruction is described in the x86 instruction reference.
The explorer names what it can: the entry point, every export, each import address
table slot as KERNEL32.dll!GlobalAlloc, so that a call through the slot
shows which function it calls, the TLS callbacks, and the functions in a COFF symbol
table, which MinGW and Wine builds keep.
- Code for other machines, such as 32-bit ARM, Alpha and MIPS, is not disassembled; the explorer names the machine.
- A .NET assembly’s
.textsection holds IL bytecode and metadata, not x86 code. Only its entry stub is x86: an indirect jump through the import address table to_CorExeMainor_CorDllMaininmscoree.dll. The rest disassembles as nonsense.
In the explorer
The tree shows every byte of the file once, in file order:
- the headers as one part: the DOS header, the stub, the Rich header, the PE headers and the section table;
- each section’s raw data, with the tables that lie inside it, and the bytes no table covers as “Code” in an executable section and “Data” in the others;
- what lies after the image: the certificate table, the COFF symbols and the overlay.
Tables are found through the data directories and the RVAs inside them, then placed where their bytes are, not where they are referred to. Many small items of one kind, such as hint/name entries, export names, resource data and unwind records, are grouped.
The Program tab sums the file up: its kind, machine, subsystem, entry point, image base, link time, linker version, the Windows version it needs, its flags, whether its checksum is right, the Rich header, the PDB path, the .NET runtime, the TLS callbacks, the signature, the COFF symbols and the overlay. It then lists the code, the sections, the data directories, the imports and exports, the resources, the debug directory, the tools from the Rich header, and a .NET summary.
Reference pages
- MS-DOS header, stub and Rich header – the 64-byte DOS header, the stub program and Microsoft’s undocumented list of tools
- PE signature and COFF file header – machine types, the time stamp and the characteristics flags
- Optional header – the 32-bit layout, subsystems, DLL characteristics and the checksum
- Data directories – the 16 entries that locate every table
- Section table and section data – section headers, flags, names, and mapping RVAs to file offsets
- Imports – import descriptors, lookup tables, hint/name entries and the import address table
- Delay-load imports – DLLs loaded on the first call
- Bound imports – the table that lets prebound addresses be used as they are
- Exports – the export directory, the address, name and ordinal tables, and forwarders
- Resources – the resource tree and the resource types
- Base relocations – the fixups applied when the image cannot load at its preferred address
- Debug directory – CodeView PDB paths, POGO, FPO and the other debug entries
- Thread-local storage – the TLS directory, the template and the callbacks
- Load configuration – the security cookie, SafeSEH, Control Flow Guard and the rest
- Exception table and unwind information – the function tables of ARM, MIPS and the other machines that have one
- CLR header and .NET metadata – the runtime header, the metadata root, streams and heaps
- Attribute certificates (Authenticode) – the signature appended to the file
- COFF symbol and string tables – symbols that GNU linkers leave in images
- Overlay – data after the image, and the installers and archives found there
Telling it apart
Every .exe format starts with an MS-DOS header. The explorer’s parser
for PE32 checks, in order, and turns the file down at the first check that fails:
- The file is at least 64 bytes long and starts with
MZ. Windows does not accept theZMthat some old DOS programs have. e_lfanew, the 32-bit value at0x3C, is at least0x40and leaves room for 24 bytes before the end of the file.- The four bytes at
e_lfaneware50 45 00 00,PE\0\0. - The optional header’s magic, the two bytes at
e_lfanew+ 24, is0x10B. A PE32+ file has0x20B. - SizeOfOptionalHeader is at least 96, the size of the fixed fields.
- The section table, 40 bytes for each section after the optional header, fits in the file.
Anything wrong after that, such as a table outside every section, two tables that
overlap or a wrong checksum, is reported as a problem rather than a reason to turn the
file down. Windows itself accepts an e_lfanew below 0x40,
where the PE header overlaps the DOS header, as some hand-made tiny programs do; the
explorer does not lay such files out.
Within PE32, the DLL flag (0x2000) in the COFF header marks
a library, the subsystem says whether a program is a console program, a GUI program, a
driver or an EFI image, and a CLR header (directory 14) marks a .NET assembly.
History
PE has no version field. These are the additions that matter; each one only added to the format.
| When | What changed |
|---|---|
| July 1993 | Windows NT 3.1, the first Windows to run PE programs. PE is built on COFF. |
| August 1995 | Windows 95 runs PE programs. |
| 1997–1998 | Microsoft’s linker starts writing the undocumented Rich header. The oldest seen are from linkers 5.10 and 5.12 (Visual C++ 5). |
| February 2002 | The .NET Framework 1.0: the CLR runtime header in directory 14. |
| Around 2007 | Windows Vista loads images flagged DYNAMIC_BASE at a random address (ASLR). |
| Around 2015 | Control Flow Guard: the GUARD_CF flag and the load configuration’s function tables, from Windows 8.1 Update and Visual Studio 2015. |
| Around 2020 | CET shadow stack compatibility, marked in an EX_DLLCHARACTERISTICS debug entry. |
The dates marked “around” come from the Windows releases in general, not from the PE specification.
Variants
- MS-DOS executable (MZ) – 16-bit DOS programs: an MZ header, relocations and the code
- 16-bit Windows executable (NE) – Windows 3.x and early OS/2 programs and DLLs, in movable segments
- Linear Executable (LE) – 32-bit Windows VxD drivers and DOS extender programs, in paged objects
- OS/2 executable (LX) – 32-bit OS/2 programs and DLLs, in paged objects
- Windows executable (PE32) – 32-bit Windows programs and DLLs, with a 32-bit optional header (magic 0x10B)
- Windows 64-bit executable (PE32+) – 64-bit Windows programs and DLLs (x64, ARM64), with a 64-bit optional header (magic 0x20B)
Links
Software
- Windows – runs PE32 programs directly, on 32-bit and 64-bit Windows alike
- Wine – runs Windows programs on other systems (Linux, macOS)
- Ghidra – the NSA’s free reverse engineering suite (Windows, macOS, Linux)
- IDA Pro – commercial disassembler and decompiler (Windows, macOS, Linux)
- x64dbg – free debugger for Windows programs, with x32dbg for 32-bit ones (Windows)
- PE-bear – free viewer for PE headers and sections (Windows, Linux)
- Resource Hacker – views and edits the resources in a PE file (Windows)
- Dependencies – shows the DLLs a program imports (Windows)
- 7-Zip – free archiver that can open a PE file’s sections and resources (Windows)
Developers
- pefile – reads and edits PE files (Python)
- LIEF – reads and modifies PE, ELF and Mach-O (C++, with Python and Rust bindings)
- goblin – parses PE, ELF and Mach-O (Rust)
- LLVM – its llvm-objdump and llvm-readobj tools dump PE headers, tables and code (C++)
- richheader – reads Rich headers, with a list of the product IDs (Python)
Reference
- PE Format – Microsoft’s specification of PE and COFF
- winnt.h – Wine’s copy of the Windows header that defines every PE structure (C)
- ARM exception handling – Microsoft’s description of the .pdata and .xdata records of 32-bit ARM Windows
- ECMA-335 – the Common Language Infrastructure standard: the CLR header and .NET metadata