Virtual Memory Layout of a 32-bit Windows Process
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
The stack is a LIFO (last-in, first-out) data structure managed automatically by the CPU.
- Grows downward: from high addresses toward low addresses
PUSHdecrements ESP by 4, then writes the valuePOPreads the value, then increments ESP by 4- Stores: local variables, saved registers, return addresses, function arguments
Each 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.
Stack frame layout (after prologue)
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
Why this matters for malware analysis
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
The heap is a pool of memory used for dynamic allocations requested at runtime.
- Grows upward: from low addresses toward high addresses
malloc()/HeapAlloc(): requests heap memoryfree()/HeapFree(): releases heap memoryVirtualAlloc(): large allocations with custom permissions
Malware and the heap
Malware 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
Stack vs Heap comparison
| 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 |
Loaded DLLs
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.
Interpreting Debugger Output
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.

