Windows heap exploitation

Windows heap exploitation

TL;DR: Modern Windows user-mode heaps split into LFH (Low Fragmentation Heap) and Segment Heap; both bucket allocations by size, randomise free lists, and use header encoding — exploitation is mostly about groom and choosing the right victim object, not about classic unlink primitives.

What it is

Windows ships two coexisting heap implementations. NT Heap with LFH (default for non-image processes, classic shape: _HEAP, _HEAP_ENTRY, encoded headers). Segment Heap introduced in Windows 10 1703 for store apps, expanded in 1809/Server 2019 to several system processes and DCOM hosts. Both keep small allocations in size-class buckets; LFH activates for a bucket after a few allocations of the same size.

Preconditions / where it applies

  • User-mode heap bug (OOB write, UAF, double free, integer underflow on size).
  • Ability to control allocation/free pattern (web request loop, COM call loop, script-engine arrays).
  • Knowledge of which heap is in use for the target process (!heap -s in WinDbg).

Technique

Groom basics.

  • Pick a bucket: any size class that sees frequent attacker-driven allocations.
  • Fill holes: allocate many objects of the target size to defragment, then free every other one to open predictable slots, then trigger the bug so the vulnerable object lands in a chosen slot adjacent to a sprayable victim.

LFH specifics.

  • Activation: ~17 allocs of the same size triggers LFH for that bucket.
  • Randomised slot selection breaks “predict the next free chunk” — spray many candidates, not one.
  • Header encoding XORs with _HEAP.Encoding, mitigating direct header overwrites.

Segment Heap specifics.

  • Three tiers: Variable Size, LFH-style buckets, Large Blocks; metadata lives in separate pages (_SEGMENT_HEAP, _HEAP_PAGE_RANGE_DESCRIPTOR).
  • Backend uses commit/decommit on page-aligned segments; large overflows often hit guard pages first.
  • Pointer encoding on freelist nodes is per-process and randomised.

Useful victim objects (browser/JS):

  • Typed-array buffers — change a length field for OOB read/write inside the renderer.
  • String objects — length + data pointer pair.
  • DOM/ArrayBuffer pairs — classic primitive in scripting engine bugs.

Classic patterns still relevant.

  • UAF with vtable reclaim → call attacker-chosen virtual on freed object.
  • Type confusion enabling a length/pointer field overlay onto a buffer.
  • Off-by-one over a chunk header → adjacent chunk’s _HEAP_ENTRY fields manipulate; mostly mitigated but situational.

Detection and defence

  • Build with /GS, /guard:cf, /CETCOMPAT; opt into segment heap (FrontEndHeapType registry).
  • Enable Application Verifier / page heap in test to surface bugs early.
  • EMET-style mitigations and CFG bookkeeping limit what UAF vtable hijacks can reach.

References

See also: heap-exploitation-linux, arbitrary-read-write-primitives, use-after-free-kernel.