Windows Heap Internals for Exploiters
Last updated: 2026-04-10
Related: Pool Internals, Heap Grooming, Use After Free, Mitigations
Tags:user-mode,nt-heap,segment-heap
Summary
Windows user-mode heap exploitation has evolved through two major allocator generations: the NT Heap (pre-Win10 RS5 for most processes) and the Segment Heap (Win10 RS5+). Understanding both is critical — legacy NT Heap still appears in many targets (older processes, specific allocator contexts), and the Segment Heap is now the primary attack surface in modern Windows user-mode exploitation.
NT Heap (Legacy)
Architecture
Each process has one or more heap instances (_HEAP). The default process heap is at PEB.ProcessHeap.
typedef struct _HEAP {
// ...
HEAP_SEGMENT Segment; // first segment embedded
ULONG Flags;
ULONG ForceFlags;
// Free lists, virtual allocation lists...
LIST_ENTRY FreeLists[128]; // 8-byte granularity, up to 1024 bytes
HEAP_LOOKASIDE* Lookaside[128]; // per-size lookaside (pre-Vista: FS:[0x18])
// ...
} HEAP;
Chunk Header (NT Heap)
typedef struct _HEAP_ENTRY {
USHORT Size; // chunk size in 8-byte units (encoded)
UCHAR Flags; // HEAP_ENTRY_BUSY | HEAP_ENTRY_LAST_ENTRY, etc.
UCHAR SmallTagIndex; // used for overflow detection
USHORT PreviousSize; // previous chunk size (encoded)
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY; // 8 bytes
Header encoding (Vista+): Size, PreviousSize, SmallTagIndex are XORed with HEAP.Encoding (random per-heap). Prevents straightforward header forgery — must leak the encoding key.
Low Fragmentation Heap (LFH)
Activated for size buckets that have ≥18 allocations. Uses _HEAP_SUBSEGMENT structures:
- Fixed-size blocks within subsegments
- No chunk headers between blocks — blocks tracked by bitmap
- Overflow into adjacent LFH block hits data directly (same as kernel Segment Heap LFH)
- Subsegment list can be corrupted to achieve larger-scope impact
Safe Unlinking (Vista+)
Free list doubly-linked list unlink validates Flink->Blink == current. Broken → heap corruption exception.
Segment Heap (Win10 RS5+ Default)
Windows 10 RS5 (October 2018) switched most processes to the Segment Heap. Controlled by PEB.NtGlobalFlag bit or image policy. The new allocator is also the kernel pool allocator since 20H1.
Components
_SEGMENT_HEAP
├── SegmentContext[2] ← VS (Variable Size) segments
├── LargeAllocMetadata ← RB tree for allocations > 128KB
├── LfhContext ← Low-Fragmentation Heap
└── StackTraceContext ← debug-only
Backend (VS Segments)
Handles allocations not serviced by LFH (generally > 128KB threshold or first few allocations per size).
Uses _HEAP_VS_CHUNK_HEADER with encoding (XOR with random key per-heap).
LFH (Segment Heap)
Handles allocations ≤ some threshold (typically 128KB with bucketing).
- 128 size classes (buckets)
- Per-bucket
_HEAP_LFH_SUBSEGMENTcontaining fixed-size blocks - Bitmap-based allocation tracking (no inline headers)
- Exploit implication: overflow within same LFH subsegment hits adjacent block’s data directly
Large Allocation
128KB →
_HEAP_LARGE_ALLOC_DATAmetadata stored in RB tree (not adjacent to data)
Overflow into large allocation → hits data, not heap metadata
Guard Pages
Random guard pages interspersed in heap — overflow detection, not fully deterministic
Heap Exploitation Techniques
1. Free List Corruption (NT Heap, encoded headers)
- Goal: corrupt a free chunk header to redirect next allocation to attacker-controlled address
- Requirement: leak heap encoding key (look for pointer leaks in heap objects, or partial overwrite)
- Steps: overflow → overwrite encoded Size/PreviousSize →
HeapAllocreturns attacker-controlled pointer
2. LFH Bucket Overflow (Both Allocators)
- Goal: overflow into adjacent same-bucket block
- Requirement: precise allocation sizing to land in same LFH bucket
Steps: spray bucket → create layout A B → overflow A → corrupt B’s data (often vtable pointer) - LFH bucket selection: sizes 8, 16, 24, … 1024 bytes in 8-byte increments; then 1040, 1056… up to 16KB
3. Heap Grooming
See Heap Grooming for detailed strategies.
4. Type Confusion via Free
- Free an object but keep a pointer (UAF)
- Re-allocate same slot with different object type
- Use pointer assuming original type → type confusion → vtable dispatch to controlled address
5. Use-After-Free
See Use After Free.
Heap Metadata Security Features
| Feature | Allocator | Effect |
|---|---|---|
| Header encoding | NT Heap + Segment Heap | Header forgery requires encoding key |
| Guard pages | Segment Heap | Random overflow detection |
| Safe unlinking | NT Heap | Free list pointer validation |
| LFH bitmap | Both | No inline chunk headers in LFH |
| Separate large alloc metadata | Segment Heap | Large alloc overflow hits data only |
| Heap base ASLR | Both | Per-heap random base address |
Useful Heap Internals for Exploitation
Getting Heap Base
PVOID heapBase = (PVOID)__readgsqword(0x60); // PEB
heapBase = *(PVOID*)((UCHAR*)heapBase + 0x30); // PEB.ProcessHeap
Forcing Specific Size Class
Control your allocation size to land in desired LFH bucket:
- Bucket 1: 1-8 bytes
- Bucket N:
(N-1)*8 + 1toN*8bytes - For exploiting specific target, size spray objects to same bucket as vulnerable object
Heap Spray Considerations
- Modern defenses: ASLR of heap base, guard pages, nonce encoding
- Spray is still viable for controlling relative layout within a heap segment
- Prioritize: land specific objects adjacent to vulnerable object (feng shui over raw spray)
Exploit Relevance
- NT Heap still present in many legacy codepaths and older Windows targets
- Segment Heap LFH exploitation (no inline headers) is now the dominant approach
- Type confusion via re-use (UAF) remains the most common user-mode exploitation pattern
- Heap grooming quality directly determines exploit reliability — invest time here
References
- “Heap Exploitation on Windows” — Chris Valasek
- “Understanding the LFH” — Ben Hawkes (Google Project Zero)
- “Segment Heap Internals” — Mark Yason, Black Hat 2016
- “Windows 10 Segment Heap” — Corentin Bayet, Synacktiv
- Phrack #68 “MS Windows NT Kernel-mode User and GDI Object Exploitation”
