AFL源码学习(三)
字数 3893 2025-08-29 08:30:30
AFL源码学习与Fuzzing技术详解
前言
本文基于AFL-2.57b版本源码,深入分析AFL(American Fuzzy Lop)的实现原理和关键技术点。AFL是一款革命性的模糊测试工具,通过编译时插桩和覆盖率引导,实现了高效的漏洞挖掘能力。
AFL整体架构
AFL运行时包含三个主要进程:
- fuzzer:主控制进程,负责变异策略和调度
- fork server:用于快速fork子进程
- child:实际执行目标程序的进程
初始化流程
1. 参数处理与环境设置
main() -> 随机化种子 -> 处理argv参数 -> 信号处理设置
信号处理设置:
- SIGHUP/SIGINT/SIGTERM:设置stop_soon标志,关闭fork server和child
- SIGALRM:处理超时,关闭child或fork server
- SIGWINCH:UI显示警告
- SIGUSR1:设置skip_requested标志,跳过当前样例变异
2. 安全检查
check_asan_opts()检查ASAN和MSAN配置:
- ASAN需要设置:
abort_on_error=1,symbolize=0 - MSAN需要设置:
exit_code=MSAN_ERROR,symbolize=0
3. 并行模式处理
并行模式(-S -M)下:
- 检查sync_id合法性
- 设置sync_dir(工作目录)和out_dir(工作目录/sync_id)
- 非并行模式直接使用"-o out_dir"
4. CPU绑定与性能设置
Linux下:
- 绑定CPU核心
- 检查并设置CPU为performance模式(最高频率)
5. 后处理模块设置
setup_post():
- 检查AFL_POST_LIBRARY环境变量
- 使用dlopen加载指定库
- 设置afl_postprocess指向库中的post_handler函数
post_handler签名:u8* post_handler(u8* data, u32* len)
- 在变异出新用例后、执行前被调用
- 可用于记录或修改变异用例
6. 共享内存初始化
setup_shm():
- 初始化2字节(16bit)的共享内存
- 相比之前版本(8bit)有显著优化
7. 文件系统准备
- 创建queue等必要目录
- 打开/dev/null等备用文件描述符
- 读取测试用例到queue链表
- 读取extra token(可自动生成或通过-x指定字典)
- 复制初始输入(-i指定的文件)
- 创建.cur_input文件用于存储变异用例
8. 初始测试执行
perform_dry_run():
- 测试queue中的所有用例
- 使用
calibrate_case()校准每个用例 - 首次执行时调用
check_map_coverage()检查覆盖率均匀性
关键函数分析
1. calibrate_case() - 用例校准
主要工作:
- 如果fork server未准备好,调用
init_forkserver() - 多次调用
run_target()测试用例路径- 发现无插桩则退出
- 发现新路径则提升测试次数到40次
- 更新用例信息
- 调用
update_bitmap_score()进行打分
2. init_forkserver() - 初始化fork server
通讯流程:
-
初始化管道
-
fork子进程:
- 设置内存限制
- 关闭core dump
- 开启新进程组
- 重定向流
- 绑定管道文件描述符(198: supervisor→fork server, 199: fork server→supervisor)
- 关闭无用fd
- 设置ASan/MSan
- 执行程序并在第一个节点暂停
- 向fd 199写入4字节hello数据包
- 阻塞等待supervisor指令
-
主进程:
- 绑定管道文件描述符
- 等待fork server的4字节数据
- 接收成功则返回,否则检查问题
3. run_target() - 执行目标程序
流程:
- 清空trace_bits
- 发送4字节启动child
- 从fork server接收4字节获取child pid
- 再次接收4字节判断退出原因
- 对trace_bits进行分桶操作
- 返回状态码
4. update_bitmap_score() - 用例打分
打分标准:
- 分数 = 执行时间(exec_us) * 文件大小(len)
- 分数越小表示用例越好
favored集合构建:
- 对于共享内存的每个位置(代表一条边)
- 记录top_rated指针,指向覆盖该边且分数最小的用例
- 最终形成的favored集合比完整corpus小5-10倍
主fuzzing循环
循环遍历queue,对每个用例:
- 调用
cull_queue()精简队列 - 调用
fuzz_one()变异当前用例 - 并行模式下每5次调用
sync_fuzzers()同步 - 如果遍历完无新发现:
- 开启use_splicing模式
- 增加cycles_wo_finds计数器
1. cull_queue() - 队列精简
流程:
- 初始化temp_v为0xff
- 清空队列的favorable标记
- 遍历trace_bits,去除冗余路径用例
- 重构favored集合
2. fuzz_one() - 用例变异
准备阶段
- 判断是否跳过变异:
- 有全新favored用例:99%跳过
- 已fuzz过或不受青睐用例:尽快安排青睐用例
- 无全新favored用例:95%跳过已fuzz,75%跳过未fuzz
- 环境准备:mmap映射用例,为out_buf分配空间
校准阶段
- 用例校准失败时尝试重新校准(最多3次)
修剪阶段
- 未修剪用例调用
trim_case() - 类似afl-tmin的block deletion:
- 分16块
- 遍历删除块
- 测试路径是否相同
- 相同则覆盖原用例
性能评分
- 调用
calculate_score()基于耗时、覆盖率、深度等因素 - 决定是否跳过deterministic变异:
- -d参数设置(skip_deterministic为true)
- 用例已fuzz过
- 用例已完成deterministic阶段
- 并行模式(-M)自行处理deterministic
Deterministic变异阶段
bitflip(字节翻转)
- bitflip 1/1:每个bit翻转
- 调用
common_fuzz_stuff()测试 - 调用
maybe_add_auto()构建自动词典
- 调用
- bitflip 8/8:每个字节翻转
- 进行敏感分析:标记路径变化的位置
- 敏感部分>90%则全标记
- 长度<128则全标记
arith(简单加减)
- arith 8/8、16/8、32/8
- 给uint8/16/32加/减[1,35]
- 跳过bitflip已覆盖位置
interest(有趣替换)
- interest 8/8、16/8、32/8
- 替换为内置interesting_n[]值
- 跳过非敏感位置和已覆盖位置
extras(词典替换)
- user extras (over):用户词典覆盖
- user extras (insert):用户词典插入
- auto extras (over):自动词典覆盖(最多前50个)
Havoc随机变异阶段
- 执行次数受用例得分和havoc_div影响
- 随机组合2-128个变异算子
- 17个变异算子:
- 随机bit翻转
- 随机字节替换为interesting_8[]
- 随机word替换为interesting_16[]
- 随机dword替换为interesting_32[]
- 随机字节减[1,35]
- 随机字节加[1,35]
- 随机word减[1,35]
- 随机word加[1,35]
- 随机dword减[1,35]
- 随机dword加[1,35]
- 随机字节替换为随机值
- 随机删除块(概率较高)
- 随机插入块(75%复制用例块,25%随机相同字节块)
- 随机覆盖块(75%复制用例块,25%随机相同字节块)
- 随机覆写词典条目
- 随机插入词典条目
Splicing拼接变异阶段
- 最多执行15次
- 每次执行后调用havoc再次变异
- 随机挑选另一用例,随机分割点拼接
- 生成新用例后调用havoc变异
3. sync_fuzzers() - 并行同步
流程:
- 遍历其他fuzzer的queue文件
- 测试所有用例
- 调用
save_if_interesting()尝试加入队列 - 通过sync_id比较避免重复测试
关键技术点
1. 覆盖率引导
- 使用共享内存trace_bits记录边覆盖
- 分桶操作减少碰撞
- 通过覆盖率变化判断用例价值
2. 变异策略
- 分层变异:deterministic -> havoc -> splicing
- 多种变异算子组合
- 敏感度分析优化变异效率
3. 队列管理
- favored集合构建
- 用例打分与优先级调度
- 精简策略减少冗余测试
4. 性能优化
- fork server减少进程创建开销
- 共享内存快速通信
- 文件级操作简化并行同步
- 短用例优先策略
总结
AFL通过创新的覆盖率引导、高效的变异策略和精细的队列管理,实现了模糊测试领域的重大突破。其关键技术包括:
- 编译时插桩获取精确覆盖率
- 分层变异策略平衡探索与利用
- fork server机制优化执行效率
- 敏感度分析和精简队列减少冗余
- 文件级并行同步简化分布式部署
这些设计思想不仅适用于安全测试,也为其他领域的自动化测试提供了宝贵参考。