def calc_one_byte(p, known_passwords, hook_func, start_addr, load_addr1, load_addr2, cmp_flag_reg, cmp_addr): byte_pos = len(known_passwords) p.hook(load_addr1, UserHook(user_func=hook_func, length=14)) p.hook(load_addr2, UserHook(user_func=hook_func, length=14)) state = p.factory.blank_state(addr=start_addr) state, password = prepare_state(state, known_passwords) sm = p.factory.simgr(state, immutable=False) sm.step(4) sm.step(size=cmp_addr - load_addr2) s0 = sm.active[0].copy() s0.add_constraints(getattr(s0.regs, cmp_flag_reg) == 0x1) candidates = s0.se.any_n_int(password[byte_pos], 256) # assert len(candidates) == 1 return chr(candidates[0])
def main(): p = angr.Project('baby-re') win = 0x4028e9 # good fail = 0x402941 # fail main = 0x4025e7 # Address of main PASS_LEN = 13 flag_addr = 0x7fffffffffeff98 # First rsi from scanf find = (win, ) avoid = (fail, ) def patch_scanf(state): print(state.regs.rsi) state.mem[state.regs.rsi:].char = state.se.BVS('c', 8) # IDA xrefs scanf_offsets = (0x4d, 0x85, 0xbd, 0xf5, 0x12d, 0x165, 0x19d, 0x1d5, 0x20d, 0x245, 0x27d, 0x2b5, 0x2ed) init = p.factory.blank_state(addr=main) # Patch scanfs (don't know how angr handles it) for offst in scanf_offsets: p.hook(main + offst, UserHook(user_func=patch_scanf, length=5)) sm = p.factory.simgr(init) # Now stuff becomes interesting ex = sm.explore(find=find, avoid=avoid) print(ex) s = ex.found[0] flag = s.se.eval(s.memory.load(flag_addr, 50), cast_to=str) # The flag is 'Math is hard!' print("The flag is '{0}'".format(flag)) return flag
def get_possible_flags(): # load the binary print '[*] loading the binary' p = angr.Project("whitehat_crypto400") # this is a statically-linked binary, and it's easer for angr if we use Python # summaries for the libc functions p.hook(0x4018B0, angr.SIM_PROCEDURES['glibc']['__libc_start_main']()) p.hook(0x422690, angr.SIM_PROCEDURES['libc']['memcpy']()) p.hook(0x408F10, angr.SIM_PROCEDURES['libc']['puts']()) # this is some anti-debugging initialization. It doesn't do much against angr, # but wastes time p.hook(0x401438, angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained']()) # from playing with the binary, we can easily see that it requires strings of # length 8, so we'll hook the strlen calls and make sure we pass an 8-byte # string def hook_length(state): state.regs.rax = 8 p.hook(0x40168e, UserHook(user_func=hook_length, length=5)) p.hook(0x4016BE, UserHook(user_func=hook_length, length=5)) # here, we create the initial state to start execution. argv[1] is our 8-byte # string, and we add an angr option to gracefully handle unsupported syscalls arg1 = claripy.BVS('arg1', 8 * 8) initial_state = p.factory.entry_state( args=["crypto400", arg1], add_options={"BYPASS_UNSUPPORTED_SYSCALL"}) # and let's add a constraint that none of the string's bytes can be null for b in arg1.chop(8): initial_state.add_constraints(b != 0) # PathGroups are a basic building block of the symbolic execution engine. They # track a group of paths as the binary is executed, and allows for easier # management, pruning, and so forth of those paths sm = p.factory.simgr(initial_state, immutable=False) # here, we get to stage 2 using the PathGroup's find() functionality. This # executes until at least one path reaches the specified address, and can # discard paths that hit certain other addresses. print '[*] executing' sm.explore(find=0x4016A3).unstash(from_stash='found', to_stash='active') sm.explore(find=0x4016B7, avoid=[0x4017D6, 0x401699, 0x40167D]).unstash(from_stash='found', to_stash='active') sm.explore(find=0x4017CF, avoid=[0x4017D6, 0x401699, 0x40167D]).unstash(from_stash='found', to_stash='active') sm.explore(find=0x401825, avoid=[0x401811]) # now, we're at stage 2. stage 2 is too complex for a SAT solver to solve, but # stage1 has narrowed down the keyspace enough to brute-force the rest, so # let's get the possible values for the passphrase and brute-force the rest. s = sm.found[0] # to reduce the keyspace further, let's assume the bytes are printable for i in range(8): b = s.memory.load(0x6C4B20 + i, 1) s.add_constraints(b >= 0x21, b <= 0x7e) # now get the possible values. One caveat is that getting all possible values # for all 8 bytes pushes a lot of complexity to the SAT solver, and it chokes. # To avoid this, we're going to get the solutions to 2 bytes at a time, and # brute force the combinations. possible_values = [ s.se.eval_upto(s.memory.load(0x6C4B20 + i, 2), 65536, cast_to=str) for i in range(0, 8, 2) ] possibilities = tuple(itertools.product(*possible_values)) return possibilities