--- name: heap-exploitation description: >- Heap exploitation playbook. Use when targeting ptmalloc2/glibc heap vulnerabilities including UAF, double free, overflow, off-by-one/null, and leveraging tcache/fastbin/unsortedbin attacks for arbitrary write or code execution. --- # SKILL: Heap Exploitation — Expert Attack Playbook > **AI LOAD INSTRUCTION**: Expert glibc heap exploitation techniques. Covers ptmalloc2 internals, bin structures, tcache mechanics, libc/heap leak methods, and attack selection by glibc version. Distilled from ctf-wiki heap sections, how2heap, and real-world exploitation. Base models often confuse glibc version constraints and miss safe-linking (PROTECT_PTR) introduced in 2.32. ## 0. RELATED ROUTING - [stack-overflow-and-rop](../stack-overflow-and-rop/SKILL.md) — when the overflow is on the stack rather than the heap - [format-string-exploitation](../format-string-exploitation/SKILL.md) — leak heap/libc addresses via format string - [arbitrary-write-to-rce](../arbitrary-write-to-rce/SKILL.md) — convert heap arbitrary write into code execution - [binary-protection-bypass](../binary-protection-bypass/SKILL.md) — bypass ASLR/RELRO to use heap write effectively ### Advanced References - [HOUSE_OF_TECHNIQUES.md](./HOUSE_OF_TECHNIQUES.md) — House of Force/Spirit/Orange/Einherjar/Roman/Pig/Banana/Cat/Apple and tcache attacks - [IO_FILE_EXPLOITATION.md](./IO_FILE_EXPLOITATION.md) — _IO_FILE vtable hijack, FSOP, stdout/stdin abuse, exit flow exploitation --- ## 1. PTMALLOC2 STRUCTURE QUICK REFERENCE ### malloc_chunk Layout (64-bit) ``` chunk pointer (returned by malloc - 0x10) ┌──────────────────────────┐ 0x00 │ prev_size (if prev free)│ 0x08 │ size | A | M | P │ ← P=PREV_INUSE, M=IS_MMAPPED, A=NON_MAIN_ARENA ├──────────────────────────┤ ← user data starts here (returned pointer) 0x10 │ fd (if free) │ ← forward pointer to next free chunk 0x18 │ bk (if free) │ ← backward pointer to prev free chunk 0x20 │ fd_nextsize (large only)│ 0x28 │ bk_nextsize (large only)│ └──────────────────────────┘ ``` ### Bin Types | Bin | Size Range (64-bit) | Structure | LIFO/FIFO | |---|---|---|---| | tcache (per-thread) | ≤ 0x410 (7 entries per size) | Singly linked (next pointer) | LIFO | | fastbin | ≤ 0x80 (default) | Singly linked (fd) | LIFO | | unsortedbin | Any freed size | Doubly linked circular | FIFO | | smallbin | < 0x400 | Doubly linked circular | FIFO | | largebin | ≥ 0x400 | Doubly linked + size-sorted | Sorted | ### Key Global Structures | Structure | Location | Purpose | |---|---|---| | `main_arena` | libc .data segment | Contains bin heads, top chunk, system_mem | | `mp_` | libc .data | malloc parameters (tcache settings, mmap threshold) | | `tcache_perthread_struct` | Heap (first allocation) | Per-thread tcache bins and counts | --- ## 2. LEAK METHODS ### Libc Base Leak | Method | Precondition | Technique | |---|---|---| | Unsortedbin fd/bk | Free a chunk > tcache range (or fill tcache) | fd/bk → `main_arena + 0x60` (or +0x70 depending on version) → libc base | | Smallbin fd/bk | Chunk moved from unsortedbin to smallbin | Same as unsortedbin leak | | stdout FILE leak | Write to `_IO_2_1_stdout_` | Corrupt `_IO_write_base` to leak libc data (see IO_FILE) | ### Heap Base Leak | Method | Precondition | Technique | |---|---|---| | Tcache fd pointer | Free two tcache chunks, read first's fd | fd → heap address (XOR'd in ≥ 2.32) | | Fastbin fd | Free two fastbin chunks | fd → heap address | | UAF read | Use-after-free on freed chunk | Read fd/bk directly | ### Safe-Linking Decode (glibc ≥ 2.32) ```python # PROTECT_PTR: fd_stored = (chunk_addr >> 12) ^ real_fd # To decode: real_fd = fd_stored ^ (chunk_addr >> 12) # To encode: fd_stored = (chunk_addr >> 12) ^ target_addr def deobfuscate(stored_fd, chunk_addr): return stored_fd ^ (chunk_addr >> 12) def obfuscate(target, chunk_addr): return (chunk_addr >> 12) ^ target ``` --- ## 3. ATTACK CATEGORIES BY GLIBC VERSION ### glibc < 2.26 (No tcache) | Attack | Primitive Needed | Result | |---|---|---| | Fastbin dup | Double free | Arbitrary allocation | | Unsortedbin attack | Corrupt unsortedbin bk | Write `main_arena` addr to target (used for __malloc_hook nearby overwrite) | | Unlink attack | Heap overflow into prev_size + fd/bk | Arbitrary write (with known heap pointer) | | House of Force | Top chunk size overwrite | Arbitrary allocation | | House of Spirit | Write fake chunk header | Fastbin allocation at fake chunk | | Off-by-one null | Null byte overflow into next chunk size | Overlapping chunks | ### glibc 2.26–2.28 (tcache, no key) | Attack | Notes | |---|---| | Tcache poisoning | Overwrite tcache fd → arbitrary allocation, no size check | | Tcache dup | Double free into tcache (no double-free detection yet) | | All previous attacks | Still work, but chunks go to tcache first | ### glibc 2.29–2.31 (tcache key introduced) | Attack | Bypass for tcache key | |---|---| | Tcache dup | Corrupt `key` field (at chunk+0x18) before second free | | House of Botcake | Double free: one in unsortedbin, one in tcache → overlapping | | Tcache stashing unlink | Abuse smallbin→tcache refill to get arbitrary chunk | ### glibc 2.32–2.33 (safe-linking / PROTECT_PTR) | Attack | Adaptation | |---|---| | Tcache poisoning | Encode target with `(chunk_addr >> 12) ^ target` | | Heap leak required | Need heap addr to decode/encode safe-linked pointers | | Fastbin dup | Same encoding required | ### glibc ≥ 2.34 (hooks removed) | Change | Impact | |---|---| | `__malloc_hook` removed | Cannot overwrite hook for one_gadget | | `__free_hook` removed | Cannot overwrite hook | | `__realloc_hook` removed | Cannot use realloc trick for one_gadget constraints | **Post-2.34 targets**: see [arbitrary-write-to-rce](../arbitrary-write-to-rce/SKILL.md) for `_IO_FILE`, `exit_funcs`, `TLS_dtor_list`, `_dl_fini`. --- ## 4. COMMON VULNERABILITY PATTERNS | Vulnerability | Description | Exploitation Path | |---|---|---| | UAF (Use-After-Free) | Access chunk after free | Read: leak fd/bk; Write: corrupt fd for tcache poisoning | | Double Free | free() same chunk twice | Tcache dup (bypass key) or fastbin dup | | Heap Overflow | Write past chunk boundary | Corrupt next chunk's metadata (size, fd, bk) | | Off-by-one | One byte overflow | Null byte → shrink next chunk size → overlapping chunks | | Off-by-null | Specifically `\x00` overflow | Clear PREV_INUSE → trigger backward consolidation | | Uninitialized read | Read heap memory without clearing | Leak fd/bk from recycled chunk | --- ## 5. TOOLS ```bash # pwndbg heap inspection pwndbg> heap # display all chunks pwndbg> bins # show all bin contents pwndbg> tcachebins # tcache status pwndbg> fastbins # fastbin status pwndbg> unsortedbin # unsortedbin content pwndbg> vis_heap_chunks # visual heap layout pwndbg> find_fake_fast &__malloc_hook # find nearby fake fastbin chunks # how2heap — reference implementations git clone https://github.com/shellphish/how2heap # heapinspect pip install heapinspect heapinspect # pwntools helpers from pwn import * libc = ELF('./libc.so.6') print(hex(libc.symbols['__malloc_hook'])) print(hex(libc.symbols['__free_hook'])) ``` --- ## 6. DECISION TREE ``` Heap vulnerability identified ├── What is the primitive? │ ├── UAF (read + write) │ │ ├── Can read freed chunk? → Leak libc (unsortedbin) or heap (tcache fd) │ │ └── Can write freed chunk? → Tcache poisoning / fastbin dup │ ├── Double free │ │ ├── glibc < 2.29 → direct tcache dup │ │ ├── glibc 2.29-2.31 → corrupt tcache key first, or House of Botcake │ │ └── glibc ≥ 2.32 → need heap leak for safe-linking encode │ ├── Heap overflow (controlled size) │ │ ├── Overwrite next chunk size → overlapping chunks → UAF │ │ └── Overwrite fd directly → arbitrary allocation │ ├── Off-by-one / off-by-null │ │ ├── Null byte into size → House of Einherjar (backward consolidation) │ │ └── One byte into size → shrink chunk, create overlap │ └── Arbitrary write (from overlap or poisoned allocation) │ ├── glibc < 2.34 → __malloc_hook / __free_hook → one_gadget │ ├── glibc ≥ 2.34 → _IO_FILE vtable, exit_funcs, TLS_dtor_list │ └── Partial RELRO → GOT overwrite │ ├── Need libc leak? │ ├── Free chunk into unsortedbin (size > 0x410 or fill 7 tcache) │ ├── Read fd/bk → main_arena offset → libc base │ └── Alternative: stdout FILE partial overwrite for leak │ └── Need heap leak? (glibc ≥ 2.32) ├── Read tcache fd from freed chunk └── Decode: real_addr = stored_fd ^ (chunk_addr >> 12) ```