在最初分析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=None, arch=None, plugins=None, memory_backer=None, permissions_backer=None, mode=None, options=None, add_options=None, remove_options=None, special_memory_filler=None, os_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中介绍。