AFL二三事——源码分析(下篇)
字数 1858 2025-08-24 07:48:33
AFL源码深度解析与教学指南
一、AFL核心架构概述
AFL (American Fuzzy Lop) 是一款革命性的模糊测试工具,其核心源码位于 afl-fuzz.c 文件中,代码量约8000行。该文件按功能可分为三大模块:
- 初始配置模块:负责fuzz环境配置
- fuzz执行模块:主循环控制流程
- 变异策略模块:测试用例变异机制
二、初始配置模块详解
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); // 共享内存映射
维护四个关键位图:
trace_bits:当前执行路径(共享内存)virgin_bits:全局路径覆盖virgin_tmout:超时路径记录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() 执行流程:
- 遍历输入队列中的所有测试用例
- 对每个用例调用
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
-
工作流程:
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); // 执行目标程序 } -
优势:避免重复的execve()调用开销
3.3 目标程序执行
run_target() 关键步骤:
- 清空共享内存:
memset(trace_bits, 0, MAP_SIZE) - 非dumb模式通过fork server执行:
write(fsrv_ctl_fd, &prev_timed_out, 4); // 发送执行请求 read(fsrv_st_fd, &child_pid, 4); // 获取子进程PID - 设置超时监控
- 执行结果分类处理:
#ifdef WORD_SIZE_64 classify_counts((u64*)trace_bits); #else classify_counts((u32*)trace_bits); #endif
3.4 路径评分系统
update_bitmap_score() 算法:
- 计算fav因子:
exec_us * len(时间×大小) - 遍历trace_bits中所有置位字节
- 比较当前用例与top_rated[i]的fav因子
- 更新更优的用例到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() 工作流程:
- 初始化temp_v为全1(未覆盖标记)
- 遍历top_rated[]:
- 对每个置位字节,清除对应temp_v位
- 标记对应用例为favored
- 结果:
- 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() 主要阶段:
-
跳过判断:
- 已fuzz过的non-favored用例:99%跳过
- 未fuzz的non-favored用例:75%跳过
-
校准阶段:
if (q->cal_failed < CAL_CHANCES) calibrate_case(argv, q, use_mem, 0, 1); -
修剪测试用例:
if (!q->trim_done) trim_case(argv, q, in_buf); -
确定性变异阶段:
- bitflip:按位翻转
- arithmetic:算术增减
- interest:特殊值替换
- dictionary:token插入
-
随机性变异阶段:
- havoc:大规模随机变异
- splice:用例拼接组合
五、关键设计思想
- 覆盖率引导:通过共享内存实时收集路径覆盖率
- 高效执行:fork server减少进程创建开销
- 智能调度:
- favored用例优先
- 自动识别无效用例
- 渐进式变异:
- 从简单到复杂
- 保留有效变异路径
六、扩展建议
-
变异策略优化:
- 增加新的变异算子
- 改进变异参数选择
-
并行化改进:
- 优化同步机制
- 动态任务分配
-
机器学习的应用:
- 基于历史数据预测有效变异
- 自动识别关键路径
本教学文档涵盖了AFL的核心实现机制,建议结合源码中的关键函数(约30个主要函数)进行深入学习,特别是变异策略部分的实现细节值得专项研究。