本博客由闲散白帽子胖胖鹏鹏胖胖鹏潜力所写,仅仅作为个人技术交流分享,不得用做商业用途。转载请注明出处,未经许可禁止将本博客内所有内容转载、商用。
0x00 官方API说明和SimState的构建函数
在前面一篇博客中,我们介绍了Angr的一种简单地使用方法。再接下来的几天中,我阅读了Angr的部分源码,包括factory类、Angr.Project类(新建工程的过程)、cle.Loader类(二进制文件的加载过程),这部分内容我推荐Conquer病毒写的博客。接下来我对求解引擎比较感兴趣,想要分析一下Claripy的源码,可是一直有个疑问,在factory类中反复提到了SimState、SimulationManager类,我就比较好奇这两个类的实现。所以这次我们先跟着官方API学习下SimState的源码,源码位置在angr/sim_state.py。
根据官方API的定义
SimState翻译: class angr.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代表着程序运行状态,包括内存、寄存器、等等。 参数: regs – 状态的寄存器的方便表示方法,每个寄存器都是一个属性 mem – 状态的内存的方便表示,是这样一个类angr.state_plugins.view.SimMemView registers – 状态寄存器文件的平坦存储区域 memory – 状态内存的平坦存储区域 solver – 状态符号求解器和变量管理器 inspect – 断点管理器,是一个 angr.state_plugins.inspect.SimInspector log – 状态历史信息 scratch – 当前执行步骤的信息 posix – MISNOMER: 操作系统或者环境模型的表示(一般用于stdin,stdout) libc – 仿真时使用的标准库信息 cgc – cgc 环境的相关信息 uc_manager – 控制约束内符号执行 unicorn – 控制 Unicorn引擎
但是这只是官方API,代码中有了更新,而之前Conquer病毒写的SimState部分的源码分析已经不再适用。更新流程可以参考此issue。我们首先来看一下SimState的init函数。
class SimState(PluginHub, ana.Storable): #继承自PluginHub类,稍后介绍 def __init__(self, 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, plugin_preset='default'): #调用PluginHub的init方法 super(SimState, self).__init__() #设置project、架构、选项等等 self.project = project self.arch = arch if arch is not None else project.arch.copy() if project is not None else None if type(self.arch) is str: self.arch = arch_from_id(self.arch) # the options if options is None: if mode is None: l.warning("SimState defaulting to symbolic mode.") mode = "symbolic" options = o.modes[mode] if isinstance(options, (set, list)): options = SimStateOptions(options) if add_options is not None: options |= add_options if remove_options is not None: options -= remove_options self._options = options self.mode = mode #检查预加载插件 if plugin_preset is not None: self.use_plugin_preset(plugin_preset) #如果参数提供了新的plugin就把他加入我们的SimState里 if plugins is not None: for n,p in plugins.iteritems(): self.register_plugin(n, p, inhibit_init=True) for p in plugins.itervalues(): p.init_state() if not self.has_plugin('memory'): # Angr不指定内存的地址上下线,因为内存地址的范围不像寄存器一样容易构造 # 如果给Angr提供内存插件或者预加载插件的话,Angr默认使用default插件 if self.plugin_preset is None: self.use_plugin_preset('default') #根据options里面的memory选线加载不同的memory插件 if o.ABSTRACT_MEMORY in self.options: # We use SimAbstractMemory in static mode. # Convert memory_backer into 'global' region. if memory_backer is not None: memory_backer = {'global': memory_backer} # TODO: support permissions backer in SimAbstractMemory sim_memory_cls = self.plugin_preset.request_plugin('abs_memory') sim_memory = sim_memory_cls(memory_backer=memory_backer, memory_id='mem') elif o.FAST_MEMORY in self.options: sim_memory_cls = self.plugin_preset.request_plugin('fast_memory') sim_memory = sim_memory_cls(memory_backer=memory_backer, memory_id='mem') else: sim_memory_cls = self.plugin_preset.request_plugin('sym_memory') sim_memory = sim_memory_cls(memory_backer=memory_backer, memory_id='mem', permissions_backer=permissions_backer) self.register_plugin('memory', sim_memory) if not self.has_plugin('registers'): # 寄存器插件也和内存插件的情况相同 if self.plugin_preset is None: self.use_plugin_preset('default') if o.FAST_REGISTERS in self.options: sim_registers_cls = self.plugin_preset.request_plugin('fast_memory') sim_registers = sim_registers_cls(memory_id="reg", endness=self.arch.register_endness) else: sim_registers_cls = self.plugin_preset.request_plugin('sym_memory') sim_registers = sim_registers_cls(memory_id="reg", endness=self.arch.register_endness) self.register_plugin('registers', sim_registers) # 设置系统名 self.os_name = os_name # This is used in static mode as we don't have any constraints there self._satisfiable = True # states are big, so let's give them UUIDs for ANA right away to avoid # extra pickling self.make_uuid() self.uninitialized_access_handler = None self._special_memory_filler = special_memory_filler # this is a global condition, applied to all added constraints, memory reads, etc self._global_condition = None self.ip_constraints = []
这里印证了官方API说明中memory和register的字符串,那么剩下的功能在哪里提供呢?代码里面反复提到的Plugin是什么呢?
0x02Angr的插件机制
这里说一些无关的内容,最开始看的时候,我以为官方API和写错了,而且跟着代码阅读的时候在我觉得报错的地方正常运行。我在这里卡了两天。直到我意识到,Python的类和实例是有区别的。从我的角度来看,Python中类也是一个对象;实例可以继承类的属性;在Java中,类不是一个对象,只是对象的生成的模板。实例的属性完全来自于类的定义。Python中类的属性,是类似于Java 中的静态变量,是可以进行修改的。这一点要记住,很重要。
好说回源码,我们先看SimState的定义,他继承自PluginHub类。PluginHub类官方说明是这样的。
“Plugin Hub对象包括了很多插件,同时有一个preset概念,preset能够提供插件的基础实现,并且能够直接附加到某一环境中。
在Angr中,像SimState、Analyses hub、SIMEngine selector等都是用这个模型,用来统一自动搜集和选择插件。采用了可配置的策略模式(设计模式)。
每个PluginHub子类若想要提供比默认插件更丰富的功能,则要有至少一个对应的plugin子类,或者一个PluginPreset子类。”
也就是说,PluginHub就像是插槽一样,在SimState上面加载了一个插槽,我们想要扩展、增加SimState的功能,只需要专心于开发插件即可,PluginHub类会自动为我们插上插件。我们再来看看是怎么插上去的。先抛开SimState的定义部分,查看官方代码的842-843行(这部分是类定义之外的代码,这部分代码在import的时候就执行了)。
default_state_plugin_preset = PluginPreset() SimState.register_preset('default', default_state_plugin_preset)
这种代码的方法,SimState就是我提到的Python和Java的区别,这里调用的是类方法,修改的是类属性。以后再实例化任何该类的时候,我们修改的属性就会保留修改的状态。PluginPreset类的定义和PluginHub同在angr/misc/plugins.py下。饭一口一口吃,先来学习下PluginPreset类的定义。
class PluginPreset(object): """ plugin preset对象包含着插件名和插件类的字典。 preset在hub上面激活之后,就可以处理插件的相关请求(激活、反激活、增、删等) 与Plugin和PluginHub类不同,每一个单独的PluginPreset实例都应定义在模组级别。 开发者应该讲preset实例添加到hub里面,并且允许插件方便将自己添加到preset列表里,而不需要显示引用preset。 """ 属性:_default_plugins :默认的插件列表 方法: activate: 调用此方法激活preset deactivate: 调用此方法反激活preset add_default_plugin: 在preset中增加一个插件 list_default_plugins(): 返回preset中可用的插件 request_plugin(self, name): 查找名为name的插件是否存在于preset中,如果没有就返回错误;否则返回插件类。 copy(self): 获得PluginPreset的一个拷贝
以及register_preset方法的定义
@classmethod def register_preset(cls, name, preset): #注册一个preset实例,以及他所属的hub。它允许单个插件使用preset的classmethod进行自动化的注册,注册时仅使用插件的名字。 if cls._presets is None: cls._presets = {} cls._presets[name] = preset
说白了Angr一次性将你可能需要的所有的插件都放到preset里面,当你需要使用此插件时,只要提供插件的名字,PluginPreset以及PluginHub类会自动为你管理。以及SimState的一些功能(比如memory、register、unicorn等等API提供的功能)都是通过Preset进行管理的。那842-843行就是为我们的SimState所使用的PluginHub注册了一个preset实例。代码看到这里,应该有所了解了,但是,我刚才说了他提供了我所需要的所有插件,可是我只看到了一个default插件,没看到其他的东西呀。这里还是通过修改类属性完成的。在state_plugins包中包含了我们所需要的所有SimState插件,那怎么加载的?请看代码。
###angr/state_plugins/solver.py### #随便打开一个solver插件,这也是我们所关心的 #还是跳过定义查看最后几行 892:from angr.sim_state import SimState 893:SimState.register_default('solver', SimSolver) #跳转到register_default函数定义 ###angr/misc/plugins.py### 34:@classmethod 35:def register_default(cls, name, plugin_cls, preset='default'): 36: if cls._presets is None or preset not in cls._presets: 37: l.error("Preset %s does not exist yet...", preset) 38: return 39: cls._presets[preset].add_default_plugin(name, plugin_cls) #add_default_plugin在PluginPreset类中有定义 227:def add_default_plugin(self, name, plugin_cls): """ Add a plugin to the preset. """ 231: self._default_plugins[name] = plugin_cls #这里大家应该明白了单个的Plugin应该怎么加入的了, #紧接着看看这些插件怎么一起加进去的 ###angr/state_plugins/__init__.py ### from .plugin import * from .libc import * from .posix import * from .inspect import * from .solver import * from .symbolic_memory import SimSymbolicMemory from .abstract_memory import * from .fast_memory import * from .log import * from .history import * from .scratch import * from .cgc import * from .gdb import * from .uc_manager import * from .unicorn_engine import Unicorn from .sim_action import * from .sim_action_object import * from .sim_event import * from .callstack import * from .globals import * from .preconstrainer import * from .loop_data import * from .view import * from .filesystem import *
emmmm,这里就分析完了插件的加载过程。总结一下,Angr通过PluginHub和PluginPreset预加载所有的需要用到的插件,而SimState通过继承PluginHub类在预加载的插件中选择自己需要的进行使用。我们在扩展Angr功能时,只需要关心插件本身的功能逻辑即可,Angr会自动进行插件的添加。
0x03 solver插件
我开头说了,我比较好奇求解的过程,所以我们就看下solver的代码。代码位置:angr/state_plugins/solver.py。目前在求解的时候,用到eval这个函数比较多,关于solver的API可以移步官方,提供了很多求解方法,我们从源码角度分析一下。以eval为例。
def eval(self, e, **kwargs): # eval_upto already throws the UnsatError, no reason for us to worry about it return self.eval_upto(e, 1, **kwargs)[0] #函数调用了eval_upto,继续跟进 def eval_upto(self, e, n, cast_to=None, **kwargs): #value的正确性检查先不管 concrete_val = _concrete_value(e) if concrete_val is not None: #调用了_cast_to指定输出格式 return [self._cast_to(e, concrete_val, cast_to)] #使用_eval求解 cast_vals = [self._cast_to(e, v, cast_to) for v in self._eval(e, n, **kwargs)] if len(cast_vals) == 0: raise SimUnsatError('Not satisfiable: %s, expected up to %d solutions' % (e.shallow_repr(), n)) return cast_vals #我们继续跟进_eval函数 def _eval(self, e, n, extra_constraints=(), exact=None): return self._solver.eval(e, n, extra_constraints=self._adjust_constraint_list(extra_constraints), exact=exact) #在这里我们发现他使用_solver进行求解,我们跟进这个定义 @property def _solver(self): """ 根据state的选项,创建或者获取一个Claripy的求解器 """ 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.SYMBOLIC in self.state.options and o.REPLACEMENT_SOLVER in self.state.options: self._stored_solver = claripy.SolverReplacement(auto_replace=False) elif o.SYMBOLIC in self.state.options and o.CACHELESS_SOLVER in self.state.options: self._stored_solver = claripy.SolverCacheless(track=track) elif o.SYMBOLIC in self.state.options and 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
从以上代码可以看出,实际上的求解工作,还是Claripy进行的,我们使用的solver实际上是对Claripy的封装,以便于更好的和Angr兼容。最后,我更正一下Angr求解的工作流程。
Angr求解的流程: 1.创建Claripy.BVS或者Claripy.BVV格式的符号化变量 2.使用factory获取程序的state,可以是程序入口状态(entry_state,也可以从任意函数开始的state,甚至是blank_state) 3.state加载memory、register、solver等插件 4.solver插件根据state的参数设置,创建Claripy求解器 5.使用step等方式获得程序的最终状态 6.获取从初始state到最终state之间的约束条件 7.使用Claripy.Solver.eval进行求解 关于如何提取约束条件、Claripy求解这部分我们我们目前还不清楚,等待以后阅读源码