Every file format you have studied so far is inert. The loader is where it stops being inert: the component that reads those headers, allocates memory, copies bytes, patches addresses, and transfers control. Understand it and you understand why ASLR exists, why an import is a memory write, why a signature is checked but never executed, and what malware actually manipulates when it injects or hollows a process.
You also stop being surprised by load failures. A relocation stripped image cannot move. A certificate is not a section. A section that ends at 0x8030 still occupies pages up to 0x9000. None of that is arbitrary — it falls out of fields you can read.
What follows is a model of the documented contract. The PE specification defines the data the loader consumes; it does not publish the operating system algorithm, and kernel internals are out of scope here. Each step below is anchored to a spec rule or a documented API — treat the sequence as the shape of the contract, not source code.
The spec rules that pin the model down:
| Rule | Spec text (condensed) |
|---|---|
| ImageBase | Preferred address of the first byte when loaded; must be a multiple of 64 K |
| SectionAlignment | Alignment of sections in memory; at least FileAlignment; default is the page size |
| FileAlignment | Power of 2 between 512 and 64 K; default 512 |
| SizeOfImage | Size of the image including all headers; must be a multiple of SectionAlignment |
| Section placement | Section VAs are assigned in ascending order and adjacent, each a multiple of SectionAlignment |
| Zero fill | If SizeOfRawData is less than VirtualSize, the remainder is padded with zeros |
| Relocations stripped | IMAGE_FILE_RELOCS_STRIPPED: no base relocations; must load at the preferred base or the loader reports an error |
| Certificate table | The virtual address in that data directory is a file offset to the first certificate entry |
Two verified files, same rules, different choices:
| Field | cli-64.exe | conpty.dll |
|---|---|---|
| ImageBase | 0x140000000 | 0x180000000 |
| SectionAlignment / FileAlignment | 0x1000 / 0x200 | 0x1000 / 0x200 |
| SizeOfHeaders / SizeOfImage | 0x400 / 0x9000 | 0x400 / 0x1D000 |
| COFF Characteristics | 0x0022 (executable, large-address-aware) | 0x2022 (plus DLL) |
| DllCharacteristics | 0x8160 (HIGH_ENTROPY_VA, DYNAMIC_BASE, NX_COMPAT, TERMINAL_SERVER_AWARE) | 0x4160 (same, with GUARD_CF instead of TERMINAL_SERVER_AWARE) |
| Subsystem | 3 (console) | 2 (Windows GUI) |
The flag values come straight from the spec table: HIGH_ENTROPY_VA is 0x0020, DYNAMIC_BASE 0x0040, NX_COMPAT 0x0100, GUARD_CF 0x4000, TERMINAL_SERVER_AWARE 0x8000. Mask and add — 0x8160 and 0x4160 decompose exactly as the table says.
The certificate directory proves the file-offset rule. In conpty.dll, data directory index 4 holds the pair 0x18600, 0x3D20. That is not an RVA: naive RVA conversion would map 0x18600 into .data and show you the wrong bytes. Read it as a file offset instead, and 0x18600 + 0x3D20 equals 0x1C320 — the exact file size, one quadword-aligned WIN_CERTIFICATE entry covering the Authenticode signature. The first bytes confirm the structure: dwLength 0x00003D20, wRevision 0x0200, wCertificateType 0x0002, which the spec defines as WIN_CERT_TYPE_PKCS_SIGNED_DATA.
00018600: 203d 0000 0002 0200 3082 3d10 0609 2a86 00018610: 4886 f70d 0107 02a0 823d 0130 823c fd02
Run the model against cli-64.exe and watch the numbers close:
Same arithmetic for conpty.dll: its sections end at 0x1C000 + 0x280 = 0x1C280, and SizeOfImage reads 0x1D000 — the next SectionAlignment boundary.
objdump reports the format, flags, and the computed start address (image base plus entry RVA):
$ objdump -f cli-64.exe
file format pei-x86-64
architecture: i386:x86-64, flags 0x0000012f:
HAS_RELOC, EXEC_P, HAS_LINENO, HAS_DEBUG, HAS_LOCALS, D_PAGED
start address 0x0000000140001d40HAS_RELOC confirms step 2 can rebase, and the start address is ImageBase 0x140000000 plus AddressOfEntryPoint 0x1D40. On Windows, dumpbin /headers prints the same optional-header fields in one place.
What to look for: whenever start address minus ImageBase does not equal AddressOfEntryPoint, you have misread a header — that single subtraction validates two fields at once.
Without looking back: a PE file carries IMAGE_FILE_RELOCS_STRIPPED and its preferred ImageBase is already taken. What happens?
A tools reads data directory index 4 of conpty.dll as RVA 0x18600 and converts it through the section table. What went wrong?
The pattern: every data directory is an RVA except the certificate table, which is a file offset the loader never maps.
The loader has mapped the image. Now that you know the rules, you can picture the result: one contiguous region from ImageBase to ImageBase plus SizeOfImage, with the stack and heap growing beside it.
Next: Process Memory Layout — ImageBase, sections in memory, heap, and stack.