Simulation Managers

原文

angr中最重要的控制接口是SimulationManager,它允许你同时控制一组状态的符号执行,应用搜索策略来探索程序的状态空间。在这里,您将学习如何使用它。
模拟管理器让您能够以一种灵活的方式处理多个状态。状态被组织成“stashes”,您可以向前推进、过滤、合并和移动。例如,这允许您以不同的速度将两个不同的状态隐藏起来,然后将它们合并在一起。大多数操作的默认存储区是活动存储区,也就是初始化新的模拟管理器时状态被放置的地方。

Steppeng

模拟管理器最基本的功能是将给定stash区中的所有状态向前步进一个基本块。您可以使用.step()来完成此操作。

>>> 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
[]

当然,stash模型的真正强大之处在于,当状态遇到符号分支条件时,两个后续状态都会出现在stash状态中,并且可以同步执行这两个状态。当您不需要非常小心地控制分析,并且您只是想逐步执行,直到没有任何东西需要逐步执行时,您可以使用.run()方法。

# Step until the first symbolic branch
>>> while len(simgr.active) == 1:
...    simgr.step()

>>> simgr

>>> simgr.active
[, ]

# Step until everything terminates
>>> simgr.run()
>>> simgr

我们现在有3个死状态!例如,当一个状态在执行期间无法产生任何后继状态,因为它到达了一个exit系统调用,它将从活动的stash中移除,并放置在终止的stash中。

Stash Management

让我们看看如何使用其他stash。
要在堆栈之间移动状态,请使用.move(),它接受from_stash、to_stash和filter_func(可选,默认是移动所有内容)。例如,让我们移动输出中包含特定字符串的所有内容:

>>> simgr.move(from_stash='deadended', to_stash='authenticated', filter_func=lambda s: b'Welcome' in s.posix.dumps(1))
>>> simgr

我们可以创建一个名为“authenticated”的新存储区,只需请求将状态转移到它。这里的所有状态在其标准输出中都有“Welcome”,这是一个很好的度量标准。
每个存储只是一个列表,您可以对列表进行索引或迭代以访问每个独立的状态,但也有一些替代方法来访问这些状态。如果你在一个隐藏的名称前加上one_,你将会得到隐藏的第一个状态。如果你在一个stash的名字前面加上mp_,你会得到一个stash的 mulpyplexed版本。

>>> 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路径的方法都可以接受一个stash参数,指定要对哪个stash进行操作。
模拟管理器为您提供了许多有趣的工具来管理您的存储。我们现在不讨论其他的,但是您应该查看API文档。待办事项:链接

Stash types

您可以使用任何您喜欢的stash,但是有一些stash将用于对某些特殊类型的状态进行分类。这些都是:

Stash Description
active 此stash包含默认步进的状态,除非指定了另一个stash。
deadended 当一个状态由于某种原因不能继续执行时(包括没有更多的有效指令、所有后继指令的unsat状态或一个无效的指令指针),它就会进入deadended stash。
pruned 使用LAZY_SOLVES时,除非绝对必要,否则不会检查状态是否满足。当在LAZY_SOLVES存在的情况下发现状态是unsat时,就会遍历状态层次结构,以确定它在历史中最初是何时变成unsat的。所有该点的后代状态(也将是unsat,因为一个状态不能成为un-unsat)都被修剪并放入这个隐藏点。
unconstrained 如果save_unconstrained选项被提供给SimulationManager构造函数,则被确定为不受约束的状态(例如,由用户数据或其他一些符号数据源控制的指令指针)被放置在这里。
unsat 如果将save_unsat选项提供给SimulationManager构造器,则将被确定为不能满足的状态(即,它们具有相互矛盾的约束,例如输入必须同时是“AAAA”和“BBBB”)放置在这里。

还有一个不是储藏的状态列表:errored。如果在执行期间引发了一个错误,那么状态将被包装在一个ErrorRecord对象中,该对象包含状态和它引发的错误,然后记录将被插入到error中。您可以用record.state在记录中获得导致错误的执行周期开始时的状态。您可以用record.error看到错误。您可以使用record.debug()在错误所在的站点启动一个调试shell。这是一个非常宝贵的调试工具!

Simple Exploration

符号执行中一个非常常见的操作是找到到达某个地址的状态,同时丢弃经过另一个地址的所有状态。模拟管理器为这种模式提供了一个快捷方式,即.explore()方法。
当用find参数启动.explore(),执行器将运行直到找到一个find条件相匹配的state,可以在一条指令的地址停止,一个地址列表停止,或一个需要一个状态并返回它是否满足某些标准的函数。当活动隐藏中的任何状态与查找条件匹配时,它们将被放置在已找到的stash中,执行将终止。然后,您可以探索找到的状态,或者决定放弃它,继续使用其他状态。您还可以使用与find相同的格式指定避免条件。当一个状态匹配避免条件时,它会被放入避免的stash区,然后继续执行。最后,num_find参数控制在返回之前应该找到的状态数,默认值为1。当然,如果您在找到这么多解决方案之前用完活动存储区中的状态,执行将会停止。

让我们看看一个简单的crackme例子:

首先,我们加载二进制文件。

>>> proj = angr.Project('examples/CSCI-4968-MBE/challenges/crackme0x00a/crackme0x00a')

接下来,我们创建一个SimulationManager。

>>> simgr = proj.factory.simgr()

现在,我们象征性地执行,直到找到符合条件的状态(即“获胜”条件)。

>>> simgr.explore(find=lambda s: b"Congrats" in s.posix.dumps(1))

现在,我们可以把flag从那个state拿出来了!

>>> s = simgr.found[0]
>>> print(s.posix.dumps(1))
Enter password: Congrats!

>>> flag = s.posix.dumps(0)
>>> print(flag)
g00dJ0B!

很简单,不是吗?
其他的例子可以通过浏览这些例子找到。

Exploration Techniques

angr附带了几个固定的功能,可以让你自定义模拟管理器的行为,称为探索技术。为什么需要探索技术的典型例子是修改探索程序状态空间的模式——默认的“一步一步”策略实际上是广度优先搜索,但是使用探索技术,您可以实现深度优先搜索。然而,这些技术的仪表功能要比这灵活得多——您可以完全改变angr的步进过程的行为。编写您自己的探索技术将在后面的章节中介绍。

要使用探索技术,调用simgr.use.technique(tech),其中tech是ExplorationTechnique子类的一个实例。angr内置的探索技术可以在angry .exploration_techniques中找到。
以下是一些内置插件的快速概述:

  • DFS:深度优先搜索,如前所述。一次只保持一个状态为活动状态,将其余状态放在延迟隐藏中,直到它终止或出错。
  • 资源管理器:这种技术实现了.explore()功能,允许您搜索和避免地址。
  • LengthLimiter:对状态经过的路径的最大长度设置上限。
  • LoopSeer:使用一个合理的近似循环计数来丢弃那些看起来经过循环太多次的状态,把它们放在一个旋转的容器中,如果我们用完其他可行的状态,再把它们取出来。
  • ManualMergepoint:将程序中的一个地址标记为一个合并点,因此到达该地址的状态将被短暂地保持,而任何其他在超时内到达同一点的状态将被合并在一起。
  • MemoryWatcher:在simgr步骤之间监视系统上有多少空闲/可用内存,如果内存过低就停止探索。
  • Oppologist:“辩护者行动”是一个特别有趣的小玩意——如果启用了这种技术和动物遗传资源遇到一个不受支持的指令,例如一个大胖和外国浮点SIMD op,它将使具体化所有指令的输入和模拟单指令使用独角兽引擎,允许继续执行。
  • Spiller:当活动状态太多时,该技术可以将其中一些状态转储到磁盘,以保持较低的内存消耗。
  • Threading:将线程级并行性添加到步进过程中。由于python的全局解释器锁,这并没有多大帮助,但如果你有一个程序,它的分析花了很多时间在angr的本地代码依赖(unicorn, z3, libvex)上,你可以获得一些好处。
  • Tracer:一种探测技术,它使执行遵循从其他来源记录的动态跟踪。动态跟踪程序存储库有一些工具来生成这些跟踪。
  • Veritesting:一篇CMU论文关于自动识别有用的合并点的实现。这是非常有用的,你可以在SimulationManager构造函数中用verittesting =True自动启用它!请注意,由于它实现静态符号执行的侵入性方式,它经常不能很好地与其他技术配合。

有关更多信息,请参阅模拟管理器和探索技术的API文档。

你可能感兴趣的:(Simulation Managers)