有关Angr的正确学习路线

[持续更新中]

General speaking

两年前开始接触并使用Angr来做CTF逆向题目,AEG的有关工作也经常会使用Angr进行动态分析。但是始终没有对Angr做一次比较系统的总结和整理。Research开发需要,终于找到了自认为比较友好的Angr学习和使用路线。

首先,按照使用example案例学习使用Angr API十分不友好, 基本都需要从entry开始进行simulate,遇到字节输入或者循环,很容易出现状态空间爆炸,无法定位到关键的context。经常需要配合IDA静态分析调整脚本。对于初学者来说,很容易陷入困惑——哪些问题可以用Angr解决,哪些则不可以。并且Angr大量的文档和案例都在教学使用他经常改动的API(至少两年前是这样的)。大量的问题混杂在一起,导致即使成功写了脚本解决了某个问题,使用Angr还是无法游刃有余。

目前最佳的解决方案是配合gdb学习Angr。扫描gdb context然后自动生成Angr模拟的状态空间,这样子就可以绕开不重要的、但是Angr会受卡的代码,从某个break stop开始,将变量或者内存符号化,然后使用Angr的SimulatorManger进行单步模拟或者explore探索。配合Instrumentation && hook机制,可以在模拟执行的时候从不同的level搜集想要获得的dynamic trace。

只有能够自由地在模拟执行中操控(增删改查)expr和constraint,才算真正入门Angr!


Details

  • angr 执行的原理
  • angr 的数据结构以及执行流程
  • angr 什么时候fork新的state
  • 每个state下的expr和constraint如何提取
  • 注意对expr和constraint的区别(在claripy求解时)
  • vex IR 和 指令的对应关系,state的粒度
  • 关于expr的提取假设




程序正常执行过程是将instruction逐条送入CPU执行,操作内存和设备的过程。而angr模拟执行的过程恰似人类去阅读代码,一边阅读的过程一边做预测、抽象执行结果,预估“执行结果”。这个预估过程就是一个symbolic execution的过程。在angr模拟执行前,必须先将变量(内存或者寄存器)符号化(声明哪些变量是符号),这些符号化的输入也正是程序的输入抽象。模拟执行过程的目标是预测“执行结果”,当输入不影响输出结果的时候,符号化的输入不会影响到输出。但是输入影响输出结果,那么符号化的输入一定是通过指令执行影响了数据流或者控制流。符号随着指令进行传播的过程,可以看做由符号和运算符构成的表达式(expression),遇到条件分支的时候,表达式和关系符号共同形成一个约束(constraint)。理想情况下,一个程序的输出可以视作由输入符号和运算关系构成的表达式,以及各种条件约束:例如 当x<1且x>0时,y=x^2,其中 0<x<1为约束,x^2为表达式。所以angr的预期返回结果是”在XXX定义域下,会得到一个由输入做XXXXX变换得到的结果“。



angr重要的数据结构包括simstate、simmanage、simsolver等等。其中我们要认清概念——

  • 状态SimulateState
  • 模拟管理器SimulatorManager

SimulatorManager是angr模拟执行的控制中心,提供了.run , .explore, .step 等不同类型的执行选项。

SimulateState则是angr的最小分析单元,对应每个step都有一个state,每个state.solver.XXX都记录了当前符号执行记录的expr和constraint (state.solver.constraints)(这里expr怎么导出还不确定)。

模拟执行到相应的条件分支的时候,会用claripy(z3的angr封装)去checkSatiesfied或者eval求解。也就是我们常用到的求解相应输出的输入了。



import claripy
a = claripy.BVS("sym_val", 32)  # 表达式
b = claripy.RotateLeft(a, 8)  # 表达式
c = b + 4
s = claripy.Solver() 
s.add(c == 0x41424344) # 约束
assert s.eval(c, 1)[0] == 0x41424344 # s.eval(要求的表达式的具体值,)
assert s.eval(a, 1)[0] == 0x40414243



每个stmgr.step()对应的是vexIR上的一个基本块,但是可以break的应该是vexIR上的逐条指令。

而每个state则应该对应了一条x86指令。而vex IR的指令比x86指令要稀疏,即 一条x86指令降级到若干条vex IR指令。


目前,还不清楚state和expr的关系,据状态空间爆炸等现象,expr应该是跟着state的没跑了。问题在于通过阅读文档,从state出发似乎没有找到expr相关的api。

如果说expr是从pyvex上下文中取出来的,那么每次遇到条件分支,要建立constraint的话,就必须反向把符号化的指令搜索出来,显然这样子效率很低。expr应该是跟着step走的。这里需要去看一下angr源码。从step函数出发,看看遇到constraint时候的处理方式?


根据开发者的回复,angr是没有通用的expr接口的,expressions存储在storage媒介当中,关键词围绕registers和memory,据此寻找线索。

Angr在模拟过程中,完全仿冒了reg和mem的变化,如果reg和mem被符号化,就记录它的变化,然后如果形成constraint,就把变化链给提取出来。

编辑于 01-15

文章被以下专栏收录