(self, job, successor, successors)
return [ pw ]
Returns a new CFGJob instance for further analysis, or None if there is no immediate state to perform the
        analysis on.

        :param CFGJob job: The current job.
Returns a new CFGJob instance for further analysis, or None if there is no immediate state to perform the
        analysis on.
Returns a new CFGJob instance for further analysis or None if there is no immediate state to perform the analysis on .
def _handle_successor(self, job, successor, successors):
        """
        Returns a new CFGJob instance for further analysis, or None if there is no immediate state to perform the
        analysis on.

        :param CFGJob job: The current job.
        """

        state = successor
        all_successor_states = successors
        addr = job.addr

        # The PathWrapper instance to return
        pw = None

        job.successor_status[state] = ""

        new_state = state.copy()
        suc_jumpkind = state.history.jumpkind
        suc_exit_stmt_idx = state.scratch.exit_stmt_idx
        suc_exit_ins_addr = state.scratch.exit_ins_addr

        if suc_jumpkind in {'Ijk_EmWarn', 'Ijk_NoDecode', 'Ijk_MapFail', 'Ijk_NoRedir',
                            'Ijk_SigTRAP', 'Ijk_SigSEGV', 'Ijk_ClientReq'}:
            # Ignore SimExits that are of these jumpkinds
            job.successor_status[state] = "Skipped"
            return [ ]

        call_target = job.extra_info['call_target']
        if suc_jumpkind == "Ijk_FakeRet" and call_target is not None:
            # if the call points to a SimProcedure that doesn't return, we don't follow the fakeret anymore
            if self.project.is_hooked(call_target):
                sim_proc = self.project._sim_procedures[call_target]
                if sim_proc.NO_RET:
                    return [ ]

        # Get target address
        try:
            target_addr = state.solver.eval_one(state.ip)
        except (SimValueError, SimSolverModeError):
            # It cannot be concretized currently. Maybe we can handle it later, maybe it just cannot be concretized
            target_addr = None
            if suc_jumpkind == "Ijk_Ret":
                target_addr = job.call_stack.current_return_target
                if target_addr is not None:
                    new_state.ip = new_state.solver.BVV(target_addr, new_state.arch.bits)

        if target_addr is None:
            # Unlucky...
            return [ ]

        if state.thumb:
            # Make sure addresses are always odd. It is important to encode this information in the address for the
            # time being.
            target_addr |= 1

        # see if the target successor is in our whitelist
        if self._address_whitelist is not None:
            if target_addr not in self._address_whitelist:
                l.debug("Successor %#x is not in the address whitelist. Skip.", target_addr)
                return [ ]

        # see if this edge is in the base graph
        if self._base_graph is not None:
            # TODO: make it more efficient. the current implementation is half-assed and extremely slow
            for src_, dst_ in self._base_graph.edges():
                if src_.addr == addr and dst_.addr == target_addr:
                    break
            else:
                # not found
                l.debug("Edge (%#x -> %#x) is not found in the base graph. Skip.", addr, target_addr)
                return [ ]

        # Fix target_addr for syscalls
        if suc_jumpkind.startswith("Ijk_Sys"):
            syscall_proc = self.project.simos.syscall(new_state)
            if syscall_proc is not None:
                target_addr = syscall_proc.addr

        self._pre_handle_successor_state(job.extra_info, suc_jumpkind, target_addr)

        if suc_jumpkind == "Ijk_FakeRet":
            if target_addr == job.extra_info['last_call_exit_target']:
                l.debug("... skipping a fake return exit that has the same target with its call exit.")
                job.successor_status[state] = "Skipped"
                return [ ]

            if job.extra_info['skip_fakeret']:
                l.debug('... skipping a fake return exit since the function it\'s calling doesn\'t return')
                job.successor_status[state] = "Skipped - non-returning function 0x%x" % job.extra_info['call_target']
                return [ ]

        # TODO: Make it optional
        if (suc_jumpkind == 'Ijk_Ret' and
                    self._call_depth is not None and
                    len(job.call_stack) <= 1
            ):
            # We cannot continue anymore since this is the end of the function where we started tracing
            l.debug('... reaching the end of the starting function, skip.')
            job.successor_status[state] = "Skipped - reaching the end of the starting function"
            return [ ]

        # Create the new call stack of target block
        new_call_stack = self._create_new_call_stack(addr, all_successor_states, job, target_addr,
                                                     suc_jumpkind)
        # Create the callstack suffix
        new_call_stack_suffix = new_call_stack.stack_suffix(self._context_sensitivity_level)
        # Tuple that will be used to index this exit
        new_tpl = self._generate_block_id(new_call_stack_suffix, target_addr, suc_jumpkind.startswith('Ijk_Sys'))

        # We might have changed the mode for this basic block
        # before. Make sure it is still running in 'fastpath' mode
        self._reset_state_mode(new_state, 'fastpath')

        pw = CFGJob(target_addr,
                    new_state,
                    self._context_sensitivity_level,
                    src_block_id=job.block_id,
                    src_exit_stmt_idx=suc_exit_stmt_idx,
                    src_ins_addr=suc_exit_ins_addr,
                    call_stack=new_call_stack,
                    jumpkind=suc_jumpkind,
                    )
        # Special case: If the binary has symbols and the target address is a function, but for some reason (e.g.,
        # a tail-call optimization) the CallStack's function address is still the old function address, we will have to
        # overwrite it here.
        if not self._is_call_jumpkind(pw.jumpkind):
            target_symbol = self.project.loader.find_symbol(target_addr)
            if target_symbol and target_symbol.is_function:
                # Force update the function address
                pw.func_addr = target_addr

        # Generate new exits
        if suc_jumpkind == "Ijk_Ret":
            # This is the real return exit
            job.successor_status[state] = "Appended"

        elif suc_jumpkind == "Ijk_FakeRet":
            # This is the default "fake" retn that generated at each
            # call. Save them first, but don't process them right
            # away
            # st = self.project._simos.prepare_call_state(new_state, initial_state=saved_state)
            st = new_state
            self._reset_state_mode(st, 'fastpath')

            pw = None # clear the job
            pe = PendingJob(job.func_addr,
                            job.extra_info['call_target'],
                            st,
                            job.block_id,
                            suc_exit_stmt_idx,
                            suc_exit_ins_addr,
                            new_call_stack
                            )
            self._pending_jobs[new_tpl].append(pe)
            self._register_analysis_job(pe.caller_func_addr, pe)
            job.successor_status[state] = "Pended"

        elif self._traced_addrs[new_call_stack_suffix][target_addr] >= 1 and suc_jumpkind == "Ijk_Ret":
            # This is a corner case for the f****** ARM instruction
            # like
            # BLEQ <address>
            # If we have analyzed the boring exit before returning from that called address, we will lose the link
            # between the last block of the function being called and the basic block it returns to. We cannot
            # reanalyze the basic block as we are not flow-sensitive, but we can still record the connection and make
            # for it afterwards.
            pass

        else:
            job.successor_status[state] = "Appended"

        if job.extra_info['is_call_jump'] and job.extra_info['call_target'] in self._non_returning_functions:
            job.extra_info['skip_fakeret'] = True

        if not pw:
            return [ ]

        # register the job
        self._register_analysis_job(pw.func_addr, pw)

        return [ pw ]
def _handle_successor ( self , job , successor , successors ) : state = successor all_successor_states = successors addr = job . addr # The PathWrapper instance to return pw = None job . successor_status [ state ] = "" new_state = state . copy ( ) suc_jumpkind = state . history . jumpkind suc_exit_stmt_idx = state . scratch . exit_stmt_idx suc_exit_ins_addr = state . scratch . exit_ins_addr if suc_jumpkind in { 'Ijk_EmWarn' , 'Ijk_NoDecode' , 'Ijk_MapFail' , 'Ijk_NoRedir' , 'Ijk_SigTRAP' , 'Ijk_SigSEGV' , 'Ijk_ClientReq' } : # Ignore SimExits that are of these jumpkinds job . successor_status [ state ] = "Skipped" return [ ] call_target = job . extra_info [ 'call_target' ] if suc_jumpkind == "Ijk_FakeRet" and call_target is not None : # if the call points to a SimProcedure that doesn't return, we don't follow the fakeret anymore if self . project . is_hooked ( call_target ) : sim_proc = self . project . _sim_procedures [ call_target ] if sim_proc . NO_RET : return [ ] # Get target address try : target_addr = state . solver . eval_one ( state . ip ) except ( SimValueError , SimSolverModeError ) : # It cannot be concretized currently. Maybe we can handle it later, maybe it just cannot be concretized target_addr = None if suc_jumpkind == "Ijk_Ret" : target_addr = job . call_stack . current_return_target if target_addr is not None : new_state . ip = new_state . solver . BVV ( target_addr , new_state . arch . bits ) if target_addr is None : # Unlucky... return [ ] if state . thumb : # Make sure addresses are always odd. It is important to encode this information in the address for the # time being. target_addr |= 1 # see if the target successor is in our whitelist if self . _address_whitelist is not None : if target_addr not in self . _address_whitelist : l . debug ( "Successor %#x is not in the address whitelist. Skip." , target_addr ) return [ ] # see if this edge is in the base graph if self . _base_graph is not None : # TODO: make it more efficient. the current implementation is half-assed and extremely slow for src_ , dst_ in self . _base_graph . edges ( ) : if src_ . addr == addr and dst_ . addr == target_addr : break else : # not found l . debug ( "Edge (%#x -> %#x) is not found in the base graph. Skip." , addr , target_addr ) return [ ] # Fix target_addr for syscalls if suc_jumpkind . startswith ( "Ijk_Sys" ) : syscall_proc = self . project . simos . syscall ( new_state ) if syscall_proc is not None : target_addr = syscall_proc . addr self . _pre_handle_successor_state ( job . extra_info , suc_jumpkind , target_addr ) if suc_jumpkind == "Ijk_FakeRet" : if target_addr == job . extra_info [ 'last_call_exit_target' ] : l . debug ( "... skipping a fake return exit that has the same target with its call exit." ) job . successor_status [ state ] = "Skipped" return [ ] if job . extra_info [ 'skip_fakeret' ] : l . debug ( '... skipping a fake return exit since the function it\'s calling doesn\'t return' ) job . successor_status [ state ] = "Skipped - non-returning function 0x%x" % job . extra_info [ 'call_target' ] return [ ] # TODO: Make it optional if ( suc_jumpkind == 'Ijk_Ret' and self . _call_depth is not None and len ( job . call_stack ) <= 1 ) : # We cannot continue anymore since this is the end of the function where we started tracing l . debug ( '... reaching the end of the starting function, skip.' ) job . successor_status [ state ] = "Skipped - reaching the end of the starting function" return [ ] # Create the new call stack of target block new_call_stack = self . _create_new_call_stack ( addr , all_successor_states , job , target_addr , suc_jumpkind ) # Create the callstack suffix new_call_stack_suffix = new_call_stack . stack_suffix ( self . _context_sensitivity_level ) # Tuple that will be used to index this exit new_tpl = self . _generate_block_id ( new_call_stack_suffix , target_addr , suc_jumpkind . startswith ( 'Ijk_Sys' ) ) # We might have changed the mode for this basic block # before. Make sure it is still running in 'fastpath' mode self . _reset_state_mode ( new_state , 'fastpath' ) pw = CFGJob ( target_addr , new_state , self . _context_sensitivity_level , src_block_id = job . block_id , src_exit_stmt_idx = suc_exit_stmt_idx , src_ins_addr = suc_exit_ins_addr , call_stack = new_call_stack , jumpkind = suc_jumpkind , ) # Special case: If the binary has symbols and the target address is a function, but for some reason (e.g., # a tail-call optimization) the CallStack's function address is still the old function address, we will have to # overwrite it here. if not self . _is_call_jumpkind ( pw . jumpkind ) : target_symbol = self . project . loader . find_symbol ( target_addr ) if target_symbol and target_symbol . is_function : # Force update the function address pw . func_addr = target_addr # Generate new exits if suc_jumpkind == "Ijk_Ret" : # This is the real return exit job . successor_status [ state ] = "Appended" elif suc_jumpkind == "Ijk_FakeRet" : # This is the default "fake" retn that generated at each # call. Save them first, but don't process them right # away # st = self.project._simos.prepare_call_state(new_state, initial_state=saved_state) st = new_state self . _reset_state_mode ( st , 'fastpath' ) pw = None # clear the job pe = PendingJob ( job . func_addr , job . extra_info [ 'call_target' ] , st , job . block_id , suc_exit_stmt_idx , suc_exit_ins_addr , new_call_stack ) self . _pending_jobs [ new_tpl ] . append ( pe ) self . _register_analysis_job ( pe . caller_func_addr , pe ) job . successor_status [ state ] = "Pended" elif self . _traced_addrs [ new_call_stack_suffix ] [ target_addr ] >= 1 and suc_jumpkind == "Ijk_Ret" : # This is a corner case for the f****** ARM instruction # like # BLEQ <address> # If we have analyzed the boring exit before returning from that called address, we will lose the link # between the last block of the function being called and the basic block it returns to. We cannot # reanalyze the basic block as we are not flow-sensitive, but we can still record the connection and make # for it afterwards. pass else : job . successor_status [ state ] = "Appended" if job . extra_info [ 'is_call_jump' ] and job . extra_info [ 'call_target' ] in self . _non_returning_functions : job . extra_info [ 'skip_fakeret' ] = True if not pw : return [ ] # register the job self . _register_analysis_job ( pw . func_addr , pw ) return [ pw ]