## https://sploitus.com/exploit?id=3717FB70-C18F-5CDC-8BC3-996702C23757
# When the Allocator Fights Back
### Debugging Modern glibc Heap Exploits
> Nearly every heap-exploitation tutorial still teaches you to overwrite `__free_hook` with
> `system`. On modern glibc that symbol is **still there** β pwntools resolves it, you overwrite
> it β and your exploit dies anyway, because the allocator stopped *calling* the hooks in 2.34.
> This repo shows what to do instead.
Companion code and reference for the BSides CT 2026 talk of the same name. It contains a minimal
use-after-free binary and a **10/10-reliable exploit** that pops a shell on **glibc 2.39** with no
hooks, plus a **source-verified matrix** of which classic heap techniques still work across glibc
2.23 β 2.41.
Everything here is public technique against a self-authored, deliberately vulnerable training
binary. Nothing targets a real system.
---
## Quickstart
```sh
gcc -no-pie -fno-stack-protector -o note note.c
pip install pwntools
python3 exploit.py # -> shell (uid of the process), ASLR on
```
or just:
```sh
make run
```
Expected tail:
```
[+] libc base : 0x7f....000000
[+] heap base : 0x........000
[+] environ : 0x7ffe........
[+] main RIP @ : 0x7ffe........ (environ - 0x130)
[+] shell popped -- life after the hooks
$ id
uid=... gid=...
```
---
## What it demonstrates
`note.c` is a menu-driven "babyheap" allocator with one bug: `free()` doesn't clear the pointer
(use-after-free). `exploit.py` turns that single primitive into a shell on modern glibc, defeating
every mitigation the allocator throws back:
| Step | Primitive | Mitigation defeated |
|------|-----------|---------------------|
| 1 | Unsorted-bin **libc leak** (guarded oversized chunk) | β |
| 2 | Safe-linking **heap leak** (a lone freed chunk leaks `addr >> 12`) | pointer mangling (2.32) |
| 3 | Count-safe **tcache poison** β read `environ` (stack leak) | double-free key (2.29); `aligned_OK` (2.32) |
| 4 | Self-locating scan for `main`'s saved RIP | (env-size independent) |
| 5 | tcache poison β overwrite saved RIP with a ROP chain β `execve("/bin/sh")` | hooks removed (2.34) |
Everything libc-relative is resolved from the target libc via pwntools, so it ports to another
glibc by swapping the libc path and re-measuring three documented constants (see **Porting**).
---
## The three gotchas tutorials skip
These are the failures that make a "correct" exploit die silently on modern glibc β and the point
of the talk:
1. **`tcache_get`'s `aligned_OK` check** β the returned chunk must be 16-byte aligned, so you poison
to `RIP - 8`, not `RIP`, and write `[saved rbp][ROP chain]`.
2. **The `target + 8` key-zeroing** β `tcache_get` zeroes the returned chunk's key field, silently
corrupting a naive read/write one quadword in. Reads pick a 16-aligned base that keeps the wanted
value off that slot.
3. **The `movaps` alignment fault in `system`** β it faults unless `rsp` is 16-aligned at the call.
The fix is one `ret` gadget β but *which* parity you need depends on where the chain starts, which
is why the folklore "just add a ret" is sometimes wrong. The exploit finds it from the core dump.
---
## What still works on modern glibc
Full reference in [`docs/version-matrix.md`](docs/version-matrix.md); printable one-pager in
[`docs/version-matrix.html`](docs/version-matrix.html). Every boundary was verified by grepping the
actual `malloc.c` / `libioP.h` at each release tag (`github.com/bminor/glibc`, 2.23β2.41) and
cross-checked against the shipped Ubuntu 24.04 libc (2.39).
Legend: β works Β· β works with a leak/adaptation Β· β dead Β· β mechanism absent
| Technique | 2.27 | 2.29 | 2.32 | 2.34 | 2.41 | Killed / gated by |
|-----------|:--:|:--:|:--:|:--:|:--:|-------------------|
| `__free_hook` β system | β | β | β | β | β | hooks not called (2.34) |
| tcache poisoning | β | β | β | β | β | safe-linking + `aligned_OK` (2.32) |
| tcache double-free (naive) | β | β | β | β | β | tcache key (2.29) |
| fastbin dup | β | β | β | β | β | tcache intercept (2.26) |
| House of Force | β | β | β | β | β | top-size check (2.29) |
| House of Spirit | β | β | β | β | β | pass size/align |
| House of Einherjar | β | β | β | β | β | needs heap leak |
| Classic House of Orange | β | β | β | β | β | vtable check (2.24) |
| Naive stdout vtable FSOP | β | β | β | β | β | vtable check (2.24) |
| House of Apple2 (modern FSOP) | β | β | β | β | β | the hookless finish |
| Unsorted-bin attack | β | β | β | β | β | removal guard (**2.28**) |
| Large-bin attack | β | β | β | β | β | incremental checks (~2.30+) |
| `environ` β stack ROP | β | β | β | β | β | not an allocator technique |
**Two boundaries the internet gets wrong:**
- **`__free_hook` is a tombstone, not a removal.** 2.34 stopped *calling* the hooks (malloc.c
references 5 β 0), but the symbols still ship for ABI compat. `nm -D libc.so.6` on 2.39 lists
`__free_hook@GLIBC_2.2.5` in `.bss`. You can overwrite it; nothing reads it.
- **The unsorted-bin attack died in 2.28, not 2.29.** The `bck->fd != victim` guard at the
"remove from unsorted list" site is absent in 2.27 and present in 2.28 β a release earlier than
nearly every writeup cites.
---
## Porting to your glibc
Point `LIBC` in `exploit.py` at your target's libc. `system`, `/bin/sh`, and the ROP gadgets all
resolve automatically once `LIBC.address` is set. Re-measure three constants:
- `UNSORTED_OFF` β the libc pointer a lone unsorted chunk leaks (`main_arena + 0x60`):
`gdb -q ./note -ex 'b main' -ex run -ex 'p/x (long)&main_arena'`, subtract libc base, add `0x60`.
- `MAIN_RET_OFF` β the value sitting at `main`'s saved RIP (the scan target):
`b main; run; up; x/gx $rbp+8`, subtract libc base.
- The heap chunk offsets (`a0`..`a3`) assume the allocation order in the script; change sizes/order
and re-dump the layout in gdb.
Measured here (glibc 2.39): `UNSORTED_OFF = 0x203b20`, `MAIN_RET_OFF = 0x2a1ca`,
`environ β saved_RIP = 0x130` (empty env; the script self-locates via the scan, so this is
informational).
---
## Repo layout
```
.
βββ note.c # the vulnerable "babyheap" binary (UAF)
βββ exploit.py # the modern chain, fully commented
βββ Makefile # make build / run / clean
βββ docs/
β βββ version-matrix.md # full mitigation + technique matrix (source-verified)
β βββ version-matrix.html # printable one-page field reference
βββ README.md
βββ LICENSE
```
---
## The talk
**When the Allocator Fights Back: Debugging Modern glibc Heap Exploits** β BSides CT 2026,
Fairfield University. The talk frames this as an investigation: run the dead `__free_hook` exploit
live, rebuild the chain the modern way, read the core dump when it breaks, and close with the
version matrix. Slides link back here.
---
## Credits & references
- [how2heap](https://github.com/shellphish/how2heap) (Shellphish) β the canonical collection of
heap techniques these build on.
- Safe-linking: Check Point Research, *Safe-Linking* (2020).
- The broader CTF and academic heap-exploitation literature; the "House of β¦" lineage.
- glibc source: [`github.com/bminor/glibc`](https://github.com/bminor/glibc).
## Authorized use
`note.c` is a deliberately vulnerable training binary you build and exploit locally. Every technique
here is publicly documented. Use it for learning, teaching, and CTF-style practice on systems you own
or are authorized to test.
## License
MIT β see [`LICENSE`](LICENSE).