4、angr的路径管理机制:SimState、SimulationManager、路径优先级与探索策略
路径爆炸,说白了就是状态太多,内存撑不住。我刚开始用angr的时候,经常跑着跑着就OOM了。后来才发现,问题出在路径管理上——你让angr怎么管这些状态,它就会怎么爆炸。
这一节,我们来聊聊angr的路径管理核心。说白了就三样东西:SimState(状态怎么存)、SimulationManager(状态怎么管)、探索策略(先跑哪个状态)。搞懂了这三样,你就能控制路径爆炸的节奏。
4.1 SimState:每个路径的“快照”
每个路径,本质上就是一个SimState对象。它记录了程序执行到某一点时的全部信息:寄存器、内存、符号约束、文件系统、网络状态……嗯,你可以把它理解成虚拟机的一个快照。
我个人习惯把SimState拆成三部分来看:
- 寄存器与内存:
state.regs和state.mem,存的是具体数值或符号变量 - 符号约束:
state.solver.constraints,记录了这个路径必须满足的条件 - 插件系统:比如
state.fs(文件系统)、state.posix(标准输入输出)
举个例子,你创建一个初始状态:
import angr
proj = angr.Project('binary')
state = proj.factory.entry_state()
这个state就是程序入口处的快照。angr每遇到一个分支,就会复制一份当前状态,然后分别加上不同的约束。复制多了,路径就爆炸了。
核心要点:SimState是angr路径管理的最小单位。你管理的不是路径,而是一堆SimState。
4.2 SimulationManager:状态的“调度器”
有了状态,谁来管它们?SimulationManager(简称simgr)就是干这个的。
我刚开始用angr时,直接写了个循环手动管理状态,结果代码又臭又长。后来发现simgr已经封装好了——它维护了一个状态列表,并且提供了各种探索方法。
创建simgr很简单:
simgr = proj.factory.simulation_manager(state)
simgr内部维护了多个“状态桶”:
| 桶名 | 含义 | 说明 |
|---|---|---|
active | 活跃状态 | 还能继续执行的状态 |
deadended | 死胡同 | 执行到exit或无法继续的状态 |
errored | 出错状态 | 执行过程中抛异常的状态 |
found | 目标状态 | 满足你设定条件的状态 |
你想想看,每次simgr.step(),angr就会把active里的每个状态往前推一步。遇到分支,状态就分裂。遇到exit,状态就移到deadended。
避坑指南:我曾经在循环里反复调用simgr.step(),结果状态数指数级增长。后来我改用simgr.run()并设置until条件,才控制住局面。
4.3 路径优先级:谁先跑,谁后跑
状态多了,先处理哪个?这就涉及到路径优先级了。angr默认是深度优先(DFS),也就是一条路走到黑,不行再回头。
但深度优先有个问题——如果路径很深,你可能永远跑不到其他分支。我遇到过这种情况:一个循环跑了1000次,angr就在那一个分支里死磕,其他分支全被晾着。
angr提供了几种优先级策略:
- 深度优先(DFS):默认策略,优先探索最深的状态
- 广度优先(BFS):优先探索最浅的状态,适合找最短路径
- 符号执行优化:根据状态复杂度排序,比如约束少的优先
设置优先级其实很简单:
# 使用广度优先
simgr.use_technique(angr.exploration_techniques.DFS()) # 默认就是DFS
simgr.use_technique(angr.exploration_techniques.BFS()) # 改成BFS
嗯,这里要注意:use_technique可以叠加使用。比如你先用DFS跑一会儿,再切到BFS,或者混合使用。
4.4 探索策略:不止是DFS和BFS
angr内置了十几种探索策略。我挑几个常用的说说:
| 策略名 | 作用 | 适用场景 |
|---|---|---|
Explorer | 找满足条件的状态 | 找指定地址、指定条件 |
LoopSeer | 检测循环,限制循环次数 | 防止循环导致路径爆炸 |
LengthLimiter | 限制路径长度 | 防止无限递归 |
Spiller | 把状态溢出到磁盘 | 内存不够时用 |
举个例子,你想找程序里某个地址:
simgr.explore(find=0x4001024)
这行代码背后,angr自动用了Explorer策略。它会优先探索可能到达目标地址的路径,其他路径暂时放一放。
注意:explore不是万能的。如果目标地址被大量分支包围,它依然可能爆炸。我建议配合LoopSeer一起用,先限制循环次数。
4.5 知识体系:一张图看懂路径管理
说了这么多,我们来画个图总结一下。下面这张SVG图展示了angr路径管理的核心逻辑:
这张图其实就说明了一件事:路径管理 = 状态快照 + 调度器 + 优先级策略。你控制好这三个环节,路径爆炸就能被有效抑制。
4.6 实战建议
最后,我分享几个实战中的小技巧:
- 别让状态无限增长:每次
step()后检查len(simgr.active),超过阈值就暂停或丢弃 - 善用
Spiller:内存不够时,把不活跃的状态写到磁盘,需要时再读回来 - 组合使用策略:比如
LoopSeer+Explorer,既能防循环爆炸,又能快速找到目标 - 手动干预:如果自动策略不理想,可以直接操作
simgr.active列表,手动排序或删除状态
个人经验:我一般会在step()循环里加个计数器,每100步打印一次状态数。一旦发现状态数超过1000,就立刻启用Spiller或LengthLimiter。这招帮我避免了好几次OOM。
路径管理,说白了就是跟状态数做斗争。你理解了SimState怎么存、SimulationManager怎么管、策略怎么选,就能在这场斗争中占据主动。下一节,我们会深入探讨如何用LoopSeer和Spiller来具体解决路径爆炸问题。
公众号:蓝海资料掘金营,微信deep3321