Heap Exploitation Development
Goal: turn a heap bug into a repeatable primitive and then into the right endgame for that allocator/version, not the most nostalgic House name.
When to activate
- Heap overflow, off-by-one, UAF, double-free, arbitrary free, overlapping chunk, or stale-pointer bug touches heap-managed objects.
- Need allocator-aware exploit design instead of generic memory corruption advice.
- Need to choose between tcache poisoning, bin corruption, FSOP, setcontext/ROP, or data-only outcomes.
- Linux target may use glibc, musl, or custom allocators.
- Windows target uses the default process heap, a private heap, LFH, VS, or Segment Heap.
First classify the environment
- Identify allocator and version
glibc/ptmalloc: determine version first; most House viability is version-gated.musl mallocng: assume group/slot shaping and application object corruption, not direct House portability.Windows: determine whether the bug is on NT heap, LFH, VS, or Segment Heap, and whether it is a private heap.Custom allocator: reverse metadata, pool headers, callback tables, and destructor paths before importing glibc assumptions.
- Classify the bug primitive
- overflow / off-by-one / null byte
- UAF / stale pointer / double free
- arbitrary free / fake chunk / pointer substitution
- overlap / leak / relative write
- Decide the capability you actually need
- heap leak
- libc leak
- arbitrary allocation
- arbitrary write
- FILE overlap / FSOP
- stack pivot / setcontext / ORW
- data-only corruption
- Map mitigations before choosing the chain
- tcache
- tcache double-free checks
- safe-linking
- FILE vtable validation
- hook removal (
__malloc_hook,__free_hook) - RELRO / PIE / ASLR
- seccomp / sandboxing
- Windows CFG / CET / heap termination on corruption
Version-first selection
| Environment | Bias toward | Avoid or de-prioritize |
|---|---|---|
| glibc < 2.26 | Force, Orange, Roman, Storm, Rabbit, Lore, Spirit, unsafe unlink, unsorted/largebin classics | Tcache-first thinking |
| glibc 2.26-2.31 | tcache poisoning, Botcake, stashing unlink, fastbin reverse into tcache, Einherjar, early IO | Assuming hooks are gone or safe-linking exists |
| glibc 2.32-2.33 | safe-linking bypasses, House of IO, House of Water, Botcake, largebin-assisted tcache control | Blind fd poisoning without a heap leak or metadata route |
| glibc 2.34-2.41 | FSOP / House of Apple 2 or 3, stdout/stderr overlap, setcontext pivots, Water, Tangerine, tcache-relative write | __free_hook / __malloc_hook as default endgame |
| glibc 2.42+ | tcache metadata hijacking, Water, Tangerine, allocator-metadata overflow, FSOP | Assuming classic largebin or early-tcache metadata layouts still hold |
| musl mallocng | slot/group shaping, application-object overlap, type confusion, atexit / app-specific callback abuse | Direct porting of ptmalloc House techniques |
| Windows NT/LFH | same-size grooming, adjacent object corruption, private-heap determinism, CRT/FILE or return-address targets after AAR/AAW | Old "just corrupt FLink/BLink" folklore without build-specific proof |
| Windows Segment Heap / VS | application object corruption, pointer replacement, vtable/function-pointer chains, leak-first workflow | Assuming Segment Heap behaves like classic LFH or exposes easy freelist writes |
House families worth prioritizing
| Family | Good fit | Typical result | Reality check |
|---|---|---|---|
| House of Spirit | arbitrary free / fake chunk | near-arbitrary allocation | Still a useful fake-chunk pattern, not a complete endgame |
| House of Lore | smallbin corruption | near-arbitrary allocation | Good for fake-chunk thinking; often a helper, not the finale |
| House of Einherjar | off-by-one null / PREV_INUSE clear |
overlap + consolidation | Excellent default when null-byte primitive exists |
| House of Force | old top chunk overflow | arbitrary allocation | Classic only; top-chunk integrity kills the stock form on modern glibc |
| House of Orange | no free() path on old glibc |
unsorted-bin + FSOP | Historical; keep for < 2.26 and for reasoning about Tangerine |
| House of Roman | leakless relative overwrite on old glibc | hook-era RCE | Strong conceptually, weak as modern default |
| House of Storm | unsorted + largebin choreography | forged chunk / arbitrary allocation | High setup, mainly old glibc |
| House of Corrosion | WAF + old/post-Orange stderr path | leakless libc writes + FSOP | Real but niche, entropy-heavy, build-sensitive |
| House of Red | staged stderr corruption + allocator-failure trigger |
failure-driven stderr / FILE dispatch |
Treat as a situational trigger pattern adjacent to Corrosion/Crust, not a first-choice primitive |
| House of Botcake | tcache double-free bypass | arbitrary allocation/write | Great modern default when double-free is involved |
| House of IO | safe-linking era with tcache-struct control | tcache poisoning without classic fd leak | Version-windowed and precondition-heavy |
| House of Water | leakless tcache-metadata control | leakless tcache takeover | Strong modern option when metadata is reachable |
| House of Tangerine | no explicit free(), top-chunk abuse |
arbitrary allocation through freed wilderness | Modern Orange replacement; excellent on newer glibc |
| House of Apple 2 | post-hook glibc | FSOP via system or setcontext |
One of the best 2.34+ endgames |
| House of Apple 3 | post-hook glibc | FSOP via _codecvt |
Advanced but worth knowing |
| House of Rust / Crust | safe-linking + TSU/largebin + FSOP | leakless or low-leak stdout/stderr overlap | Powerful, but too brittle to be your first plan |
| House of Muney | mmapped chunk overlap into libc mapping | symbol-table / mapping corruption | Expert-only, RELRO-sensitive, rare in real targets |
| House of Blindness | one-byte / LSB writes into mmap-relative space (link_map->l_addr, l_info[DT_*], _r_debug) |
leakless escalation: byte write → mis-resolved _dl_fixup → arbitrary call via _dl_fini at exit() |
Modern glibc (verified on 2.34, pepsipu Feb 2022); requires _dl_fixup reachable (partial RELRO) and exit() path; CTF-grade, invaluable when no leak / no IO |
Treat House names as indexed solution families, not trophies. Primitive + allocator + version wins over naming aesthetics.
Mitigation-driven rules
- Safe-linking (glibc 2.32+): do not plan blind tcache poisoning unless you have a heap leak or a metadata route such as House of Water or safe-link double-protect.
- Hook removal (glibc 2.34+): if the exploit sketch ends at
__free_hookor__malloc_hook, it is probably outdated unlesslibc_malloc_debug.so.0is explicitly involved. - FILE vtable validation: assume valid jump-table regions only. Prefer Apple 2/3,
_wide_data,_codecvt, or other in-section routes over fake arbitrary vtables. - Largebin hardening: verify the exact libc. Classic largebin attack is not a universal assumption on 2.42+.
- musl mallocng: cycling offsets, guard pages, cookies, and
mmap()-backed groups change heap shaping completely; validate adjacency and slot reuse empirically. - Windows modern heaps: encoded metadata, heap isolation, safe unlinking, CFG, and CET push you toward adjacent-object corruption and post-primitive ROP/data-only goals, not nostalgic metadata-only exploits.
- Seccomp / SUID / restricted shells: prefer ORW or
setcontext/ROP oversystem("/bin/sh"). - Zeroing / self-using write primitive: if your only path to write a target is an allocation that zeroes the returned chunk (calloc / explicit
memset/rep stosq) and the program then immediately uses that region (prints through it, re-reads it), an in-place forge over a libc stream/struct self-destructs before you can finish. Model the write primitive's side effects explicitly; when blocked, route to a print-free / cross-stream write (seefsop-dev: input-buffer redirection) or a second-thread/free()-deposited write rather than fighting the ordering. - Per-thread allocator state: each thread has its own arena + own tcache, and
free()uses the calling thread's tcache. A worker thread'sfree()of a main-arena chunk lands in the worker's tcache, not main's — so it does not create a same-bin double-free for the main thread. Confirm which thread/tcache a free targets before assuming overlap.
Endgame selection
- glibc < 2.34: hooks,
_IO_list_all, stdout/stderr corruption, or classic one-gadget chains may still fit. - glibc >= 2.34: bias toward FSOP, House of Apple 2/3,
_wide_dataor_codecvtcontrol,setcontext+61, or clean ORW. Once the allocator work is done and the hard part becomes libio dispatch/trigger selection, hand off tooffensive-coding/fsop-dev. - SUID targets:
system("/bin/sh")can drop privilege; usesetcontextor ROP to normalize credentials first. - No libc leak: only choose Roman/Corrosion/Crust-like chains if the target really blocks easier leak paths and you can afford entropy/bruteforce.
- Only heap-relative corruption available: favor overlap-driven data-only attacks, allocator metadata hijack, or tcache-relative writes before chasing code execution.
- Windows: use the heap bug to get AAR/AAW, then pivot to vtable/function-pointer/IAT/stack targets with CFG-aware gadget choice.
Reliability checklist
- Confirm exact allocator, heap type, and libc version before naming a technique.
- Cross-check named glibc techniques against a matching how2heap version folder when available; a House name is a hypothesis, not compatibility proof.
- Validate chunk sizes against the real bin class and alignment rules.
- Track whether the victim chunk will land in tcache, fastbin, unsorted, smallbin, largebin, LFH, VS, or Segment Heap.
- Map aliases between allocator metadata and attacker-used storage. A tcache entry, bin link, chunk field, or application slot may be the same qword; allocator maintenance can destroy a primitive that worked in isolation.
- Re-run later helper allocations after the full corruption sequence. A parser's internal
mallocorreallocis part of the exploit state machine, not an independent trigger. - When a later allocation must bypass sacrificial metadata, test the exact normalized size. A merely larger victim may be sorted and force the allocator to validate the next corrupted node.
- Prove the leak first if the chain needs libc or heap base.
- Model trigger paths separately from corruption paths; many House chains fail at the final call site, not the heap step.
- Stress the exploit under multiple runs and slightly different heap schedules.
- Prefer the shortest stable chain, not the fanciest multi-House stack.
Anti-patterns
- Recommending
__free_hookon modern glibc by reflex. - Assuming House names transfer unchanged across glibc, musl, jemalloc, tcmalloc, and Windows heaps.
- Treating Windows LFH or Segment Heap as if they were old-school unsafe unlink playgrounds.
- Chasing code execution when a stable data-only or ORW path is enough.
- Cargo-culting a public writeup without checking allocator version, build quirks, or mitigation deltas.
Resources
- references/glibc-techniques.md — glibc era map, House families, how2heap-backed compatibility workflow, latest tcache-metadata routes, and version-aware selection.
- references/house-family-atlas.md — dedicated atlas of classic, modern, and post-hook House families with status, prerequisites, and when to de-prioritize them.
- references/patterns-and-mitigations.md — primitive taxonomy, allocator comparison, and mitigation-driven decision rules.
- references/fsop-and-post-hook-targets.md — Apple 2/3, FSOP paths, and post-hook endgames.
- ../fsop-dev/SKILL.md — standalone FSOP router for libio-specific dispatch, trigger, and endgame selection once stream overlap is already on the table.
- references/windows-techniques.md — NT/LFH/Segment Heap notes, realistic primitives, and Windows-specific caveats.
- Upstream methodology:
offensive-techniques/binary-exploitation-technique(primitive→impact strategy, mitigation selection) andoffensive-ctf/pwn-ctf/references/heap.md(concrete recipes).
Load the reference files only after allocator, version, primitive, and intended endgame are clear; they hold the depth that does not belong in this routing file.