1、angr初探:符号执行是什么?angr能做什么?安装angr与第一个angr脚本
各位同学好,我是老周。今天咱们开始聊angr符号执行。说实话,我第一次接触符号执行这个概念时,也是一头雾水。但别急,我保证用最接地气的方式给你讲明白。
1.1 符号执行到底是什么?
先说说传统程序分析是怎么做的。你写个程序,输入一个具体的值,比如密码是"123456",程序跑一遍,要么成功要么失败。这叫具体执行。
符号执行呢?说白了,就是把输入当成一个未知的符号,而不是具体的数字或字符串。程序跑的时候,遇到条件判断(比如if语句),它不急着选一条路走,而是把两条路都记下来,分别加上对应的约束条件。
举个例子:
if (input > 10) {
// 路径A
} else {
// 路径B
}
符号执行会怎么做?它会说:「好,如果input > 10,走路径A;如果input <= 10,走路径B。」然后它用约束求解器(比如Z3)去算,到底什么样的input值能让程序走到某条路径。
我个人习惯把符号执行想象成「程序侦探」——它不满足于只走一条路,而是要把所有可能的路都探一遍,然后告诉你每条路需要什么条件才能走通。
核心要点:符号执行 = 符号化输入 + 路径探索 + 约束求解。这三板斧砍下去,很多传统方法搞不定的问题就迎刃而解了。
1.2 angr能做什么?
angr是一个基于符号执行的二进制分析框架。它能做的事情,我列一下:
- 自动寻找路径:给定一个目标地址,angr能自动找到从入口到目标的所有可行路径
- 破解授权验证:比如软件需要输入序列号,angr能自动算出合法的序列号
- 漏洞挖掘:通过探索程序的所有路径,发现潜在的崩溃点
- 反混淆:对付一些简单的控制流混淆,angr可以直接还原原始逻辑
- 二进制插桩:在二进制级别插入分析代码
我在项目中遇到过最典型的场景:一个商业软件用了很复杂的注册算法,人工逆向要花好几天。用angr,写个几十行的脚本,几分钟就把合法的注册码算出来了。嗯,这就是符号执行的威力。
小提示:angr不是万能的。遇到路径爆炸(路径太多)、复杂系统调用、或者反调试很强的程序,angr也会头疼。但对付大多数软件保护,它已经够用了。
1.3 安装angr
安装angr其实很简单。我个人建议用Python 3.8或3.9版本,太新的版本可能会有兼容性问题。
# 推荐使用虚拟环境
python -m venv angr_env
source angr_env/bin/activate # Linux/Mac
# 或者 angr_env\Scripts\activate # Windows
# 安装angr
pip install angr
如果你在国内,网络可能有点慢。可以加个镜像:
pip install angr -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,验证一下:
python -c "import angr; print(angr.__version__)"
如果输出版本号,恭喜你,安装成功了。
注意:我曾经在Windows上遇到过angr安装失败的情况,多半是因为缺少VC++编译环境。建议装个Visual Studio Build Tools,或者直接用Linux/WSL。说实话,angr在Linux上最稳。
1.4 第一个angr脚本
光说不练假把式。咱们写个最简单的angr脚本,感受一下符号执行到底怎么玩。
假设我们有这样一个C程序(编译成二进制):
#include <stdio.h>
#include <string.h>
int main() {
char input[10];
printf("Enter password: ");
scanf("%s", input);
if (strcmp(input, "secret") == 0) {
printf("Access granted!\n");
} else {
printf("Access denied!\n");
}
return 0;
}
我们的目标是:让angr自动找到能输出"Access granted!"的输入。
angr脚本如下:
import angr
# 1. 加载二进制文件
proj = angr.Project('./crackme', auto_load_libs=False)
# 2. 设置起始状态
state = proj.factory.entry_state()
# 3. 创建符号执行模拟器
simgr = proj.factory.simulation_manager(state)
# 4. 探索路径,找到打印"Access granted"的地址
# 假设"Access granted"的地址是0x4006a0(实际需要反汇编确认)
simgr.explore(find=0x4006a0)
# 5. 检查结果
if simgr.found:
found_state = simgr.found[0]
# 获取符号化的输入
password = found_state.posix.dumps(0) # 标准输入
print(f"Found password: {password}")
else:
print("No path found")
这个脚本干了什么?
- 加载二进制:angr把程序读进来,分析它的结构
- 设置初始状态:告诉angr从程序入口开始执行
- 创建模拟器:相当于一个「路径探索器」
- 探索路径:告诉angr,找到能到达0x4006a0这个地址的路径
- 提取结果:如果找到了,就把标准输入的内容打印出来
你想想看,传统方法你得反汇编、分析strcmp逻辑、手动推算密码。angr呢?几行代码,自动搞定。
关键理解:angr不是暴力破解,它不会一个一个试密码。它把输入当作符号,通过约束求解器算出「什么样的输入能让strcmp返回0」。这就是符号执行的核心思想。
1.5 知识体系总览
下面这张图,是我自己整理的angr入门知识体系。你看一眼,心里就有谱了:
这张图把本章的核心内容串起来了。你从「符号执行」出发,理解它的三大支柱,然后看angr能做什么,接着安装环境,最后上手写第一个脚本。逻辑很清晰。
1.6 避坑指南
最后,分享几个我踩过的坑:
- 路径爆炸:程序里循环太多,angr会生成海量路径。我建议先用
explore的avoid参数排除无关路径 - 库函数问题:
auto_load_libs=False这个参数很重要。不设的话,angr会尝试模拟libc,慢得你想哭 - 地址不准确:找目标地址时,记得用
objdump或IDA确认一下。我吃过一次亏,地址写错了,跑了半天没结果
好了,第一章就到这里。你先把环境搭好,把第一个脚本跑通。后面咱们再深入聊angr的各种高级玩法。