def test_is_floatingpoint_function(): filepath = os.path.join(bin_location, "CROMU_00071") backend = DetourBackend(filepath) cfg = backend.cfg floatingpoint_functions = [ ff for ff in cfg.functions.values() if cfg_utils.is_floatingpoint_function(backend, ff) ] floatingpoint_functions = sorted(floatingpoint_functions, key=lambda f: f.addr) #print "floatingpoint_functions in CROMU_00071" #print "\n".join(map(lambda f:hex(f.addr),floatingpoint_functions)) first = floatingpoint_functions[0].addr ff = floatingpoint_functions[-1] if ff.ret_sites == None: last = ff.addr else: if len(ff.ret_sites) == 0: last = ff.addr else: last = max([e.addr for e in ff.blocks]) print hex(first), hex(last) real_start = 0x804d5c6 real_end = 0x0804D78b nose.tools.assert_true(first == real_start) nose.tools.assert_true(last <= real_end) nose.tools.assert_true(last > real_end - 0x20) #I allow some imprecision filepath = os.path.join(bin_location, "CROMU_00070") backend = DetourBackend(filepath) cfg = backend.cfg floatingpoint_functions = [ ff for ff in cfg.functions.values() if cfg_utils.is_floatingpoint_function(backend, ff) ] floatingpoint_functions = sorted(floatingpoint_functions, key=lambda f: f.addr) #print "floatingpoint_functions in CROMU_00071" #print "\n".join(map(lambda f:hex(f.addr),floatingpoint_functions)) first = floatingpoint_functions[0].addr ff = floatingpoint_functions[-1] if ff.ret_sites == None: last = ff.addr else: if len(ff.ret_sites) == 0: last = ff.addr else: last = max([e.addr for e in ff.blocks]) print hex(first), hex(last) real_start = 0x0804D75f real_end = 0x0804D924 nose.tools.assert_true(first == real_start) nose.tools.assert_true(last <= real_end) nose.tools.assert_true(last > real_end - 0x20) #I allow some imprecision
def function_to_patch_locations(self, ff): # TODO tail-call is handled lazily just by considering jumping out functions as not sane if cfg_utils.is_sane_function(ff) and cfg_utils.detect_syscall_wrapper(self.patcher,ff) == None \ and not cfg_utils.is_floatingpoint_function(self.patcher,ff) and not ff.addr in self.safe_functions: if cfg_utils.is_longjmp(self.patcher, ff): self.found_longjmp = ff.addr elif cfg_utils.is_setjmp(self.patcher, ff): self.found_setjmp = ff.addr else: start = ff.startpoint ends = set() for ret_site in ff.ret_sites: bb = self.patcher.project.factory.fresh_block( ret_site.addr, ret_site.size) last_instruction = bb.capstone.insns[-1] if last_instruction.mnemonic != u"ret": msg = "bb at %s does not terminate with a ret in function %s" l.debug(msg % (hex(int(bb.addr)), ff.name)) break else: ends.add(last_instruction.address) else: if len(ends) == 0: l.debug("cannot find any ret in function %s" % ff.name) else: return int(start.addr), map( int, ends) #avoid "long" problems l.debug("function %s has problems and cannot be patched" % ff.name) return None, None
def test_fullcfg_properties(): binaries = [ "KPRCA_00009","KPRCA_00025","NRFIN_00004","CROMU_00071", "CADET_00003", # "CROMU_00070", "EAGLE_00005", # "KPRCA_00019" ] # these are either "slides" into a call or jump to the beginning of a call # ("KPRCA_00025",0x804b041) is a very weird case, but Fish convinced me that it is correct legittimate_jumpouts = [("KPRCA_00025",0x80480bf),("KPRCA_00025",0x804b041), ("KPRCA_00025",0x804bd85),("KPRCA_00025",0x804c545),("KPRCA_00025",0x804c5b5), ("KPRCA_00025", 0x804c925), ("KPRCA_00019",0x8048326), ("KPRCA_00019",0x8048b41),("KPRCA_00019",0x804882e),("KPRCA_00019",0x8048cd1), ("KPRCA_00019",0x8048cca),("KPRCA_00019",0x8049408), ("KPRCA_00019", 0x8048846), ("KPRCA_00019", 0x804884b), ("KPRCA_00019", 0x804885f), ("KPRCA_00019", 0x804886f), ("KPRCA_00019", 0x8048877), ("CROMU_00071", 0x804d77d), ("CROMU_00071", 0x804d783), ] for binary in binaries: print "testing",binary,"..." filepath = os.path.join(bin_location, binary) backend = DetourBackend(filepath) cfg = backend.cfg node_addrs_dict = defaultdict(set) for k,ff in cfg.functions.iteritems(): for node_addr in ff.block_addrs_set: node_addrs_dict[node_addr].add(ff) # check that endpoints are the union of callouts, rets, and jumpouts endpoint_union = set(ff.callout_sites).union(set(ff.ret_sites).union(set(ff.jumpout_sites))) nose.tools.assert_equal(set(ff.endpoints),endpoint_union) # check that we do not encounter any unexpected jumpout if not ff.is_syscall and ff.returning and not ff.has_unresolved_calls and \ not ff.has_unresolved_jumps and ff.startpoint != None and ff.endpoints: if not cfg_utils.is_floatingpoint_function(backend,ff): if len(ff.jumpout_sites) > 0: unexpected_jumpout = [(binary,int(jo.addr)) for jo in ff.jumpout_sites \ if (binary,int(jo.addr)) not in legittimate_jumpouts] if len(unexpected_jumpout)>0: print "unexpected jumpouts in",binary,map(lambda x:hex(x[1]),unexpected_jumpout) nose.tools.assert_equal(len(unexpected_jumpout),0) # check that every node only belongs to a single function for k,v in node_addrs_dict.iteritems(): if len(v)>1: print "found node in multiple functions:",hex(k),repr(v) nose.tools.assert_equal(len(v),1) # check that every node only appears once in the cfg nn = set() instruction_set = set() for n in cfg.nodes(): nose.tools.assert_true(n.addr not in nn) nn.add(n.addr) # check that every instruction appears only in one node for iaddr in n.instruction_addrs: nose.tools.assert_true(iaddr not in instruction_set) instruction_set.add(iaddr)
def get_patches(self): patches = [] patches.extend(self.get_common_patches()) cfg = self.patcher.cfg if self.contains_executable_allocation(cfg): l.warning( "found executable allocation, I will not apply indirect CFI") self.allocate_executable = True else: self.allocate_executable = False self.safe_addrs = self.get_safe_functions() # the overlapping instruction issue seems to be fixed, at least partially # I am still using a dict and raising warnings in case of problems. sci = {} for ff in cfg.functions.values(): if not ff.is_syscall and ff.startpoint != None and ff.endpoints != None and \ cfg_utils.detect_syscall_wrapper(self.patcher,ff) == None and \ not cfg_utils.is_floatingpoint_function(self.patcher,ff)\ and ff.addr not in self.safe_addrs: for bb in ff.blocks: for ci in bb.capstone.insns: if ci.group( capstone.x86_const.X86_GRP_CALL) or ci.group( capstone.x86_const.X86_GRP_JUMP): if len(ci.operands) != 1: l.warning( "Unexpected operand size for CALL/JUMP: %s" % str(ci)) else: op = ci.operands[0] if op.type != capstone.x86_const.X86_OP_IMM: if ci.address in sci: old_ci = sci[ci.address] tstr = "instruction at %08x (bb: %08x, function %08x) " % \ (ci.address,bb.addr,ff.addr) tstr += "previously found at bb: %08x in function: %08x" % \ (old_ci[1].addr,old_ci[2].addr) l.warning(tstr) else: sci[ci.address] = (ci, bb, ff) for instruction, bb, ff in sci.values(): l.info("Found indirect CALL/JUMP: %s" % str(instruction)) cj_type = self.classify_cj(instruction) if cj_type == "standard": try: new_patches = self.handle_standard_cj(instruction, ff) except utils.NasmException: l.warning( "NASM exception while compiling mem_access for %s" % instruction) continue patches.extend(new_patches) return patches
def get_patches(self): cfg = self.patcher.cfg for k, ff in cfg.functions.items(): if ff.is_syscall or ff.is_simprocedure: # don't patch syscalls or SimProcedures continue if not ff.is_syscall and ff.startpoint != None and ff.endpoints != None and \ cfg_utils.detect_syscall_wrapper(self.patcher,ff) == None and \ not cfg_utils.is_floatingpoint_function(self.patcher,ff): call_sites = ff.get_call_sites() for cs in call_sites: nn = ff.get_node(cs) # max stack size is 8MB if any([ 0xba2aa000 <= n.addr < 0xbaaab000 for n in nn.successors() ]): l.warning("found call to stack at %#8x, avoiding nx" % nn.addr) return [] for block in ff.blocks: for s in block.vex.statements: if any([ 0xba2aa000 <= v.value <= 0xbaaab000 for v in s.constants ]): l.warning( "found constant that looks stack-related at %#8x, avoiding nx" % block.addr) return [] patches = [] nxsegment_after_stack = (0x1, 0x0, 0xbaaab000, 0xbaaab000, 0x0, 0x1000, 0x6, 0x1000) patches.append( AddSegmentHeaderPatch(nxsegment_after_stack, name="nxstack_segment_header")) added_code = ''' ; int 3 ; this can be placed before or after the stack shift add esp, 0x1000 ; restore flags, assume eax=0 since we are after restore push 0x202 popf mov DWORD [esp-4], eax ''' patches.append( AddEntryPointPatch(added_code, name="move_stack_to_nx", after_restore=True)) return patches
def _should_skip(self, ff): if cfg_utils.detect_syscall_wrapper( self.patcher, ff) or ff.is_syscall or ff.startpoint is None: return True if cfg_utils.is_floatingpoint_function(self.patcher, ff): return True all_pred_addrs = set(x.addr for x in self.patcher.cfg.get_predecessors( self.patcher.cfg.get_any_node(ff.addr))) if len(all_pred_addrs) > 5: return True return False