Label: PE
PE (Portable Executable) Format: Structure, Sections, and How Windows Loads Binaries
What is the PE format
The PE (Portable Executable) format is the standard binary format used by Windows for:
- executables (
.exe) - dynamic libraries (
.dll) - drivers and system components
It defines how code and data are organized in a file so that the Windows loader can map it into memory and execute it.
In simple terms:
PE is the blueprint that tells Windows what to load, where to load it, and how to run it.
Where PE comes from
PE is based on the older COFF (Common Object File Format) and was introduced with Windows NT.
It is used across all modern Windows systems and is tightly integrated with the OS loader.
High-level structure of a PE file
A PE file is not just raw machine code. It is a structured container with multiple headers and sections.
At a high level, the layout looks like this:
[ DOS Header ]
[ DOS Stub ]
[ PE Header ]
[ Optional Header ]
[ Section Table ]
[ Sections (.text, .data, ...) ]
Each part serves a specific purpose.
DOS Header and stub
Every PE file starts with a legacy DOS header.
- Signature:
MZ - Contains a pointer to the PE header (
e_lfanew)
There is also a DOS stub program:
- historically prints “This program cannot be run in DOS mode”
- mostly irrelevant today, but still required for compatibility
PE Header (NT Headers)
The PE header starts with the signature:
"PE\0\0"
It includes:
File Header
- target machine (x86, x64)
- number of sections
- flags (executable, DLL, etc.)
Optional Header (not actually optional)
This is the most important part.
It defines:
- entry point address
- image base
- memory layout
- alignment rules
- data directories
Entry point
The entry point is where execution begins after the loader initializes the process.
This is typically:
- program startup code
- runtime initialization (CRT)
Not necessarily your main() function.
Sections in a PE file
The actual code and data live in sections.
Common ones include:
.text
- executable code
- CPU instructions
.data
- initialized global variables
.rdata
- read-only data (constants, strings, import tables)
.bss (virtual)
- uninitialized data (allocated at runtime)
.rsrc
- resources (icons, dialogs, version info)
.reloc
- relocation information (used if image base changes)
Virtual memory vs file layout
A critical concept in PE:
- Raw file layout — how data is stored on disk
- Virtual layout — how it appears in memory
The loader maps sections into memory based on:
- virtual addresses (RVA)
- alignment rules
This is why:
- offsets in file ≠ addresses in memory
Import table
The import table defines external dependencies.
It tells the loader:
- which DLLs are required
- which functions must be resolved
Example:
kernel32.dllLoadLibraryAGetProcAddress
At load time:
- Windows resolves these symbols
- fills the Import Address Table (IAT)
Export table
Used mostly in DLLs.
It defines:
- which functions are exposed
- how other modules can call them
Relocations
If the binary cannot be loaded at its preferred base address:
- relocation table is used
- absolute addresses are adjusted
Modern systems often use ASLR (Address Space Layout Randomization), so relocations are important.
PE and runtime patching
Understanding PE is essential if you work with:
- binary patching
- hooks
- loaders
- reverse engineering
For example:
- patching code in
.textrequires correct RVA → VA translation - modifying imports involves rewriting the IAT
- injecting code may require adding new sections
Common pitfalls
Mixing file offsets and RVAs
Very common mistake:
- reading raw file offsets as memory addresses
They are not the same.
Ignoring alignment
Sections are aligned differently:
- in file
- in memory
Incorrect assumptions break parsing.
Corrupting headers
Even small changes in:
- section table
- data directories
can make the binary unloadable.
Why PE still matters
Even today, PE remains central to:
- Windows internals
- debugging and profiling
- malware analysis
- game modding and patching
- custom loaders and tooling
If you modify binaries, you are working with PE whether you want it or not.
Final thoughts
The PE (Portable Executable) format is not just a file format — it’s a contract between your binary and the Windows loader.
It defines:
- how code is structured
- how dependencies are resolved
- how execution begins
Understanding PE gives you control over:
- low-level debugging
- binary manipulation
- runtime behavior
And without it, patching or hooking quickly turns into guesswork.