迈向自动化的第二步——
0xFF Reference
1. Symbolic Execution Leaning Note (1) — Basis
2. 2020年Angr-Api非官方介绍
3. angr_ctf里介绍angr的ppt(讲的真的挺好的)
0x00 Recall-Symbolic Execution
笔者水平有限,如有错误请指出 ;-)
我们先回顾一下什么是符号执行:
我们在逆向一个程序、去找一个程序的漏洞时,我们会希望找到一个input
,来触发我们需要的漏洞或者找到我们需要的flag,而符号执行的功能就是通过遍历程序所有可能的“状态(State)”,找到我们需要的State,进而通过约束求解器,求解出我们最后需要的input
宏观上看,符号执行可以分为三步
- 注入符号
- 分支
- 执行分支
我们用一个简单的函数来分别看一下这三步是什么1
2
3
4
5
6
7
8
9// demo func
Line1 void check_func(char* passwd)
Line2 {
Line3 if(passwd == "HeyGap")
Line4 printf("Access granted");
Line5 else
Line6 printf("Access denied");
Line7 }
1. 注入符号(Inject Symbols)
- 什么是注入符号?
我们在具体执行
的时候,传入的passwd是一个具体的字符串;可我们在符号执行
时,传入的是一个符号
。 - 什么是符号?
类比于我们小学学的方程x + 1 = 2
,我们可以通过这样一个方程来求解出变量x,我们也称“这个方程约束了这个变量x”;而符号
就是方程中的变量
,用来约束符号的执行路径
就可以类比成方程
- 什么是执行路径?
从State_A到State_B的执行路径
就是从起点A可以执行到终点B的一条指令(instructions)路径,在每条执行路径中都会维护一个符号变量pc
,pc记录了“从A点出发,要到达B的全部条件”,这个符号变量的类型是逻辑表达式,我们可以将这个符号变量扔到SMT求解器(比如z3)中去求解满足这个符号变量中所有约束条件的1~n个解 - Example
在上述例子中,我们将一个符号传入passwd中,就是注入符号。
2. 分支(Branching)
- 什么是branching
由于我们的符号是一个待确定的值,所以我们在遇到判断语句
时,当前的State会设置为执行完毕(already executed),然后激活(active)两个新的state,由于要遍历全部state,符号在判断语句中得到不同返回值,而这两个state就是不同返回值进入的不同分支的起始状态 - Example
在上述例子中,可以理解为Line1-2为state1,当执行到Line3时,state1设置为执行完毕,同时激活state2和state3,state2为Line4,state3为Line6
3. 评估分支(Evaluate Each Branches)
- 什么是Evaluate Branches?
我们会评估(Evaluate)每一个active的state,判断他们是否符合我们的需求,如果不满足就设置terminated,如果遇到判断语句就进入第二步 - Example
假设当前激活的state为2和3,如果我们需要“标准输出中包含’granted’”,那符号执行器就会评估state2,发现这个state符合我们的要求,于是将state2加到found数组中;然后再评估state3,发现不符合,设置为terminated
0x01 An Introduction to Angr
1. 出现的class
笔者在此记录相对重要的class,读者可以通过阅读源码或官方手册等方式,先弄清楚这些class的作用1
2SimState
Simulation Manager(simgr)
2. Symbolic Execution In Angr
通过0x00,我们知道在符号执行中,执行路径
和符号
是最重要的两个东西
Angr中的执行路径
- 我们知道每branch一次,就会添加两个state,而在Angr中,维护路径信息的pc由
SimState
Object组成,SimState用∩链在一起就组成了pc - 由于我们要遍历全部state,所以我们需要有一个管理器来管理全部执行路径(a Set of Path),这个管理器就是
Simulation Manager (simgr)
,下图为Angr中生成全部路径的过程 - 上图提到
until we find what we want
,simgr提供了函数explore
,可以让我们通过两种方式来找到我们需要的state- 通过指令在text段的地址
- 通过任意体现state特征的函数
- 我们知道每branch一次,就会添加两个state,而在Angr中,维护路径信息的pc由
State Explosion
- 但是单纯的遍历每一条路径会存在状态爆炸(State Explosion,有些地方也叫路径爆炸)的问题,来看下面这个循环
1
2
3
4
5
6
7
8int x;
for(int 0;i<100;i++)
{
if(x > 10)
printf("Good");
else
printf("Wrong");
} - 当i=1时,管理器会创建两个state,而i=2时,先前的两个state有分别会创建两个state,当i=100时,我们就会出现2^100个state,这就是状态爆炸问题
- 而explore也为我们提供了一个参数avoid,效果就是
剪枝
,在explore中添加avoid参数会帮助我们提前terminate那些我们不需要的state,因此这个state后续的branch就不会再跟进
- 但是单纯的遍历每一条路径会存在状态爆炸(State Explosion,有些地方也叫路径爆炸)的问题,来看下面这个循环
0x01 库架构分析
1 | angr |
0x02 API手册
Project类(参数、功能、重载、返回值)
- 构造函数参数:“命令行中运行程序的指令”(eg: “./00_angr_find”)
- 功能:“构建一个符号执行项目”
- 附属成员
- factory
- 附属成员
- state
- 重载函数
- entry_state()
- 返回值:程序入口点
- blank_state(addr)
- 参数:模拟执行开始的地址
- entry_state()
- 附属成员
- regs
- 附属成员: 各种寄存器
- fs
- 全称: filesystem
- 附属成员:
- insert(string filename,angr.storage.simFile)
- 参数1:文件名称
- 参数2:创建过的符号化文件对象
- insert(string filename,angr.storage.simFile)
- regs
- 重载函数
- simulation_manager类
- 构造函数参数:起始状态
- 功能:设置模拟执行的起始地址,并返回实例化对象
- 附属成员
- explore(find, avoid)
- 参数find:希望程序抵达的地址
- 参数avoid:希望程序不抵达的地址
- 功能:用符号探索到find指向的地址,并避免avoid指向的地址
- 重载:explore(find=func1,avoid=func2)
1
2
3
4def func1(state):
return b"Good Job!" in state.posix.dumps(1)
def func2(state):
return b"Try Again!" in state.posix.dumps(1)- 功能:探索具有func1特征的的函数分支,回避具有func2特征的函数分支
- found数组
- found_state的实例化数组
- explore(find, avoid)
- state
- 附属成员
- factory
found_state类
- posix
- dumps(int std)
- 参数std: stdin(0)/stdout(1)/stderr(2)
- 功能: 将std指向的内容打印出来
- dumps(int std)
- solver
- eval(claripy.BVS arg1)
- 参数:要求解的约束公式
- 功能:求解arg1并返回结果
- eval(claripy.BVS arg1)
- posix
claripy类
- BVS类
- 构造函数参数:arg1: 别名 | arg2: 符号向量占多少位
- 功能:构建一个空的约束公示
- BVS类
storage类
- SimFile(filename,content,size)
- 参数filename:要引入的filename
- 参数content:符号化向量
- 参数size:要从文件中读取的字节数*8(单位:bits)
- 功能:创建一个符号化的文件对象
- SimFile(filename,content,size)
sim_options类
- SYMBOL_FILL_UNCONSTRAINED_MEMORY
- 功能:自动用符号填充未约束的内存(模拟过程中没有明确值的内存位置)
- SYMBOL_FILL_UNCONSTRAINED_REGISTERS
- 功能:自动用符号填充未约束的寄存器
- SYMBOL_FILL_UNCONSTRAINED_MEMORY
0x01 Angr语法
1. 准备阶段
引入与初始化
1
2
3
4
5
6
7
8import angr
p = angr.Project(exec_path)
init_state = p.factory.entry_state()
# init_state = p.factory.blank_state(addr) # addr = 0xbeef
sm = p.factory.simulation_manager(init_state)命令行引入参数
1
2
3
4
5
6
7
8
9import sys
def main(argv):
arg1 = argv[1]
...
if __name__ == '__main__':
main(sys.argv)符号向量
1
2
3import claripy
pass1 = claripy.BVS('pass1', 32)符号化寄存器
1
init_state.regs.eax = pass1
符号化文件
1
2
3
4
5
6
7filename = ""
filesize =
password = init_state.solver.BVS('password',filesize*8)
sim_file = angr.storage.SimFile(filename,content=password,size=filesize)
init_state.fs.insert(sim_file)
2. 探索阶段
探索指定地址并查看标准流
1
sm.explore(find = addr_to_find)
利用函数explore分支
1
2
3
4
5
6
7
8def is_good(state):
return b"Good Job!" in state.posix.dumps(1)
def is_bad(state):
return b"Try Again!" in state.posix.dumps(1)
sm.explore(find=is_good, avoid=is_bad)
# sm.explore(find=addr_find, avoid=addr_avoid)
3. 输出阶段
查看结果
1
2
3
4if sm.found:
found_state = sm.found[0]
print('[x] Solution: {}'.format(found_state.posix.dumps(0)))求解寄存器的符号向量
1
result = init_state.solver.eval(BVS)