A crash dump shows you addresses like 0x1400061A4. A hex editor shows file offsets like 0x3254. Debuggers, exploit writeups, and disassemblers speak in three dialects — file offset, RVA, and VA — and the memory layout is where they all meet. Get the layout right in your head and every address in every tool becomes the same number seen from a different window.
It also explains physical behavior: why .reloc can be thrown away after loading (it is marked discardable), why writing to .text faults (NX protection from section flags), and why your stack behaves like a reserve-plus-commit region rather than one giant allocation.
Picture one process address space with three pieces:
The model's missing piece: sections are adjacent in the section table but leave small gaps in memory. Those gaps are not wasted — they are the rounding to SectionAlignment, and they are why the last section can end at 0x8030 while the image still occupies a full page more.
The spec rules that build the map:
| Field | Spec rule |
|---|---|
| ImageBase | Preferred address of the first byte when loaded; must be a multiple of 64 K |
| Section placement | Section VAs in ascending order and adjacent, each a multiple of SectionAlignment |
| SizeOfImage | Size of the image including all headers; must be a multiple of SectionAlignment |
| SizeOfStackReserve / Commit | Only the commit size is committed; the rest of the reserve is made available one page at a time |
| SizeOfHeapReserve / Commit | Same reserve-versus-commit pattern for the local heap |
And the conversion that ties every tool together: VA = ImageBase + RVA, always. File offset is the odd one out — it comes from the section table, per section, and no single formula covers it.
Section characteristics become memory protection. Our samples decode straight from the spec flag table: .text reads 0x60000020 — IMAGE_SCN_CNT_CODE (0x20), MEM_EXECUTE (0x20000000), MEM_READ (0x40000000). .data reads 0xC0000040 — initialized data, read, and write. .reloc reads 0x42000040 — initialized data, read, and MEM_DISCARDABLE (0x02000000), so the loader may drop it once relocations are done.
cli-64.exe that delta is 0xC00 for .text but 0x3C00 for .rsrc. One equation never converts across sections.The loaded image of cli-64.exe, base 0x140000000 (computed as base plus each VirtualAddress):
| VA | Section | Ends at | Holds |
|---|---|---|---|
| 0x140000000 | headers | 0x140000400 | DOS stub, PE headers, section table (SizeOfHeaders 0x400) |
| 0x140001000 | .text | 0x1400027BC | Code, including the entry at 0x140001D40 |
| 0x140003000 | .rdata | 0x14000432C | Imports, load config, read-only constants |
| 0x140005000 | .data | 0x140005648 | Writable globals, including the security cookie slot at 0x140005008 |
| 0x140006000 | .pdata | 0x1400061EC | 41 RUNTIME_FUNCTION entries |
| 0x140007000 | .rsrc | 0x1400071E0 | The manifest resource tree |
| 0x140008000 | .reloc | 0x140008030 | Base relocations, discardable |
| 0x140009000 | (image end) | — | SizeOfImage 0x9000 rounds the last section up to a page boundary |
Watch the gaps: .text runs out at 0x27BC and .rdata does not start until 0x3000 — 0x844 bytes of padding, exactly the rounding to SectionAlignment. The stack and heap of this process each reserve 0x100000 bytes and commit only 0x1000 up front, read from the optional header we decoded earlier.
conpty.dll shows the same shape at its own base, 0x180000000: .text ends at RVA 0x10674, .rdata waits at 0x11000, and the image closes at SizeOfImage 0x1D000 after .reloc ends at 0x1C280.
Convert RVAs to VAs yourself — the whole trick is one addition:
$ python3 -c "print(hex(0x140000000 + 0x6000))"
0x140006000
$ python3 -c "print(hex(0x140000000 + 0x1d40))"
0x140001d40Then confirm the image end from the section table: add the last VirtualAddress and VirtualSize, round up to SectionAlignment, and compare against SizeOfImage. For our sample: 0x8000 plus 0x30 rounds to 0x9000 — and the header says 0x9000.
What to look for: the second line is the entry point of the whole program. Every address you will disassemble in the next lesson lives in this map.
Without looking back: cli-64.exe loads at 0x140000000 and you see a fault at VA 0x140005008. What is its RVA, and which section is it in?
The last section of a PE32+ image ends at RVA 0x8030, and SectionAlignment is 0x1000. What SizeOfImage does the spec require?
The pattern: memory sizes round to SectionAlignment, file sizes round to FileAlignment — never mix the two rulers.
The stage is set: image mapped, protections applied, stack and heap reserved. Only one address has not fired yet — AddressOfEntryPoint.
Next: The Startup Sequence — from entry point to CRT initialization to main.