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
Advanced References
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)
# 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 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
# 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 <pid>
# 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)
1---2name: heap-exploitation3description: 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.4---56# SKILL: Heap Exploitation — Expert Attack Playbook78> **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.910## 0. RELATED ROUTING1112- [stack-overflow-and-rop](../stack-overflow-and-rop/SKILL.md) — when the overflow is on the stack rather than the heap13- [format-string-exploitation](../format-string-exploitation/SKILL.md) — leak heap/libc addresses via format string14- [arbitrary-write-to-rce](../arbitrary-write-to-rce/SKILL.md) — convert heap arbitrary write into code execution15- [binary-protection-bypass](../binary-protection-bypass/SKILL.md) — bypass ASLR/RELRO to use heap write effectively1617### Advanced References1819- [HOUSE_OF_TECHNIQUES.md](./HOUSE_OF_TECHNIQUES.md) — House of Force/Spirit/Orange/Einherjar/Roman/Pig/Banana/Cat/Apple and tcache attacks20- [IO_FILE_EXPLOITATION.md](./IO_FILE_EXPLOITATION.md) — _IO_FILE vtable hijack, FSOP, stdout/stdin abuse, exit flow exploitation2122---2324## 1. PTMALLOC2 STRUCTURE QUICK REFERENCE2526### malloc_chunk Layout (64-bit)2728```29 chunk pointer (returned by malloc - 0x10)30 ┌──────────────────────────┐31 0x00 │ prev_size (if prev free)│32 0x08 │ size | A | M | P │ ← P=PREV_INUSE, M=IS_MMAPPED, A=NON_MAIN_ARENA33 ├──────────────────────────┤ ← user data starts here (returned pointer)34 0x10 │ fd (if free) │ ← forward pointer to next free chunk35 0x18 │ bk (if free) │ ← backward pointer to prev free chunk36 0x20 │ fd_nextsize (large only)│37 0x28 │ bk_nextsize (large only)│38 └──────────────────────────┘39```4041### Bin Types4243| Bin | Size Range (64-bit) | Structure | LIFO/FIFO |44|---|---|---|---|45| tcache (per-thread) | ≤ 0x410 (7 entries per size) | Singly linked (next pointer) | LIFO |46| fastbin | ≤ 0x80 (default) | Singly linked (fd) | LIFO |47| unsortedbin | Any freed size | Doubly linked circular | FIFO |48| smallbin | < 0x400 | Doubly linked circular | FIFO |49| largebin | ≥ 0x400 | Doubly linked + size-sorted | Sorted |5051### Key Global Structures5253| Structure | Location | Purpose |54|---|---|---|55| `main_arena` | libc .data segment | Contains bin heads, top chunk, system_mem |56| `mp_` | libc .data | malloc parameters (tcache settings, mmap threshold) |57| `tcache_perthread_struct` | Heap (first allocation) | Per-thread tcache bins and counts |5859---6061## 2. LEAK METHODS6263### Libc Base Leak6465| Method | Precondition | Technique |66|---|---|---|67| Unsortedbin fd/bk | Free a chunk > tcache range (or fill tcache) | fd/bk → `main_arena + 0x60` (or +0x70 depending on version) → libc base |68| Smallbin fd/bk | Chunk moved from unsortedbin to smallbin | Same as unsortedbin leak |69| stdout FILE leak | Write to `_IO_2_1_stdout_` | Corrupt `_IO_write_base` to leak libc data (see IO_FILE) |7071### Heap Base Leak7273| Method | Precondition | Technique |74|---|---|---|75| Tcache fd pointer | Free two tcache chunks, read first's fd | fd → heap address (XOR'd in ≥ 2.32) |76| Fastbin fd | Free two fastbin chunks | fd → heap address |77| UAF read | Use-after-free on freed chunk | Read fd/bk directly |7879### Safe-Linking Decode (glibc ≥ 2.32)8081```python82# PROTECT_PTR: fd_stored = (chunk_addr >> 12) ^ real_fd83# To decode: real_fd = fd_stored ^ (chunk_addr >> 12)84# To encode: fd_stored = (chunk_addr >> 12) ^ target_addr8586def deobfuscate(stored_fd, chunk_addr):87 return stored_fd ^ (chunk_addr >> 12)8889def obfuscate(target, chunk_addr):90 return (chunk_addr >> 12) ^ target91```9293---9495## 3. ATTACK CATEGORIES BY GLIBC VERSION9697### glibc < 2.26 (No tcache)9899| Attack | Primitive Needed | Result |100|---|---|---|101| Fastbin dup | Double free | Arbitrary allocation |102| Unsortedbin attack | Corrupt unsortedbin bk | Write `main_arena` addr to target (used for __malloc_hook nearby overwrite) |103| Unlink attack | Heap overflow into prev_size + fd/bk | Arbitrary write (with known heap pointer) |104| House of Force | Top chunk size overwrite | Arbitrary allocation |105| House of Spirit | Write fake chunk header | Fastbin allocation at fake chunk |106| Off-by-one null | Null byte overflow into next chunk size | Overlapping chunks |107108### glibc 2.26–2.28 (tcache, no key)109110| Attack | Notes |111|---|---|112| Tcache poisoning | Overwrite tcache fd → arbitrary allocation, no size check |113| Tcache dup | Double free into tcache (no double-free detection yet) |114| All previous attacks | Still work, but chunks go to tcache first |115116### glibc 2.29–2.31 (tcache key introduced)117118| Attack | Bypass for tcache key |119|---|---|120| Tcache dup | Corrupt `key` field (at chunk+0x18) before second free |121| House of Botcake | Double free: one in unsortedbin, one in tcache → overlapping |122| Tcache stashing unlink | Abuse smallbin→tcache refill to get arbitrary chunk |123124### glibc 2.32–2.33 (safe-linking / PROTECT_PTR)125126| Attack | Adaptation |127|---|---|128| Tcache poisoning | Encode target with `(chunk_addr >> 12) ^ target` |129| Heap leak required | Need heap addr to decode/encode safe-linked pointers |130| Fastbin dup | Same encoding required |131132### glibc ≥ 2.34 (hooks removed)133134| Change | Impact |135|---|---|136| `__malloc_hook` removed | Cannot overwrite hook for one_gadget |137| `__free_hook` removed | Cannot overwrite hook |138| `__realloc_hook` removed | Cannot use realloc trick for one_gadget constraints |139140**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`.141142---143144## 4. COMMON VULNERABILITY PATTERNS145146| Vulnerability | Description | Exploitation Path |147|---|---|---|148| UAF (Use-After-Free) | Access chunk after free | Read: leak fd/bk; Write: corrupt fd for tcache poisoning |149| Double Free | free() same chunk twice | Tcache dup (bypass key) or fastbin dup |150| Heap Overflow | Write past chunk boundary | Corrupt next chunk's metadata (size, fd, bk) |151| Off-by-one | One byte overflow | Null byte → shrink next chunk size → overlapping chunks |152| Off-by-null | Specifically `\x00` overflow | Clear PREV_INUSE → trigger backward consolidation |153| Uninitialized read | Read heap memory without clearing | Leak fd/bk from recycled chunk |154155---156157## 5. TOOLS158159```bash160# pwndbg heap inspection161pwndbg> heap # display all chunks162pwndbg> bins # show all bin contents163pwndbg> tcachebins # tcache status164pwndbg> fastbins # fastbin status165pwndbg> unsortedbin # unsortedbin content166pwndbg> vis_heap_chunks # visual heap layout167pwndbg> find_fake_fast &__malloc_hook # find nearby fake fastbin chunks168169# how2heap — reference implementations170git clone https://github.com/shellphish/how2heap171172# heapinspect173pip install heapinspect174heapinspect <pid>175176# pwntools helpers177from pwn import *178libc = ELF('./libc.so.6')179print(hex(libc.symbols['__malloc_hook']))180print(hex(libc.symbols['__free_hook']))181```182183---184185## 6. DECISION TREE186187```188Heap vulnerability identified189├── What is the primitive?190│ ├── UAF (read + write)191│ │ ├── Can read freed chunk? → Leak libc (unsortedbin) or heap (tcache fd)192│ │ └── Can write freed chunk? → Tcache poisoning / fastbin dup193│ ├── Double free194│ │ ├── glibc < 2.29 → direct tcache dup195│ │ ├── glibc 2.29-2.31 → corrupt tcache key first, or House of Botcake196│ │ └── glibc ≥ 2.32 → need heap leak for safe-linking encode197│ ├── Heap overflow (controlled size)198│ │ ├── Overwrite next chunk size → overlapping chunks → UAF199│ │ └── Overwrite fd directly → arbitrary allocation200│ ├── Off-by-one / off-by-null201│ │ ├── Null byte into size → House of Einherjar (backward consolidation)202│ │ └── One byte into size → shrink chunk, create overlap203│ └── Arbitrary write (from overlap or poisoned allocation)204│ ├── glibc < 2.34 → __malloc_hook / __free_hook → one_gadget205│ ├── glibc ≥ 2.34 → _IO_FILE vtable, exit_funcs, TLS_dtor_list206│ └── Partial RELRO → GOT overwrite207│208├── Need libc leak?209│ ├── Free chunk into unsortedbin (size > 0x410 or fill 7 tcache)210│ ├── Read fd/bk → main_arena offset → libc base211│ └── Alternative: stdout FILE partial overwrite for leak212│213└── Need heap leak? (glibc ≥ 2.32)214 ├── Read tcache fd from freed chunk215 └── Decode: real_addr = stored_fd ^ (chunk_addr >> 12)216```