模拟管理器(Simulation Managers) =================== angr 中最重要的控制接口是 SimulationManager,它允许你同时控制多个状态的符号执行,应用搜索策略来探索程序的状态空间。在这里,你将学习如何使用它。 模拟管理器让你以一种巧妙的方式管理多个状态。状态被组织成“存储区”,你可以根据需要前进、过滤、合并和移动这些存储区。这使你能够以不同的速率前进两个不同的状态存储区,然后将它们合并在一起。大多数操作的默认存储区是 ``active`` 存储区,这是你初始化新的模拟管理器时状态被放置的地方。 Stepping ^^^^^^^^ 模拟管理器最基本的功能是将给定存储区(stash)中的所有状态向前推进一个基本块。你可以使用 ``.step()`` 来实现这一点。 .. code-block:: python >>> import angr >>> proj = angr.Project('examples/fauxware/fauxware', auto_load_libs=False) >>> state = proj.factory.entry_state() >>> simgr = proj.factory.simgr(state) >>> simgr.active [] >>> simgr.step() >>> simgr.active [] 当然,存储区模型的真正强大之处在于,当一个状态遇到符号分支条件时,两个后继状态都会出现在存储区中,你可以同步推进它们。当你不太关心精确控制分析,只是想一步步执行直到没有可执行的步骤时,你可以使用 ``.run()`` 方法。 .. code-block:: python # Step until the first symbolic branch >>> while len(simgr.active) == 1: ... simgr.step() >>> simgr >>> simgr.active [, ] # Step until everything terminates >>> simgr.run() >>> simgr 我们现在有 3 个 deadended 状态!当一个状态在执行过程中未能产生任何后继状态时,例如,因为它到达了一个 ``exit`` 系统调用,它将从 active 存储区中移除并放置在 ``deadended`` 存储区中。 存储区(Stash)管理 ^^^^^^^^^^ 让我们看看如何处理其他存储区。 要在存储区之间移动状态,请使用 ``.move()``,它接受 ``from_stash``、 ``to_stash`` 和 ``filter_func``(可选,默认是移动所有内容)。例如,让我们移动输出中包含某个字符串的所有状态: .. code-block:: python >>> simgr.move(from_stash='deadended', to_stash='authenticated', filter_func=lambda s: b'Welcome' in s.posix.dumps(1)) >>> simgr 我们能够通过请求将状态移动到新创建的名为 "authenticated" 的存储区中。该存储区中的所有状态在其 stdout 中都有 "Welcome",这目前是一个不错的指标。 每个存储区只是一个列表,你可以索引或迭代该列表以访问每个单独的状态,但也有一些替代方法来访问这些状态。如果在存储区名称前加上 ``one_``,你将获得存储区中的第一个状态。如果在存储区名称前加上 ``mp_``,你将获得该存储区的 `mulpyplexed `_ 版本。 .. code-block:: python >>> for s in simgr.deadended + simgr.authenticated: ... print(hex(s.addr)) 0x1000030 0x1000078 0x1000078 >>> simgr.one_deadended >>> simgr.mp_authenticated MP([, ]) >>> simgr.mp_authenticated.posix.dumps(0) MP(['\x00\x00\x00\x00\x00\x00\x00\x00\x00SOSNEAKY\x00', '\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x80\x80\x80\x80@\x80@\x00']) 当然, ``step`` 、 ``run`` 和任何其他操作单个存储区路径的方法都可以接受一个 ``stash`` 参数,指定要操作的存储区。 模拟管理器为你提供了许多有趣的工具来管理你的存储区。我们现在不会介绍它们的全部内容,但你应该查看 :ref:`API 参考手册 ` : 首先,我们加载二进制文件。 .. code-block:: python >>> proj = angr.Project('examples/CSCI-4968-MBE/challenges/crackme0x00a/crackme0x00a') 接下来,我们创建一个 SimulationManager。 .. code-block:: python >>> simgr = proj.factory.simgr() 现在,我们符号执行直到找到符合我们条件的状态(即,“win”条件)。 .. code-block:: python >>> simgr.explore(find=lambda s: b"Congrats" in s.posix.dumps(1)) 现在,我们可以从该状态中获取标志! .. code-block:: python >>> s = simgr.found[0] >>> print(s.posix.dumps(1)) Enter password: Congrats! >>> flag = s.posix.dumps(0) >>> print(flag) g00dJ0B! 很简单,不是吗? 其他示例可以通过浏览 :ref:`examples ` 找到。 探索机制 -------- angr 附带了几种预定义的功能,称为 *探索机制* ,可以让你自定义模拟管理器的行为。 其中典型的例子是修改程序状态空间的探索模式——默认的“同时执行所有步骤”策略(即广度优先搜索),使用探索机制,你可以实现例如深度优先搜索。不过,这些技术的能力远不止于此——你可以完全改变 angr 的 step 过程。 如何编写你自己的探索机制将在后面的章节中介绍。 要使用探索机制,请调用 ``simgr.use_technique(tech)``,其中 tech 是 ExplorationTechnique 子类的一个实例。 angr 的内置探索机制可以在 ``angr.exploration_techniques`` 下找到。 以下是一些内置机制的快速概述: * *DFS* : 深度优先搜索,如前所述。一次只保持一个状态处于活动状态,将其余状态放入 ``deferred`` 存储区,直到它们死锁或出错。 * *Explorer* : 该机制实现了 ``.explore()`` 功能,允许你搜索和避开地址。 * *LengthLimiter* : 限制状态路径的最大长度。 * *LoopSeer* : 使用合理的循环计数近似值丢弃似乎经过太多次循环的状态,将它们放入 ``spinning`` 存储区,如果没有其他可行状态,则重新提取它们。 * *ManualMergepoint* : 将程序中的一个地址标记为合并点,到达该地址的状态将被短暂保留,并在超时内到达相同点的其他状态将被合并。 * *MemoryWatcher* : 监视 simgr 步骤之间系统上可用的内存量,如果内存过低则停止探索。 * *Oppologist* : “操作辩护者”是一个特别有趣的小工具——如果启用此技术并且 angr 遇到不支持的指令,例如奇怪的外来浮点 SIMD 操作,它将具体化该指令的所有输入,并使用 unicorn 引擎模拟单个指令,从而允许执行继续。 * *Spiller* : 当有太多状态处于活动状态时,该技术可以将其中一些状态转储到磁盘以保持内存消耗较低。 * *Threading* : 为步进过程添加线程级并行性。这并没有太大帮助,因为 Python 的全局解释器锁,但如果你的程序分析在 angr 的本地代码依赖项(unicorn、z3、libvex)中花费了大量时间,你可以看到一些收益。 * *Tracer* : 一种探索机制,使执行遵循从其他来源记录的动态跟踪。`动态跟踪器库 `_ 有一些生成这些跟踪的工具。 * *Veritesting* : 实现了 `CMU 论文 `_ 中关于自动识别有用合并点的内容。这非常有用,你可以在 SimulationManager 构造函数中通过 ``veritesting=True`` 自动启用它!请注意,由于它实现静态符号执行的侵入方式,它经常与其他技术不兼容。 查看 :py:class:`~angr.sim_manager.SimulationManager` 和 :py:class:`~angr.exploration_techniques.ExplorationTechnique` 类的 API 文档以获取更多信息。