Angr-Learn-0x5
注意
本文可以理解为官方文档的简单翻译+一部分个人理解
模拟管理
模拟管理器可让您以灵活的方式处理多个状态。状态被组织成“stashes”,您可以根据需要向前、过滤、合并和移动。例如,这允许您以不同的速率步进两个不同的状态存储,然后将它们合并在一起。大多数操作的默认存储是存储active
,这是在初始化新的模拟管理器时放置状态的位置。
步进
模拟管理器最基本的功能是将给定存储中的所有状态向前推进一个基本块。我们可以用.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
[<SimState @ 0x400580>]
simgr.step()
simgr.active
[<SimState @ 0x400540>]
本例中,给定的状态是proj.factory.entry_state()
,
当然,存储模型的真正威力在于,当一个状态遇到符号分支条件时,两个后继状态都会出现在存储中,并且您可以同步执行这两个状态。可以使用该.run()
方法来解决并不用真正关心非常仔细地控制分析、只想逐步执行直到没有什么可执行的情况。
# Step until the first symbolic branch
>>> while len(simgr.active) == 1:
... simgr.step()
>>> simgr
<SimulationManager with 2 active>
>>> simgr.active
[<SimState @ 0x400692>, <SimState @ 0x400699>]
# Step until everything terminates
>>> simgr.run()
>>> simgr
<SimulationManager with 3 deadended>
本例中有 3 个死亡状态!当一个状态在执行过程中无法产生任何后继时,例如,因为它到达了exit
系统调用,它就会从活动存储中删除并放入deadended
存储中。
状态管理
现在我们来了解不同状态集是如何一起工作的。
使用.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
<SimulationManager with 2 authenticated, 1 deadended>
本例的目的是将状态移动到一个名为authenticated的状态集,此状态集的所有状态都在输出中包含Welcome
。
每个存储只是一个列表,您可以索引或迭代列表来访问每个单独的状态,但也有一些替代方法来访问状态。如果您在存储的名称前面加上one_
,您将获得存储中的第一个状态。如果您在存储的名称前面加上mp_
,您将获得该存储的多重版本。
for s in simgr.deadended + simgr.authenticated:
print(hex(s.addr))
0x1000030
0x1000078
0x1000078
simgr.one_deadended
<SimState @ 0x1000030>
simgr.mp_authenticated
MP([<SimState @ 0x1000078>, <SimState @ 0x1000078>])
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 | Description |
---|---|
active | This stash contains the states that will be stepped by default, unless an alternate stash is specified. |
deadended | A state goes to the deadended stash when it cannot continue the execution for some reason, including no more valid instructions, unsat state of all of its successors, or an invalid instruction pointer. |
pruned | When using LAZY_SOLVES , states are not checked for satisfiability unless absolutely necessary. When a state is found to be unsat in the presence of LAZY_SOLVES , the state hierarchy is traversed to identify when, in its history, it initially became unsat. All states that are descendants of that point (which will also be unsat, since a state cannot become un-unsat) are pruned and put in this stash. |
unconstrained | If the save_unconstrained option is provided to the SimulationManager constructor, states that are determined to be unconstrained (i.e., with the instruction pointer controlled by user data or some other source of symbolic data) are placed here. |
unsat | If the save_unsat option is provided to the SimulationManager constructor, states that are determined to be unsatisfiable (i.e., they have constraints that are contradictory, like the input having to be both “AAAA” and “BBBB” at the same time) are placed here. |
简单的路径探索
符号执行中一个极其常见的操作是找到到达某个地址的状态,同时丢弃所有经过另一个地址的状态。模拟管理器有一个该模式的快捷方式,即.explore()
方法。
.explore()
当使用参数启动时find
,执行将一直运行,直到找到与查找条件匹配的状态,该状态可以是要停止的指令的地址、要停止的地址列表或接受状态并返回是否它符合一些标准。当活动存储中的任何状态与find
条件匹配时,它们将被放置在 found
存储中,并且执行终止。然后,您可以探索找到的状态,或者决定放弃它并继续其他状态。您还可以使用avoid
与 相同的格式指定条件find
。当状态与避免条件匹配时,它会被放入avoided
存储中,并继续执行。最后,该num_find
参数控制返回之前应找到的状态数,默认值为 1。当然,如果在找到这么多解决方案之前用完了活动存储中的状态,则执行无论如何都会停止。
探索技巧
angr
附带了一些固定功能,可以让您自定义模拟管理器的行为,称为探索技术。为什么您需要探索技术的典型示例是修改程序状态空间探索的模式 - 默认的“一次执行所有内容”策略实际上是广度优先搜索,但使用探索技术您可以例如,实现深度优先搜索。然而,这些技术的仪器功能比这要灵活得多 - 您可以完全改变 angr
的步进过程的行为。编写自己的探索技术将在后面的章节中介绍。
要使用探索技术,请调用simgr.use_technique(tech)
,其中 tech 是 ExplorationTechnique
子类的实例。angr
的内置探索技术可以在 下找到angr.exploration_techniques
。
以下是一些内置功能的快速概述:
DFS
:深度优先搜索,如前所述。一次仅保持一种状态处于活动状态,将其余状态放入deferred
存储中,直到它死机或出错。Explorer
:此技术实现了.explore()
功能,允许您搜索和避开地址。LengthLimiter
:对状态经过的路径的最大长度设置上限。LoopSeer
:使用循环计数的合理近似来丢弃似乎多次循环的状态,将它们放入存储中spinning
,并在我们用完其他可行状态时再次将它们拉出。ManualMergepoint
:将程序中的一个地址标记为合并点,因此到达该地址的状态将被短暂保留,并且在超时内到达同一点的任何其他状态将被合并在一起。MemoryWatcher
:监视 simgr 步骤之间系统上有多少内存可用/可用,如果内存太低,则停止探索。Oppology
:“操作辩护者”是一个特别有趣的小工具 - 如果启用此技术并且angr
遇到不受支持的指令,例如奇怪的外国浮点 SIMD 操作,它将具体化该指令的所有输入并模拟单个指令使用独角兽引擎,允许执行继续。Spiller
:当有太多活动状态时,该技术可以将其中一些状态转储到磁盘,以保持较低的内存消耗。线程
:为步进过程添加线程级并行性。由于 Python 的全局解释器锁,这并没有多大帮助,但如果您有一个程序,其分析花费大量时间在angr
的本机代码依赖项(unicorn
、z3
、libvex
)上,您似乎会有所收获。Tracer
:一种探索技术,使执行遵循从其他来源记录的动态跟踪。动态跟踪器存储库有一些工具可以生成这些跟踪。Veritesting
: [CMU 论文](https://users.ece.cmu.edu/~dbrumley/pdf/Avgerinos et al._2014_Enhancing Symbolic Execution with Veritesting.pdf) 的自动识别有用合并点的实现。这非常有用,您可以veritesting=True
在SimulationManager
构造函数中自动启用它!请注意,由于它以侵入性方式实现静态符号执行,因此它通常无法与其他技术很好地配合。