angr源码分析——Simulation Manager

angr中符号执行涉及的代码比较多,前面已经介绍了符号执行中获取程序状态的类factory,以及状态对象SimState涉及到的属性,方法,插件,下面将介绍关于符号执行路径探索的主要对象Simulation Manager。

代码位于:angr/angr/manager.py

angr.manager.SimulationManager(project, active_states=None, stashes=None, hierarchy=None, veritesting=None, veritesting_options=None, immutable=None, resilience=None, save_unconstrained=None, save_unsat=None, threads=None, errored=None, completion_mode=)

The Simulation Manager is the future future.

根据名称来看,simulationmanager似乎是对模拟状态的管理。模拟管理器允许我们以光滑的方式来描述多个状态。状态被组织成“stashes”,我们可以按照自己的意愿执行,过滤,合并和移动。例如,我们可以以不同的速率步进两个不同的状态stashes,然后将它们合并在一起。

stashes可以作为属性被访问(即.active)。可以通过将名称预先加上mp_来检索mulpyplexed stash,例如 .mp_active。一个单独的状态stash可以通过将名称预先加上one_来检索,如,one_active。

在解决密集型约束的情况下,多线程处理非常有用。事实上,由于Python的GIL,Python不能实现多线程,但用C编写的z3可以。

注意:我们不能直接利用构造器构造SimulationManagers,而是通过Project.factory类中的方法创建它们,能够创建它的方法有:path_group()和simgr()。

参数

:project(angr.project.Project) - 一个Project实例。

以下参数是可选的。
参数:
active_states - active状态产生“active”stash(翻译不清楚,原句:Active states to seed the “active” stash with)。
stashes - 用作存储stash的字典。
hierarchy - 用于跟踪状态之间关系的StateHierarchy对象。
immutable - 如果为True,所有操作将返回一个新的SimulationManager。 否则(默认),所有操作都将修改SimulationManager(并返回它,以实现一致性和链接)。
threads - 同时分析状态的工作线程数量(在z3密集型情况下有用)。

变量:
errored - 不是stash,而是ErrorRecords的列表。每当一个步骤产生一个我们捕获的异常,状态和有关错误的一些信息就会放在这个列表中。 您可以使用弹性参数调整捕获的异常列表。
stashes - 这个实例中的所有stashes,作为字典存储。

completion_mode - 一个函数,描述多个探索技术如何与complete的hook set相互作用。默认情况下,为内置函数any()。

方法

对于SimulationManagers我们主要关注的方法有:step(), explore(), use_technique()

step(n=None, selector_func=None, step_func=None, stash=None, successor_func=None, until=None, **kwargs)

函数作用为推动状态stash前进,并对successors进行适当的分类。这个函数的参数允许我们控制前进和分类进程的所有内容。

参数:

stash -要step(前进)的stash的名称(默认:active)
n -step的次数(默认:1,如果until参数不提供的话)
selector_func -如果提供,则应该是一个函数,它接收状态state作为参数并返回一个bool值。如果为true,状态将会前进,否则保持原样。
step_func -如果提供,则应该是一个函数,它接收SimulationManager作为参数并返回一个SimulationManager。将在每一步通过SimulationManager调用。注意,这个函数实际上不应该执行任何step,它意味着在每个step后调用一个维护函数。
successor_func -如果提供,应该是一个函数,它接收状态state作为参数,并返回它的successors。否则project.factory.successors将会被使用。
until -如果提供,则应该是一个函数,它接收一个SimulationManger作为参数,并返回true或false。step将会在返回true时终止。

此外,我们可以将下面任何关键参数传递给project.factory.sim_run函数(即successors函数)。
jumpkind - 前一个跳转类型jumpkind
addr - 要执行的地址而不是状态的ip。
stmt_whitelist - 限制执行的stmt索引列表。
last_stmt - 停止执行的语句索引。
thumb - 是否应该在ARM的THUMB模式下解除块。
backup_state - 读取字节而不是使用project内存的状态。
opt_level - 要使用的VEX优化级别。
insn_bytes - 用于block而不是project的字节串。
size - 块的最大值,以字节为单位。
num_inst - 指令的最大数量。
traceflags - traceflags传递给VEX。 默认值:0

以下参数是针对于unicorn-engine(用于构建模拟环境)的。
参数:extra_stop_points - unicorn应该停止的地址集合,以及unicorn停止的默认程序点(例如,hook点)。
返回:生成的SimulationManager。

返回类型:SimulationManager

代码分析

@ImmutabilityMixin.immutable
    def step(self, n=None, selector_func=None, step_func=None, stash=None,
             successor_func=None, until=None, filter_func=None, **run_args):
        stash = stash or 'active'
        l.info("Stepping %s of %s", stash, self)
        # 8<----------------- Compatibility layer -----------------
        if n is not None or until is not None:
            if once('simgr_step_n_until'):
                print "\x1b[31;1mDeprecation warning: the use of `n` and `until` arguments is deprecated. " \
                      "Consider using simgr.run() with the same arguments if you want to specify " \
                      "a number of steps or an additional condition on when to stop the execution.\x1b[0m"
            return self.run(stash, n, until, selector_func=selector_func, step_func=step_func,
                            successor_func=successor_func, filter_func=filter_func, **run_args)
        # ------------------ Compatibility layer ---------------->8
        bucket = defaultdict(list)

        for state in self._fetch_states(stash=stash):#根据stach name取出里面的state

            goto = self.filter(state, filter_func)#通常为none 返回state对应的stach
            if isinstance(goto, tuple):
                goto, state = goto

            if goto not in (None, stash):
                bucket[goto].append(state) #如果得到的stach 不在 stash里面 则将这个goto staches 添加到bucket里面
                continue

            if not self.selector(state, selector_func): #如果state不满足selector的函数,则跳过不执行 selector_func可以帮我们过滤一些state
                bucket[stash].append(state)
                continue

            pre_errored = len(self._errored)
            successors = self.step_state(state, successor_func, **run_args) #调用step_state函数,获取所有的successors
            if not any(successors.itervalues()) and len(self._errored) == pre_errored: #如果successors为None 则将这个state添加到deadended的state中
                bucket['deadended'].append(state)
                continue   #跳过

            for to_stash, successor_states in successors.iteritems():
                bucket[to_stash or stash].extend(successor_states)

        self._clear_states(stash=stash)
        for to_stash, states in bucket.iteritems():
            self._store_states(to_stash or stash, states)

        if step_func is not None:
            return step_func(self)
        return self

上述代码中的注释已经十分清楚啦,直接看关键的函数step_state()

代码如下:

def step_state(self, state, successor_func=None, **run_args):
        """
        Don't use this function manually - it is meant to interface with exploration techniques.
        """
        try:
            successors = self.successors(state, successor_func, **run_args) #直接调用的就是successors函数 获取所有的successors
            stashes = {None: successors.flat_successors, 
                       'unsat': successors.unsat_successors,
                       'unconstrained': successors.unconstrained_successors}# 根据successors的类型,分类到不同的stash中

        except (SimUnsatError, claripy.UnsatError) as e:
            if self._hierarchy:
                self._hierarchy.unreachable_state(state)
                self._hierarchy.simplify()
            stashes = {'pruned': [state]}

        except tuple(self._resilience) as e:
            self._errored.append(ErrorRecord(state, e, sys.exc_info()[2]))
            stashes = {}

        return stashes 

这里面主要的函数就是 self.successors了,它的代码为:

 def successors(self, state, successor_func=None, **run_args):
        """
        Don't use this function manually - it is meant to interface with exploration techniques.
        """
        if successor_func is not None:
            return successor_func(state, **run_args)
        return self._project.factory.successors(state, **run_args)#可以看出 直接调用的是factory中的successors方法来获得所有的successors

explore(stash=None, n=None, find=None, avoid=None, find_stash='found', avoid_stash='avoid', cfg=None, num_find=1, step_func=None, **kwargs)

explore函数功能就是查找满足或者不满足条件的staches,并存储起来。

向前拖动“stash”(最多“n”次或直到找到“num_find”个状态),查找条件“find”,避免条件“avoid”。 存储发现的状态到“find_stash”,并存储避免的状态到“avoid_stash”。

“find”和“avoid”参数可以是以下任何一种:
1.要寻找的地址
2.一个set集合或list列表,包含要查找的地址
3.一个函数,它接受一个状态并返回它是否匹配。
4.如果一个angr的CFG(控制流图)作为“cfg”参数传入并且“find”是一个数字或一个列表或一个集合,那么任何不经过失败状态都不可能达到成功状态的状态将被抢先避免(这句话好绕)。

explore函数内容比较短,贴出来看一下:

 def explore(self, stash=None, n=None, find=None, avoid=None, find_stash='found', avoid_stash='avoid', cfg=None, num_find=1, step_func=None, **kwargs):
        num_find += len(self.stashes[find_stash]) if find_stash in self.stashes else 0
        tech = exploration_techniques.Explorer(find=find,
                                   avoid=avoid,
                                   find_stash=find_stash,
                                   avoid_stash=avoid_stash,
                                   cfg=cfg,
                                   num_find=num_find)
        self.use_technique(tech)
        out = self.run(stash=stash,
                       step_func=step_func,
                       n=n,
                       **kwargs)
        out.remove_tech(tech)
        self.remove_tech(tech)
        return out

调用了exploration_techniques.Explorer()函数,获得了tech,然后调用use_technique()函数,最后调用self.run()函数获得out,最后返回out。根据后面run函数的分析,我们发现其实out就是新的simulation manager对象,这个对象中包含了名称为found和avoid的stashes。

use_technique(tech)

使用此SimulationManager的探索技术tech。各种技术tech可以在angr.exploration_techniques中找到。
参数:tech - 包含用于修改此SimulationManager行为的代码的ExplorationTechnique对象

返回:与传入相同的tech实例。允许在调用use_technique内部编写ExplorationTechnique 构造器调用,并仍然保留对这个technique的引用。

run(stash=None, n=None, step_func=None, **kwargs)

根据当前的exploration technique运行,直到SimulationManager达到完成状态。
TODO:step_func不能用于验证,因为veritesting替换了默认的步骤逻辑。
参数:
stash - 在这个stash处操作
n - step的最多次数
step_func - 如果提供,应该是一个带有SimulationManager并返回一个新的SimulationManager的函数。将在每个步骤中使用当前的SimulationManager调用。
返回:
由此产生的SimulationManager。
返回类型:

SimulationManager

run的代码为:

 def run(self, stash=None, n=None, step_func=None, **kwargs):
        if len(self._hooks_complete) == 0 and n is None:
            l.warn("No completion state defined for SimulationManager; stepping until all states deadend")

        until_func = lambda pg: self.completion_mode(h(pg) for h in self._hooks_complete)
        return self.step(n=n, step_func=step_func, until=until_func, stash=stash, **kwargs)

对SimulationManager分析到这里有些迷茫。已知确定的是,SimulationManager管理的是stashes,访问stashes时只需要.name即可(例如访问默认的为.active,访问explore中发现的stashes为.found,访问explore中避免的stashes为.avoid)。stashes是states的集合,包含了很多的state,这些state是什么关系目前还不知道,猜测是一条路径上的所有states集合,毕竟simulation manager 是也是通过path_group函数得到的,所以本身simulation manager就是path的集合。

在模拟程序运行过程中本质也就是状态state在变化,step过程改变的就是SimulationManger对象中stashes也就是state的集合,从一个状态集合stashes更新到另一个状态集合stashes。至于如何在state上执行,则需要了解SimEngine这个对象的代码了。




你可能感兴趣的:(物联网,漏洞挖掘)