angr源码分析——SimState

在最初分析angr的符号执行流程时,我们首先介绍了angr中claripy的应用代码,如下:

import angr, claripy  
b = angr.Project('/bin/true')  
path = b.factory.path()  
rax_start = claripy.BVS('rax_start', 64)  
path.state.regs.rax = rax_start  
path_new = path.step()[0]  //step()返回的是SimSuccessors对象
rax_new = path_new.state.regs.rax  
path_new.state.se.add(rax_new == 1337)  
print path_new.state.se.eval(rax_start, 1)[0]  

通过分析claripy.ast.bv.py文件,我们知道了如何创建一个符号变量,BVS或BVV。然后我们知道在执行符号执行前,需要提供程序分析的初始状态,与获取程序状态有关的类是factory,并且对factory里面包含的函数进行了介绍。结果我们发现factory类中函数返回值总是涉及到SimState和SimulationManager这两个类。(希望以后能画个图,把这一段话说明白。。。)所以今天我们首先对SimState进行介绍。

classangr.sim_state.SimState(project=Nonearch=Noneplugins=Nonememory_backer=Nonepermissions_backer=Nonemode=Noneoptions=Noneadd_options=Noneremove_options=Nonespecial_memory_filler=Noneos_name=None)

SimState表示一个程序的状态,包括内存和寄存器等。

1. 变量解释

    :ivar regs:         状态的寄存器的方便表示, 其中每一个寄存器都是一个property(属性)
    :ivar mem:          状态的内存的方便表示, 一个类:`angr.state_plugins.view.SimMemView`
    :ivar registers:    状态的寄存器文件作为平坦的存储区域。
    :ivar memory:       状态的内存,一个平坦的存储区域。
    :ivar solver:       该状态的符号求解器和变量管理器
    :ivar inspect:      断点管理器, 一个类:`angr.state_plugins.inspect.SimInspector`
    :ivar log:          关于该状态的历史信息
    :ivar scratch:      关于当前执行步骤的信息
    :ivar posix:        MISNOMER: 关于操作系统或环境模型的信息
    :ivar libc:         关于我们正在模拟的标准库的信息
    :ivar cgc:          关于cgc环境的信息
    :ivar uc_manager:   控制无约束的符号执行
    :ivar unicorn:      控制Unicorn Engine(参考链接:https://skymz.cc/?post=466)

使用SimState时,其实我们更关心它的属性以及方法。

2. 属性

部分简单属性:
ip 
(self.regs.ip)
指令指针表达式,触发SimInspect断点,并生成SimAction
_ip 
(self.regs._ip)
指令指针表达式,不触发SimInspect断点。
addr 
(self.se.eval_one(self.regs._ip))
指令指针的具体地址,而不触发SimInspect断点或生成SimActions。一个整数,如果指令指针是符号,则会抛出异常。

通过插件访问的属性:

@property
    def memory(self):
        return self.get_plugin('memory')

    @property
    def registers(self):
        return self.get_plugin('registers')

    @property
    def se(self):
        return self.get_plugin('solver_engine')

    @property
    def solver(self):
        return self.get_plugin('solver_engine')

    @property
    def inspect(self):
        return self.get_plugin('inspector')

    @property
    def log(self):
        return self.get_plugin('log')

    @property
    def scratch(self):
        return self.get_plugin('scratch')

    @property
    def history(self):
        return self.get_plugin('history')

    @property
    def posix(self):
        return self.get_plugin('posix')

    @property
    def libc(self):
        return self.get_plugin('libc')

    @property
    def cgc(self):
        return self.get_plugin('cgc')

    @property
    def regs(self):
        return self.get_plugin('regs')

    @property
    def mem(self):
        return self.get_plugin('mem')

    @property
    def gdb(self):
        return self.get_plugin('gdb')

    @property
    def globals(self):
        return self.get_plugin('globals')

    @property
    def uc_manager(self):
        return self.get_plugin('uc_manager')

    @property
    def unicorn(self):
        return self.get_plugin('unicorn')

    @property
    def preconstrainer(self):
        return self.get_plugin('preconstrainer')
    
    @property
    def callstack(self):
        return self.get_plugin('callstack')

这时候,我们可能会对plugin是什么感兴趣。plugin的基类是class angr.state_plugins.plugin.SimStatePlugin。当状态发生分支时,SimState插件将于状态一起复制。它们旨在用于诸如跟踪打开文件,跟踪heap详细信息,并为SimProcedures提供存储和持久性功能。这样说起来非常笼统,等到后面遇到关于plugin使用时,再进行详细描述。

了解了SimState的属性后,我们也要关心状态的方法,也就是状态能做些什么,或对状态能做什么。

3. 方法:

simplify(*args)

简化这个状态的约束条件。

add_constraints(*args, **kwargs)

向状态添加一些约束。可以将任意数量的符号布尔值作为可变的位置参数传递。

satisfiable(**kwargs)

状态的约束是否满足

downsize()

在求解器引擎之后进行清理。当一个状态不再需要被解决时调用它,将减少内存的使用。

step(**kwargs)

使用这个状态执行一个符号执行步骤。AngrObjectFactory.successors的任何参数可以由这个函数传递。也就是说,它仅是对successors函数进行调用。

返回值:一个SimSuccessors对象,对步骤的结果进行分类。(如何在一个状态上进行符号执行,是以这个状态为起点吗?那么路径是什么,结束条件是什么?通过观察successors的源码,我们发现succesors()函数目的是返回一个能运行的引擎engine。调用engine.check(state, inline=inline, **kwargs)和engine.process(state, inline=inline,**kwargs),判断引擎是否执行。第一个能执行的就会作为结果返回。)

block(*args, **kwargs)

表示这个状态的指令指针中的基本块。AngrObjectFactory.block的任何参数可以通过这个函数传递。也就是说它仅对block()函数进行调用。

返回值:一个块对象,描述了这一点的基本代码块。

copy()

复制这个状态。

merge(*others, **kwargs)

将这个状态与其他状态合并。返回合并结果,合并状态和合并标志。
参数:
state - 要合并的状态
merge_conditions - 一个元组,每个状态持有的条件。
common_ancestor - 表示正在合并的状态之间的公共历史记录的状态。通常只有在启用EFFICIENT_STATE_MERGING时才可用,否则弱状态可能会从状态历史记录实例中删除。
plugin_whitelist - 将被合并的插件名称列表。如果给出该选项并且不是无,则不在该列表内的任何插件将不被合并,并且将在新状态中被创建为新实例。
common_ancestor_history - 一个SimStateHistory实例,表示被合并状态之间的公共历史记录。这是为了在EFFICIENT_STATE_MERGING被禁用时允许最佳状态合并。
返回:
(合并状态,合并标志,指示是否发生合并的布尔变量)

widen(*others)

在本身和其他状态之间进行扩展。
参数:其他状态。其具体与merge()函数的区别还不清楚。

reg_concrete(*args, **kwargs)

返回一个寄存器的内容,但如果该寄存器是符号的,则引发一个SimValueError。

mem_concrete(*args, **kwargs)

返回一个内存的内容,但如果该内存是符号的,则引发一个SimValueError。

stack_push(*args, **kwargs)

把“thing”push到堆栈,写到内存中,然后调整堆栈指针。

stack_pop(*args, **kwargs)

从堆栈弹出并返回弹出的内容。长度将是架构字的大小。

stack_read(*args, **kwargs)

在堆栈中offset偏移位置,读取长为length的字节。
参数:
偏移——堆栈指针的偏移量。
长度——要读取的字节数。

bp——如果是true,offset来于BP而不是SP。默认为false。

dbg_print_stack(depth=None, sp=None)

仅用于调试目的。以格式化的字符串返回当前的堆栈信息。如果depth为None,则当前的堆栈帧(从sp到bp)将被打印出来。

4. 插件

下面主要说一下SimState的插件plugin,以插件solver为例。(注:源代码中 se() 和 solver() 返回的是一个插件)

在SimState中,访问插件solver的代码为:

@property
    def solver(self):
        return self.get_plugin('solver_engine')

get_plugin(name)函数的代码为:

def get_plugin(self, name):
        if name not in self.plugins:
            p = default_plugins[name]()
            self.register_plugin(name, p)
            return p
        return self.plugins[name]
代码中default_plugins是一个字典,根据name返回相应的插件对象p。default_plugins初始化的位置为: angr/state_plugins/plugin.py
 @staticmethod
    def register_default(name, cls):
        if name in default_plugins:
            raise Exception("%s is already set as the default for %s" % (default_plugins[name], name))
        default_plugins[name] = cls

这个plugin.py中的类angr.state_plugins.plugin.SimStatePlugin我们在前面提及过,是所有插件的基类,所以插件可以调用register_default(name, cls)方法,将自己注册到default_plugins字典中。例如,solver插件的注册代码如下:

SimStatePlugin.register_default('solver_engine', SimSolver)

每个插件对象又包含了很多自己的方法,其中最为重要的插件就是solver了,下面介绍它的关键方法。

5. solver插件

solver插件的代码位于:angr/angr/state_plugins/solver.py  class SimSolver(SimStatePlugin):中

该插件是我们用来与符号变量交互,创建与评估它们的插件。它在state.solver下可用。claripy模块的任何顶层变量都可以作为这个对象的属性访问。(也就是说可以通过这个对象对claripy中顶层变量进行访问,如backends)

下面介绍这个插件中常用方法:

reload_solver()

重新加载求解器,在改变求解器选项时有用。

get_variables(*keys)

迭代所有名称以key为前缀的变量。元素是一个元组,第一个元素是完整的tracking key,第二个元素是符号。(API上有例子)

register_variable(v, key, eternal=True)

在变量追踪系统中注册一个值。
参数:
v - 要注册的BVS(符号变量)
key - 要在其下注册变量的元组
eternal -这是否是一个永恒的变量,默认为True。如果为False,则会在该按键上附加递增计数器

describe_variables(v)

给定一个AST(抽象语法树),迭代树中注册的所有BVS叶节点的key。

eval_to_ast(**kwargs)

求解一个表达式,必要时使用求解器。返回一个AST对象。
参数:
e -表达式
n -所需解决方案的数量
extra_constraints -应用于求解器的额外约束
exact -如果为false,则返回近似求解。
返回值:
元组表示的解决方法,以claripy AST节点的形式出现。

eval_upto(e, n, cast_to=None, **kwargs)

求解一个表达式,必要时使用求解器。返回由cast_to参数指定的primitives(原语)。只支持某些primitives,请检查_cast_to的实现以查看有哪些primitives。
参数:
e - 表达式
n - 所需解决方案的数量
extra_constraints - 应用于求解器的额外约束
exact - 如果为false,则返回近似解
cast_to - 指定将结果值转换为的类型
返回:
这些解决方案的一个元组,以Python 原语的形式出现
返回类型:
元组

eval(e, **kwargs)

评估一个表达式以获得任何可能的解决方案。所需的输出类型可以使用cast_to参数指定。extra_constraints可以用来指定返回值必须满足的附加约束。
参数:
e - 表达式
kwargs - 任何额外的kwargs将传递给eval_upto
Raises:

SimUnsatError - 如果找不到满足给定约束的解决方案。

该函数是对eval_upto的一个特殊调用,其中设置n=1.

eval_one(e, **kwargs)

求解表达式以获得唯一可能的解决方案。如果没有或多个解决方案返回,则会出现错误。kwarg参数默认值可以被指定为返回而不是失败!
参数:
e - 要求解的表达式
default - 一个值可以在这里作为kwarg传递。如果失败,它将被return。
kwargs - 任何额外的kwargs将传递给eval_upto
Raises:
SimUnsatError - 如果找不到满足给定约束的解决方案
SimValueError - 如果发现多个解决方案满足给定约束
返回:
表达式的值。

eval_atmost(e, n, **kwargs)

求解一个表达式以获得最多n个可能的解决方案。如果没有或超过n个解决方案,则会出错。
         :参数e:要求解的表达式
         :参数n:解决方案数量的包含上限
         :param kwargs:任何额外的kwargs都会传递给`eval_upto`
         :raises SimUnsatError:如果找不到满足给定约束条件的解决方案
         :raises SimValueError:如果发现多于'n`个解决方案满足给定约束
         :return:表达式的解决方案

eval_atleast(e, n, **kwargs)

求解一个表达式以获得至少n个可能的解决方案。如果少于n个解决方案,则会出错。
         :参数e:要求解的表达式
         :参数n:解决方案数量的包含下限
         :param kwargs:任何额外的kwargs都会传递给`eval_upto`
         :raises SimUnsatError:如果找不到满足给定约束条件的解决方案
         :raises SimValueError:如果发现少于'n`个解决方案满足给定约束
         :return:表达式的解决方案

eval_exact(e, n, **kwargs)

求解一个表达式以获得正好n个可能的解决方案。如果获得除n个以外的解决方案,则会出错。
         :参数e:要求解的表达式
         :参数n:解决方案数量的包含下限
         :param kwargs:任何额外的kwargs都会传递给`eval_upto`
         :raises SimUnsatError:如果找不到满足给定约束条件的解决方案
         :raises SimValueError:如果发现不等于'n`个解决方案满足给定约束
         :return:表达式的解决方案

其实所有以eval为前缀的函数,最终调用的都是eval_upto函数,进行求解。那么如果想了解angr是如何求解的就可以深入这个函数去看一下了。

其实solver插件就是claripy模块的一个实例,可以使用的solver有如下:

@property
    def _solver(self):
        if self._stored_solver is not None:
            return self._stored_solver

        track = o.CONSTRAINT_TRACKING_IN_SOLVER in self.state.options

        if o.ABSTRACT_SOLVER in self.state.options:
            self._stored_solver = claripy.SolverVSA()
        elif o.REPLACEMENT_SOLVER in self.state.options:
            self._stored_solver = claripy.SolverReplacement(auto_replace=False)
        elif o.CACHELESS_SOLVER in self.state.options:
            self._stored_solver = claripy.SolverCacheless(track=track)
        elif o.COMPOSITE_SOLVER in self.state.options:
            self._stored_solver = claripy.SolverComposite(track=track)
        elif o.SYMBOLIC in self.state.options and o.approximation & self.state.options:
            self._stored_solver = claripy.SolverHybrid(track=track)
        elif o.SYMBOLIC in self.state.options:
            self._stored_solver = claripy.Solver(track=track)
        else:
            self._stored_solver = claripy.SolverConcrete()

        return self._stored_solver

总结一下angr中符号执行的使用流程:

首先,通过claripy.BVS或BVV初始化一个符号变量或具体值,这个变量可以在获取程序状态是传入。然后利用factory获取一个程序状态state,可以是entry_state,也可以是blank_state。获取状态state的solver插件,solver的claripy求解器类型根据state.options中的选项而定。获取到求解器,就可以调用solver中的方法,添加约束条件,对约束条件求解等操作。目前还没涉及到路径,路径的探索在simulation manager中介绍。

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