Example #1
0
    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)
Example #2
0
    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
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
    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")
Example #10
0
    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()
Example #11
0
    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()
Example #12
0
    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.")
Example #13
0
    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")