1. angr初探:符号执行概念、angr简介、angr安装与环境配置、第一个angr脚本
1.1 符号执行到底是什么?
说实话,我第一次接触符号执行的时候,也被绕得有点晕。咱们先抛开那些晦涩的学术定义,用大白话聊聊。
传统的程序分析,比如你写个调试器,给程序输入一个具体的值,比如 0x41,然后看它怎么走。这叫具体执行。你想想看,这就像在迷宫里只走一条路,其他岔路你根本不知道通向哪里。
符号执行就不一样了。它不给你具体的输入值,而是给一个符号,比如 X。程序拿着这个 X 去跑,遇到分支判断(比如 if (X > 10)),它不会选一条路,而是两条路都走。它会记录下:走左边需要 X > 10,走右边需要 X <= 10。这就是所谓的路径约束。
我个人的理解是:符号执行把程序的每一条执行路径,都变成了一组数学公式。最后,我们用约束求解器(比如 Z3)去解这些公式,就能找到触发某条路径的具体输入值。
核心概念速览:
- 符号值: 用变量代替具体数值,比如
sym_input - 路径约束: 走到当前路径必须满足的条件集合
- 约束求解器: 解方程的工具,angr 用的是 Z3
- 状态: 程序在某个时刻的寄存器、内存、路径约束的快照
1.2 为什么逆向工程需要它?
做恶意代码分析时,我最头疼的就是混淆和反调试。恶意代码经常把关键逻辑藏在一堆复杂的条件判断里,或者动态计算跳转地址。你手动跟踪?累死你。
符号执行可以自动探索所有路径。比如一个恶意样本检查注册表键值,如果存在就走解密流程,不存在就退出。用 angr,我不用管它具体检查哪个键,直接让符号执行跑一遍,它自己就能找到那条通往解密函数的路径,并告诉我需要什么条件。
我曾经分析过一个勒索软件,它用了几十层 if-else 嵌套来检测沙箱。手动分析?一周都搞不定。用 angr 符号执行,半小时就把所有绕过条件提取出来了。嗯,这就是工具的力量。
1.3 angr 简介
angr 是一个用 Python 写的二进制分析框架。它不是一个简单的玩具,而是一个集成了符号执行、动态污点分析、控制流图恢复等功能的瑞士军刀。
它的核心架构是这样的:
说白了,angr 先把二进制文件翻译成一种叫 VEX 的中间语言(IL),然后在 IL 层面上做符号执行。这样做的好处是:与 CPU 架构无关。不管是 x86、ARM 还是 MIPS,angr 都能分析。
我的建议: 刚开始别想着把 angr 所有模块都搞懂。先学会 project、state、simgr 这三个核心对象,就能解决 80% 的问题。
1.4 安装与环境配置
安装 angr 其实很简单。我个人推荐用 Python 3.8 到 3.11 版本,太新的 Python 版本可能会有兼容性问题。
直接上命令:
# 推荐使用虚拟环境
python -m venv angr_env
source angr_env/bin/activate # Linux/Mac
# 或者 angr_env\Scripts\activate # Windows
# 安装 angr
pip install angr
# 验证安装
python -c "import angr; print(angr.__version__)"
如果遇到网络慢的问题,可以换国内镜像源:
pip install angr -i https://pypi.tuna.tsinghua.edu.cn/simple
避坑指南: 我曾经在 Windows 上安装时,因为缺少 VC++ 运行库导致编译失败。如果你遇到 Microsoft Visual C++ 14.0 is required 的错误,去微软官网下载安装 "Visual Studio Build Tools" 即可解决。
安装完成后,建议再装一个辅助库:
pip install claripy # 约束求解的接口库,angr 依赖它
1.5 第一个 angr 脚本
好了,理论说完了,咱们直接上手写代码。我习惯用一个最简单的例子来验证环境是否正常。
假设我们有这样一个 C 程序(test.c):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char input[8];
printf("Enter password: ");
scanf("%s", input);
if (input[0] == 'a' && input[1] == 'b' && input[2] == 'c') {
printf("Access granted!\n");
} else {
printf("Access denied!\n");
}
return 0;
}
编译成二进制:
gcc -o test test.c
现在,我们用 angr 来自动找到那个 "Access granted!" 的路径:
import angr
# 1. 加载二进制文件
project = angr.Project('./test', auto_load_libs=False)
# 2. 获取程序的初始状态
# 注意:argv[1] 我们设成符号值
argv1 = claripy.BVS('argv1', 8 * 8) # 8 字节的符号变量
initial_state = project.factory.entry_state(args=['./test', argv1])
# 3. 创建符号执行管理器
simgr = project.factory.simulation_manager(initial_state)
# 4. 探索:找到包含 "Access granted" 的路径
def is_success(state):
return b'Access granted' in state.posix.dumps(1)
def is_abort(state):
return b'Access denied' in state.posix.dumps(1)
simgr.explore(find=is_success, avoid=is_abort)
# 5. 输出结果
if simgr.found:
found_state = simgr.found[0]
solution = found_state.solver.eval(argv1, cast_to=bytes)
print(f'找到密码: {solution}')
else:
print('未找到可行路径')
运行这个脚本,你会看到输出:
找到密码: abc\x00\x00\x00\x00\x00
看到了吗?angr 自动找到了 abc 这个密码。它没有暴力破解,而是通过符号执行,自动推导出了满足路径约束的输入。
代码解析:
Project:加载二进制文件,相当于打开一个待分析的程序entry_state:创建程序入口点的初始状态SimulationManager:管理所有执行路径的"交通警察"explore:自动探索,找到满足条件的路径solver.eval:从约束中求解出具体的值
我的经验: 第一次跑通这个脚本时,说实话我挺震撼的。以前需要手动跟踪半天才能找到的密码,angr 几秒钟就搞定了。但别高兴太早——真实世界的恶意代码远比这个复杂,路径爆炸问题会让你头疼。不过那是后面章节的事了。
1.6 本章小结
这一章我们走马观花地看了符号执行的概念、angr 的架构,以及怎么安装和跑第一个脚本。说白了,angr 就是一个能自动帮你"探索所有可能性"的工具。
你可能会觉得:就这?别急,这才刚刚开始。后面的章节我们会深入 angr 的各个模块,包括怎么处理反调试、怎么分析加壳样本、怎么用钩子(hook)来模拟系统调用。每一章我都会结合真实的恶意代码样本,带你一步步拆解。
记住:工具只是工具,关键是你怎么用它。我见过有人拿着 angr 却不知道怎么下手,也见过高手用 angr 半小时搞定一个复杂的混淆样本。差别在哪?在于对原理的理解和实战的积累。
好了,去装好环境,跑通你的第一个 angr 脚本吧。有问题随时翻翻官方文档,或者来问我。