Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
House of Lore is the small-bin analogue of tcache/fastbin poisoning: it corrupts the doubly-linked list of a small bin (and, in its extended form, a large bin) so that a later malloc returns a chunk at an attacker-chosen address. Where fastbin and tcache attacks abuse a singly-linked next/fd pointer, House of Lore targets the bk (‘backward’) pointer that small bins use when serving allocations from the tail of the list. By overwriting a small-bin chunk’s bk to point at a fake chunk in controlled memory — and making that fake chunk’s links self-consistent — the attacker splices a rogue node into the bin and has malloc hand it out.
The technique is one of the original *Malloc Maleficarum* houses and it remains conceptually important because small bins are the fallback the allocator uses once tcache is full or bypassed, so House of Lore is the go-to when a challenge steers frees into small bins rather than the caches. It is *constrained* on modern glibc by the small-bin integrity check added around the 2.26 era — when unlinking a chunk from the tail of a small bin, glibc verifies that the candidate’s forward neighbour links back to it (bck->fd == victim), aborting with “malloc(): smallbin double linked list corrupted” otherwise. House of Lore survives by forging a *pair* of consistent fake chunks, which is the modern discipline of the attack.
Attack Prerequisites
House of Lore needs a write into a small-bin chunk’s metadata plus controlled memory to host the fake node(s). Typical requirements:
- A write into a freed small-bin chunk’s
bkpointer — via a UAF, an overflow into the adjacent chunk, or an edit-after-free primitive. - Controlled memory for a fake chunk at the target address (stack,
.bss, or elsewhere on the heap), wheremallocwill ultimately return. - A second consistent fake chunk so the small-bin unlink check passes: the fake node’s
fd/bkmust form a valid two-way link back to the corrupted chunk (bck->fd == victim). - The ability to move a chunk into a small bin — allocate, free, and (for small bins) ensure the chunk is sorted out of the unsorted bin into its small bin first, typically by triggering an intervening allocation.
- An address for the target (a leak, unless a static non-PIE destination is used) so the corrupted
bkand fake links can be computed.
How It Works
Small bins are doubly-linked, size-segregated lists of free chunks, each holding chunks of one exact size in FIFO order via fd (forward, toward the head) and bk (backward, toward the tail) pointers. When malloc serves a request from a small bin, it takes the chunk at the *tail* of the list — the one reached through the bin head’s bk. It reads victim = bin->bk, then computes bck = victim->bk and relinks the bin around the removed node. House of Lore attacks that bk traversal: if the attacker controls victim->bk, they control which node the allocator treats as the next tail-of-list chunk to hand out.
The attack plants a fake chunk at the target and points a real small-bin chunk’s bk at it. On the first small-bin allocation the real chunk is returned; on the *next* one the allocator follows the corrupted bk to the fake chunk and returns a pointer to attacker-controlled memory. For this to succeed on modern glibc, the fake chunk cannot be a lone forgery: when the allocator unlinks it, it validates bck->fd == victim — meaning the fake chunk’s *own* bk must point at a second controlled location whose fd in turn points back at the fake chunk. In practice the attacker lays out two fake chunks (or a fake chunk plus a controlled cell) whose forward and backward pointers are mutually consistent, so the safe-unlink-style check holds even though the entire chain is fabricated.
The large-bin variant extends the same idea to large bins, which additionally carry fd_nextsize/bk_nextsize skip-list pointers for size ordering; corrupting those lets the attacker influence large-bin insertion and, in combination, achieve a similar out-of-bin allocation. In all forms the payoff is identical to a fastbin/tcache poison — malloc returns a pointer to memory the attacker controls the address of — but House of Lore reaches it through the bins that remain in play when the caches are exhausted or hardened, which is exactly when the simpler poisons are unavailable.
Vulnerable Code / Setup
The enabling primitive is an edit or overflow that reaches a freed chunk sitting in a small bin, letting the attacker overwrite its bk. A menu allocator with a use-after-free on a small-bin-sized object is the archetype:
#include <stdlib.h>
#include <string.h>
/* Objects are small-bin sized (> 0x410 so they skip tcache, or with tcache
pre-filled) so frees land in a small bin rather than the cache. */
char *slot[8];
void bug(void) {
slot[0] = malloc(0x420); /* small-bin sized */
char *guard = malloc(0x420); /* prevents consolidation with top */
free(slot[0]); /* -> unsorted bin */
malloc(0x500); /* forces slot[0] to sort into a small bin */
/* BUG: slot[0] was never nulled -> edit-after-free overwrites its bk. */
/* Attacker writes: slot[0]->bk = &fake_chunk (in controlled memory). */
memcpy(slot[0] + 8, &fake_chunk_ptr, 8); /* corrupt bk */
malloc(0x420); /* returns the real chunk (tail) */
char *evil = malloc(0x420); /* follows corrupted bk -> fake chunk */
}
CThe fake chunk must be paired with a consistent partner so the small-bin unlink check (bck->fd == victim) holds. Lay out the fake chunk’s fd/bk to reference a second controlled cell that links back to it:
/* fake_chunk at target address T:
T+0x00: prev_size (unused here)
T+0x08: size (match the bin's size class, e.g. 0x421)
T+0x10: fd -> (any valid, e.g. points to real chunk)
T+0x18: bk -> &partner, where partner+0x10 (its fd) == T
This makes bck->fd == victim hold when malloc unlinks the fake chunk. */
CTriage the binary to confirm small bins are the operative path and whether a leak is required for the target/fake addresses:
$ checksec --file=./lore
RELRO STACK CANARY NX PIE
Full RELRO Canary found NX enab. PIE <-- leak needed for targets
$ ldd ./lore | grep libc # version determines the smallbin check
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (2.31)
BashWalkthrough / Exploitation
Confirm the chunk actually reaches a small bin before corrupting it — a chunk freed into the unsorted bin has different link semantics, so you must force the sort first. In gdb with pwndbg:
gdb -q ./lore
pwndbg> run
pwndbg> bins # after free + an intervening large malloc,
# the chunk should appear in a SMALL bin, not unsorted
pwndbg> vis_heap_chunks # locate the chunk whose bk you will overwrite
pwndbg> # after corrupting bk, check the bin's bk points at your fake chunk
BashThe full sequence: place a chunk into a small bin; overwrite its bk to point at a fake chunk you have made link-consistent; allocate once to drain the real chunk; allocate again to receive the fake chunk. A pwntools skeleton:
from pwn import *
io = process('./lore')
def alloc(sz, data=b''): ... # menu wrappers
def free(idx): ...
def edit(idx, data): ...
target = leak + 0x100 # where we want malloc to return
# 1) forge a link-consistent fake chunk (+ partner) at target
make_fake_chunk(target)
# 2) get a chunk into a small bin, then overwrite its bk -> target
free(0) # -> unsorted
alloc(0x500) # sort chunk 0 into its small bin
edit(0, p64(0) + p64(target)) # fd unchanged, bk = target (corruption)
# 3) drain the real chunk, then receive the fake one
alloc(0x420) # returns the legitimate tail chunk
evil = alloc(0x420) # follows corrupted bk -> target
edit_via(evil, p64(0xdeadbeef)) # write at attacker-chosen address
PythonOnce malloc returns a pointer to your target, the escalation matches any arbitrary-allocation primitive: place the fake chunk over a writable function pointer, a GOT entry (Partial RELRO), a saved return address on the stack, or allocator/FILE state, and let the program’s next write into the ‘new’ chunk corrupt it. Because House of Lore operates in small bins, it is frequently the second stage after a leak obtained from an unsorted-bin chunk, chaining a libc disclosure into a precise write.
Note: The most common failure is skipping the sort step. A freshly freed small-sized chunk goes to the *unsorted* bin, not directly to a small bin; it is only moved into its small bin during a later allocation that scans the unsorted list. If you overwrite
bkwhile the chunk is still unsorted, you are attacking the wrong list and the layout won’t behave as expected. Always confirm withbinsthat the chunk is in a *small* bin before corrupting it.
Opsec: On glibc >= 2.26 the small-bin unlink check (
bck->fd == victim, “smallbin double linked list corrupted”) means a single unpaired fake chunk aborts the allocation. Forge the partner so the two-way link is consistent, and compute both from a live leak — a stale address is the usual cause of the abort. Verify the fake chain in gdb (x/6gx <fake>) before triggering the second allocation.
Detection and Defense
House of Lore is enabled by a write into freed small-bin metadata and by predictable target addresses, so the defenses mirror other bin-poisoning attacks:
- Null pointers after free and enforce ownership so a freed small-bin chunk cannot be edited — the UAF write into
bkis the root cause. - Keep glibc current — the small-bin (and unsorted-bin) doubly-linked-list integrity checks force the attacker into a precise paired forgery and abort on any inconsistency.
- Full RELRO + PIE + ASLR — remove static write targets and force a leak for the fake-chunk and destination addresses.
- Sanitizers in CI — AddressSanitizer catches the underlying use-after-free or overflow into the freed chunk deterministically.
- Hardened allocators — out-of-line metadata and list integrity that does not live inside freed user data deny the
fd/bkcorruption the technique depends on.
Real-World Impact
House of Lore is less ubiquitous in write-ups than tcache poisoning simply because tcache intercepts most small allocations first, but it remains the reference technique whenever the caches are unavailable — full tcache bins, sizes above the tcache range, or challenges deliberately built around small/large bins. It keeps the doubly-linked-bin attack surface relevant and is a direct intellectual descendant of the classic unlink attack, sharing the same fd/bk consistency requirements. The bug classes it consumes — use-after-free (CWE-416) and heap overflow (CWE-122) into free-list metadata — are among the most common and highest-severity memory-safety weaknesses in C/C++ software, which is why understanding every bin’s traversal semantics, not just the caches, still matters for real exploitation.
Conclusion
House of Lore poisons a small bin’s bk pointer so that malloc, serving from the tail of the list, follows a forged link into attacker-controlled memory and returns a chunk there. Modern glibc constrains it with the small-bin double-linked-list check, so the technique now requires a consistent pair of fake chunks and a live leak rather than a lone forgery — but it survives, and it is the tool of choice when tcache and fastbins are off the table. It is a reminder that the allocator’s *every* free-list, not just the fast paths, trusts in-band pointers that a single stray write can turn into an arbitrary allocation.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments