Simulation Managers

Last updated on May 26, 2023 am

Stepping Stash-Management Simple-Exploration Exploration-Techniques

The most important control interface in angr is the SimulationManager, which allows you to control symbolic execution over groups of states simultaneously, applying search strategies to explore a program’s state space. Here, you’ll learn how to use it.

概括:angr里面最重要的控制接口就是SimulationManager,可以以此同时控制不同的states符号执行,我们也可以以此来执行我们的搜索策略

Simulation managers let you wrangle multiple states in a slick way. States are organized into “stashes”, which you can step forward, filter, merge, and move around as you wish. This allows you to, for example, step two different stashes of states at different rates, then merge them together. The default stash for most operations is the active stash, which is where your states get put when you initialize a new simulation manager.

概括:我们可以用用Simulation Managers来管理多个states,States又是由stashes组成,这些stashes我们可以步入、过滤、融合甚至移到另一个stashes,对于绝大多数的stashes都是active

note: 这里一直说的stashes其实就是一种状态标志,意味这个block是否还能继续走下去,比如说一个block是active的,那么它就还有后继而且还能继续往下执行。所以当我们初始丢给Simulation Manager的state应该是active的,这样它Simulation Manager才会为我们模拟执行

Stepping

一个简单的demo

我们使用.step()来让当前的state执行一步,去到下一个block。demo中给出了两个block的地址

1
2
3
4
5
6
7
8
9
10
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>]

你一定想问,那如果一个state的后继不止有一个呢?答案是.step()都会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
# Step until the first symbolic branch
>>> while len(simgr.active) == 1: #只有当前处于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>

如果我们不关心中间过程,只希望它能一直跑下去,直到再也没有后继块可以执行,那么我们可以用.run()

现在我们使用了.run()得到最终的state,它有三个stash,并且都是deadended状态,也就是跑不动了

Stash Management

我们可以用.move()来转移状态,它有三个参数from_stash,to_statsh,以及一个可选参数filter_func

1
2
3
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>

这个demo让我们把输出含有Welcome的state转移到authenticated,也就是说authenticated里的state都含有Welcome

每一个stash都是一个列表,我们可以用常规的去索引,也可以用其他方法。如果我们加一个前缀one_那么我们就会访问这个stash的第一个元素,如果用mp_就可以同时访问全部,关于mp_这有一个说明

1
2
3
4
5
6
7
8
9
10
11
12
13
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'])

Of course, step, run, and any other method that operates on a single stash of paths can take a stash argument, specifying which stash to operate on.

也就是所有的step,run方法都可以接受一个stash参数,来决定执行哪一个

Stash types

You can use stashes for whatever you like, but there are a few stashes that will be used to categorize some special kinds of states. These are:

Stash Description
active 可执行的
deadended 再也无法执行的,可能是因为无法到达,没有后继等
pruned (不太确定)当使用LAZY_SOLVES时,states不会再检查可满足性,当一个state发现是unsat时,这个states路径上的所有states都会变成pruned
unconstrained 如果 save_unconstrained 传递给SimelationManager constructor, 那些无约束的就时unconstrained
unsat 如果 save_unsat 传递给SimelationManager constructor, 那些无解的就时unsat,比如要求输入同时满足即是’AAAA’又是’BBBB’

Simple Exploration

符号执行很普遍的用法是找到一个可以到达某个地址的states,而忽略其他所有的states,angr提供.explore()来方便执行。

当我们使用.explore()方法时,它可以接受两个参数find以及avoid。其中find可以是一个地址,一个列表,一个以state为参数返回某些值的函数,当任何一个active符合find的要求,那么它就会被放入foundstash里面,同时停止执行,avoid一样,不过是放入avoidedstash里面,然后继续符号执行。num_find可以决定找多少个found,简单demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 装载
proj = angr.Project('examples/CSCI-4968-MBE/challenges/crackme0x00a/crackme0x00a')
# 创建对象
simgr = proj.factory.simgr()
# 开始符号执行 搜索
simgr.explore(find=lambda s: b"Congrats" in s.posix.dumps(1))
<SimulationManager with 1 active, 1 found>
# 找到一个 利用索引的方式得到该state
s = simgr.found[0]
# 查看该state的输出
print(s.posix.dumps(1))
Enter password: Congrats!
# 查看该state的输入
flag = s.posix.dumps(0)
print(flag)
g00dJ0B!

Exploration Techniques

angr内置了很多种的搜索算法,但是我们也可以自己写一个搜索算法,并通过simgr.use_technique(tech)调用,这里的techExplorationTechnique子类。后面会再详谈,这里先列出angr内置的一些搜索算法

  • DFS: 深度优先,只会让一个state为active,把其他的放进deferredstash
  • Explorer: 实现.explore(),可以使用find,avoid方法
  • LengthLimiter:最长路径截断
  • LoopSeer: 如果可能在一个循环里面,放入spiningstash,直到其他可达路径都走完
  • ManualMergepoint: 标记一个地址作为merge点,当一个stata到达此处时将其挂起,当其他state在某个时间内到达这个点时会被merge
  • Memory:在.step()之间监视内存释放/使用情况,如果内存释放/使用得少的话,停止探索
  • Oppologist: 当angr遇到一个不支持的指令的时候,会具体化所有的输入,并使用unicorn引擎来单独模拟这条指令,使得可以继续符号执行
  • Spiller: 当有太多states处于active状态时,这可以把一些states放进磁盘中,使得内存开销比较小
  • Threading: 使用多线程并行处理,但由于python全局变量线程锁的原因,这并不会有多大提升。但是如果在angr的native-code依赖(z3,unicorn,libvex)上花太多时间可以考虑这项技术
  • Tracer:可以动态追踪资源
  • Veritesting:自动识别有用的可合并点,使用veritesting=True来启用,这会提高搜索效率,但与其他算法配合得并不是很好

Look at the API documentation for the SimulationManager and ExplorationTechnique classes for more information.


Simulation Managers
http://example.com/2023/05/24/Simulation-Managers/
Author
yring
Posted on
May 24, 2023
Licensed under