The cruellest heap challenges give you no info leak and a stingy allocator — a handful of fixed-size chunks, no obvious read primitive. The House of Corrosion is the answer. It weaponises a single arena field so that the fastbin index arithmetic — normally a tiny lookup into an array of ten — starts indexing into libc's own writable data. From there you write your way to a leak, then a shell, having started with nothing.
- arch
- amd64-64-little
- libc
- 2.27
- technique
- global_max_fast overwrite -> fastbin index reaches into libc -> no-leak FSOP
- premise
- NO libc leak available, restrictive allocator
the arithmetic that becomes a write-what-where#
When you free a fastbin-sized chunk, glibc files it at
fastbinsY[fastbin_index(sz)], where fastbin_index(sz) = (sz >> 4) - 2.
Normally sz ≤ global_max_fast (0x80), so the index stays inside the ten-entry
fastbin array. But fastbinsY lives inside main_arena, which lives inside
libc's writable data. So if you first overwrite global_max_fast with a huge
value (an unsorted bin attack writes exactly the
kind of giant value that does this), then a chunk of a much larger sz indexes far
past the array — to a libc address you choose. Each fastbin slot is 8 bytes, so to
land on target:
Now free of a chunk with size sz writes that chunk's address (a heap pointer)
to target — anywhere in libc's writable region. No leak needed: every offset
here is a libc-version constant, measured from the arena.
from a heap pointer in libc to a leak#
Writing heap pointers into libc sounds weak — you don't control the value. The
move is to aim those writes at _IO_2_1_stdout_: corrode its _flags and its
_IO_write_base/_IO_write_ptr so the next buffered puts/printf dumps a span
of libc memory to you. That's your leak — manufactured from the writes themselves.
you're freeing into libc — the checks still run
Each "free" is a real fastbin free: glibc still checks the chunk's size matches
the (now absurd) fastbin index, that the chunk after it has a sane size, and the
fasttop double-free. So the libc bytes around target have to already hold
plausible values — much of the House of Corrosion is choosing targets whose
neighbouring libc data passes those checks, and staging them in the right order.
This is the most libc-version-sensitive technique in the series; the offsets are
not portable across builds.
the kill#
With a leak in hand you're back on solid ground: forge a FILE for
FSOP, or — on libc that still has them — drop system
into __free_hook. Either way, you've gone from no leak and a stingy allocator to
a shell, on the strength of one corrupted global_max_fast.
from nothing
the end of the series#
That's the board, corner to corner: from a chunk's size
field to forging FILE structs out of arena arithmetic.
Fourteen techniques, one idea — malloc trusts its metadata, so every attack is
just a more elaborate lie. Modern glibc has hardened most of these into specific
eras (the checksec-style panel at the top of each part tells you when it died),
but they remain the vocabulary: every new House is a recombination of these moves
against whatever check shipped last release.
Go read the glibc source,
keep pwndbg's vis open, and break some heaps.