cscg24-license

CSCG 2024 Challenge 'Most unique license checker'
git clone https://git.sinitax.com/sinitax/cscg24-license
Log | Files | Refs | sfeed.txt

commit 413e5c55f8ed8ae1b1885eae05062f556050412a
parent f73a986ae16975455d86ebd440d86d90d270af6b
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 13 Apr 2024 17:55:17 +0200

Add solution

Diffstat:
A.gitignore | 2++
Asolve/chall.hash | 1+
Asolve/crack | 4++++
Asolve/flag | 1+
Asolve/license | 1+
Msolve/licensecheck.bndb | 0
Msolve/notes | 15+++++++++++++++
Msolve/solve | 187++++++++++++++++++++++++++++++++++---------------------------------------------
8 files changed, 105 insertions(+), 106 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +.gdb_history +core.dump diff --git a/solve/chall.hash b/solve/chall.hash @@ -0,0 +1 @@ +c5366939f46a0f6bc44cfad38f99c0ce diff --git a/solve/crack b/solve/crack @@ -0,0 +1,4 @@ +#!/bin/sh + +hashcat -a 3 -m 0 chall.hash -1 ?u?d '?10?1?1?1-?1?1?1N?1' + diff --git a/solve/flag b/solve/flag @@ -0,0 +1 @@ +CSCG{l1c3ns3_r3v_m4st3r} diff --git a/solve/license b/solve/license @@ -0,0 +1 @@ +A1K9B-O33LS-BB821-00GHK-L1MNS diff --git a/solve/licensecheck.bndb b/solve/licensecheck.bndb Binary files differ. diff --git a/solve/notes b/solve/notes @@ -7,3 +7,18 @@ its only a few functions, could do it by hand.. but to actually learn something i should try to automate it. basically want that angr can make it through the init process so the funcs are init. + +we can avoid a state blowup by skipping the input checks and encoding them +into input constraints. then we just jump ahead after the checks have been performed (0x4020d0). + +to avoid another blowup, we use lazy evaluation. + +a hacky way to get simulation state output is to use sigalarm and reschedule in the +handler. more standard way is logging.getLogger("..").setLevel(logging.DEBUG) + + +if angr just fails and you know it should be possible - its probably more +advanced crypto that you would not be able to reverse with symbolic execution anyways + +the binarydb is fucked, bad signature matching caused the md5 function to look +like it was caused address sanitizer compile.. diff --git a/solve/solve b/solve/solve @@ -2,125 +2,100 @@ import angr import claripy -import sys -import signal - -def sigalarm(_a, _b): - print("STATE", sim, sim.active) - if len(sim.active) > 0: - print(sim.active[0].callstack) - signal.signal(signal.SIGALRM, sigalarm) - signal.alarm(1) -signal.signal(signal.SIGALRM, sigalarm) - -def sigint(_a, _b): - from IPython import embed - embed() -signal.signal(signal.SIGINT, sigint) - -key_addr = 0x1000 -keylen = 29 -key = [claripy.BVS(f"key_{i}", 8) for i in range(keylen)] - -project = angr.Project("./licensecheck", auto_load_libs=False) - -blank = project.factory.blank_state() -options = {angr.options.INITIALIZE_ZERO_REGISTERS} -state = project.factory.call_state(0x402030, key_addr, - ret_addr=0x4016d6, - prototype="int check(const char *)", - add_options=options) - -state.memory.store(state.regs.rsp - 0x2000, 0x2000 * b"\x00") -state.memory.store(0x4cd000, open("got.plt", "rb").read()) -state.regs.rbp = state.regs.rsp -state.regs.fs = blank.regs.fs - -#state.memory[0x401980].uint8_t = 0xc3 - -binfile = angr.SimFile("licensecheck", open("licensecheck", "rb").read()) -binfile.set_state(state) - -for i in range(0, keylen): - if (i + 1) % 6 == 0: - key[i] = b'-' - elif i in (4, 12, 14): - key[i] = b'B' - else: - isalpha = claripy.And(key[i] >= 0x41, key[i] <= 0x41 + 0x19) - isnum = claripy.And(key[i] >= 0x30, key[i] <= 0x30 + 9) - state.solver.add(claripy.Or(isalpha, isnum)) - #state.solver.add(key.get_byte(i) >= 0x30) - #state.solver.add(key.get_byte(i) <= 0x41+0x19) - -state.memory.store(key_addr, claripy.Concat(*key, b"\x00" * 0x40)) - -def print_key(state): - keystr = b"" - for c in key: - if type(c) != bytes: - keystr += state.solver.eval(c, cast_to=bytes) - else: - keystr += c - print(keystr) -print_key(state) - -sim = project.factory.simgr(state) +import logging +import subprocess +import cle -# sim.explore(find=0x403800) -# print("__libc_start_main", sim) -# sim.stash(from_stash="found", to_stash="active") -# -# sim.explore(find=0x4061c0) -# print("main", sim) +try: + import binaryninja +except: + pass -#raise False - -def checkpoint(addr, text): - sim.explore(find=addr) - print(text, sim) - assert(len(sim.found) == 1 and len(sim.active) == 0) - sim.stash(from_stash="found", to_stash="active") +check1 = b"A@F01" +check2 = b"\xc56i9\xf4j\x0fk\xc4L\xfa\xd3\x8f\x99\xc0\xce" +check2_rev = b"00GHK-L1MNS" # from cracked md5 hash -def findall(addr, text, avoid=[]): +def checkpoint(sim, find, text, **kwargs): while len(sim.active) > 0: - sim.explore(find=addr, avoid=avoid) + sim.explore(find=find, **kwargs, step_func=step_func) print(text, sim) + assert(len(sim.found) > 0) + sim.stash(from_stash="found", to_stash="active") + +def step_func(sim): + if sim.active != [] and bv != None: + for state in sim.active: + for bb in bv.get_basic_blocks_at(state.solver.eval(state.regs.rip)): + bb.set_user_highlight(binaryninja.HighlightStandardColor.MagentaHighlightColor) + sim.drop(stash="avoid") -checkpoint(0x402058, "before strlen") -checkpoint(0x40205d, "after strlen") -checkpoint(0x40207f, "after len check") + print(sim.active) -assert(len(sim.active) == 1) -sim.active[0].regs.rip = 0x4020e4 -signal.alarm(1) -sim.explore(find=0x402538, avoid=0x4020a4) -print("found!", sim) -state = sim.found[0] -state.solver.add(state.regs.rax != 1) -print_key() +def run(_bv=None): + global bv + bv = _bv -raise 0 + logging.getLogger('angr.sim_manager').setLevel(logging.DEBUG) -signal.alarm(1) -findall(0x4020d0, "before content check") -print(sim.found[0].solver.eval(key, cast_to=bytes)) + key = [claripy.BVS(f"key_{i}", 8) for i in range(29)] + [b"\x00"] -sim.active[0].options.add(angr.options.LAZY_SOLVES) + cmd = ["gdb", "-ex", "b *0x402030", "-ex", "run", + "-ex", "gcore core.dump", "-ex", "exit", "--batch", + "--args", "./licensecheck", "12345-12345-12345-12345-12345"] + subprocess.run(cmd) -while True: - sim.explore(find=0x4016ed) - print("found", sim) - for state in sim.found: - print(state.solver.eval(key, cast_to=bytes)) - sim.stash(from_stash="found", to_stash="deadended") + project = angr.Project("core.dump", main_opts={"backend": "elfcore"}, + rebase_granularity=0x1000) + binfile = angr.SimFile("licensecheck", open("licensecheck", "rb").read()) + state = project.factory.blank_state(fs={"licensecheck": binfile}) -raise 0 + elfcore = None + for o in project.loader.all_objects: + if type(o) == cle.backends.elf.elfcore.ELFCore: + elfcore = o + break + assert(elfcore is not None) + for reg in elfcore.initial_register_values(): + try: + setattr(state.regs, reg[0], reg[1]) + except Exception as e: + print("skip-reg", e) + state.memory.store(state.solver.eval(state.regs.rdi), claripy.Concat(*key)) -sim.explore(find=0x4016ed, avoid=[]) -print("found", sim) -for state in sim.found: - print(state.solver.eval_upto(key, 2, cast_to=bytes)) + for i in range(29): + if i >= 18: + state.solver.add(key[i] == check2_rev[i-18]) + elif (i + 1) % 6 == 0: + state.solver.add(key[i] == ord(b'-')) + else: + isalpha = claripy.And(key[i] >= ord('A'), key[i] <= ord('Z')) + isnum = claripy.And(key[i] >= ord('0'), key[i] <= ord('9')) + state.solver.add(claripy.Or(isalpha, isnum)) + + # plt sim hooks + project.hook(0x4010e0, angr.SIM_PROCEDURES["libc"]["strncpy"]()) + project.hook(0x4010f0, angr.SIM_PROCEDURES["libc"]["memcmp"]()) + project.hook(0x401140, angr.SIM_PROCEDURES["libc"]["strcmp"]()) + project.hook(0x401190, angr.SIM_PROCEDURES["libc"]["strlen"]()) + + # libc hooks + project.hook(0x40ce40, angr.SIM_PROCEDURES["libc"]["fopen"]()) + project.hook(0x40cf40, angr.SIM_PROCEDURES["libc"]["fread"]()) + project.hook(0x40d040, angr.SIM_PROCEDURES["libc"]["puts"]()) + + sim = project.factory.simgr(state) + checkpoint(sim, 0x402092, "start of loop", avoid=0x4020cb) + + for state in sim.active: + state.regs.rip = 0x4020d0 + + checkpoint(sim, 0x40245c, "after check1", avoid=0x4020cb) + checkpoint(sim, 0x4024aa, "after check2", avoid=0x4020cb) + checkpoint(sim, 0x4016ed, "final", avoid=0x4016ec) + print(sim.active[0].solver.eval_upto(claripy.Concat(*key), cast_to=bytes, n=2)) + +if __name__ == "__main__": + run()