How Windows arranges a process in virtual memory — the stack, heap, PE sections, and kernel space — and why understanding this layout is essential for interpreting debugger output and recognising exploitation patterns.
⚡ Quick Bite · 40s
Stack, Heap, and PE Section mapping; tracking EBP and ESP for debugging; spotting PAGE_EXECUTE_READWRITE injection indicators in memory.
Watch this 40-second summary before diving into the full article. Sign in to earn 5 leaderboard points.
When the Windows loader maps a PE file into memory, it arranges the sections into a virtual address space. Every 32-bit process gets a private 4 GB virtual address space, split between user mode (0x00000000–0x7FFFFFFF) and kernel mode (0x80000000–0xFFFFFFFF).
High address (0xFFFFFFFF)
┌─────────────────────────┐
│ Kernel Space │ OS kernel — not accessible from user mode
├─────────────────────────┤ (0x80000000)
│ Stack │ grows downward ↓
│ │
│ (unallocated) │
│ │
│ Heap │ grows upward ↑
├─────────────────────────┤
│ .bss / .data │ Global variables
├─────────────────────────┤
│ .text │ Executable code
├─────────────────────────┤
│ PE Headers │
└─────────────────────────┘ (0x00400000 — typical ImageBase)
Low address
Refer to REMA eBook 2026, Figure 2.14 for the full virtual memory layout diagram with annotated address ranges.
The stack is a LIFO (last-in, first-out) data structure managed automatically by the CPU.
PUSH decrements ESP by 4, then writes the valuePOP reads the value, then increments ESP by 4Each function call creates a stack frame: a region of the stack holding the function's local variables, the saved EBP, and the return address. When the function returns, its frame is destroyed.
High address
┌─────────────────────────┐
│ Arg 2 [EBP+0x0C] │ ← Passed by caller
│ Arg 1 [EBP+0x08] │
│ Return Address [EBP+04]│ ← Where to resume after RET
│ Saved EBP [EBP+0x00] │ ← EBP points here
│ Local var 1 [EBP-0x04] │ ← Current frame begins
│ Local var 2 [EBP-0x08] │
│ Buffer [EBP-0x??] │
└─────────────────────────┘ ← ESP points here
Low address
A buffer overflow on the stack writes past the buffer boundary upward — toward the saved EBP and then the return address. When RET executes, the CPU pops the corrupted value into EIP and jumps to attacker-controlled code.
Buffer at [EBP-0x40]
Overflow direction: low → high
Overwrites: local vars → saved EBP → return address
Recognising this layout in a debugger is the basis for understanding every stack exploitation technique.
The heap is a pool of memory used for dynamic allocations requested at runtime.
malloc() / HeapAlloc() — requests heap memoryfree() / HeapFree() — releases heap memoryVirtualAlloc() — large allocations with custom permissionsMalware frequently uses the heap to store unpacked payloads. If a binary calls VirtualAlloc with PAGE_EXECUTE_READWRITE, it is allocating a heap region where it can both write data and execute code — a strong indicator of runtime unpacking or shellcode injection.
push PAGE_EXECUTE_READWRITE ; flProtect = 0x40
push MEM_COMMIT ; flAllocationType
push payload_size ; dwSize
push 0 ; lpAddress (OS chooses)
call VirtualAlloc ; returns base address in EAX
; EAX now points to RWX memory — malware writes shellcode here
| Property | Stack | Heap |
|---|---|---|
| Management | Automatic (CPU adjusts ESP) | Manual (programmer calls alloc/free) |
| Growth direction | Downward (high → low) | Upward (low → high) |
| Lifetime | Until function returns | Until explicitly freed |
| Speed | Fast (single SUB ESP) | Slower (heap manager search) |
| Typical abuse | Buffer overflow overwrites return address | Heap spray fills memory with shellcode |
In addition to the PE sections, a process's memory contains all loaded DLLs mapped into the same address space. In a debugger, the Modules panel (x64dbg: View → Modules) shows every DLL currently mapped.
Malware injected as a DLL appears here — but reflective DLL injection and some advanced techniques can hide from this list. The Volatility dlllist plugin (covered in Unit 5) detects discrepancies between the module list and actual memory mappings.
Understanding the memory layout transforms the register panel from noise into signal:
| Register Value | Meaning |
|---|---|
EIP in .text range | Normal code execution |
| EIP in heap range | Shellcode executing from heap — likely injection |
| ESP decreasing | Stack growing — function calls happening |
| EBP stable across loop | Loop executing within same frame |
| EAX = 0 after API call | API returned failure — check GetLastError |
When EIP points outside .text or any known module, the process is executing injected code. This is the most direct indicator of code injection visible in a debugger.
Sign in to mark this article as read and track your progress.