Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
#!/usr/bin/env python3
## -*- coding: utf-8 -*-
##
## Jonathan Salwan - 2018-10-26
##
## Description: Solution of the unbreakable challenge from the Google 2016 CTF.
## In this solution, we fully emulate the binary and we solve each branch
## to go through the good path.
##
## Output:
##
## $ time python3 ./solve.py
## [+] Loading 0x400040 - 0x400200
## [+] Loading 0x400200 - 0x40021c
## [+] Loading 0x400000 - 0x403df4
## [+] Loading 0x604000 - 0x604258
## [+] Loading 0x604018 - 0x6041e8
## [+] Loading 0x40021c - 0x400260
## [+] Loading 0x403590 - 0x40378c
## [+] Loading 0x000000 - 0x000000
## [+] Hooking strncpy
## [+] Hooking puts
## [+] Hooking printf
## [+] Hooking __libc_start_main
## [+] Hooking exit
## [+] Starting emulation.
## [+] __libc_start_main hooked
## [+] argv[0] = ./unbreakable-enterprise-product-activation
## [+] argv[1] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
## [+] strncpy hooked
## [+] puts hooked
## Thank you - product activated!
## [+] exit hooked
## Flag: CTF{0The1Quick2Brown3Fox4Jumped5Over6The7Lazy8Fox9}
## python3 solve.py 8.04s user 0.02s system 99% cpu 8.060 total
##
from __future__ import print_function
from triton import *
import random
import string
import sys
import lief
import os
TARGET = os.path.join(os.path.dirname(__file__), 'unbreakable-enterprise-product-activation')
DEBUG = True
# The debug function
def debug(s):
if DEBUG: print(s)
# Memory mapping
BASE_PLT = 0x10000000
BASE_ARGV = 0x20000000
BASE_STACK = 0x9fffffff
# These instruction conditions must set zf to 1.
conditions = [
0x402819,
0x402859,
0x4028A3,
0x4028F3,
0x402927,
0x402969,
0x4029A9,
0x4029E0,
0x402A1F,
0x402A56,
0x402A99,
0x402AD9,
0x402B07,
0x402B37,
0x402B79,
0x402BA7,
0x402BD7,
0x402C22,
0x402C69,
0x402CA9,
0x402CD7,
0x402D22,
0x402D73,
0x402DB0,
0x402DF9,
0x402E43,
0x402E89,
0x402EC9,
0x402EF7,
0x402F30,
0x402F79,
0x402FB9,
0x402FF9,
0x403039,
0x403079,
0x4030C5,
0x403109,
0x403149,
0x403189,
0x4031B7,
0x4031F9,
0x403239,
0x403270,
0x4032B0,
0x403302,
0x403337,
0x403379,
0x4033B9,
0x4033F0,
0x403427,
0x403472,
]
def getMemoryString(ctx, addr):
s = str()
index = 0
while ctx.getConcreteMemoryValue(addr+index):
c = chr(ctx.getConcreteMemoryValue(addr+index))
if c not in string.printable: c = ""
s += c
index += 1
return s
def getFormatString(ctx, addr):
return getMemoryString(ctx, addr) \
.replace("%s", "{}").replace("%d", "{:d}").replace("%#02x", "{:#02x}") \
.replace("%#x", "{:#x}").replace("%x", "{:x}").replace("%02X", "{:02x}") \
.replace("%c", "{:c}").replace("%02x", "{:02x}").replace("%ld", "{:d}") \
.replace("%*s", "").replace("%lX", "{:x}").replace("%08x", "{:08x}") \
.replace("%u", "{:d}").replace("%lu", "{:d}") \
# Simulate the printf() function
def printfHandler(ctx):
debug('[+] printf hooked')
# Get arguments
arg1 = getFormatString(ctx, ctx.getConcreteRegisterValue(ctx.registers.rdi))
arg2 = ctx.getConcreteRegisterValue(ctx.registers.rsi)
arg3 = ctx.getConcreteRegisterValue(ctx.registers.rdx)
arg4 = ctx.getConcreteRegisterValue(ctx.registers.rcx)
arg5 = ctx.getConcreteRegisterValue(ctx.registers.r8)
arg6 = ctx.getConcreteRegisterValue(ctx.registers.r9)
nbArgs = arg1.count("{")
args = [arg2, arg3, arg4, arg5, arg6][:nbArgs]
s = arg1.format(*args)
if DEBUG:
sys.stdout.write(s)
# Return value
return len(s)
# Simulate the putchar() function
def putcharHandler(ctx):
debug('[+] putchar hooked')
# Get arguments
arg1 = ctx.getConcreteRegisterValue(ctx.registers.rdi)
sys.stdout.write(chr(arg1) + '\n')
# Return value
return 2
# Simulate the puts() function
def putsHandler(ctx):
debug('[+] puts hooked')
# Get arguments
arg1 = getMemoryString(ctx, ctx.getConcreteRegisterValue(ctx.registers.rdi))
sys.stdout.write(arg1 + '\n')
# Return value
return len(arg1) + 1
# Simulate the strncpy() function
def strncpyHandler(ctx):
debug('[+] strncpy hooked')
dst = ctx.getConcreteRegisterValue(ctx.registers.rdi)
src = ctx.getConcreteRegisterValue(ctx.registers.rsi)
cnt = ctx.getConcreteRegisterValue(ctx.registers.rdx)
for index in range(cnt):
dmem = MemoryAccess(dst + index, 1)
smem = MemoryAccess(src + index, 1)
cell = ctx.getMemoryAst(smem)
expr = ctx.newSymbolicExpression(cell, "strncpy byte")
ctx.setConcreteMemoryValue(dmem, cell.evaluate())
ctx.assignSymbolicExpressionToMemory(expr, dmem)
return dst
def exitHandler(ctx):
debug('[+] exit hooked')
ret = ctx.getConcreteRegisterValue(ctx.registers.rdi)
ast = ctx.getAstContext()
pco = ctx.getPathPredicate()
# Ask for a new model which set all symbolic variables to ascii printable characters
mod = ctx.getModel(ast.land(
[pco] +
[ast.variable(ctx.getSymbolicVariable(0)) == ord('C')] +
[ast.variable(ctx.getSymbolicVariable(1)) == ord('T')] +
[ast.variable(ctx.getSymbolicVariable(2)) == ord('F')] +
[ast.variable(ctx.getSymbolicVariable(3)) == ord('{')] +
[ast.variable(ctx.getSymbolicVariable(50)) == ord('}')] +
[ast.variable(ctx.getSymbolicVariable(x)) >= 0x30 for x in range(4, 49)] +
[ast.variable(ctx.getSymbolicVariable(x)) <= 0x7a for x in range(4, 49)] +
[ast.variable(ctx.getSymbolicVariable(x)) != 0x00 for x in range(4, 49)]
))
flag = str()
for k, v in sorted(mod.items()):
flag += chr(v.getValue())
print('Flag: %s' %(flag))
sys.exit(not (flag == 'CTF{0The1Quick2Brown3Fox4Jumped5Over6The7Lazy8Fox9}'))
def libcMainHandler(ctx):
debug('[+] __libc_start_main hooked')
# Get arguments
main = ctx.getConcreteRegisterValue(ctx.registers.rdi)
# Push the return value to jump into the main() function
ctx.setConcreteRegisterValue(ctx.registers.rsp, ctx.getConcreteRegisterValue(ctx.registers.rsp)-CPUSIZE.QWORD)
ret2main = MemoryAccess(ctx.getConcreteRegisterValue(ctx.registers.rsp), CPUSIZE.QWORD)
ctx.setConcreteMemoryValue(ret2main, main)
# Setup argc / argv
ctx.concretizeRegister(ctx.registers.rdi)
ctx.concretizeRegister(ctx.registers.rsi)
argvs = [
bytes(TARGET.encode('utf-8')), # argv[0]
bytes(b'a' * 70), # argv[1]
]
# Define argc / argv
base = BASE_ARGV
addrs = list()
index = 0
for argv in argvs:
addrs.append(base)
ctx.setConcreteMemoryAreaValue(base, argv+b'\x00')
base += len(argv)+1
debug('[+] argv[%d] = %s' %(index, argv))
index += 1
argc = len(argvs)
argv = base
for addr in addrs:
ctx.setConcreteMemoryValue(MemoryAccess(base, CPUSIZE.QWORD), addr)
base += CPUSIZE.QWORD
ctx.setConcreteRegisterValue(ctx.registers.rdi, argc)
ctx.setConcreteRegisterValue(ctx.registers.rsi, argv)
# Symbolize the first 51 bytes of the argv[1]
argv1 = ctx.getConcreteMemoryValue(MemoryAccess(ctx.getConcreteRegisterValue(ctx.registers.rsi) + 8, CPUSIZE.QWORD))
for index in range(51):
var = ctx.symbolizeMemory(MemoryAccess(argv1+index, CPUSIZE.BYTE))
return 0
# Functions to emulate
customRelocation = [
('__libc_start_main', libcMainHandler, BASE_PLT + 0),
('exit', exitHandler, BASE_PLT + 1),
('printf', printfHandler, BASE_PLT + 2),
('putchar', putcharHandler, BASE_PLT + 3),
('puts', putsHandler, BASE_PLT + 4),
('strncpy', strncpyHandler, BASE_PLT + 5),
]
def hookingHandler(ctx):
pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
for rel in customRelocation:
if rel[2] == pc:
# Emulate the routine and the return value
ret_value = rel[1](ctx)
if ret_value is not None:
ctx.setConcreteRegisterValue(ctx.registers.rax, ret_value)
# Get the return address
ret_addr = ctx.getConcreteMemoryValue(MemoryAccess(ctx.getConcreteRegisterValue(ctx.registers.rsp), CPUSIZE.QWORD))
# Hijack RIP to skip the call
ctx.setConcreteRegisterValue(ctx.registers.rip, ret_addr)
# Restore RSP (simulate the ret)
ctx.setConcreteRegisterValue(ctx.registers.rsp, ctx.getConcreteRegisterValue(ctx.registers.rsp)+CPUSIZE.QWORD)
return
# Emulate the binary.
def emulate(ctx, pc):
global conditions
count = 0
while pc:
# Fetch opcodes
opcodes = ctx.getConcreteMemoryAreaValue(pc, 16)
# Create the Triton instruction
instruction = Instruction()
instruction.setOpcode(opcodes)
instruction.setAddress(pc)
# Process
if ctx.processing(instruction) == EXCEPTION.FAULT_UD:
debug('[-] Instruction not supported: %s' %(str(instruction)))
break
count += 1
#print(instruction)
if instruction.getType() == OPCODE.X86.HLT:
break
# Simulate routines
hookingHandler(ctx)
if instruction.getAddress() in conditions:
zf = ctx.getSymbolicRegister(ctx.registers.zf).getAst()
ast = ctx.getAstContext()
ctx.pushPathConstraint(zf == 1)
mod = ctx.getModel(ctx.getPathPredicate())
for k,v in list(mod.items()):
ctx.setConcreteVariableValue(ctx.getSymbolicVariable(v.getId()), v.getValue())
# Next
pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
debug('[+] Instruction executed: %d' %(count))
return
def loadBinary(ctx, binary):
# Map the binary into the memory
phdrs = binary.segments
for phdr in phdrs:
size = phdr.physical_size
vaddr = phdr.virtual_address
debug('[+] Loading 0x%06x - 0x%06x' %(vaddr, vaddr+size))
ctx.setConcreteMemoryAreaValue(vaddr, list(phdr.content))
return
def makeRelocation(ctx, binary):
# Perform our own relocations
try:
for rel in binary.pltgot_relocations:
symbolName = rel.symbol.name
symbolRelo = rel.address
for crel in customRelocation:
if symbolName == crel[0]:
debug('[+] Hooking %s' %(symbolName))
ctx.setConcreteMemoryValue(MemoryAccess(symbolRelo, CPUSIZE.QWORD), crel[2])
except:
pass
# Perform our own relocations
try:
for rel in binary.dynamic_relocations:
symbolName = rel.symbol.name
symbolRelo = rel.address
for crel in customRelocation:
if symbolName == crel[0]:
debug('[+] Hooking %s' %(symbolName))
ctx.setConcreteMemoryValue(MemoryAccess(symbolRelo, CPUSIZE.QWORD), crel[2])
except:
pass
return
def run(ctx, binary):
# Define a fake stack
ctx.setConcreteRegisterValue(ctx.registers.rbp, BASE_STACK)
ctx.setConcreteRegisterValue(ctx.registers.rsp, BASE_STACK)
# Let's emulate the binary from the entry point
debug('[+] Starting emulation.')
emulate(ctx, binary.entrypoint)
debug('[+] Emulation done.')
return
def main():
# Get a Triton context
ctx = TritonContext()
# Set the architecture
ctx.setArchitecture(ARCH.X86_64)
# Set optimization
ctx.setMode(MODE.ALIGNED_MEMORY, True)
ctx.setMode(MODE.ONLY_ON_SYMBOLIZED, True)
# AST representation as Python syntax
ctx.setAstRepresentationMode(AST_REPRESENTATION.SMT)
# Parse the binary
binary = lief.parse(TARGET)
# Load the binary
loadBinary(ctx, binary)
# Perform our own relocations
makeRelocation(ctx, binary)
# Init and emulate
run(ctx, binary)
return -1
if __name__ == '__main__':
retValue = main()
sys.exit(retValue)