#!/usr/bin/python3 import angr import claripy proj = angr.Project( 'crackme', main_opts = {'base_addr': 0x0}, load_options = {'auto_load_libs': False} ) # Flag is 10 characters flag = claripy.BVS("flag", 8 * 10) state = proj.factory.entry_state(stdin = flag) # Silence the warnings state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY) # Flags consists only on numbers ('0' -> '9') for i in range(10): state.solver.add(flag.get_byte(i) >= 48) state.solver.add(flag.get_byte(i) <= 57) sm = proj.factory.simulation_manager(state) FIND_ADDR = 0x1219 # 'Congratualtions ...' message AVOID_ADDR = 0x1227 # 'Try again ...' message sm.explore(find = FIND_ADDR, avoid = AVOID_ADDR)
def __init__(self, binary, input=None, pov_file=None, simprocedures=None, hooks=None, seed=None, preconstrain_input=True, preconstrain_flag=True, resiliency=True, chroot=None, add_options=None, remove_options=None, trim_history=True, project=None, dump_syscall=False, dump_cache=True, max_size = None, exclude_sim_procedures_list=None, argv = None): """ :param binary: path to the binary to be traced :param input: concrete input string to feed to binary :param povfile: CGC PoV describing the input to trace :param hooks: A dictionary of hooks to add :param simprocedures: dictionary of replacement simprocedures :param seed: optional seed used for randomness, will be passed to QEMU :param preconstrain_input: should the path be preconstrained to the provided input :param preconstrain_flag: should the path have the cgc flag page preconstrained :param resiliency: should we continue to step forward even if qemu and angr disagree? :param chroot: trace the program as though it were executing in a chroot :param add_options: add options to the state which used to do tracing :param remove_options: remove options from the state which is used to do tracing :param trim_history: Trim the history of a path. :param project: The original project. :param dump_syscall: True if we want to dump the syscall information :param max_size: Optionally set max size of input. Defaults to size of preconstrained input. :param exclude_sim_procedures_list: What SimProcedures to hook or not at load time. Defaults to ["malloc","free","calloc","realloc"] :param argv: Optionally specify argv params (i,e,: ['./calc', 'parm1']) defaults to binary name with no params. """ self.binary = binary self.input = input self.pov_file = pov_file self.preconstrain_input = preconstrain_input self.preconstrain_flag = preconstrain_flag self.simprocedures = {} if simprocedures is None else simprocedures self._hooks = {} if hooks is None else hooks self.input_max_size = max_size or len(input) self.exclude_sim_procedures_list = exclude_sim_procedures_list or ["malloc","free","calloc","realloc"] self.argv = argv or [binary] for h in self._hooks: l.debug("Hooking %#x -> %s", h, self._hooks[h].__name__) if isinstance(seed, (int, long)): seed = str(seed) self.seed = seed self.resiliency = resiliency self.chroot = chroot self.add_options = set() if add_options is None else add_options self.trim_history = trim_history self.constrained_addrs = [] # the final state after execution with input/pov_file self.final_state = None # the path after execution with input/pov_file self.path = None cm = LocalCacheManager(dump_cache=dump_cache) if GlobalCacheManager is None else GlobalCacheManager # cache managers need the tracer to be set for them self._cache_manager = cm self._cache_manager.set_tracer(self) # set by a cache manager self._loaded_from_cache = False if remove_options is None: self.remove_options = set() else: self.remove_options = remove_options if self.pov_file is None and self.input is None: raise ValueError("must specify input or pov_file") if self.pov_file is not None and self.input is not None: raise ValueError("cannot specify both a pov_file and an input") # validate seed if self.seed is not None: try: iseed = int(self.seed) if iseed > 4294967295 or iseed < 0: raise ValueError except ValueError: raise ValueError( "the passed seed is either not an integer or is not between 0 and UINT_MAX" ) # set up cache hook receive.cache_hook = self._cache_manager.cacher # a PoV was provided if self.pov_file is not None: self.pov_file = TracerPoV(self.pov_file) self.pov = True else: self.pov = False # internal project object, useful for obtaining certain kinds of info if project is None: self._p = angr.Project(self.binary) else: self._p = project self.base = None self.tracer_qemu = None self.tracer_qemu_path = None self._setup() l.debug("accumulating basic block trace...") l.debug("self.tracer_qemu_path: %s", self.tracer_qemu_path) # does the input cause a crash? self.crash_mode = False # if the input causes a crash, what address does it crash at? self.crash_addr = None self.crash_state = None self.crash_type = None # CGC flag data self.cgc_flag_bytes = [claripy.BVS("cgc-flag-byte-%d" % i, 8) for i in xrange(0x1000)] # content of the magic flag page as reported by QEMU # we need this to keep symbolic traces following the same path # as their dynamic counterpart self._magic_content = None # will set crash_mode correctly and also discover the QEMU base addr self.trace = self.dynamic_trace() l.info("trace consists of %d basic blocks", len(self.trace)) # Check if we need to rebase to QEMU's addr if self.qemu_base_addr != self._p.loader.main_bin.get_min_addr(): l.warn("Our base address doesn't match QEMU's. Changing ours to 0x%x",self.qemu_base_addr) self.preconstraints = [] # map of variable string names to preconstraints, for re-applying # constraints self.variable_map = {} # initialize the basic block counter to 0 self.bb_cnt = 0 # keep track of the last basic block we hit self.previous = None self.previous_addr = None # whether we should follow the qemu trace self.no_follow = False # this will be set by _prepare_paths self.unicorn_enabled = False # initilize the syscall statistics if the flag is on self._dump_syscall = dump_syscall if self._dump_syscall: self._syscall = [] self.path_group = self._prepare_paths() # this is used to track constrained addresses self._address_concretization = []
def scan_target(file_path, target_addr): """ Scans the target binary for memory vulnurabilites """ logging.getLogger('angr.anager').setLevel(logging.CRITICAL) if not os.path.isfile(file_path): subprocess.call("make clean", cwd=os.path.dirname(file_path), shell=True) subprocess.call("make", cwd=os.path.dirname(file_path), shell=True) project = angr.Project(file_path,\ load_options={'auto_load_libs':False}) print("Architecture: " + project.arch.name + " Starting at address: " + hex(project.entry)) print("Endianness: " + project.arch.memory_endness) assert project.loader.aslr == False, "ASLR is enabled, analysis is not possible" assert project.loader.main_object.execstack == True, "Stack is not executable" assert project.loader.main_object.pic == False, "Position Independant Code\ enabled" entry_block = project.factory.block(project.entry) entry_block.pp() cfg = project.analyses.CFG() def get_func_addr(func_name, plt=None): """ Returns function address of a non stripped binary """ found = [ addr for addr, func in cfg.kb.functions.items() if func_name == func.name and (plt is None or func.is_plt == plt) ] if len(found) > 0: print("Found " + func_name + "'s address at " + hex(found[0]) + "!") return found[0] else: raise Exception("No address found for function : " + func_name) argv = [project.filename] sym_arg_size = 550 sym_arg = claripy.BVS('sym_arg', 8 * sym_arg_size) argv.append(sym_arg) argv.append("Found-the-canary") state = project.factory.entry_state(args=argv) target_addr[0] = get_func_addr("exit") exit_addr = get_func_addr("exit") strcpy_addr = get_func_addr("strcpy") strcat_addr = get_func_addr("strcat") sm = project.factory.simulation_manager(state) def find_vuln_func(state): """ 'Lambda' to simulation manager that returns true if the address of strcpy is found """ if (state.ip.args[0] == strcpy_addr): print("Strcpy found during simulation!") bv_strcpy_mem = state.memory.load(state.regs.rdi, len(argv[1])) strcpy_src = state.solver.eval(bv_strcpy_mem, cast_to=bytes) return True if argv[2].encode() in strcpy_src else False elif (state.ip.args[0] == strcat_addr): print("Strcat found during simulation!") bv_strcat_mem = state.memory.load(state.regs.rdi, len(argv[1])) strcat_src = state.solver.eval(bv_strcat_mem, cast_to=bytes) return True if argv[2].encode() in strcat_src else False else: return False sm = sm.explore(find=find_vuln_func, avoid=(exit_addr, )) found = sm.found result = "" if len(found) > 0: print(f"Number of possible exploits: {len(sm.found)}") found = sm.found[0] print(found.solver.eval(sm.found[0].regs.rbp, cast_to=bytes)) result = found.solver.eval(argv[1], cast_to=bytes) else: result = "ERROR: Could not find any paths!" buffer_size = found.solver.eval(found.regs.rbp - found.regs.rdi, cast_to=int) return (result, buffer_size)
def main(argv): path_to_binary = argv[1] project = angr.Project(path_to_binary) # Make a symbolic input that has a decent size to trigger overflow # (!) symbolic_input = claripy.BVS("input", 100 * 8) # Create initial state and set stdin to the symbolic input initial_state = project.factory.entry_state( stdin=symbolic_input, add_options={ angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS }) # The save_unconstrained=True parameter specifies to Angr to not throw out # unconstrained states. Instead, it will move them to the list called # 'simulation.unconstrained'. Additionally, we will be using a few stashes # that are not included by default, such as 'found' and 'not_needed'. You will # see how these are used later. # (!) simulation = project.factory.simgr(initial_state, save_unconstrained=True, stashes={ 'active': [initial_state], 'unconstrained': [], 'found': [], 'not_needed': [] }) # Explore will not work for us, since the method specified with the 'find' # parameter will not be called on an unconstrained state. Instead, we want to # explore the binary ourselves. To get started, construct an exit condition # to know when the simulation has found a solution. We will later move # states from the unconstrained list to the simulation.found list. # Create a boolean value that indicates a state has been found. def has_found_solution(): return simulation.found # An unconstrained state occurs when there are too many possible branches # from a single instruction. This occurs, among other ways, when the # instruction pointer (on x86, eip) is completely symbolic, meaning # that user input can control the address of code the computer executes. # For example, imagine the following pseudo assembly: # # mov user_input, eax # jmp eax # # The value of what the user entered dictates the next instruction. This # is an unconstrained state. It wouldn't usually make sense for the execution # engine to continue. (Where should the program jump to if eax could be # anything?) Normally, when Angr encounters an unconstrained state, it throws # it out. In our case, we want to exploit the unconstrained state to jump to # a location of our choosing. Check if there are still unconstrained states # by examining the simulation.unconstrained list. # (!) def has_unconstrained_to_check(): return simulation.unconstrained # The list simulation.active is a list of all states that can be explored # further. # (!) def has_active(): return simulation.active while (has_active() or has_unconstrained_to_check()) and (not has_found_solution()): for unconstrained_state in simulation.unconstrained: # Look for unconstrained states and move them to the 'found' stash. # A 'stash' should be a string that corresponds to a list that stores # all the states that the state group keeps. Values include: # 'active' = states that can be stepped # 'deadended' = states that have exited the program # 'errored' = states that encountered an error with Angr # 'unconstrained' = states that are unconstrained # 'found' = solutions # anything else = whatever you want, perhaps you want a 'not_needed', # you can call it whatever you want # (!) simulation.move('unconstrained', 'found') # Advance the simulation. simulation.step() if simulation.found: solution_state = simulation.found[0] # Constrain the instruction pointer to target the print_good function and # (!) solution_state.add_constraints(solution_state.regs.eip == 0x4d435250) # Constrain the symbolic input to fall within printable range (capital # letters) for the web UI. Ensure UTF-8 encoding. # (!) for byte in symbolic_input.chop(bits=8): solution_state.add_constraints(byte >= 'A'.encode(), byte <= 'Z'.encode()) # Solve for the symbolic_input solution = solution_state.solver.eval(symbolic_input, cast_to=bytes).decode() print(solution) else: raise Exception('Could not find the solution')
def state_blank(self, addr=None, initial_prefix=None, stack_size=1024 * 1024 * 8, **kwargs): """ Initialize a blank state. All parameters are optional. :param addr: The execution start address. :param initial_prefix: :return: The initialized SimState. :rtype: simuvex.SimState """ if kwargs.get('mode', None) is None: kwargs['mode'] = self.proj._default_analysis_mode if kwargs.get('permissions_backer', None) is None: # just a dict of address ranges to permission bits permission_map = {} for obj in self.proj.loader.all_objects: for seg in obj.segments: perms = 0 # bit values based off of protection bit values from sys/mman.h if seg.is_readable: perms |= 1 # PROT_READ if seg.is_writable: perms |= 2 # PROT_WRITE if seg.is_executable: perms |= 4 # PROT_EXEC permission_map[(obj.rebase_addr + seg.min_addr, obj.rebase_addr + seg.max_addr)] = perms permissions_backer = (self.proj.loader.main_bin.execstack, permission_map) kwargs['permissions_backer'] = permissions_backer if kwargs.get('memory_backer', None) is None: kwargs['memory_backer'] = self.proj.loader.memory if kwargs.get('arch', None) is None: kwargs['arch'] = self.proj.arch if kwargs.get('os_name', None) is None: kwargs['os_name'] = self.name state = SimState(**kwargs) stack_end = state.arch.initial_sp if o.ABSTRACT_MEMORY not in state.options: state.memory.mem._preapproved_stack = IRange( stack_end - stack_size, stack_end) if o.INITIALIZE_ZERO_REGISTERS in state.options: highest_reg_offset, reg_size = max(state.arch.registers.values()) for i in range(0, highest_reg_offset + reg_size, state.arch.bytes): state.registers.store(i, state.se.BVV(0, state.arch.bits)) state.regs.sp = stack_end if initial_prefix is not None: for reg in state.arch.default_symbolic_registers: state.registers.store( reg, claripy.BVS(initial_prefix + "_" + reg, state.arch.bits, explicit_name=True)) for reg, val, is_addr, mem_region in state.arch.default_register_values: region_base = None # so pycharm does not complain if is_addr: if isinstance(mem_region, tuple): # unpack it mem_region, region_base = mem_region elif mem_region == 'global': # Backward compatibility region_base = 0 else: raise AngrSimOSError( 'You must specify the base address for memory region "%s". ' % mem_region) if o.ABSTRACT_MEMORY in state.options and is_addr: address = claripy.ValueSet(state.arch.bits, mem_region, region_base, val) state.registers.store(reg, address) else: state.registers.store(reg, val) if addr is None: addr = self.proj.entry state.regs.ip = addr state.scratch.ins_addr = addr state.scratch.bbl_addr = addr state.scratch.stmt_idx = 0 state.scratch.jumpkind = 'Ijk_Boring' state.procedure_data.hook_addr = self.continue_addr return state
import sys sys.path.append( '/home/soyccan/.local/share/virtualenvs/vwvwvw-SJfVni3y/lib/python3.6/site-packages/' ) import angr import claripy angr.l.setLevel('DEBUG') p = angr.Project('./verify', load_options={"auto_load_libs": False}) args = claripy.BVS('args', 8 * 24) initial_state = p.factory.entry_state( args=[p.filename, args], add_options={'BYPASS_UNSUPPORTED_SYSCALL'}) prefix = 'FLAG{' for i, b in enumerate(args.chop(8)): initial_state.add_constraints(b >= 0x21, b <= 0x7e) # if i < len(prefix): # initial_state.add_constraints(b == prefix[i]) # if i == 23: # initial_state.add_constraints(b == '}') pg = p.factory.simulation_manager(initial_state) pg.explore(find=[0x4022cb], avoid=[]) print(pg) #print(pg.found[0].posix.dumps(0).strip('\0\n')) #args_str = pg.found[0].state.se.any_str(args) #stdin = f.state.posix.dumps(0) #ans = pg.found[0].state.se._solver.result.model
import angr import claripy proj = angr.Project("./main") input_size = 12 argv1 = claripy.BVS("argv1", input_size * 8) initial_state = proj.factory.entry_state(args=["./main"], stdin=argv1, add_options=angr.options.unicorn) for i in range(input_size - 1): initial_state.add_constraints(argv1.get_byte(i) >= ord(' ')) initial_state.add_constraints(argv1.get_byte(i) <= ord('}')) initial_state.add_constraints(argv1.get_byte(input_size - 1) == 0x10) find_address = 0x400D32 avoid_address = (0x400D58, 0x400D08) sm = proj.factory.simulation_manager(initial_state) sm.explore(find=find_address, avoid=avoid_address) if sm.found: found = sm.found[0] solution = found.solver.eval(argv1, cast_to=bytes) print(f"Flag is: {str(solution)}") else: print("Failed! No flag for u")
exit(0) alogger = logging.getLogger() alogger.setLevel(logging.INFO) logger = logging.getLogger('solve.py') logging.basicConfig() logger.setLevel(logging.INFO) p = angr.Project('baby-re', auto_load_libs=False) CheckSolution_addr = p.loader.find_symbol("CheckSolution").rebased_addr CheckSolution = p.factory.callable(CheckSolution_addr) argv = claripy.BVS("all_args", 32 * 13) args = [argv.get_bytes(i, 4) for i in range(0, 13 * 4, 4)] logger.info("Starting Symbolic Execution") r = CheckSolution(args) s = CheckSolution.result_state logger.info("Symbolic Execution Complete") logger.info("Attempting to reverse the function") try: resolved_argv = s.solver.eval(argv, extra_constraints=[r != 0]) logger.info("Got: %s", resolved_argv) raw = hex(resolved_argv)[2:].strip('L').decode('hex') resolved_args = struct.unpack('<' + 'I' * 13, raw) logger.info("FOUND FLAG: %s", resolved_args) logger.info(''.join(map(chr, resolved_args)))
#!/usr/bin/env python3 import angr import claripy p = angr.Project('./angrme') base = 0x400000 flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(0x24)] flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')]) state = p.factory.entry_state(stdin=flag) sm = p.factory.simulation_manager(state) good = base + 0x2370 bad = base + 0x2390 sm.explore(find=good, avoid=bad) print(sm.found[0].posix.dumps(0))
def test_inspect_concretization(): # some values for the test x = claripy.BVS('x', 64) y = claripy.BVS('y', 64) # # This tests concretization-time address redirection. # def change_symbolic_target(state): if state.inspect.address_concretization_action == 'store': state.inspect.address_concretization_expr = claripy.BVV( 0x1000, state.arch.bits) s = SimState(arch='AMD64') s.inspect.b('address_concretization', BP_BEFORE, action=change_symbolic_target) s.memory.store(x, 'A') assert list(s.solver.eval_upto(x, 10)) == [0x1000] assert list(s.solver.eval_upto(s.memory.load(0x1000, 1), 10)) == [0x41] # # This tests disabling constraint adding through siminspect -- the write still happens # def dont_add_constraints(state): state.inspect.address_concretization_add_constraints = False s = SimState(arch='AMD64') s.inspect.b('address_concretization', BP_BEFORE, action=dont_add_constraints) s.memory.store(x, 'A') assert len(s.solver.eval_upto(x, 10)) == 10 # # This tests raising an exception if symbolic concretization fails (i.e., if the address # is too unconstrained). The write aborts. # class UnconstrainedAbort(Exception): def __init__(self, message, state): Exception.__init__(self, message) self.state = state def abort_unconstrained(state): print(state.inspect.address_concretization_strategy, state.inspect.address_concretization_result) if (isinstance( state.inspect.address_concretization_strategy, concretization_strategies.SimConcretizationStrategyRange) and state.inspect.address_concretization_result is None): raise UnconstrainedAbort("uh oh", state) s = SimState(arch='AMD64') s.memory.write_strategies.insert( 0, concretization_strategies.SimConcretizationStrategyRange(128)) s.memory._write_address_range = 1 s.memory._write_address_range_approx = 1 s.add_constraints(y == 10) s.inspect.b('address_concretization', BP_AFTER, action=abort_unconstrained) s.memory.store(y, 'A') assert list(s.solver.eval_upto(s.memory.load(y, 1), 10)) == [0x41] try: s.memory.store(x, 'A') print("THIS SHOULD NOT BE REACHED") assert False except UnconstrainedAbort as e: assert e.state.memory is s.memory
def main(argv): path_to_binary = argv[1] project = angr.Project(path_to_binary) # Sometimes, you want to specify where the program should start. The variable # start_address will specify where the symbolic execution engine should begin. # Note that we are using blank_state, not entry_state. # (!) start_address = 0x80488a0 # :integer (probably hexadecimal) initial_state = project.factory.blank_state(addr=start_address) # Create a symbolic bitvector (the datatype Angr uses to inject symbolic # values into the binary.) The first parameter is just a name Angr uses # to reference it. # You will have to construct multiple bitvectors. Copy the two lines below # and change the variable names. To figure out how many (and of what size) # you need, dissassemble the binary and determine the format parameter passed # to scanf. # (!) password0_size_in_bits = 32 # :integer password1_size_in_bits = 32 # :integer password2_size_in_bits = 32 # :integer password0 = claripy.BVS('password0', password0_size_in_bits) password1 = claripy.BVS('password1', password1_size_in_bits) password2 = claripy.BVS('password2', password2_size_in_bits) # Set a register to a symbolic value. This is one way to inject symbols into # the program. # initial_state.regs stores a number of convenient attributes that reference # registers by name. For example, to set eax to password0, use: # # initial_state.regs.eax = password0 # # You will have to set multiple registers to distinct bitvectors. Copy and # paste the line below and change the register. To determine which registers # to inject which symbol, dissassemble the binary and look at the instructions # immediately following the call to scanf. # (!) initial_state.regs.eax = password0 initial_state.regs.ebx = password1 initial_state.regs.edx = password2 simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) if re.findall(r'Good Job.', stdout_output): return True # Return whether 'Good Job.' has been printed yet. # (!) return False # :boolean def should_abort(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) if re.findall(r'Try again.', stdout_output): return True # Return whether 'Good Job.' has been printed yet. # (!) return False # :boolean simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0] # Solve for the symbolic values. If there are multiple solutions, we only # care about one, so we can use eval, which returns any (but only one) # solution. Pass eval the bitvector you want to solve for. # (!) solution0 = solution_state.se.eval(password0) solution1 = solution_state.se.eval(password1) solution2 = solution_state.se.eval(password2) # Aggregate and format the solutions you computed above, and then print # the full string. Pay attention to the order of the integers, and the # expected base (decimal, octal, hexadecimal, etc). solution = str(hex(solution0)).strip('L') + ' ' + str( hex(solution1)).strip('L') + ' ' + str(hex(solution2)).strip( 'L') # :string print solution else: raise Exception('Could not find the solution')
def checkFormat(binary_name, inputType="STDIN"): p = angr.Project(binary_name, load_options={"auto_load_libs": False}) # Stdio based ones p.hook_symbol("printf", printf_model.printFormat(0)) p.hook_symbol("fprintf", printf_model.printFormat(1)) p.hook_symbol("dprintf", printf_model.printFormat(1)) p.hook_symbol("sprintf", printf_model.printFormat(1)) p.hook_symbol("snprintf", printf_model.printFormat(2)) # Stdarg base ones p.hook_symbol("vprintf", printf_model.printFormat(0)) p.hook_symbol("vfprintf", printf_model.printFormat(1)) p.hook_symbol("vdprintf", printf_model.printFormat(1)) p.hook_symbol("vsprintf", printf_model.printFormat(1)) p.hook_symbol("vsnprintf", printf_model.printFormat(2)) extras = { so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS } # Setup state based on input type argv = [binary_name] input_arg = claripy.BVS("input", 300 * 8) if inputType == "STDIN": state = p.factory.full_init_state( args=argv, stdin=input_arg, add_options=extras, ) state.globals["user_input"] = input_arg elif inputType == "LIBPWNABLE": handle_connection = p.loader.main_object.get_symbol( "handle_connection") state = p.factory.entry_state( addr=handle_connection.rebased_addr, add_options=extras, ) state.globals["user_input"] = input_arg else: argv.append(input_arg) state = p.factory.full_init_state( args=argv, add_options=extras, ) state.globals["user_input"] = input_arg state.libc.buf_symbolic_bytes = 0x100 state.globals["inputType"] = inputType simgr = p.factory.simgr(state, save_unconstrained=True) run_environ = {} run_environ["type"] = None end_state = None # Lame way to do a timeout try: @timeout_decorator.timeout(1200) def exploreBinary(simgr): simgr.explore(find=lambda s: "type" in s.globals) exploreBinary(simgr) if "found" in simgr.stashes and len(simgr.found): end_state = simgr.found[0] run_environ["type"] = end_state.globals["type"] run_environ["position"] = end_state.globals["position"] run_environ["length"] = end_state.globals["length"] except (KeyboardInterrupt, timeout_decorator.TimeoutError) as e: print("[~] Format check timed out") if "input" in end_state.globals.keys(): run_environ["input"] = end_state.globals["input"] print("[+] Triggerable with input : {}".format( end_state.globals["input"])) return run_environ
import angr import claripy """ Not working, find and fix the bugs ;-) """ """ Load the exercise binary into angr """ proj = angr.Project('exercise1') """ Create our symbolic input to pass via argv """ input = claripy.BVS('input', 2 * 8) """ Start angr analysis at the entry point of the binary """ state = proj.factory.entry_state(args=[proj.filename, input]) pg = proj.factory.path_group(state) """ Try to find a path to our destination basic block """ pg = pg.explore(find=0x400b15) """ Extract our found path's state for analysis """ state = pg.found[0].state """ Look at the constraints found to execute the given path """ print(state.simplify()) """
#!/usr/bin/env python import angr import claripy p = angr.Project('./test') flag = claripy.BVS('flag', 5 * 8) s = p.factory.blank_state(addr=0x400550, stdin=flag) for c in flag.chop(8): s.solver.add(s.solver.And(c<='~', c>=' ')) sm = p.factory.simulation_manager(s) sm.use_technique(angr.exploration_techniques.Explorer(find=0x4006C1, avoid=0x4006CD)) sm.run() print(sm.one_found.posix.dumps(0))
# next, create constraints representing an unsafe condition. In this case, # let's check if the return address can point *outside* of the program. unsafe_constraints = [ state.se.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] # check if the state is satisfiable with these conditions, and return True if it is return state.se.satisfiable(extra_constraints=unsafe_constraints) # This time, the initialization is a bit different. The application takes a commandline argument, so we must: # first, create a symbolic bitvector representing the argument. # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a # concatination of 60 concrete bytes and 60 symbolic bytes. arg = claripy.BVV("A" * 60).concat(claripy.BVS("arg", 240)) # next, create a state with this argument state = project.factory.entry_state(args=['overflow3', arg]) # now, create the simulation manager with that state as the initial state simgr = project.factory.simgr(state) # initiate a "vuln" stash simgr.stashes['vuln'] = [] # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. print "Initializing initial state..." while simgr.active[0].addr != project.kb.functions['main'].addr: simgr.step() # Now that we are all set up, let's loop until a vulnerable state has been found print "Searching for the vulnerability!"
#!/usr/bin/env python import angr import claripy p = angr.Project("arg_string") arg1 = claripy.BVS("arg1", 8*2) st = p.factory.entry_state(args=["a.out", arg1]) sm = p.factory.simulation_manager(st) sm.explore(find=(0x4006b9,)) found = sm.found[0] print found.state.se.eval(arg1, cast_to=str)
def BVS(self, name, size, min=None, max=None, stride=None, uninitialized=False, explicit_name=None, key=None, eternal=False, inspect=True, events=True, **kwargs): #pylint:disable=redefined-builtin """ Creates a bit-vector symbol (i.e., a variable). Other keyword parameters are passed directly on to the constructor of claripy.ast.BV. :param name: The name of the symbol. :param size: The size (in bits) of the bit-vector. :param min: The minimum value of the symbol. Note that this **only** work when using VSA. :param max: The maximum value of the symbol. Note that this **only** work when using VSA. :param stride: The stride of the symbol. Note that this **only** work when using VSA. :param uninitialized: Whether this value should be counted as an "uninitialized" value in the course of an analysis. :param explicit_name: Set to True to prevent an identifier from appended to the name to ensure uniqueness. :param key: Set this to a tuple of increasingly specific identifiers (for example, ``('mem', 0xffbeff00)`` or ``('file', 4, 0x20)`` to cause it to be tracked, i.e. accessable through ``solver.get_variables``. :param eternal: Set to True in conjunction with setting a key to cause all states with the same ancestry to retrieve the same symbol when trying to create the value. If False, a counter will be appended to the key. :param inspect: Set to False to avoid firing SimInspect breakpoints :param events: Set to False to avoid generating a SimEvent for the occasion :return: A BV object representing this symbol. """ # should this be locked for multithreading? if key is not None and eternal and key in self.eternal_tracked_variables: r = self.eternal_tracked_variables[key] # pylint: disable=too-many-boolean-expressions if size != r.length or min != r.args[1] or max != r.args[ 2] or stride != r.args[3] or uninitialized != r.args[ 4] or bool(explicit_name) ^ (r.args[0] == name): l.warning( "Variable %s being retrieved with differnt settings than it was tracked with", name) else: r = claripy.BVS(name, size, min=min, max=max, stride=stride, uninitialized=uninitialized, explicit_name=explicit_name, **kwargs) if key is not None: self.register_variable(r, key, eternal) if inspect: self.state._inspect('symbolic_variable', BP_AFTER, symbolic_name=next(iter(r.variables)), symbolic_size=size, symbolic_expr=r) if events: self.state.history.add_event('unconstrained', name=next(iter(r.variables)), bits=size, **kwargs) if o.TRACK_SOLVER_VARIABLES in self.state.options: self.all_variables = list(self.all_variables) self.all_variables.append(r) return r
import angr import time import re import claripy p = angr.Project("./baby-re") flag_buf = [claripy.BVS("flag_%d" % x, 32) for x in range(13)] s = p.factory.blank_state(addr=0x4028E0) for x in range(13): s.memory.store(s.regs.rdi + x * 4, flag_buf[x]) sim = p.factory.simulation_manager(s) sim.active[0].options.add(angr.options.LAZY_SOLVES) start_time = time.time() sim.explore(find=0x4028E9, avoid=0x402941) print sim.found[0].solver.eval(flag_buf[0]) print str(time.time() - start_time)
print('EXP: ' + str(exp)) with open('all0', 'wb') as fp: fp.write(dump_objs(state, 0x26308, 0x26344, 0x26348, 0x26384)) sol = bk(exp) print('SOL: ' + str(sol)) rep = 0 for i in range(rep): sol = bk(sol) print('SOL: ' + str(sol)) check = fw(sol) print('CHK: ' + str(check)) for i in range(rep): check = fw(check) print('CHK: ' + str(check)) assert check == exp with open('all1', 'wb') as fp: fp.write(dump_objs(state, 0x26290, 0x262c8, 0x262cc, 0x26304) + sol) if False: input_string = claripy.BVS('input_string', 8 * 0x100) output_string = bytes(0x100) length = claripy.BVS('length', 32, 1, 0x100) pack_addr = 0x10ad4 pack_ret_addr = 0x10ba8 state = p.factory.call_state(pack_addr, input_string, output_string, length) state.regs.t9 = pack_addr # report to angr? https://www.cr0.org/paper/mips.elf.external.resolution.txt simgr = p.factory.simgr(state) simgr.explore(find=pack_ret_addr) print('OK')
def claripy_ast_from_ail_condition(self, condition) -> claripy.ast.Base: # Unpack a condition all the way to the leaves if isinstance(condition, claripy.ast.Base): # pylint:disable=isinstance-second-argument-not-valid-type return condition def _op_with_unified_size(op, conv, operand0, operand1): # ensure operand1 is of the same size as operand0 if isinstance(operand1, ailment.Expr.Const): # amazing - we do the eazy thing here return op(conv(operand0), operand1.value) if operand1.bits == operand0.bits: return op(conv(operand0), conv(operand1)) # extension is required assert operand1.bits < operand0.bits operand1 = ailment.Expr.Convert(None, operand1.bits, operand0.bits, False, operand1) return op(conv(operand0), conv(operand1)) def _dummy_bvs(condition): var = claripy.BVS('ailexpr_%s' % repr(condition), condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var _mapping = { 'LogicalAnd': lambda expr, conv: claripy.And(conv(expr.operands[0]), conv(expr.operands[1])), 'LogicalOr': lambda expr, conv: claripy.Or(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpEQ': lambda expr, conv: conv(expr.operands[0]) == conv(expr.operands[1]), 'CmpNE': lambda expr, conv: conv(expr.operands[0]) != conv(expr.operands[1]), 'CmpLE': lambda expr, conv: conv(expr.operands[0]) <= conv(expr.operands[1]), 'CmpLEs': lambda expr, conv: claripy.SLE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpLT': lambda expr, conv: conv(expr.operands[0]) < conv(expr.operands[1]), 'CmpLTs': lambda expr, conv: claripy.SLT(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGE': lambda expr, conv: conv(expr.operands[0]) >= conv(expr.operands[1]), 'CmpGEs': lambda expr, conv: claripy.SGE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGT': lambda expr, conv: conv(expr.operands[0]) > conv(expr.operands[1]), 'CmpGTs': lambda expr, conv: claripy.SGT(conv(expr.operands[0]), conv(expr.operands[1])), 'Add': lambda expr, conv: conv(expr.operands[0]) + conv(expr.operands[1]), 'Sub': lambda expr, conv: conv(expr.operands[0]) - conv(expr.operands[1]), 'Mul': lambda expr, conv: conv(expr.operands[0]) * conv(expr.operands[1]), 'Div': lambda expr, conv: conv(expr.operands[0]) / conv(expr.operands[1]), 'Not': lambda expr, conv: claripy.Not(conv(expr.operand)), 'Xor': lambda expr, conv: conv(expr.operands[0]) ^ conv(expr.operands[1]), 'And': lambda expr, conv: conv(expr.operands[0]) & conv(expr.operands[1]), 'Or': lambda expr, conv: conv(expr.operands[0]) | conv(expr.operands[1]), 'Shr': lambda expr, conv: _op_with_unified_size(claripy.LShR, conv, expr.operands[0], expr.operands[1]), 'Shl': lambda expr, conv: _op_with_unified_size(operator.lshift, conv, expr.operands[0], expr.operands[1]), 'Sar': lambda expr, conv: _op_with_unified_size(operator.rshift, conv, expr.operands[0], expr.operands[1]), # There are no corresponding claripy operations for the following operations 'DivMod': lambda expr, _: _dummy_bvs(expr), 'CmpF': lambda expr, _: _dummy_bvs(expr), 'Mull': lambda expr, _: _dummy_bvs(expr), 'Mulls': lambda expr, _: _dummy_bvs(expr), } if isinstance(condition, (ailment.Expr.Load, ailment.Expr.DirtyExpression, ailment.Expr.BasePointerOffset, ailment.Expr.ITE, ailment.Stmt.Call)): return _dummy_bvs(condition) elif isinstance(condition, ailment.Expr.Register): var = claripy.BVS('ailexpr_%s-%d' % (repr(condition), condition.idx), condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var elif isinstance(condition, ailment.Expr.Convert): # convert is special. if it generates a 1-bit variable, it should be treated as a BVS if condition.to_bits == 1: var_ = self.claripy_ast_from_ail_condition(condition.operands[0]) name = 'ailcond_Conv(%d->%d, %s)' % (condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BoolS(name, explicit_name=True) else: var_ = self.claripy_ast_from_ail_condition(condition.operands[0]) name = 'ailexpr_Conv(%d->%d, %s)' % (condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BVS(name, condition.to_bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var elif isinstance(condition, ailment.Expr.Const): var = claripy.BVV(condition.value, condition.bits) return var elif isinstance(condition, ailment.Expr.Tmp): l.warning("Left-over ailment.Tmp variable %s.", condition) if condition.bits == 1: var = claripy.BoolV('ailtmp_%d' % condition.tmp_idx) else: var = claripy.BVS('ailtmp_%d' % condition.tmp_idx, condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var lambda_expr = _mapping.get(condition.verbose_op, None) if lambda_expr is None: raise NotImplementedError("Unsupported AIL expression operation %s. Consider implementing." % condition.verbose_op) r = lambda_expr(condition, self.claripy_ast_from_ail_condition) if r is NotImplemented: r = claripy.BVS("ailexpr_%r" % condition, condition.bits, explicit_name=True) self._condition_mapping[r.args[0]] = condition else: # don't lose tags r = r.annotate(TagsAnnotation(**condition.tags)) return r
def _has_side_effect(self, f): # TODO: Assumes any write to outside the base of the stack frame is a # side effect, and everything else is not # Not sure if address concretization messes with this for b in f.blocks: # Technically I don't need this? for stmt in b.vex.statements: if isinstance(stmt, pyvex.stmt.Dirty): return True if b.vex.jumpkind == 'Ijk_Call' and isinstance(b.vex.next, pyvex.expr.Const) \ and b.vex.next.con.value in self._is_summarized: continue if b.vex.jumpkind not in ['Ijk_Boring', 'Ijk_Ret']: return True cc = f.calling_convention if f.calling_convention is not None \ else simuvex.DefaultCC[self._p.arch.name](self._p.arch) deadend_addr = self._p._simos.return_deadend num_args = f.num_arguments - 1 args = [claripy.BVS(self._fresh_name(), 64) for _ in range(num_args)] state = self._p.factory.call_state( f.addr, *args, cc=cc, base_state=None, ret_addr=deadend_addr, add_options={simuvex.o.REPLACEMENT_SOLVER}, toc=None) self._abstract_globals(state) base_sp = state.regs.sp stack_grows_to_zero = self._p.arch.stack_change < 0 stack_lim = self._p.arch.initial_sp - self._p.arch.stack_size \ if stack_grows_to_zero \ else self._p.arch.initial_sp + self._p.arch.stack_size class BPCallback: def __init__(self): self.has_effect = False def check_side_effect(self, s): if self.has_effect: return write_addr = s.inspect.mem_write_address if stack_grows_to_zero: cond = (claripy.Or(write_addr > base_sp, write_addr <= stack_lim), ) else: cond = (claripy.Or(write_addr < base_sp, write_addr >= stack_lim), ) #l.debug(cond) self.has_effect = s.se.satisfiable(extra_constraints=cond) callback = BPCallback() state.inspect.b('mem_write', when=simuvex.BP_BEFORE, action=callback.check_side_effect) caller = self._p.factory.path_group(state) caller.step( until=lambda pg: callback.has_effect or len(pg.active) == 0) return callback.has_effect
def _dummy_bvs(condition): var = claripy.BVS('ailexpr_%s' % repr(condition), condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var
def heap_address(self, offset: int) -> claripy.ast.Base: base = claripy.BVS("heap_base", self.arch.bits, explicit_name=True) if offset: return base + offset return base
import angr import claripy base_addr = 0x100000 project = angr.Project("Riga", main_opts={'base_addr': base_addr}) args = claripy.BVS('flag', 30 * 8) # target = 0x1194 # IDA target = 0x00101194 # Ghidra state = project.factory.entry_state(args=["./Riga", args]) simum = project.factory.simulation_manager(state) print(simum.explore(find=target)) print("SIMUM : " + str(simum.found)) result = simum.found[0] print(result.solver.eval(args, cast_to=bytes))
import angr import time import claripy p = angr.Project('./angrybird') s = p.factory.blank_state(addr=0x4007DA) @p.hook(0x400590, length=6) def n132_puts(ptr): print "A" s.rax = 1 flag = [claripy.BVS("flag_%d" % i, 8) for i in range(21)] for x in range(21): s.memory.store(s.regs.rbp - 0x50 + x, flag[x]) s.regs.rbp = s.regs.rsp + 0x80 #s.globals['idx']=0 s.mem[s.regs.rbp - 0x74].dword = 21 s.mem[s.regs.rbp - 0x70].qword = 0x605018 s.mem[s.regs.rbp - 0x68].qword = 0x605020 s.mem[s.regs.rbp - 0x60].qword = 0x605028 s.mem[s.regs.rbp - 0x58].qword = 0x605038 sim = p.factory.simgr(s) sim.active[0].options.add(angr.options.LAZY_SOLVES) #sim.explore(find=0x000000000404FB7) av = [ 2 + 0x00000000004007ED, 43 + 0x00000000004007ED, 75 + 0x00000000004007ED, 116 + 0x00000000004007ED, 163 + 0x00000000004007ED, 204 +
def main(file): with open(file, encoding='utf-8') as json_file: global EXPLORE_OPT EXPLORE_OPT = json.load(json_file) # Options parser # JSON can't handle with hex values, so we need to do it manually if "blank_state" in EXPLORE_OPT: blank_state = int(EXPLORE_OPT["blank_state"], 16) find = int(EXPLORE_OPT["find"], 16) if "avoid" in EXPLORE_OPT: avoid = [int(x, 16) for x in EXPLORE_OPT["avoid"].split(',')] # User can input hex or decimal value (argv length / symbolic memory length) argv = [EXPLORE_OPT["binary_file"]] if "Arguments" in EXPLORE_OPT: index = 1 for arg, length in EXPLORE_OPT["Arguments"].items(): argv.append( claripy.BVS("argv" + str(index), int(str(length), 0) * 8)) index += 1 if EXPLORE_OPT["auto_load_libs"] is True: p = angr.Project(EXPLORE_OPT["binary_file"], load_options={"auto_load_libs": True}) else: p = angr.Project(EXPLORE_OPT["binary_file"], load_options={"auto_load_libs": False}) global REGISTERS REGISTERS = p.arch.default_symbolic_registers if len(argv) > 1: state = p.factory.entry_state(args=argv) elif "blank_state" in locals(): state = p.factory.blank_state(addr=blank_state) else: state = p.factory.entry_state() # Store symbolic vectors in memory if "Memory" in EXPLORE_OPT: Memory = {} for addr, length in EXPLORE_OPT["Memory"].items(): symbmem_addr = int(addr, 16) symbmem_len = int(length, 0) Memory.update({symbmem_addr: symbmem_len}) symb_vector = claripy.BVS('input', symbmem_len * 8) state.memory.store(symbmem_addr, symb_vector) # Write to memory if "Store" in EXPLORE_OPT: for addr, value in EXPLORE_OPT["Store"].items(): store_addr = int(addr, 16) store_value = int(value, 16) store_length = len(value) - 2 state.memory.store(store_addr, state.solver.BVV(store_value, 4 * store_length)) # Handle Symbolic Registers if "Registers" in EXPLORE_OPT: for register, data in EXPLORE_OPT["Registers"].items(): if "sv" in data: symbvector_length = int(data[2:], 0) symbvector = claripy.BVS('symvector', symbvector_length * 8) SYMVECTORS.append(symbvector) data = symbvector else: data = int(str(data), 0) for REG in REGISTERS: if REG == register: setattr(state.regs, register, data) break # Handle Hooks if "Hooks" in EXPLORE_OPT: for object in EXPLORE_OPT["Hooks"]: for frame in object.items(): hook_address = frame[0] for option, data in frame[1].items(): data = int(str(data), 0) if option == "Length": hook_length = data break p.hook(int(hook_address, 16), hook_function, length=hook_length) simgr = p.factory.simulation_manager(state) if "avoid" in locals(): simgr.use_technique( angr.exploration_techniques.Explorer(find=find, avoid=avoid)) else: simgr.use_technique(angr.exploration_techniques.Explorer(find=find)) simgr.run() if simgr.found: found_path = simgr.found[0] win_sequence = "" for win_block in found_path.history.bbl_addrs.hardcopy: win_block = p.factory.block(win_block) addresses = win_block.instruction_addrs for address in addresses: win_sequence += hex(address) + "," win_sequence = win_sequence[:-1] print("Trace:" + win_sequence) if len(argv) > 1: for i in range(1, len(argv)): print("argv[{id}] = {solution}".format( id=i, solution=found_path.solver.eval(argv[i], cast_to=bytes))) if "Memory" in locals() and len(Memory) != 0: for address, length in Memory.items(): print("{addr} = {value}".format(addr=hex(address), value=found_path.solver.eval( found_path.memory.load( address, length), cast_to=bytes))) if len(SYMVECTORS) > 0: for SV in SYMVECTORS: print(found_path.solver.eval(SV, cast_to=bytes)) found_stdins = found_path.posix.stdin.content if len(found_stdins) > 0: std_id = 1 for stdin in found_stdins: print("stdin[{id}] = {solution}".format( id=std_id, solution=found_path.solver.eval(stdin[0], cast_to=bytes))) std_id += 1 else: print("") return
next_simgr = simgr.explore(find=ECHOCOMMAND) print("Reached the command dispatcher echocommand") next_state = next_simgr.found[0] # Ok, right now we have the input to reach the dispatcher, but due to previous # approximation ( strstr ) we have a very simple input ( ! \r\n ). # Instead of waiting for the correct input to show up we can do 2 things: # 1- Either synchronize the concrete process up to this point and reset the symbuffer. # 2- Just reset the symbolic buffer where the command dispatcher expects the command to be. # # We are going for the second one here. print("Restoring symbolic buffer") command_buffer_address2 = next_state.regs.rax command_buffer_symbolic2 = claripy.BVS('CommandBuffer2', 8 * symbolic_buffer_size) # This is another buffer that the malware uses to decide what to do, let's make it symbolic. command_tokens_symbolic_size = claripy.BVS('CommandTokens', 8 * 8) print("Setting symbolic buffer at {} on state {}".format( str(command_buffer_address2), str(next_state))) print("Setting symbolic register edi on state {}".format(str(next_state))) next_state.memory.store(command_buffer_address2, command_buffer_symbolic2) next_state.regs.edi = command_tokens_symbolic_size # Now we want to reach a specific functionality in the program, for example the ovhflood. # To do that we want to see which BB we have to execute to reach that call, let's compute this # information statically using the CFG! print("Creating malware CFG") whole_cfg = ANGR_PROJECT.analyses.CFGFast(regions=[ (ECHOCOMMAND, CUSTOM_END_CFG_REGION)
def top(bits) -> claripy.ast.BV: if bits in VariableRecoveryStateBase._tops: return VariableRecoveryStateBase._tops[bits] r = claripy.BVS("top", bits, explicit_name=True) VariableRecoveryStateBase._tops[bits] = r return r
def main(): ''' Just a helper function to grab function names from resolved symbols. This will not be so easy if the binary is stripped. You will have to open the binary in a disassembler and find the addresses of the functions you are trying to find/avoid in your paths rather than using this helper function. ''' def getFuncAddress(funcName): found = [ addr for addr, func in cfg.function_manager.functions.iteritems() if funcName == func.name ] if len(found) > 0: print "Found " + funcName + "'s address at " + hex(found[0]) + "!" return found[0] else: raise Exception("No address found for function : " + funcName) def get_byte(s, i): pos = s.size() / 8 - 1 - i return s[pos * 8 + 7:pos * 8] ''' load the binary, don't load extra libs to save time/memory from state explosion ''' project = angr.Project("strcpy_test", load_options={'auto_load_libs': False}) ''' Set up CFG so we can grab function addresses from symbols. I set the fail_fast option to True to minimize how long this process takes. ''' cfg = project.analyses.CFG(fail_fast=True) ''' Get addresses of our functions to find or avoid ''' addrStrcpy = getFuncAddress('strcpy') addrBadFunc = getFuncAddress('func3') ''' Create the list of command-line arguments and add the program name ''' argv = [project.filename] #argv[0] ''' Add symbolic variable for the password buffer which we are solving for: ''' sym_arg_size = 40 #max number of bytes we'll try to solve for ''' We use 8 * sym_arg_size because the size argument is in BITS, not bytes ''' sym_arg = claripy.BVS('sym_arg', 8 * sym_arg_size) argv.append(sym_arg) #argv[1] ''' Add the buffer we will copy in if the password is correct When we find a path to strcpy, we will check to make sure that this is the value that is being copied! ''' argv.append("HAHAHAHA") # argv[2] ''' Initializes an entry state starting at the address of the program entry point We simply pass it the same kind of argument vector that would be passed to the program, in execv() for example. ''' state = project.factory.entry_state(args=argv) ''' Create a new path group from the entry state ''' path_group = project.factory.path_group(state) ''' Since we want to find a path to strcpy ONLY where we have control of the source buffer, we have to have a custom check function which takes a Path as an argument. You might be wondering what we should do to instruct angr to find our target address since we're replacing the 'find=' argument with this 'check' function. Just check p.state.ip.args[0] (the current instruction pointer) to make sure we're at our intended path destination before checking to make sure the other conditions are satisfied. ''' def check(p): if (p.state.ip.args[0] == addrStrcpy): # Ensure that we're at strcpy ''' By looking at the disassembly, I've found that the pointer to the source buffer given to strcpy() is kept in RSI. Here, we dereference the pointer in RSI and grab 8 bytes (len("HAHAHAHA")) from that buffer. ''' BV_strCpySrc = p.state.memory.load(p.state.regs.rsi, len(argv[2])) ''' Now that we have the contents of the source buffer in the form of a bit vector, we grab its string representation using the current state's solver engine's function "any_str". ''' strCpySrc = p.state.se.any_str(BV_strCpySrc) ''' Now we simply return True (found path) if we've found a path to strcpy where we control the source buffer, or False (keep looking for paths) if we don't control the source buffer ''' return True if argv[2] in strCpySrc else False else: ''' If we aren't in the strcpy function, we need to tell angr to keep looking for new paths. ''' return False ''' Call the function at the entry_state and find a path that satisfies the check function. If you specify a tuple/list/set for find or avoid, it translates to an address to find/avoid. If you just give a function it will pass a Path to the function and check to see if the function returns True or False and proceed accordingly. Here, we tell the explore function to find a path that satisfies our check method and avoids any paths that end up in addrBadFunc ('func3') ''' path_group = path_group.explore(find=check, avoid=(addrBadFunc, )) found = path_group.found ''' Retrieve a concrete value for the password value from the found path. If you put this password in the program's first argument, you should be able to strcpy() any string you want into the destination buffer and cause a segmentation fault if it is too large :) ''' if (len(found) > 0): # Make sure we found a path before giving the solution found = path_group.found[0] result = found.state.se.any_str(argv[1]) else: # Aww somehow we didn't find a path. Time to work on that check() function! result = "Couldn't find any paths which satisfied our conditions." return result
import angr import claripy import glob def number(state, n): return state.se.And(n <= '9', n >= '0') FIND = 0x40203c AVOID = 0x40205a # bins = glob.glob("binaries/*.exe") bins = ["./binaries/00000.exe"] for binary in bins: p = angr.Project(binary) state = p.factory.blank_state() _input = claripy.BVS("input", 32, explicit_name=True) state.add_constraints(_input < 2147483648) state.add_constraints(_input >= 0) path = p.factory.path(state) pg = p.factory.path_group(state) pg.explore(find=FIND, avoid=AVOID) for pp in pg.deadended: print pp.state.posix.dumps(0) # print pg.deadended[0] # pg.explore(find=FIND, avoid=AVOID) found = pg.found[0].state flag = found.se.BVS("input", 32, explicit_name="True")