def _prep_challenge_response(self, format_infos=None): # need to re-trace the binary with stdin symbolic remove_options = {so.SUPPORT_FLOATING_POINT} p = angr.Project(self.binary) p._simos.syscall_library.update(angr.SIM_LIBRARIES['cgcabi_tracer']) s = p.factory.tracer_state(input_content=self.payload, magic_content=self._runner.magic, remove_options=remove_options) self._simgr = p.factory.simgr(s, save_unsat=True, hierarchy=False, save_unconstrained=self._runner.crash_mode) self._t = angr.exploration_techniques.Tracer(trace=self._runner.trace) c = angr.exploration_techniques.CrashMonitor(trace=self._runner.trace, crash_mode=self._runner.crash_mode, crash_addr=self._runner.crash_addr) self._simgr.use_technique(c) self._simgr.use_technique(self._t) self._simgr.use_technique(angr.exploration_techniques.Oppologist()) s = self._simgr.one_active ZenPlugin.prep_tracer(s) ChallRespInfo.prep_tracer(s, format_infos) assert self.causes_leak(), "challenge did not cause leak when trying to recover challenge-response" return self.attempt_pov(enabled_chall_resp=True)
def point_to_flag(self): ''' Create a testcase which points an arbitrary-read crash at the flag page. ''' if not self.one_of( [Vulnerability.ARBITRARY_READ, Vulnerability.ARBITRARY_TRANSMIT]): raise CannotExploit( "only arbitrary-reads can be exploited this way") violating_actions = [] if self.one_of([Vulnerability.ARBITRARY_READ]): if self.violating_action: violating_actions.append( (self.state, self.violating_action.addr)) zp = self.state.get_plugin('zen_plugin') if zp is not None: for st, addr in zp.controlled_transmits: st.preconstrainer.remove_preconstraints() violating_actions.append((st, addr)) for st, va in violating_actions: try: cp = self._get_state_pointing_to_flag(st, va) self._reconstrain_flag_data(cp) yield ChallRespInfo.atoi_dumps(cp) except CannotExploit: l.warning("crash couldn't be pointed at flag skipping") pass # look for contiguous flag bytes of length 4 or longer and try to leak only one max_tries = 20 num_tries = 0 for start, length in self.flag_mem.items(): if length < 4: continue data = self.state.memory.load(start, length) four_flag_offset = self._four_flag_bytes_offset(data) if four_flag_offset is not None: leak_addr = start + four_flag_offset l.debug("found flag at addr %#x", leak_addr) for st, va in violating_actions: if num_tries > max_tries: l.warning("passed the maximum number of tries") break num_tries += 1 try: cp = self._get_state_pointing_to_addr( st, va, leak_addr) self._reconstrain_flag_data(cp) l.debug("pointed successfully") yield ChallRespInfo.atoi_dumps(cp) # okay we got one we are done return except CannotExploit: l.warning("crash couldn't be pointed at flag skipping") pass
def _prep_challenge_response(self, format_infos=None): """ Set up the internal tracer for challenge-response analysis :param format_infos: a list of atoi FormatInfo objects that should be used when analyzing the crash """ # need to re-trace the binary with stdin symbolic remove_options = {angr.options.SUPPORT_FLOATING_POINT} add_options = angr.options.unicorn | { angr.options.CGC_NO_SYMBOLIC_RECEIVE_LENGTH, angr.options.UNICORN_THRESHOLD_CONCRETIZATION, angr.options.REPLACEMENT_SOLVER } state = self.project.factory.full_init_state( add_options=add_options, remove_options=remove_options) # Make our own special posix state.register_plugin( 'posix', SimSystemPosix( stdin=SimFileStream( 'stdin', ident='aeg_input_stdin' ), # we do tests against the name of the variable... stdout=SimFileStream('stdout'), stderr=SimFileStream('stderr'))) # Create the preconstrainer plugin state.register_plugin('preconstrainer', SimStatePreconstrainer()) state.preconstrainer.preconstrain_flag_page(self._runner.magic) state.preconstrainer.preconstrain_file(self.payload, state.posix.stdin) # Set up zen ZenPlugin.prep_tracer(state) ChallRespInfo.prep_tracer(state, format_infos) self._simgr = self.project.factory.simulation_manager( state, save_unsat=True, hierarchy=False, save_unconstrained=self._runner.crash_mode) self._t = angr.exploration_techniques.Tracer(trace=self._runner.trace, resiliency=False) self._simgr.use_technique(self._t) self._simgr.use_technique(angr.exploration_techniques.Oppologist()) assert self.causes_leak( ), "challenge did not cause leak when trying to recover challenge-response" return self.attempt_pov(enabled_chall_resp=True)
def _generate_formula(self, extra_vars_to_solve=None): """ This function is used to generate the equations which are inserted inside C exploits """ if extra_vars_to_solve is None: extra_vars_to_solve = [] st = self.crash.state.copy() self._prepare_chall_resp(st) # get variables representing stdin stdin = st.posix.get_file(0) length = st.se.eval(stdin.pos) ft = st.se._solver._merged_solver_for(lst=[self._mem] + extra_vars_to_solve) # filter out constants the_vars = set() split = ft.split() for solver in split: if len(solver.variables) > 1: the_vars.update(solver.variables) ft = st.se._solver._merged_solver_for(names=the_vars) self._payload_len = length self._raw_payload = ChallRespInfo.atoi_dumps(st) self._create_solvers(ft, extra_vars_to_solve)
def _explore_arbitrary_read(self, path_file=None): # crash type was an arbitrary-read, let's point the violating address at a # symbolic memory region largest_regions = sorted(self.symbolic_mem.items(), key=operator.itemgetter(1), reverse=True) min_read = self.state.se.min(self.violating_action.addr) max_read = self.state.se.max(self.violating_action.addr) largest_regions = map(operator.itemgetter(0), largest_regions) # filter addresses which fit between the min and max possible address largest_regions = filter(lambda x: (min_read <= x) and (x <= max_read), largest_regions) # populate the rest of the list with addresses from the binary min_addr = self.project.loader.main_object.min_addr max_addr = self.project.loader.main_object.max_addr pages = range(min_addr, max_addr, 0x1000) pages = filter(lambda x: (min_read <= x) and (x <= max_read), pages) read_addr = None constraint = None for addr in largest_regions + pages: read_addr = addr constraint = self.violating_action.addr == addr if self.state.se.satisfiable(extra_constraints=(constraint, )): break constraint = None if constraint is None: raise CannotExploit( "unable to find suitable read address, cannot explore") self.state.add_constraints(constraint) l.debug("constraining input to read from address %#x", read_addr) l.info( "starting a new crash exploration phase based off the crash at address 0x%x", self.violating_action.ins_addr) new_input = ChallRespInfo.atoi_dumps(self.state) if path_file is not None: l.info("dumping new crash evading input into file '%s'", path_file) with open(path_file, 'w') as f: f.write(new_input) # create a new crash object starting here use_rop = False if self.rop is None else True self.__init__(self.binary, new_input, explore_steps=self.explore_steps + 1, constrained_addrs=self.constrained_addrs + [self.violating_action], use_rop=use_rop, angrop_object=self.rop)
def _explore_arbitrary_write(self, path_file=None): # crash type was an arbitrary-write, this routine doesn't care about taking advantage # of the write it just wants to try to find a more valuable crash by pointing the write # at some writable memory # find a writable data segment elf_objects = self.project.loader.all_elf_objects assert len(elf_objects ) > 0, "target binary is not ELF or CGC, unsupported by rex" min_write = self.state.solver.min(self.violating_action.addr) max_write = self.state.solver.max(self.violating_action.addr) segs = [] for eobj in elf_objects: segs.extend(filter(lambda s: s.is_writable, eobj.segments)) segs = [ s for s in segs if s.min_addr <= min_write <= s.max_addr or min_write <= s.min_addr <= max_write ] write_addr = None constraint = None for seg in segs: for page in range(seg.min_addr, seg.max_addr, 0x1000): write_addr = page constraint = self.violating_action.addr == page if self.state.solver.satisfiable( extra_constraints=(constraint, )): break constraint = None if constraint is None: raise CannotExploit("Cannot point write at any writeable segments") self.state.add_constraints(constraint) l.debug("constraining input to write to address %#x", write_addr) l.info( "starting a new crash exploration phase based off the crash at address %#x", self.violating_action.ins_addr) new_input = ChallRespInfo.atoi_dumps(self.state) if path_file is not None: l.info("dumping new crash evading input into file '%s'", path_file) with open(path_file, 'w') as f: f.write(new_input) use_rop = False if self.rop is None else True self.__init__(self.binary, new_input, explore_steps=self.explore_steps + 1, constrained_addrs=self.constrained_addrs + [self.violating_action], use_rop=use_rop, angrop_object=self.rop)
def _generate_formula(self, extra_vars_to_solve=None): """ This function is used to generate the equations which are inserted inside C exploits """ if extra_vars_to_solve is None: extra_vars_to_solve = [] st = self.crash.state.copy() self._prepare_chall_resp(st) # get variables representing stdin length = st.solver.eval(st.posix.fd[0].read_pos) ft = st.solver._solver._merged_solver_for(lst=[self._mem] + extra_vars_to_solve) # filter out constants the_vars = set() split = ft.split() for solver in split: if len(solver.variables) > 1: the_vars.update(solver.variables) ft = st.solver._solver._merged_solver_for(names=the_vars) # try to use original crash_input instead of random ones if some input is still symbolic at this stage size = st.solver.eval(st.posix.stdin.size) # there is a weird crash type called FakeCrash which is totally dummy # have no idea why we still keep it if hasattr( self.crash, 'use_crash_input') and self.crash.use_crash_input and hasattr( self.crash, 'crash'): for i in range(size): byte_value = st.posix.stdin.load(i, 1) if not st.solver.symbolic(byte_value): continue constraint = (byte_value == self.crash.crash[i]) if not st.solver.satisfiable(extra_constraints=[constraint]): continue st.solver.add(constraint) self._payload_len = length self._raw_payload = ChallRespInfo.atoi_dumps(st) self._create_solvers(ft, extra_vars_to_solve)
def _prepare_chall_resp(self, state): # now we need to find the challenge response stuff # first break constraints at And's constraints = [] for c in state.se.constraints: if c.op == "And": constraints.extend(c.args) else: constraints.append(c) # filter for possible flag constraints filtered_constraints = [] for c in constraints: if any( v.startswith("cgc-flag") or v.startswith("random") for v in c.variables): filtered_constraints.append(c) self.filter_uncontrolled_constraints(state) # now separate into constraints we can probably control, and those we can't controllable_constraints = [] uncontrollable_constraints = [] if not state.has_plugin("chall_resp_info"): # register a blank one state.register_plugin("chall_resp_info", ChallRespInfo()) chall_resp_info = state.get_plugin("chall_resp_info") for c in filtered_constraints: if any(v.startswith("file_/dev/stdin") for v in c.variables) or \ any(v in chall_resp_info.vars_we_added for v in c.variables): controllable_constraints.append(c) elif any(v.startswith("output_var") for v in c.variables): # an output like a leak pass else: # uncontrollable constraints will show up as zen constraints etc uncontrollable_constraints.append(c) if len(controllable_constraints) > 0: l.warning("challenge response detected!") file_1 = state.posix.get_file(1) stdout = file_1.content.load(0, file_1.pos) stdout_len = state.se.eval(file_1.pos) stdout_bvs = [ claripy.BVS("file_stdout_0_%#x" % i, 8, explicit_name=True) for i in range(stdout_len) ] stdout_bv = claripy.Concat(*stdout_bvs) state.add_constraints(stdout == stdout_bv) # we call simplify to separate the contraints/dependencies state.se.simplify() merged_solver = state.se._solver._merged_solver_for( lst=[self._mem] + controllable_constraints) # todo here we can verify that there are actually stdout bytes here, otherwise we have little hope # add the important stdout vars to mem needed_vars = [] for bv in stdout_bvs: if len(bv.variables & merged_solver.variables) != 0: needed_vars.append(bv) # add the str_to_int vars and int_to_str vars for _, v in chall_resp_info.str_to_int_pairs: needed_vars.append(v) for v, _ in chall_resp_info.int_to_str_pairs: needed_vars.append(v) self._mem = claripy.Concat(self._mem, *needed_vars)
def _create_solvers(self, ft, extra_vars_to_solve=None): split_solvers = ft.split() if extra_vars_to_solve is None: extra_vars_to_solve = [] # make sure there is a chall_resp_info plugin if not self.crash.state.has_plugin("chall_resp_info"): # register a blank one self.crash.state.register_plugin("chall_resp_info", ChallRespInfo()) # figure out start indices for all solvers stdin_solver = [] for solver in split_solvers: stdin_indices = self._get_stdin_start_indices(solver) for idx, min_stdout_needed in stdin_indices: stdin_solver.append((idx, min_stdout_needed, solver)) # get an extra solver for the extra_vars_to_solve merged_extra_solver = None if len(extra_vars_to_solve) > 0: extra_vars_to_solve = set(extra_vars_to_solve) important_solvers = [ x for x in split_solvers if len(x.variables & extra_vars_to_solve) > 0 ] if len(important_solvers) > 0: merged_extra_solver = important_solvers[0] for s in important_solvers[1:]: merged_extra_solver = merged_extra_solver.combine(s) # sort them stdin_solver = sorted(stdin_solver, key=lambda x: x[0]) # get int nums self._stdin_int_infos = {} self._stdout_int_infos = {} for solver in split_solvers: for info in self._get_stdin_int_infos(solver): self._stdin_int_infos[info.var_name] = info for info in self._get_stdout_int_infos(solver): self._stdout_int_infos[info.var_name] = info # sort them self._sorted_stdin_int_infos = sorted(self._stdin_int_infos.values(), key=lambda x: x.start) self._sorted_stdout_int_infos = sorted(self._stdout_int_infos.values(), key=lambda x: x.start) # FIXME FLAG THIS WILL NEED TO BE CHANGED if extra_vars_to_solve is not None and len(extra_vars_to_solve) > 0: stdin_solver.append((self.crash.state.se.eval( self.crash.state.posix.get_file(0).pos), self.crash.state.se.eval( self.crash.state.posix.get_file(1).pos), merged_extra_solver)) l.debug("There are %d solvers after splitting", len(stdin_solver)) self._solver_code = "" for i, (min_stdin, min_stdout_needed, solver) in enumerate(stdin_solver): formula = CGCFormula(solver) self._formulas.append(formula) btor_name = "btor_%d" % i formula.name = btor_name solver_code = "" # possibly send more solver_code += self._create_send_stdin(min_stdin, min_stdout_needed) # we need to read until we get the bytes solver_code += self._create_read_bytes(min_stdout_needed) # now we have all the bytes we needed # parse the formula solver_code += self._create_boolector_parse(btor_name, formula) # constrain any "input" variables (regval, addr) solver_code += self._create_constrain_vals(solver, btor_name, formula) # add constraints to any stdin we've already sent solver_code += self._create_constrain_stdin( solver, btor_name, formula) # add constraints to stdout for the bytes we got solver_code += self._create_constrain_stdout( solver, btor_name, formula) # add constraints to any integers we have already used solver_code += self._create_constrain_integers( solver, btor_name, formula) # now create the byte setters solver_code += self._create_byte_setters(solver, btor_name, formula) self._solver_code += solver_code + "\n" # we are done l.debug("done creating solvers")
def __init__(self, binary, crash=None, pov_file=None, aslr=None, constrained_addrs=None, crash_state=None, prev_path=None, hooks=None, format_infos=None, rop_cache_tuple=None, use_rop=True, explore_steps=0, angrop_object=None): ''' :param binary: path to the binary which crashed :param crash: string of input which crashed the binary :param pov_file: CGC PoV describing a crash :param aslr: analyze the crash with aslr on or off :param constrained_addrs: list of addrs which have been constrained during exploration :param crash_state: an already traced crash state :param prev_path: path leading up to the crashing block :param hooks: dictionary of simprocedure hooks, addresses to simprocedures :param format_infos: a list of atoi FormatInfo objects that should be used when analyzing the crash :param rop_cache_tuple: a angrop tuple to load from :param use_rop: whether or not to use rop :param explore_steps: number of steps which have already been explored, should only set by exploration methods :param angrop_object: an angrop object, should only be set by exploration methods ''' self.binary = binary self.crash = crash self.constrained_addrs = [] if constrained_addrs is None else constrained_addrs self.hooks = {} if hooks is None else hooks self.explore_steps = explore_steps if self.explore_steps > 10: raise CannotExploit( "Too many steps taken during crash exploration") self.project = angr.Project(binary) for addr, proc in self.hooks.iteritems(): self.project.hook(addr, proc) l.debug("Hooking %#x -> %s...", addr, proc.display_name) if self.project.loader.main_object.os == 'cgc': self.project._simos.syscall_library.update( angr.SIM_LIBRARIES['cgcabi_tracer']) # we search for ROP gadgets now to avoid the memory exhaustion bug in pypy # hash binary contents for rop cache name binhash = hashlib.md5(open(self.binary).read()).hexdigest() rop_cache_path = os.path.join( "/tmp", "%s-%s-rop" % (os.path.basename(self.binary), binhash)) if use_rop: if angrop_object is not None: self.rop = angrop_object else: self.rop = self.project.analyses.ROP() if rop_cache_tuple is not None: l.info("loading rop gadgets from cache tuple") self.rop._load_cache_tuple(rop_cache_tuple) elif os.path.exists(rop_cache_path): l.info("loading rop gadgets from cache '%s'", rop_cache_path) self.rop.load_gadgets(rop_cache_path) else: self.rop.find_gadgets(show_progress=False) self.rop.save_gadgets(rop_cache_path) else: self.rop = None self.os = self.project.loader.main_object.os # determine the aslr of a given os and arch if aslr is None: if self.os == "cgc": # cgc has no ASLR, but we don't assume a stackbase self.aslr = False else: # we assume linux is going to enfore stackbased aslr self.aslr = True else: self.aslr = aslr if crash_state is None: # run the tracer, grabbing the crash state remove_options = { so.TRACK_REGISTER_ACTIONS, so.TRACK_TMP_ACTIONS, so.TRACK_JMP_ACTIONS, so.ACTION_DEPS, so.TRACK_CONSTRAINT_ACTIONS, so.LAZY_SOLVES } add_options = { so.MEMORY_SYMBOLIC_BYTES_MAP, so.TRACK_ACTION_HISTORY, so.CONCRETIZE_SYMBOLIC_WRITE_SIZES, so.CONCRETIZE_SYMBOLIC_FILE_READ_SIZES, so.TRACK_MEMORY_ACTIONS } # faster place to check for non-crashing inputs # optimized crash check if self.project.loader.main_object.os == 'cgc': if not tracer.QEMURunner(binary, input=self.crash).crash_mode: if not tracer.QEMURunner(binary, input=self.crash, report_bad_args=True).crash_mode: l.warning("input did not cause a crash") raise NonCrashingInput if pov_file is None and self.crash is None: raise ValueError("must specify crash or pov_file") if pov_file is not None and self.crash is not None: raise ValueError("cannot specify both a pov_file and an crash") if pov_file is not None: input = TracerPoV(pov_file) else: input = self.crash r = tracer.QEMURunner(binary=binary, input=input) s = self.project.factory.tracer_state( input_content=input, magic_content=r.magic, add_options=add_options, remove_options=remove_options, constrained_addrs=self.constrained_addrs) simgr = self.project.factory.simgr(s, save_unsat=True, hierarchy=False, save_unconstrained=r.crash_mode) self._t = angr.exploration_techniques.Tracer(trace=r.trace, resiliency=False, keep_predecessors=2) self._c = angr.exploration_techniques.CrashMonitor( trace=r.trace, crash_mode=r.crash_mode, crash_addr=r.crash_addr) simgr.use_technique(self._c) simgr.use_technique(self._t) simgr.use_technique(angr.exploration_techniques.Oppologist()) s = simgr.one_active ChallRespInfo.prep_tracer(s, format_infos) ZenPlugin.prep_tracer(s) simgr.run() # if there was no crash we'll have to use the previous path's state if 'crashed' in simgr.stashes: # the state at crash time self.state = simgr.crashed[0] # a path leading up to the crashing basic block self.prev = self._t.predecessors[-1] else: self.state = simgr.traced[0] self.prev = self.state zp = self.state.get_plugin('zen_plugin') if 'crashed' not in simgr.stashes and (zp is not None and len( zp.controlled_transmits) == 0): l.warning("input did not cause a crash") raise NonCrashingInput l.debug("done tracing input") else: self.state = crash_state self.prev = prev_path self._t = None self._c = None # list of actions added during exploitation, probably better object for this attribute to belong to self.added_actions = [] # hacky trick to get all bytes #memory_writes = [ ] #for var in self.state.memory.mem._name_mapping.keys(): # memory_writes.extend(self.state.memory.addrs_for_name(var)) memory_writes = sorted(self.state.memory.mem.get_symbolic_addrs()) l.debug("filtering writes") memory_writes = [m for m in memory_writes if m / 0x1000 != 0x4347c] user_writes = [ m for m in memory_writes if any( "stdin" in v for v in self.state.memory.load(m, 1).variables) ] flag_writes = [ m for m in memory_writes if any( v.startswith("cgc-flag") for v in self.state.memory.load(m, 1).variables) ] l.debug("done filtering writes") self.symbolic_mem = self._segment(user_writes) self.flag_mem = self._segment(flag_writes) # crash type self.crash_types = [] # action (in case of a bad write or read) which caused the crash self.violating_action = None l.debug("triaging crash") self._triage_crash()
def __init__(self, binary, crash=None, pov_file=None, aslr=None, constrained_addrs=None, crash_state=None, prev_path=None, hooks=None, format_infos=None, rop_cache_tuple=None, use_rop=True, fast_mode=False, explore_steps=0, angrop_object=None, argv=None, concrete_fs=False, chroot=None, rop_cache_path=None, trace_timeout=10, input_type=CrashInputType.STDIN, port=None, use_crash_input=False, tracer_args=None, initial_state=None): """ :param binary: Path to the binary which crashed. :param crash: String of input which crashed the binary. :param pov_file: CGC PoV describing a crash. :param aslr: Analyze the crash with aslr on or off. :param constrained_addrs: List of addrs which have been constrained during exploration. :param crash_state: An already traced crash state. :param prev_path: Path leading up to the crashing block. :param hooks: Dictionary of simprocedure hooks, addresses to simprocedures. :param format_infos: A list of atoi FormatInfo objects that should be used when analyzing the crash. :param rop_cache_tuple: A angrop tuple to load from. :param use_rop: Whether or not to use rop. :param explore_steps: Number of steps which have already been explored, should only set by exploration methods. :param angrop_object: An angrop object, should only be set by exploration methods. :param argv: Optionally specify argv params (i,e,: ['./calc', 'parm1']). :param concrete_fs: Use the host's filesystem for analysis :param chroot: For concrete_fs: use this host directory as the guest root :param trace_timeout: Time the tracing operation out after this number of seconds """ self.binary = binary self.crash = crash self.constrained_addrs = [] if constrained_addrs is None else constrained_addrs self.hooks = {} if hooks is None else hooks self.explore_steps = explore_steps self.use_crash_input = use_crash_input self.input_type = input_type self.initial_state = initial_state if tracer_args is None: tracer_args = {} if self.explore_steps > 10: raise CannotExploit( "Too many steps taken during crash exploration") self.project = angr.Project(binary) for addr, proc in self.hooks.items(): self.project.hook(addr, proc) l.debug("Hooking %#x -> %s...", addr, proc.display_name) # we search for ROP gadgets now to avoid the memory exhaustion bug in pypy # hash binary contents for rop cache name binhash = hashlib.md5(open(self.binary, 'rb').read()).hexdigest() if not rop_cache_path: rop_cache_path = os.path.join( "/tmp", "%s-%s-rop" % (os.path.basename(self.binary), binhash)) if use_rop: if angrop_object is not None: self.rop = angrop_object else: self.rop = self.project.analyses.ROP(fast_mode=fast_mode) if rop_cache_tuple is not None: l.info("loading rop gadgets from cache tuple") self.rop._load_cache_tuple(rop_cache_tuple) elif os.path.exists(rop_cache_path): l.info("loading rop gadgets from cache '%s'", rop_cache_path) self.rop.load_gadgets(rop_cache_path) else: if angr.misc.testing.is_testing: self.rop.find_gadgets_single_threaded( show_progress=False) else: self.rop.find_gadgets(show_progress=False) self.rop.save_gadgets(rop_cache_path) else: self.rop = None if self.project.loader.main_object.os == 'cgc': self.project.simos.syscall_library.update( angr.SIM_LIBRARIES['cgcabi_tracer']) self.os = self.project.loader.main_object.os if self.os.startswith('UNIX'): self.os = 'unix' # determine the aslr of a given os and arch if aslr is None: if self.os == "cgc": # cgc has no ASLR, but we don't assume a stackbase self.aslr = False else: # we assume linux is going to enforce stack-based ASLR self.aslr = True else: self.aslr = aslr if crash_state is None: # run the tracer, grabbing the crash state remove_options = { so.TRACK_REGISTER_ACTIONS, so.TRACK_TMP_ACTIONS, so.TRACK_JMP_ACTIONS, so.ACTION_DEPS, so.TRACK_CONSTRAINT_ACTIONS, so.LAZY_SOLVES, so.SIMPLIFY_MEMORY_WRITES, so.ALL_FILES_EXIST } add_options = { so.MEMORY_SYMBOLIC_BYTES_MAP, so.TRACK_ACTION_HISTORY, so.CONCRETIZE_SYMBOLIC_WRITE_SIZES, so.CONCRETIZE_SYMBOLIC_FILE_READ_SIZES, so.TRACK_MEMORY_ACTIONS } # faster place to check for non-crashing inputs # optimized crash check if self.os == 'cgc': if not tracer.QEMURunner( binary, input=self.crash, **tracer_args).crash_mode: if not tracer.QEMURunner(binary, input=self.crash, report_bad_args=True, **tracer_args).crash_mode: l.warning("input did not cause a crash") raise NonCrashingInput if pov_file is None and self.crash is None: raise ValueError("must specify crash or pov_file") if pov_file is not None and self.crash is not None: raise ValueError("cannot specify both a pov_file and an crash") if pov_file is not None: input_data = TracerPoV(pov_file) else: input_data = self.crash if input_type == CrashInputType.TCP: # Feed input to the QEMURunner _ = NetworkFeeder("tcp", "localhost", port, input_data) elif input_type == CrashInputType.UDP: raise NotImplementedError() r = tracer.QEMURunner(binary=binary, input=input_data, argv=argv, trace_timeout=trace_timeout, **tracer_args) kwargs = {} if self.project.loader.main_object.os == 'cgc': cgc = True elif self.project.loader.main_object.os.startswith('UNIX'): if argv is None: argv = ['./binary'] kwargs['args'] = argv cgc = False kwargs['concrete_fs'] = concrete_fs kwargs['chroot'] = chroot else: raise ValueError("Can't analyze binary for OS %s" % self.project.loader.main_object.os) socket_queue = None stdin_file = None # the file that will be fd 0 input_file = None # the file that we want to preconstrain if input_type == CrashInputType.TCP: input_file = input_sock = SimFileStream(name="aeg_tcp_in", ident='aeg_stdin') output_sock = SimFileStream(name="aeg_tcp_out") socket_queue = [None, None, None, (input_sock, output_sock) ] # FIXME THIS IS A HACK else: input_file = stdin_file = SimFileStream(name='stdin', ident='aeg_stdin') if initial_state is None: initial_state = self.project.factory.full_init_state( mode='tracing', add_options=add_options, remove_options=remove_options, **kwargs) initial_state.register_plugin( 'posix', SimSystemPosix( stdin=stdin_file, stdout=SimFileStream(name='stdout'), stderr=SimFileStream(name='stderr'), argc=initial_state.posix.argc, argv=initial_state.posix.argv, environ=initial_state.posix.environ, auxv=initial_state.posix.auxv, socket_queue=socket_queue, )) initial_state.register_plugin( 'preconstrainer', SimStatePreconstrainer(self.constrained_addrs)) initial_state.preconstrainer.preconstrain_file(input_data, input_file, set_length=True) if cgc: initial_state.preconstrainer.preconstrain_flag_page( r.magic) # Loosen certain libc limits on symbolic input initial_state.libc.buf_symbolic_bytes = 3000 initial_state.libc.max_symbolic_strchr = 3000 initial_state.libc.max_str_len = 3000 initial_state.libc.max_buffer_size = 16384 simgr = self.project.factory.simulation_manager( initial_state, save_unsat=False, hierarchy=False, save_unconstrained=r.crash_mode) self.initial_state = initial_state self._t = angr.exploration_techniques.Tracer( trace=r.trace, resiliency=False, keep_predecessors=2, crash_addr=r.crash_addr) simgr.use_technique(self._t) simgr.use_technique(angr.exploration_techniques.Oppologist()) if cgc: s = simgr.one_active ChallRespInfo.prep_tracer(s, format_infos) ZenPlugin.prep_tracer(s) simgr.run() # if there was no crash we'll have to use the previous path's state if 'crashed' in simgr.stashes: # the state at crash time self.state = simgr.crashed[0] # a path leading up to the crashing basic block self.prev = self._t.predecessors[-1] else: self.state = simgr.traced[0] self.prev = self.state zp = self.state.get_plugin('zen_plugin') if cgc else None if 'crashed' not in simgr.stashes and zp is not None and len( zp.controlled_transmits) == 0: l.warning("input did not cause a crash") raise NonCrashingInput l.debug("done tracing input") else: self.state = crash_state self.prev = prev_path self._t = None # list of actions added during exploitation, probably better object for this attribute to belong to self.added_actions = [] # hacky trick to get all bytes #memory_writes = [ ] #for var in self.state.memory.mem._name_mapping.keys(): # memory_writes.extend(self.state.memory.addrs_for_name(var)) memory_writes = sorted(self.state.memory.mem.get_symbolic_addrs()) l.debug("filtering writes") memory_writes = [m for m in memory_writes if m // 0x1000 != 0x4347c] user_writes = [ m for m in memory_writes if any("aeg_stdin" in v for v in self.state.memory.load(m, 1).variables) ] flag_writes = [ m for m in memory_writes if any( v.startswith("cgc-flag") for v in self.state.memory.load(m, 1).variables) ] l.debug("done filtering writes") self.symbolic_mem = self._segment(user_writes) self.flag_mem = self._segment(flag_writes) # crash type self.crash_types = [] # action (in case of a bad write or read) which caused the crash self.violating_action = None l.debug("triaging crash") self._triage_crash()
def _trace(self, pov_file=None, format_infos=None): """ Symbolically trace the target program with the given input. A NonCrashingInput exception will be raised if the target program does not crash with the given input. :param pov_file: CGC-specific setting. :param format_infos: CGC-specific setting. :return: None. """ # sanity check if pov_file is None and self.crash is None: raise ValueError("Must specify either crash or pov_file.") if pov_file is not None and self.crash is not None: raise ValueError("Cannot specify both a pov_file and a crash.") # faster place to check for non-crashing inputs if self.is_cgc: cgc_flag_page_magic = self._cgc_get_flag_page_magic() else: cgc_flag_page_magic = None # Prepare the initial state if pov_file is not None: test_case = TracerPoV(pov_file) channel = None else: input_data = self.crash channel = self.input_type_to_channel_type(self.input_type) if channel != "stdio": channel += ":0" test_case = input_data # collect a concrete trace save_core = True if isinstance(self.tracer_bow, archr.arsenal.RRTracerBow): save_core = False r = self.tracer_bow.fire(testcase=test_case, channel=channel, save_core=save_core) if save_core: # if a coredump is available, save a copy of all registers in the coredump for future references if r.core_path and os.path.isfile(r.core_path): tiny_core = TinyCore(r.core_path) self.core_registers = tiny_core.registers else: l.error("Cannot find core file (path: %s). Maybe the target process did not crash?", r.core_path) if self.initial_state is None: self.initial_state = self._create_initial_state(input_data, cgc_flag_page_magic=cgc_flag_page_magic) simgr = self.project.factory.simulation_manager( self.initial_state, save_unsat=False, hierarchy=False, save_unconstrained=r.crashed ) # trace symbolically! self._t = r.tracer_technique(keep_predecessors=2, copy_states=False, mode=TracingMode.Strict) simgr.use_technique(self._t) simgr.use_technique(angr.exploration_techniques.Oppologist()) if self.is_cgc: s = simgr.one_active ChallRespInfo.prep_tracer(s, format_infos) ZenPlugin.prep_tracer(s) simgr.run() # tracing completed # if there was no crash we'll have to use the previous path's state if 'crashed' in simgr.stashes: # the state at crash time self.state = simgr.crashed[0] # a path leading up to the crashing basic block self.prev = self._t.predecessors[-1] else: self.state = simgr.traced[0] self.prev = self.state zp = self.state.get_plugin('zen_plugin') if self.is_cgc else None if 'crashed' not in simgr.stashes and zp is not None and len(zp.controlled_transmits) == 0: l.warning("Input did not cause a crash.") raise NonCrashingInput l.debug("Done tracing input.")
def _trace(self, pov_file=None, format_infos=None): # faster place to check for non-crashing inputs # optimized crash check if self.os == 'cgc': r = self.tracer_bow.fire(save_core=True, record_magic=True, testcase=self.crash) if not r.crashed: if not self.tracer_bow.fire(save_core=True, testcase=self.crash, report_bad_args=True).crashed: l.warning("input did not cause a crash") raise NonCrashingInput cgc_flag_page_magic = r.magic_contents else: cgc_flag_page_magic = None if pov_file is None and self.crash is None: raise ValueError("must specify crash or pov_file") if pov_file is not None and self.crash is not None: raise ValueError("cannot specify both a pov_file and an crash") if pov_file is not None: input_data = TracerPoV(pov_file) else: input_data = self.crash nf = None stdin_data = None if self.input_type == CrashInputType.TCP: # Feed input to the QEMURunner if isinstance(self.target, archr.targets.DockerImageTarget): ip_address = self.target.ipv4_address elif isinstance(self.target, archr.targets.LocalTarget): ip_address = "localhost" else: raise NotImplementedError() nf = NetworkFeeder("tcp", ip_address, self.target_port, input_data) elif self.input_type == CrashInputType.UDP: raise NotImplementedError('UDP is not supported yet.') elif self.input_type == CrashInputType.STDIN: stdin_data = input_data else: raise NotImplementedError('Input type %s is not supported yet.' % self.input_type) # who the f**k do you think you are I am!!! #if not self.core_registers: # with archr.arsenal.CoreBow(self.target).fire_context(timeout=self.trace_timeout, aslr=False, # **tracer_args) as r: # # Fire it once to get a core on the native target # if nf is not None: # thread_id = nf.fire() # nf.join(thread_id) # if stdin_data is not None: # r.p.stdin.write(stdin_data) # r.p.stdin.close() # # Now it's done # # If a coredump is available, save a copy of all registers in the coredump for future references # if os.path.isfile(r.local_core_path): # tiny_core = tracer.TinyCore(r.local_core_path) # self.core_registers = tiny_core.registers # else: # l.error("Cannot find core file (path: %s). Maybe the target process did not crash?", # r.local_core_path) thread_id = None if nf is not None: thread_id = nf.fire() r = self.tracer_bow.fire(testcase=input_data, save_core=False) if nf is not None: nf.join(thread_id) if self.initial_state is None: self.initial_state = self._create_initial_state( input_data, cgc_flag_page_magic=cgc_flag_page_magic) simgr = self.project.factory.simulation_manager( self.initial_state, save_unsat=False, hierarchy=False, save_unconstrained=r.crashed) self._t = r.tracer_technique(keep_predecessors=2) simgr.use_technique(self._t) simgr.use_technique(angr.exploration_techniques.Oppologist()) if self.is_cgc: s = simgr.one_active ChallRespInfo.prep_tracer(s, format_infos) ZenPlugin.prep_tracer(s) simgr.run() # if there was no crash we'll have to use the previous path's state if 'crashed' in simgr.stashes: # the state at crash time self.state = simgr.crashed[0] # a path leading up to the crashing basic block self.prev = self._t.predecessors[-1] else: self.state = simgr.traced[0] self.prev = self.state zp = self.state.get_plugin('zen_plugin') if self.is_cgc else None if 'crashed' not in simgr.stashes and zp is not None and len( zp.controlled_transmits) == 0: l.warning("input did not cause a crash") raise NonCrashingInput l.debug("done tracing input")