Exemple #1
0
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
Exemple #3
0
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)
Exemple #4
0
    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
Exemple #5
0
    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
Exemple #6
0
    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