AFL二三事——源码分析(下篇)
字数 1858 2025-08-24 07:48:33

AFL源码深度解析与教学指南

一、AFL核心架构概述

AFL (American Fuzzy Lop) 是一款革命性的模糊测试工具,其核心源码位于 afl-fuzz.c 文件中,代码量约8000行。该文件按功能可分为三大模块:

  1. 初始配置模块:负责fuzz环境配置
  2. fuzz执行模块:主循环控制流程
  3. 变异策略模块:测试用例变异机制

二、初始配置模块详解

2.1 环境参数解析

while ((opt = getopt(argc, argv, "+i:o:f:m:b:t:T:dnCB:S:M:x:QV")) > 0)

通过getopt循环解析命令行参数,包括:

  • 输入/输出目录(-i/-o)
  • 内存限制(-m)
  • 超时设置(-t)
  • 运行模式(-d/-n等)

2.2 信号处理设置

setup_signal_handlers() 注册关键信号处理函数:

信号 处理内容
SIGHUP/SIGINT/SIGTERM 处理停止请求
SIGALRM 超时处理
SIGWINCH 窗口大小变化
SIGUSR1 自定义跳过信号

2.3 共享内存初始化

setup_shm() 关键操作:

memset(virgin_bits, 255, MAP_SIZE);  // 初始化覆盖率位图
memset(virgin_tmout, 255, MAP_SIZE); // 超时位图
memset(virgin_crash, 255, MAP_SIZE); // 崩溃位图

shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
trace_bits = shmat(shm_id, NULL, 0);  // 共享内存映射

维护四个关键位图:

  1. trace_bits:当前执行路径(共享内存)
  2. virgin_bits:全局路径覆盖
  3. virgin_tmout:超时路径记录
  4. virgin_crash:崩溃路径记录

2.4 目录结构初始化

setup_dirs_fds() 创建的标准目录结构:

out_dir/
├── queue/               # 测试用例队列
│   └── .state/          # 状态元数据
│       ├── deterministic_done/
│       ├── auto_extras/
│       ├── redundant_edges/
│       └── variable_behavior/
├── crashes/             # 崩溃用例
├── hangs/               # 超时用例
└── plot_data            # 统计信息

三、Fuzz主流程分析

3.1 初始校准阶段

perform_dry_run() 执行流程:

  1. 遍历输入队列中的所有测试用例
  2. 对每个用例调用calibrate_case()进行校准
  3. 根据返回值判断用例有效性:
    • FAULT_NONE:正常执行
    • FAULT_TMOUT:执行超时
    • FAULT_CRASH:导致崩溃
    • FAULT_NOBITS:无新路径

3.2 Fork Server机制

init_forkserver() 建立的高效执行架构:

  1. 通信管道

    • 控制管道(ctl_pipe):fuzzer→fork server
    • 状态管道(st_pipe):fork server→fuzzer
  2. 工作流程

    forksrv_pid = fork();  // 创建fork server进程
    if (!forksrv_pid) {
        setsid();
        dup2(ctl_pipe[0], FORKSRV_FD);    // 绑定控制管道
        dup2(st_pipe[1], FORKSRV_FD + 1); // 绑定状态管道
        execv(target_path, argv);         // 执行目标程序
    }
    
  3. 优势:避免重复的execve()调用开销

3.3 目标程序执行

run_target() 关键步骤:

  1. 清空共享内存:memset(trace_bits, 0, MAP_SIZE)
  2. 非dumb模式通过fork server执行:
    write(fsrv_ctl_fd, &prev_timed_out, 4); // 发送执行请求
    read(fsrv_st_fd, &child_pid, 4);       // 获取子进程PID
    
  3. 设置超时监控
  4. 执行结果分类处理:
    #ifdef WORD_SIZE_64
    classify_counts((u64*)trace_bits);
    #else
    classify_counts((u32*)trace_bits);
    #endif
    

3.4 路径评分系统

update_bitmap_score() 算法:

  1. 计算fav因子:exec_us * len(时间×大小)
  2. 遍历trace_bits中所有置位字节
  3. 比较当前用例与top_rated[i]的fav因子
  4. 更新更优的用例到top_rated[]数组
for (i = 0; i < MAP_SIZE; i++)
    if (trace_bits[i]) {
        if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len)
            continue;
        top_rated[i] = q;  // 更新最优用例
        q->tc_ref++;
    }

3.5 队列精简策略

cull_queue() 工作流程:

  1. 初始化temp_v为全1(未覆盖标记)
  2. 遍历top_rated[]:
    • 对每个置位字节,清除对应temp_v位
    • 标记对应用例为favored
  3. 结果:
    • queued_favored:优选用例计数
    • pending_favored:待fuzz优选用例
for (i = 0; i < MAP_SIZE; i++)
    if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {
        // 清除该用例覆盖的所有位
        while (j--)
            if (top_rated[i]->trace_mini[j])
                temp_v[j] &= ~top_rated[i]->trace_mini[j];
        top_rated[i]->favored = 1;
    }

四、主循环逻辑

4.1 循环控制结构

while (1) {
    cull_queue();  // 精简队列
    if (!queue_cur) {
        queue_cycle++;  // 完成一轮循环
        // 初始化下一轮参数
    }
    skipped_fuzz = fuzz_one(use_argv);  // 关键变异函数
    // 同步和终止检查
    queue_cur = queue_cur->next;
}

4.2 变异调度策略

fuzz_one() 主要阶段:

  1. 跳过判断

    • 已fuzz过的non-favored用例:99%跳过
    • 未fuzz的non-favored用例:75%跳过
  2. 校准阶段

    if (q->cal_failed < CAL_CHANCES)
        calibrate_case(argv, q, use_mem, 0, 1);
    
  3. 修剪测试用例

    if (!q->trim_done)
        trim_case(argv, q, in_buf);
    
  4. 确定性变异阶段

    • bitflip:按位翻转
    • arithmetic:算术增减
    • interest:特殊值替换
    • dictionary:token插入
  5. 随机性变异阶段

    • havoc:大规模随机变异
    • splice:用例拼接组合

五、关键设计思想

  1. 覆盖率引导:通过共享内存实时收集路径覆盖率
  2. 高效执行:fork server减少进程创建开销
  3. 智能调度
    • favored用例优先
    • 自动识别无效用例
  4. 渐进式变异
    • 从简单到复杂
    • 保留有效变异路径

六、扩展建议

  1. 变异策略优化

    • 增加新的变异算子
    • 改进变异参数选择
  2. 并行化改进

    • 优化同步机制
    • 动态任务分配
  3. 机器学习的应用

    • 基于历史数据预测有效变异
    • 自动识别关键路径

本教学文档涵盖了AFL的核心实现机制,建议结合源码中的关键函数(约30个主要函数)进行深入学习,特别是变异策略部分的实现细节值得专项研究。

AFL源码深度解析与教学指南 一、AFL核心架构概述 AFL (American Fuzzy Lop) 是一款革命性的模糊测试工具,其核心源码位于 afl-fuzz.c 文件中,代码量约8000行。该文件按功能可分为三大模块: 初始配置模块 :负责fuzz环境配置 fuzz执行模块 :主循环控制流程 变异策略模块 :测试用例变异机制 二、初始配置模块详解 2.1 环境参数解析 通过getopt循环解析命令行参数,包括: 输入/输出目录(-i/-o) 内存限制(-m) 超时设置(-t) 运行模式(-d/-n等) 2.2 信号处理设置 setup_signal_handlers() 注册关键信号处理函数: | 信号 | 处理内容 | |------|----------| | SIGHUP/SIGINT/SIGTERM | 处理停止请求 | | SIGALRM | 超时处理 | | SIGWINCH | 窗口大小变化 | | SIGUSR1 | 自定义跳过信号 | 2.3 共享内存初始化 setup_shm() 关键操作: 维护四个关键位图: trace_bits :当前执行路径(共享内存) virgin_bits :全局路径覆盖 virgin_tmout :超时路径记录 virgin_crash :崩溃路径记录 2.4 目录结构初始化 setup_dirs_fds() 创建的标准目录结构: 三、Fuzz主流程分析 3.1 初始校准阶段 perform_dry_run() 执行流程: 遍历输入队列中的所有测试用例 对每个用例调用 calibrate_case() 进行校准 根据返回值判断用例有效性: FAULT_NONE :正常执行 FAULT_TMOUT :执行超时 FAULT_CRASH :导致崩溃 FAULT_NOBITS :无新路径 3.2 Fork Server机制 init_forkserver() 建立的高效执行架构: 通信管道 : 控制管道(ctl_ pipe):fuzzer→fork server 状态管道(st_ pipe):fork server→fuzzer 工作流程 : 优势 :避免重复的execve()调用开销 3.3 目标程序执行 run_target() 关键步骤: 清空共享内存: memset(trace_bits, 0, MAP_SIZE) 非dumb模式通过fork server执行: 设置超时监控 执行结果分类处理: 3.4 路径评分系统 update_bitmap_score() 算法: 计算fav因子: exec_us * len (时间×大小) 遍历trace_ bits中所有置位字节 比较当前用例与top_ rated[ i ]的fav因子 更新更优的用例到top_ rated[ ]数组 3.5 队列精简策略 cull_queue() 工作流程: 初始化temp_ v为全1(未覆盖标记) 遍历top_ rated[ ]: 对每个置位字节,清除对应temp_ v位 标记对应用例为favored 结果: queued_ favored:优选用例计数 pending_ favored:待fuzz优选用例 四、主循环逻辑 4.1 循环控制结构 4.2 变异调度策略 fuzz_one() 主要阶段: 跳过判断 : 已fuzz过的non-favored用例:99%跳过 未fuzz的non-favored用例:75%跳过 校准阶段 : 修剪测试用例 : 确定性变异阶段 : bitflip:按位翻转 arithmetic:算术增减 interest:特殊值替换 dictionary:token插入 随机性变异阶段 : havoc:大规模随机变异 splice:用例拼接组合 五、关键设计思想 覆盖率引导 :通过共享内存实时收集路径覆盖率 高效执行 :fork server减少进程创建开销 智能调度 : favored用例优先 自动识别无效用例 渐进式变异 : 从简单到复杂 保留有效变异路径 六、扩展建议 变异策略优化 : 增加新的变异算子 改进变异参数选择 并行化改进 : 优化同步机制 动态任务分配 机器学习的应用 : 基于历史数据预测有效变异 自动识别关键路径 本教学文档涵盖了AFL的核心实现机制,建议结合源码中的关键函数(约30个主要函数)进行深入学习,特别是变异策略部分的实现细节值得专项研究。