Every technique so far corrupted a real chunk. The House of Spirit flips it: you
build a fake chunk in memory you already control — the stack, a global, a
larger heap buffer — and convince free() to file it into a bin. The next request
of that size hands your fake chunk straight back, so you've made malloc return an
address it should never return. Point that at a saved return address and the heap
becomes a stack write.
- arch
- amd64-64-little
- libc
- 2.31
- technique
- free() a forged chunk -> malloc returns attacker-chosen memory
- requires
- free() on a pointer you influence + somewhere writable to forge a chunk
the idea#
free(p) doesn't care whether p came from malloc — it reads the size field at
p - 0x10, decides which bin it belongs in, and links it there. So if you can make
a program free a pointer into a buffer you control, and you've laid out a valid
chunk header there, that buffer becomes a free chunk. Allocate that size again and
it comes back to you.
The fake chunk only has to survive a couple of sanity checks. For a fastbin-sized fake:
- the size must be a valid fastbin size and
0x10-aligned; - the chunk after it (at
fake + size) must itself have a plausible size — between0x20and the arena'ssystem_mem— orfreeaborts withfree(): invalid next size.
So you forge two size fields: your chunk's, and a believable one for its "successor."
00000000 00 00 00 00 00 00 00 00 41 00 00 00 00 00 00 00 |........A.......| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 41 00 00 00 00 00 00 00 |........A.......|
(First 0x41 = the fake chunk's size — 0x40 | PREV_INUSE. The second 0x41,
0x40 bytes later, is the "next chunk" size that passes the next-size check.)
driving it#
|
In a real target you rarely call free(&fake) yourself — you get there by
overwriting a stored pointer the program later frees, or via a type confusion. The
point is the same: a free you steer + a chunk you forge = malloc returning
your address.
the stack payoff#
Forge the fake chunk on the stack, straddling a saved return address. After the free→malloc round-trip you're handed a write primitive pointed at the stack:
|
the shell
The forged chunk overlaps the saved return address; one write drops a ROP chain
(or a one-gadget) over RIP, and the function returns into it:
segfault{m4ll0c_r3turn3d_th3_st4ck}
the tcache makes this trivial
On glibc ≥ 2.26 a fastbin-sized free hits the tcache first, and the tcache
skips the invalid next size check entirely — so the tcache House of Spirit
only needs a valid, aligned size field, not a believable successor. That's why
forging a chunk and freeing it is one of the most reliable modern primitives
once you have any free-a-pointer-you-control bug.
limitations#
- you need a
freeon a pointer you influence (the whole premise), - the fake chunk's size must match a real bin and (≥ 2.32) be
0x10-aligned, - the fastbin variant needs that believable next-size field.
It pairs naturally with the earlier moves: forge the chunk, free it, then
poison the tcache from the duplicate. Next:
the poison null byte, where a single stray \x00 is enough
to make two chunks overlap.