Windows Heap Internals: The NT Heap and Segment Heap

Windows Internals
Time it takes to read this article 6 minutes.

Earlier in this series we looked at how Windows manages virtual memory through page tables, PTEs, and the VAD tree — the machinery that hands out memory a page (4KB) at a time. But real programs rarely want memory in 4KB units; they want to allocate a 40-byte structure here and a 300-byte buffer there, thousands of times a second. Bridging that gap is the job of the heap: a user-mode allocator that carves the large, page-granular regions it obtains from the virtual memory manager into the small, arbitrarily sized blocks that malloc, new, and HeapAlloc return. This article dissects how the Windows heap is built, the two allocator designs that ship today, and why the heap is one of the most heavily fought-over structures in exploitation.

Heaps and the Process Heap

Every process starts with a default process heap, retrievable with GetProcessHeap, which the C runtime and countless Windows APIs use for their allocations. A process can also create any number of additional private heaps with HeapCreate — useful for isolating a subsystem’s allocations, reducing lock contention, or being able to tear down a whole set of allocations at once with HeapDestroy.

Whatever the heap, its memory ultimately comes from the virtual memory manager. A heap reserves a large region of virtual address space up front and commits pages from it on demand as allocations grow, requesting more from the VMM when it runs out. In other words, the heap is a cache and sub-allocator layered on top of VirtualAlloc-style memory: it amortizes the cost of expensive page-level operations across many cheap small allocations.

The NT Heap (Legacy)

The classic Windows allocator — often just called the NT heap — is built around the _HEAP structure, which anchors one or more segments of committed memory. Within a segment, memory is divided into chunks, and each chunk is prefixed by a small _HEAP_ENTRY header recording its size (in allocation-granularity units) and flags (busy, free, last-in-segment, and so on). Because the header sits immediately before the data the application uses, the allocator can find the next chunk by adding the size, and it walks these headers to manage the heap.

Free chunks are tracked on free lists so they can be reused, and adjacent free chunks are coalesced to fight fragmentation. Critically, on modern Windows the _HEAP_ENTRY header is encoded — XORed with a random per-heap key — so that its size and flags are not stored in cleartext. This is an integrity check: when the allocator decodes a header, a corrupted or attacker-forged value is very unlikely to produce a self-consistent result, and the heap can detect the tampering.

Sitting in front of the backend is the Low Fragmentation Heap (LFH), a front-end allocator that kicks in for busy small-size allocations. Instead of searching free lists, the LFH groups allocations of the same size class into pre-carved buckets, handing out fixed-size slots very quickly and with far less fragmentation. The LFH activates adaptively once a given size sees enough traffic.

The Segment Heap (Modern)

Starting with Windows 10, Microsoft introduced the Segment Heap, a redesigned allocator that is the default for many system processes and all UWP apps (and can be opted into per-image). It keeps the same public API but reorganizes the internals around several specialized components:

  • Backend — services larger allocations directly from VirtualAlloc-backed memory in page-granular units.
  • Variable Size (VS) allocator — handles mid-range allocations with its own metadata scheme.
  • LFH front end — as in the NT heap, buckets small same-size allocations for speed and low fragmentation.
  • Large blocks — very large allocations are satisfied straight from the VMM, bypassing the sub-allocators.

The Segment Heap’s metadata layout differs substantially from the NT heap’s, and much of that metadata is deliberately kept out-of-line — stored away from the user data rather than in an inline header immediately before it. That separation is a security improvement: an overflow past the end of a buffer is less likely to land on the very metadata the allocator trusts.

The Allocation Path

When a program requests memory, the heap routes the request by size class. Small requests that qualify go to the LFH and are served from a same-size bucket; mid-size requests go to the variable-size/backend path and its free lists; and very large requests are carved straight out of virtual memory as their own regions. Freeing reverses the process: the block returns to its bucket or free list, and on the backend path adjacent free blocks are coalesced so the space can satisfy a future larger request. This tiering is why heap behavior — and heap exploitation — is so size-dependent: the same program can exercise three different allocators depending on how big its objects are.

Heap Security Mitigations

Because the heap has been an exploitation target for two decades, it has accumulated a stack of defenses:

  • Header encoding / cookies. As noted, chunk headers are XOR-encoded with a random key, and heaps carry cookies, so corruption is detected when metadata is decoded and validated.
  • Safe unlinking. Before removing a free chunk from a doubly linked list, the allocator validates that the neighbors point back correctly (chunk->Flink->Blink == chunk), defeating the classic “unlink write-what-where” primitive.
  • Guard pages. Large allocations and certain regions can be bracketed by non-committed guard pages so a linear overflow hits an unmapped page and faults instead of corrupting neighbors.
  • Terminate-on-corruption. With HeapEnableTerminationOnCorruption (default for most modern processes), the process is killed the instant heap corruption is detected rather than allowed to continue in a compromised state.
  • Segment Heap metadata hardening. Out-of-line metadata, additional integrity checks, and randomization make the modern allocator’s internal structures much harder to forge than the legacy inline headers.

Inspecting the Heap

The debugger exposes the heap’s structure directly, and the gflags tooling can turn on aggressive validation for finding corruption:

# WinDbg: summarize every heap in the process (base, type, flags)
!heap -s

# Dump a specific heap's segments and free lists
!heap -h <heap-address>

# Given a suspect address, identify which heap block contains it
!heap -x <address>

# Enable full page heap for an image so each allocation is placed
# on its own page with a guard page - turns overflows into instant faults
gflags /p /enable myapp.exe /full

# Driver/heap verification and instrumentation for corruption hunting
# (Application Verifier: appverif.exe)

!heap -s is the quick inventory of every heap in the process. Page heap (via gflags) is the single most useful debugging aid for heap bugs: it puts each allocation at the end of its own page followed by a guard page, so a one-byte overflow triggers an immediate access violation at the exact faulting instruction instead of silent, delayed corruption. Application Verifier layers on additional checks for handle and heap misuse.

Security Relevance

The heap is a primary target for memory-corruption exploitation, and understanding its structure is what separates reliable defense from guesswork. The main bug classes, described at a conceptual level:

  • Heap overflow. Writing past the end of an allocated chunk corrupts whatever follows — historically the adjacent chunk’s metadata header, and in modern exploitation more often an adjacent object whose fields (a pointer, a length, a function-table pointer) can be turned into control of execution.
  • Use-after-free (UAF). A dangling pointer to a freed chunk is used after the allocator has handed that memory to a different allocation; if an attacker can control what gets placed there, they control the contents the stale pointer dereferences.
  • Heap grooming / “feng shui.” Because the allocator’s placement is deterministic given a known allocation pattern, attackers shape the heap — allocating and freeing in a specific sequence — to position a target object immediately after a buffer they can overflow, or into a slot they can later reclaim.

The mitigations above are precisely the response to these techniques: header encoding and safe unlinking neutered the old metadata-corruption primitives, terminate-on-corruption removes the attacker’s second chance, and the Segment Heap’s out-of-line, hardened metadata makes grooming and forgery far harder than on the legacy heap. None of this is a reason for complacency, but it explains why heap exploitation grew from “overwrite a free-list pointer” into the elaborate, allocator-aware discipline it is today. (Everything here is for defensive understanding and authorized testing only.)

Conclusion

The Windows heap is the layer that turns page-granular virtual memory into the fine-grained allocations real code depends on — first through the classic NT heap with its encoded inline headers and Low Fragmentation front end, and now increasingly through the hardened, component-based Segment Heap. Its evolution is a direct dialogue with attackers: every metadata protection and terminate-on-corruption default is an answer to a specific exploitation technique. With allocation and its defenses covered, the next article turns to how Windows deals with the errors and faults that allocation and every other operation can raise: exception handling, SEH, VEH, and x64 unwinding.

You Might Also Like

This article is part of the Windows Internals series. These related deep-dives cover adjacent parts of the system:

Comments

Copied title and URL