exploiting-glibc-heap-vulnerabilities

$npx mdskill add xalgord/xalgorix/exploiting-glibc-heap-vulnerabilities

- 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).

SKILL.md

.github/skills/exploiting-glibc-heap-vulnerabilitiesView on GitHub ↗
---
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.
```

More from xalgord/xalgorix