--- name: exploiting-glibc-heap-vulnerabilities description: Methodology for exploiting glibc ptmalloc2 heap vulnerabilities during authorized engagements — use-after-free, double-free, heap overflow, and bin-based attacks (tcache poisoning, fast-bin dup, unsorted/large-bin) — including modern mitigations (tcache key, safe-linking, hook removal) and how to obtain leaks and arbitrary read/write. domain: cybersecurity subdomain: binary-exploitation tags: - binary-exploitation - heap-exploitation - exploit-development version: '1.0' author: xalgorix license: Apache-2.0 --- # Exploiting glibc Heap Vulnerabilities ## When to Use - During authorized exploitation of programs that dynamically manage memory with `malloc`/`calloc`/`free` and contain use-after-free, double-free, heap overflow, or off-by-one (poison-null-byte) bugs. - When the stack is not the corruption surface but you control freed-chunk contents, chunk size fields, or allocation ordering, and want to convert that into an arbitrary read/write or code-pointer overwrite. - When selecting a bin-specific primitive: tcache poisoning, fast-bin dup, unsorted-bin attack, large-bin attack, House of Force/Einherjar/Botcake, etc. - When you must account for the target glibc version's mitigations (tcache `key`, safe-linking, removed malloc hooks). ## Critical: Concepts/Steps Most Often Missed - **Identify the glibc version FIRST — it dictates the whole strategy.** tcache double-free detection (2.29+), safe-linking of singly-linked lists (2.32+), and removal of `__malloc_hook`/`__free_hook` (2.34+) each invalidate "classic" techniques. A how2heap PoC for 2.27 will simply abort on 2.35. - **Safe-linking mangles the `next` pointer.** From glibc 2.32, tcache/fastbin `next` is stored as `stored = target ^ (chunk_addr >> 12)`. You almost always need a **heap leak** to forge a valid poisoned pointer; without it you hit `malloc(): unaligned tcache chunk detected`. - **Returned pointers must be 16-byte aligned.** A poisoned target that is not aligned aborts before you get control. Target aligned addresses (or use the chunk's own alignment). - **tcache fills before fastbins/unsorted.** The tcache (7 entries per size) intercepts frees first. To exercise a fast-bin or unsorted-bin attack you must first fill the tcache (free 7 chunks of that size) so the 8th lands in the intended bin. - **Modern end goals are not hooks.** With hooks gone, pivot to: overwrite an application code pointer/vtable, get a chunk over another heap object for arbitrary R/W, or set up FSOP/`__free_hook`-equivalents/ROP. House of Botcake is the standard way to create the overlap needed for modern tcache poisoning. - **Double-free needs the `key` bypass.** `free(A); free(B); free(A)` works on fastbins (free another chunk in between) but tcache's `key` field aborts plain double-frees on 2.29+ unless bypassed. ### How to CONFIRM Use a heap-aware debugger: `pwndbg`/`gef` commands `heap`, `bins`, `tcachebins`, `vis_heap_chunks`, or `muslheap`'s `mchunkinfo` for musl. Confirm a double-free/overlap by allocating after the corruption and observing **two pointers with the same address** (e.g. `i1` and `i2` equal). Confirm tcache poisoning by allocating twice and verifying the second `malloc` returns your target address. Confirm a leak by reading a freed unsorted/small-bin chunk's `fd`/`bk`, which points into `main_arena` (libc) — a recognizable libc-relative address. ## Workflow ### Step 1: Fingerprint the Allocator and Bug ```bash pwn checksec ./vuln ./vuln &; cat /proc/$!/maps | grep libc # find libc path/version strings libc.so.6 | grep "GNU C Library" # exact version -> picks the technique ``` Classify the primitive: UAF (read/write after free), double-free, linear heap overflow (overwrite next chunk's size/`fd`), or off-by-one NUL (shrink/extend a size field). ### Step 2: Get an Info Leak (libc / heap base) ```text # Fill tcache (7 frees), free one more so it enters the unsorted bin, # then read the victim's fd/bk -> points into main_arena (libc). # A heap leak (needed for safe-linking) comes from reading a freed tcache fd. ``` ```python from pwn import * libc = ELF('./libc.so.6') # after leaking an unsorted-bin fd into 'leak': libc.address = leak - (libc.symbols['__malloc_hook'] + 0x10) # main_arena offset heap_base = (heap_leak << 12) # invert safe-linking shift ``` ### Step 3: Tcache Poisoning (most common modern primitive) ```python # Requires: ability to edit a freed chunk + (glibc>=2.32) a heap leak victim = malloc(0x40) # then free it into tcache target = libc.symbols['__free_hook'] # or an app code pointer / __malloc_hook (legacy) # Forge the safe-linked next pointer (glibc 2.32+) fake_next = target ^ (victim_chunk_addr >> 12) edit_freed_chunk(victim, p64(fake_next)) a = malloc(0x40) # consumes corrupted entry b = malloc(0x40) # returns chunk AT target -> arbitrary write ``` ### Step 4: Fast-bin Dup / Double-Free Variant ```python # Fast-bin dup (tcache full): free A, free B, free A again -> A appears twice free(A); free(B); free(A) p1 = malloc(sz) # = A edit(p1, p64(target - 0x10)) # overwrite fd to point near target (mind size check) malloc(sz); malloc(sz) # second malloc returns the target region # Modern: use House of Botcake to overlap, then tcache-poison (see Step 3) ``` Then escalate: overwrite `__free_hook` with `system` and free a chunk containing `"/bin/sh"`, or overwrite a GOT entry (Partial RELRO), or drop a one_gadget. Under Full RELRO + hook removal, pivot to FSOP or a controlled vtable. ## Key Concepts | Concept | Description | |---------|-------------| | **Chunk metadata** | `prev_size`, `size` (low 3 bits = A/M/P flags), and `fd`/`bk` reused as list pointers when free. | | **tcache** | Per-thread cache, 7 entries/size, LIFO singly-linked; first bin to receive frees and serve allocs. | | **Fast bins** | Singly-linked LIFO for small sizes; minimal checks -> classic `fd` overwrite to arbitrary alloc. | | **Unsorted bin** | Holds recently freed large/coalesced chunks; `fd`/`bk` leak `main_arena` (libc address). | | **tcache poisoning** | Overwrite a freed entry's `next` so a later malloc returns an attacker-chosen address. | | **Safe-linking** | glibc 2.32+ stores `next` as `ptr ^ (chunk>>12)`; needs a heap leak to forge. | | **Double-free / fast-bin dup** | Free a chunk twice (key/intermediate-free bypass) to alias two live pointers. | | **House of Botcake** | Modern technique using unsorted-bin consolidation to overlap chunks for tcache poisoning. | | **global_max_fast** | Overwriting it enlarges fast-bin size range, re-enabling fast-bin attacks on big chunks. | ## Tools & Systems | Tool | Purpose | |------|---------| | **gdb + pwndbg/GEF** | `heap`, `bins`, `tcachebins`, `vis_heap_chunks`, `arena`, `find` for leaks/layout. | | **pwntools** | Allocation driver scripting, `ELF` for libc symbol offsets, `p64`/`u64`, leak math, IO. | | **how2heap (shellphish)** | Reference PoCs for each technique per glibc version. | | **one_gadget** | Single-shot `execve("/bin/sh")` gadget in libc after a leak. | | **libc-database / libc.blukat.me** | Identify the exact libc version from two leaked symbol addresses. | | **muslheap** | musl mallocng equivalent inspection (`mchunkinfo`: stride, cycling offset) for Alpine targets. | | **radare2 / Ghidra** | Reverse the allocation logic and locate target app structures/code pointers. | ## Common Scenarios ### Scenario 1: UAF + heap overflow -> arbitrary write (modern glibc) Fill tcache, push a chunk to the unsorted bin, re-allocate overwriting only the first 8 bytes to leak the residual libc pointer. Then use a 1-byte size overflow to overlap chunks, free into tcache, and poison `next` (with the heap leak for safe-linking) to return a chunk over a sensitive structure. ### Scenario 2: Double-free (fast-bin dup) -> hook overwrite (legacy) Tcache full; `free(h); free(i); free(h)` aliases `h`. Allocate, point `fd` just before `__malloc_hook`, allocate twice, write a one_gadget. (Only valid on pre-2.34 glibc.) ### Scenario 3: Off-by-one NUL -> double-free via size confusion Allocate A,B,C (0x110); free B; free A and re-alloc to trigger the NUL overflow shrinking B's size to 0x100; free B again -> two tcache bins (0x110 and 0x100) point to the same address. Leverage with tcache poisoning. ### Scenario 4: tcache metadata (indexes) corruption Free the chunk holding tcache bookkeeping so a `0x100`-shaped value is treated as a chunk; allocate it back to overwrite multiple bin heads at once, pivoting several size classes. ## Output Format ``` ## glibc Heap Exploitation Finding **Vulnerability**: Heap use-after-free / double-free (CWE-416 / CWE-415) **Severity**: Critical (arbitrary read/write -> RCE) **Binary**: ./vuln (amd64), glibc 2.35 (safe-linking, hooks removed) ### Primitives - UAF read on freed unsorted-bin chunk -> libc base 0x7f...000 - Heap leak from freed tcache fd -> heap base 0x55...000 (defeats safe-linking) - House of Botcake overlap -> tcache poisoning -> arbitrary write ### Exploitation Poisoned tcache next (mangled) to return a chunk over the FILE vtable (FSOP), redirecting exit() flow to system("/bin/sh"). ### Impact Arbitrary code execution as the service account. ### Recommendation 1. Fix the lifetime bug: null pointers after free, prevent reuse/double-free. 2. Build with -D_FORTIFY_SOURCE=2, Full RELRO, PIE; keep glibc current (safe-linking, hook removal). 3. Add allocation hardening (e.g. hardened allocators, MALLOC_CHECK_/glibc tcache checks) where feasible. 4. Validate all size/length inputs to prevent overflow into chunk metadata. ```