def __init__(self, binary, crash=None): """ :param binary: path to the binary which crashed :param crash: string of input which crashed the binary """ self.binary = binary self.crash = crash # verify it actually crashes the binary r = tracer.Runner(self.binary, input=self.crash) if not r.crash_mode: raise CrashFuzzerException("input did not crash the binary") self._p = angr.Project(self.binary) self.orig_regs = r.reg_vals self.pool = None self.byte_analysis = dict() self._bases = dict() self.skip_bytes = set() self.skip_sets = set() self.regs_to_numbers = dict() self.used_bytes = set() self.byte_translation_funcs = list() self.byte_translation_calls = dict() self._bit_patterns = dict() self.make_bases() self.run()
def test_unicorn(): p = angr.Project( os.path.join(test_location, 'binaries-private/cgc_qualifier_event/cgc/99c22c01_01')) s_unicorn = p.factory.entry_state( add_options=so.unicorn | {so.CGC_NO_SYMBOLIC_RECEIVE_LENGTH, so.STRICT_PAGE_ACCESS}, remove_options={so.LAZY_SOLVES}) # unicorn s_angr = p.factory.entry_state(add_options={ so.CGC_NO_SYMBOLIC_RECEIVE_LENGTH, so.INITIALIZE_ZERO_REGISTERS, so.STRICT_PAGE_ACCESS }, remove_options={so.LAZY_SOLVES }) # pure angr pg_unicorn = p.factory.path_group(s_unicorn) pg_angr = p.factory.path_group(s_angr) # input = 'x\n\0\0\0\0' inp = 'L\x0alaehdamfeg\x0a10\x2f28\x2f2014\x0a-2147483647:-2147483647:-2147483647\x0ajfifloiblk\x0a126\x0a63\x0a47\x0a31\x0a3141\x0a719\x0a' stdin = s_unicorn.posix.get_file(0) stdin.write(inp, len(inp)) stdin.seek(0) stdin.size = len(inp) stdin = s_angr.posix.get_file(0) stdin.write(inp, len(inp)) stdin.seek(0) stdin.size = len(inp) t = tracer.Runner(p.filename, inp, record_trace=True) t.dynamic_trace() real_trace = t.trace pg_unicorn.run() uc_trace = pg_unicorn.one_errored.addr_trace.hardcopy + [ pg_unicorn.one_errored.addr ] pg_angr.run() angr_trace = pg_angr.one_errored.addr_trace.hardcopy + [ pg_angr.one_errored.addr ] uc_trace_filtered = [ a for a in uc_trace if not p._extern_obj.contains_addr(a) and not p._syscall_obj.contains_addr(a) ] # not assert_equal because it tries to use a fancy algorithm that blows up on long lists nose.tools.assert_true(uc_trace_filtered == real_trace) nose.tools.assert_true(uc_trace == angr_trace) nose.tools.assert_equal(pg_angr.one_errored.error.addr, pg_unicorn.one_errored.error.addr) uc_calltrace = _get_calltrace(pg_unicorn.one_errored) angr_calltrace = _get_calltrace(pg_angr.one_errored) nose.tools.assert_true(uc_calltrace == angr_calltrace)
def _get_reg_vals(binary_input_byte): binary, test_input, c = binary_input_byte r = tracer.Runner(binary, input=test_input) if not r.crash_mode: return [c, None] else: reg_vals = dict() for reg in CGC_GENERAL_REGS + ["eip"]: reg_vals[reg] = r.reg_vals[reg] return [c, r.reg_vals]
def _concrete_leak_info(self, seed=None): if seed is None: seed = random.randint(0, 2**32) r1 = tracer.Runner(self.binary, input=self.payload, record_magic=True, record_stdout=True, seed=seed) return (r1.stdout, r1.magic)
def __init__(self, binary, crash=None, ids_rules=None, time_limit=None): """ :param binary: path to the binary which crashed (or list of binaries) :param crash: string of input which crashed the binary :param ids_rules: the string ids rules :param time_limit: the max time in seconds """ if isinstance(binary, (list, tuple)): self.binaries = binary else: self.binaries = [binary] self.crash = crash global glob_ids_rules glob_ids_rules = ids_rules self.ids_rules = ids_rules # verify it actually crashes the binary if USE_ANGR: # print('use_angr: ',USE_ANGR) if len(self.binaries) > 0: # print('binary length: ',len(self.binaries)) raise ValueError("No support for MultiCBs with USE_ANGR=True") r = tracer.Runner(self.binaries[0], input=self.crash) else: # print('not use angr: ',len(self.binaries)) r = CustomRunner(self.binaries, payload=self.crash, ids_rules=glob_ids_rules) if not r.crash_mode: raise CrashFuzzerException("input did not crash the binary") self.orig_regs = r.reg_vals self.pool = None self.byte_analysis = dict() self._bases = dict() self.skip_bytes = set() self.skip_sets = set() self.regs_to_numbers = dict() self.used_bytes = set() self.byte_translation_funcs = list() self.byte_translation_calls = dict() self._bit_patterns = dict() time_limit = time_limit if time_limit is not None else 2000 self._end_time = time.time() + time_limit self.make_bases() self.run()
def _get_reg_vals(binary_input_byte): global glob_ids_rules binary, test_input, c = binary_input_byte try: if USE_ANGR: r = tracer.Runner(binary, input=test_input) else: r = CustomRunner(binary, payload=test_input, ids_rules=glob_ids_rules) if not r.crash_mode: return [c, None] else: reg_vals = dict() for reg in CGC_GENERAL_REGS + ["eip"]: reg_vals[reg] = r.reg_vals[reg] return [c, r.reg_vals] except Exception as e: print e return [c, None]
def _get_reg_vals(binary_input_byte): global glob_ids_rules binaries, test_input, c = binary_input_byte try: if USE_ANGR: if len(binaries) > 1: raise ValueError("No support for MultiCB with USE_ANGR=True") r = tracer.Runner(binaries[0], input=test_input) else: r = CustomRunner(binaries, payload=test_input, ids_rules=glob_ids_rules) if not r.crash_mode: return [c, None] else: reg_vals = dict() for reg in CGC_GENERAL_REGS + ["eip"]: reg_vals[reg] = r.reg_vals[reg] return [c, r.reg_vals] except Exception as e: print e return [c, None]
def __init__(self, binary, crash=None): """ :param binary: path to the binary which crashed :param crash: string of input which crashed the binary """ self.binary = binary self.crash = crash # verify it actually crashes the binary r = tracer.Runner(self.binary, input=self.crash, record_stdout=True) if not r.crash_mode: raise CrashFuzzerException("input did not crash the binary") self.orig_stdout = r.stdout self.addr_ast = None try: self._p = angr.Project(self.binary) self.orig_regs = r.reg_vals s = rop_utils.make_symbolic_state(self._p, reg_list=CGC_GENERAL_REGS) self._reg_asts = dict() for r in CGC_GENERAL_REGS: ast = s.se.BVS(r, 32, explicit_name=True) self._reg_asts[r] = ast s.registers.store(r, ast) s.ip = self.orig_regs["eip"] p = self._p.factory.path(s) p.step(num_inst=1) all_succ = p.successors + p.unconstrained_successors if len(all_succ) == 0: raise CannotExploit("no successors") succ = all_succ[0] for a in succ.actions.hardcopy: if a.type == "mem" and a.action == "read": dependencies = a.addr.ast.variables self.addr_ast = a.addr.ast self.reg_deps = dependencies | {"AST"} self.orig_regs = self._fix_reg_vals(self.orig_regs) l.debug("REG DEPS: %s", self.reg_deps) except CLEError as e: l.warning("CLEError: %s", e) pass if self.addr_ast is None: self.reg_deps = set(CGC_GENERAL_REGS) l.warning("couldn't find read addr depenency") self.pool = None self.byte_analysis = dict() self._bases = dict() self.skip_bytes = set() self.skip_sets = set() self.regs_to_numbers = dict() self.used_bytes = set() self.byte_translation_funcs = list() self.byte_translation_calls = dict() self._bit_patterns = dict() self._raw_payload = None self.output_leak_idx = None self.cgc_type = 2 self.make_bases() self.run() self.post_filter() self.post_analysis()
def post_analysis(self): regs_to_check = [] if "AST" in self.orig_regs: regs_to_check.append("AST") else: regs_to_check = CGC_GENERAL_REGS for reg in regs_to_check: flag_bits = CGC_FLAG_PAGE >> 12 # shouldn't unset any already good bits orig_bits = self.orig_regs[reg] >> 12 orig_matching_bits = (~(flag_bits ^ orig_bits)) & 0xfffff curr_best_matches = [] for i in self.byte_analysis: ast_vals = [ x["AST"] for x in self.byte_analysis[i].reg_vals.values() ] for a in ast_vals: bits = a >> 12 matching_bits = ~(flag_bits ^ bits) & 0xfffff if matching_bits & orig_matching_bits != orig_matching_bits: continue else: is_better_than_curr = True for b in list(curr_best_matches): matching_bits_b = ~(flag_bits ^ b) & 0xfffff if matching_bits & matching_bits_b == matching_bits: is_better_than_curr = False elif matching_bits & matching_bits_b == matching_bits_b: curr_best_matches.remove(b) if is_better_than_curr: curr_best_matches.append(bits) # verify it can be pointed at flag page all_bits = reduce( operator.__or__, [~(x ^ flag_bits) & 0xfffff for x in curr_best_matches], initial=0) if bin(all_bits).count("1") < 20: continue match_dict = defaultdict(set) # now get all bytes that match each best for i in self.byte_analysis: for b in self.byte_analysis[i].reg_vals: a = self.byte_analysis[i].reg_vals[b][reg] bits = a >> 12 if bits in curr_best_matches: match_dict[bits].add((i, b)) # now pick a random from each set, dump an input and see if we get more output choices = [] for bits in match_dict: choices.append(random.choice(list(match_dict[bits]))) new_input = self.crash for index, b in choices: new_input = self._replace_indices(new_input, b, [index]) r = tracer.Runner(self.binary, input=new_input, record_stdout=True, record_magic=True) new_stdout = r.stdout if len(new_stdout) > len(self.orig_stdout): # okay we have a leak # now we should try to guess what we leaked leak_idx = None for i in range(len(new_stdout)): if new_stdout[i:i + 4] in r.magic: leak_idx = i break if leak_idx is None: # need to send to colorguard... l.warning("need to send to colorguard but not implemented") self._raw_payload = new_input else: self._raw_payload = new_input self.output_leak_idx = leak_idx num_good = 0 if self.test_binary(enable_randomness=False): num_good += 1 l.warning("works with no randomness") for i in range(5): if self.test_binary(enable_randomness=True): num_good += 1 l.warning("worked %d/6 times", num_good) if num_good > 0: break return None
def _quick_triage(self, binary, crash): l.debug("quick triaging crash against '%s'", binary) arbitrary_syscall_arg = False r = tracer.Runner(binary, crash, record_trace=True, use_tiny_core=True) self.bb_count = len(r.trace) if not r.crash_mode: # try again to catch bad args r = tracer.Runner(binary, crash, report_bad_args=True) arbitrary_syscall_arg = True if not r.crash_mode: raise NonCrashingInput("input did not cause a crash") l.debug("detected an arbitrary transmit or receive") if r.os != "cgc": raise ValueError("QuickCrash is only available for CGC binaries") if r.is_multicb: project = angr.Project(binary[r.crashed_binary]) else: project = angr.Project(binary) # triage the crash based of the register values and memory at crashtime # look for the most valuable crashes first pc = r.reg_vals['eip'] l.debug('crash occured at %#x', pc) if arbitrary_syscall_arg: l.debug("checking which system call had bad args") syscall_num = r.reg_vals['eax'] vulns = {2: Vulnerability.ARBITRARY_TRANSMIT, \ 3: Vulnerability.ARBITRARY_RECEIVE} # shouldn't ever happen but in case it does if syscall_num not in vulns: return pc, None return pc, vulns[syscall_num] l.debug("checking if ip is null") if pc < 0x1000: return pc, Vulnerability.NULL_DEREFERENCE l.debug("checking if ip register points to executable memory") start_state = project.factory.entry_state(addr=pc) # was ip mapped? ip_overwritten = False try: perms = start_state.memory.permissions(pc) # check if the execute bit is marked, this is an AST l.debug("ip points to mapped memory") if not perms.symbolic and not ((perms & 4) == 4).args[0]: l.debug("ip appears to be uncontrolled") return pc, Vulnerability.UNCONTROLLED_IP_OVERWRITE except angr.SimMemoryError: ip_overwritten = True if ip_overwritten: # let's see if we can classify it as a partial overwrite # this is done by seeing if the most signifigant bytes of # pc could be a mapping cgc_object = project.loader.all_elf_objects[0] base = cgc_object.min_addr & 0xff000000 while base < cgc_object.max_addr: if pc & 0xff000000 == base: l.debug("ip appears to only be partially controlled") return pc, Vulnerability.PARTIAL_IP_OVERWRITE base += 0x01000000 l.debug("ip appears to be completely controlled") return pc, Vulnerability.IP_OVERWRITE # wasn't an ip overwrite, check reads and writes l.debug("checking if a read or write caused the crash") # set registers start_state.regs.eax = r.reg_vals['eax'] start_state.regs.ebx = r.reg_vals['ebx'] start_state.regs.ecx = r.reg_vals['ecx'] start_state.regs.edx = r.reg_vals['edx'] start_state.regs.esi = r.reg_vals['esi'] start_state.regs.edi = r.reg_vals['edi'] start_state.regs.esp = r.reg_vals['esp'] start_state.regs.ebp = r.reg_vals['ebp'] next_pth = project.factory.successors(start_state, num_inst=1) posit = None for a in next_pth.history.recent_actions: if a.type == 'mem': target_addr = start_state.se.eval(a.addr) if target_addr < 0x1000: l.debug("attempt to write or read to address of NULL") return pc, Vulnerability.NULL_DEREFERENCE # we will take the last memory action, so things like an `add` instruction # are triaged as a 'write' opposed to a 'read' if a.action == 'write': l.debug("write detected") posit = Vulnerability.WRITE_WHAT_WHERE # if it's trying to write to a non-writeable address which is mapped # it's most likely uncontrolled if target_addr & 0xfff00000 == 0: l.debug( "write attempt at a suspiciously small address, assuming uncontrolled" ) return pc, Vulnerability.UNCONTROLLED_WRITE try: perms = start_state.memory.permissions(target_addr) if not perms.symbolic and not ( (perms & 2) == 2).args[0]: l.debug( "write attempt at a read-only page, assuming uncontrolled" ) return pc, Vulnerability.UNCONTROLLED_WRITE except angr.SimMemoryError: pass elif a.action == 'read': l.debug("read detected") posit = Vulnerability.ARBITRARY_READ else: # sanity checking raise ValueError( "unrecognized memory action encountered %s" % a.action) if posit is None: l.debug("crash was not able to be triaged") posit = 'unknown' # returning 'unknown' if crash does not fall into one of our obvious categories return pc, posit
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.pov_file = pov_file self.constrained_addrs = [] if constrained_addrs is None else constrained_addrs self.hooks = 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) # 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 } # faster place to check for non-crashing inputs # optimized crash check if self.project.loader.main_object.os == 'cgc': if not tracer.Runner(binary, input=self.crash).crash_mode: if not tracer.Runner(binary, input=self.crash, report_bad_args=True).crash_mode: l.warning("input did not cause a crash") raise NonCrashingInput self._tracer = tracer.Tracer(binary, input=self.crash, pov_file=self.pov_file, resiliency=False, hooks=self.hooks, add_options=add_options, remove_options=remove_options, keep_predecessors=2) ChallRespInfo.prep_tracer(self._tracer, format_infos) ZenPlugin.prep_tracer(self._tracer) prev, crash_state = self._tracer.run(constrained_addrs) # if there was no crash we'll have to use the previous path's state if crash_state is None: self.state = prev else: # the state at crash time self.state = crash_state zp = self.state.get_plugin('zen_plugin') if crash_state is None 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") # a path leading up to the crashing basic block self.prev = prev else: self.state = crash_state self.prev = prev_path self._tracer = 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, ids_rules=None, time_limit=None): """ :param binary: path to the binary which crashed (or list of binaries) :param crash: string of input which crashed the binary :param ids_rules: the string ids rules :param time_limit: the max time in seconds """ if isinstance(binary, (list, tuple)): self.binaries = binary else: self.binaries = [binary] self.crash = crash global glob_ids_rules glob_ids_rules = ids_rules self.ids_rules = ids_rules # verify it actually crashes the binary if USE_ANGR: if len(self.binaries) > 0: raise ValueError("No support for MultiCBs with USE_ANGR=True") r = tracer.Runner(self.binaries, input=self.crash, record_stdout=True) else: r = CustomRunner(self.binaries, payload=self.crash, record_stdout=True, ids_rules=glob_ids_rules) if not r.crash_mode: raise CrashFuzzerException("input did not crash the binary") self.orig_stdout = r.stdout self.orig_regs = r.reg_vals self.addr_eqn = self._get_reg_eqn() self.orig_regs = self._fix_reg_vals(self.orig_regs) if self.addr_eqn is None: raise CrashFuzzerException("couldnt get eqn") l.debug("crashing read eqn is %s", self.addr_eqn) self.reg_deps = set(r for r in CGC_GENERAL_REGS if r in self.addr_eqn) self.reg_deps.add("AST") l.debug("reg deps: %s", self.reg_deps) self.pool = None self.byte_analysis = dict() self._bases = dict() self.skip_bytes = set() self.skip_sets = set() self.regs_to_numbers = dict() self.used_bytes = set() self.byte_translation_funcs = list() self.byte_translation_calls = dict() self._bit_patterns = dict() self._raw_payload = None self.output_leak_idx = None self.cgc_type = 2 time_limit = time_limit if time_limit is not None else 2000 self._end_time = time.time() + time_limit self.make_bases() self.run() self.post_filter() self.post_analysis()