Heap exploitation isn’t a set-it-and-forget-it skill. The moment a new mitigation lands in a major allocator, the exploit dev community gets to work, hunting for the bypass, the oversight, or the fresh angle that makes the defense irrelevant. This back-and-forth has been running hot for over two decades, and it’s not cooling off anytime soon. To really get why heap attacks keep morphing, you have to dig into the guts of modern allocators, the economics of vulnerability research, and the clever little tricks that turn a minor heap corruption into a full-blown code execution chain.

Close-up of a glowing circuit board with intricate pathways

The Allocator as a Moving Target

Early heap exploits had it easy. Doug Lea’s malloc, the basis for glibc’s allocator for years, used straightforward doubly-linked lists and barely checked anything. Overwriting a few bytes of a free chunk’s metadata let you unlink it and write an arbitrary value to an arbitrary location. The classic “unlink” technique was so reliable it became a CTF staple and a real-world weapon.

Then glibc 2.3.5 dropped, and the unlink macro got a sanity check: the chunk’s forward and backward pointers had to actually point back to the chunk being unlinked. That single check torched the old unlink write primitive. But it didn’t end heap exploitation—it just pushed attackers to mess with other parts of the allocator. The fastbin free list, the unsorted bin, and later the tcache (introduced in glibc 2.26) all became prime targets. The tcache was a speed hack, a per-thread stash of freed chunks with almost no integrity checks at first. Corrupt a next pointer in a freed tcache chunk, and you’d get an arbitrary-address allocation with zero fuss. Defenders eventually added a tcache key to spot double frees and a PROTECT_PTR mangling scheme to obfuscate that next pointer. The cycle just keeps spinning.

From Metadata Smashing to Application Logic Abuse

These days, going straight for allocator metadata is often a fool’s errand. The surfaces are locked down tight. So attackers pivot: they go after the application’s own heap structures. If you can’t forge a fake chunk to get overlapping allocations, maybe you can corrupt a length field stored in a heap buffer that controls a later memcpy. Suddenly you’re not playing the heap game anymore—you’ve got a generic memory corruption that feeds into ret2libc or ROP chains.

Look at the old “House of” techniques—House of Einherjar, House of Force, House of Spirit. They’re not just folklore. Each one is a distinct strategy for tricking the allocator into handing you a chunk that overlaps with a target region. House of Force, for instance, corrupts the “top chunk” size field to make malloc return an address way outside the heap. When a top chunk size check got added, the technique didn’t vanish; it just needed a heap leak to calculate the exact offset. The principles—know the internal invariants, find the weakest link, violate an unchecked assumption—haven’t changed. The details have.

Rows of server racks in a dark data center with blinking lights

Why the Underground Keeps Pouring Effort into Heap Research

There’s a cold, practical reason heap exploitation keeps advancing: the payout is massive. Browsers, document parsers, chat apps, kernel drivers—they all lean heavily on dynamic memory. One solid heap bug in a widely deployed component can be weaponized into a reliable exploit chain that hits millions of devices. State-sponsored groups, surveillance vendors, and organized crime all have a direct financial stake in staying ahead of the patch cycle.

Public research cuts both ways. When a team drops a detailed write-up on, say, exploiting io_uring via heap grooming, defenders get a blueprint for new mitigations. But that same write-up also trains a generation of exploit devs who will refine and extend the technique. The result is a pressure cooker where the state of the art moves fast.

Money also steers the research. As glibc’s ptmalloc gets tougher, attention drifts to other allocators: jemalloc in Firefox, PartitionAlloc in Chrome, the Windows NT heap, and custom allocators in embedded gear. Each one has its own quirks and unpatched assumptions. The underground is pragmatic—it follows the path of least resistance.

Heap Grooming: Shaping Memory Layout on Purpose

One of the most slept-on aspects of modern heap exploitation is heap grooming (sometimes called feng shui). Having a corruption primitive isn’t enough; you need the heap in a predictable state so your corruption lands on the right target. That means carefully sequencing allocations and frees to create holes of specific sizes, co-locate objects, and control the order of free lists.

In browser exploitation, grooming is often what separates a crash from a reliable exploit. JavaScript engines let you spray the heap with arrays of known size and content. By allocating and freeing in a precise pattern, you can place a vulnerable buffer right next to a sensitive object—like an ArrayBuffer’s backing store pointer or a DOM object’s vtable. When the corruption fires, it overwrites exactly the field you need.

Grooming has gotten so refined that it can beat probabilistic defenses like ASLR. Spray enough objects, and you can build a predictable heap layout even with randomized base addresses. That’s why modern allocators keep adding randomness—shuffling free lists, inserting guard pages, encrypting pointers. But randomness is just another variable to model and work around.

Case Study: The Tcache Poisoning Arms Race

Let’s follow one technique to see evolution up close. When tcache landed in glibc 2.26, it was pure performance: each thread got a small cache of recently freed chunks in a singly-linked list. The next pointer sat in the user-data part of the freed chunk, with no checks on allocation. To get an arbitrary write, you corrupted a freed chunk’s next pointer to point at your target, then allocated twice. The first allocation gave you the corrupted chunk; the second gave you your target.

Defenders added a tcache key—a random value in the chunk—to catch double frees. Attackers responded by leaking the key (when possible) or using a different primitive that didn’t need to free the same chunk twice. Then came PROTECT_PTR, which XORs the next pointer with the chunk’s user-data address shifted right by 12 bits. Now forging a pointer requires a heap leak to compute the right XOR value. But heap leaks are often available through other primitives, so the technique adapts instead of dying.

This back-and-forth is typical. Each mitigation raises the bar, but it also creates a new puzzle. The exploit dev’s job is to find the missing piece—a leak here, a controlled free there, a type confusion that skips the check entirely. The allocator’s complexity grows, and so does the attack surface.

Close-up of a computer motherboard with glowing red and blue circuits

Why Mitigations Alone Won’t End the Arms Race

It’s easy to think that enough mitigations—safe unlinking, pointer mangling, guard pages, hardened metadata—will make heap exploitation impossible. That misses the point. Heap vulnerabilities are fundamentally about logic errors in how programs use memory. The allocator can enforce invariants on its own metadata; it can’t stop a program from misinterpreting the contents of a chunk.

Take a use-after-free (UAF) in a complex C++ app. The allocator might catch a double-free, but it can’t tell that the program is still holding a dangling pointer to a freed object. When that object gets reallocated and filled with attacker-controlled data, the program’s logic is subverted—no metadata corruption needed. That’s why UAFs remain one of the most powerful and common bug classes, even on heavily hardened systems.

Virtualization and sandboxing add layers, but they also expand the attack surface. Hypervisors have their own heap allocators. Sandboxed processes talk over shared memory, which opens up new races and heap manipulation tricks. The sheer complexity of modern software stacks means there’s always another layer to peel back.

Why the Underground Stays Ahead

Public mitigations are reactive. They address known techniques, often with a significant lag. Meanwhile, the underground is actively researching the next generation of attacks. Zero-day brokers pay top dollar for exploits that slip past the latest Windows or iOS heap defenses. That money funds a small army of researchers who treat heap exploitation as a full-time job.

These folks don’t just hunt for bugs; they build frameworks and methodologies. They write custom allocator fuzzers, create heap state visualizers, and keep private databases of allocator internals across versions. When a new glibc release drops, they diff the source to spot changes that might introduce new assumptions or weaken old ones. The public sees a security advisory; the underground sees a new attack surface.

This asymmetry means heap exploitation techniques will keep evolving as long as there’s value in compromising systems. The only question is which techniques surface publicly and which stay in the shadows, used against high-value targets until they’re eventually burned.

FAQ

Why can’t we just build a perfectly secure heap allocator?

A perfectly secure allocator would have to prevent all forms of memory corruption, which is impossible without solving the broader problem of memory safety in C and C++. Allocators can protect their own metadata, but they can’t stop a program from writing out of bounds within a chunk or using a pointer after freeing it. Languages like Rust provide memory safety at compile time, but rewriting all legacy C/C++ codebases isn’t practical. The best we can do is raise the cost of exploitation and combine allocator hardening with other defenses like sandboxing and control-flow integrity.

What’s the difference between a heap overflow and a use-after-free?

A heap overflow happens when a program writes past the end of a heap-allocated buffer, corrupting adjacent memory. This can overwrite other chunks’ metadata or application data. A use-after-free occurs when a program keeps using a pointer after the memory it points to has been freed. The freed memory might be reallocated for a different purpose, so the dangling pointer now references data of a different type or attacker-controlled content. Both are powerful primitives, but they demand different exploitation strategies.

How do modern allocators detect heap corruption?

Modern allocators use a mix of integrity checks. Glibc’s ptmalloc, for example, verifies that a chunk’s size field is consistent with its position, checks that free list pointers are valid, and uses a tcache key to detect double frees. Windows’ LFH (Low Fragmentation Heap) takes a similar approach with encoded free list entries. PartitionAlloc in Chrome uses guard pages, checksums, and a quarantine list for freed objects. These checks make simple metadata corruption much harder, but they don’t eliminate the possibility of corrupting application data stored in adjacent chunks.

What is heap spraying and why is it still effective?

Heap spraying is a technique where an attacker allocates many copies of controlled data to fill the heap with a predictable pattern. This is often used to place shellcode or ROP chains at a known address, bypassing ASLR. Even with modern mitigations, heap spraying remains relevant because it can create predictable heap layouts for grooming—ensuring that a vulnerable object sits next to a target object. Defenses like isolated heap and guard pages make spraying harder, but not impossible, especially in large applications like browsers that allocate many objects.