The PE File Format for Incident Responders
What matters in a Windows PE file when you triage a suspicious EXE, DLL or driver: headers, sections, imports, resources, signature and overlay.
TL;DR. A Windows executable is a map: an MS-DOS header points to the PE header, the PE header describes how to load the file, and a table of sections says which bytes go where. Most first-triage questions — what is it, who built it, when, what can it do, is it signed, is something hidden in it — are answered by reading that map, without running anything. The PE Parser does it in your browser.
This article is the entry point of the series. It walks through the structure in the order a responder needs it, with the field names used by Microsoft's PE Format specification.
The layout at a glance
| Part | Where | What you get from it |
|---|---|---|
| MS-DOS header | offset 0, starts with MZ | e_lfanew, the offset of the PE header; the DOS stub and, often, the Rich header |
| PE signature + COFF file header | at e_lfanew | machine (x86, x64, ARM64), number of sections, TimeDateStamp, characteristics (EXE or DLL) |
| Optional header | right after | PE32 or PE32+, entry point, image base, subsystem, checksum, DLL characteristics (ASLR, DEP, CFG), 16 data directories |
| Section table | after the optional header | name, virtual and raw size, file offset and permissions of each section |
| Data directories | inside sections | imports, exports, resources, certificates, debug, TLS, load config, relocations, .NET |
| Overlay | after the last section | anything appended: installer payloads, archives, configuration, signatures |
Everything is little-endian. Addresses inside the image are RVAs (relative virtual addresses); converting an RVA to a file offset goes through the section table, which is why a damaged section table breaks every tool at once.
Headers: identity and build
The COFF header's TimeDateStamp is the classic compile time, in Unix seconds. It can be forged, zeroed, or — in recent Microsoft and .NET builds — replaced by a hash, as explained in PE timestamps. The optional header tells you whether the file is 32-bit (magic 0x10b, PE32) or 64-bit (0x20b, PE32+), where execution starts (AddressOfEntryPoint) and which exploit mitigations were requested (DYNAMIC_BASE for ASLR, NX_COMPAT for DEP, GUARD_CF for Control Flow Guard).
The CheckSum field is ignored for ordinary executables but required for drivers. A stored checksum that no longer matches the recomputed one hints that bytes changed after linking.
Sections: where code and data live
A typical Microsoft-linked program has .text (code), .rdata (read-only data and import tables), .data, .pdata (x64 exception data), .rsrc and .reloc. Three things deserve attention:
- Permissions. A section that is both writable and executable is unusual for compiler output and common for packers.
- Entropy. Close to 8 bits per byte means compressed or encrypted content. In a code section it usually means packing — see detecting packed executables.
- Names. Names are free text.
UPX0/UPX1,.vmp0or.themidaare hints, not proof, and a packer can be renamed.
Imports and exports: capabilities
The import directory lists every DLL and function the loader must resolve and patches their addresses into the import address table. Imports are the fastest capability overview: WinHttpOpen means HTTP, CreateRemoteThread means code in another process, RegSetValueExW means registry writes. Very few imports plus LoadLibrary/GetProcAddress suggests the real ones are resolved at run time. The import list also gives the imphash, used to cluster samples (imphash, Rich header and TLSH).
DLLs export functions by name and ordinal; a DLL with a single export such as DllRegisterServer or ServiceMain tells you how it expects to be loaded.
Resources, debug data and signature
Resources carry the version information (CompanyName, FileDescription, OriginalFilename), the manifest (requested privilege level), icons, dialogs and string tables — and sometimes an embedded executable. OriginalFilename that differs from the name on disk is a quick masquerading check.
The debug directory usually holds a CodeView record with the PDB path, a GUID and an age. Build paths such as C:\Users\<name>\source\repos\… leak the author's environment (PDB path).
The certificate table holds the Authenticode signature. It is not mapped into memory and sits after the sections, which is why it is excluded from the file hash that gets signed. What a signature does and doesn't prove is covered in Authenticode offline.
A first-triage checklist
- Hash it (MD5, SHA-1, SHA-256) and search your intelligence sources; compute the imphash.
- Read the times: compile, debug, signature. Do they agree with each other and with the incident?
- Check the signature: present, matching the file, who signed, when.
- Scan imports and strings for capabilities and indicators (URLs, IP addresses, paths, registry keys).
- Look at sections: permissions, entropy, entry point location; check for an overlay.
- Read the version information and PDB path; compare with the file name and location.
The PE Parser runs this checklist on one file or a whole collection and explains each indicator in plain words. It is static only: it unpacks UPX and extracts executables hidden inside a file, but other packers have to be unpacked elsewhere, and nothing it shows is a verdict on its own.
FAQ
What is a PE file?
Portable Executable is the file format of Windows programs and libraries: .exe, .dll, .sys drivers, .scr, .cpl, .ocx and EFI binaries. It starts with an MS-DOS header (MZ), followed by the PE signature, a COFF file header, an optional header and a section table.
Which PE fields matter most in a first triage?
Hashes and imphash for intelligence lookups, the compile, debug and signing times, the import table, the section table with entropy and permissions, the version information, the Authenticode signature and any data appended after the last section (overlay).