AFL源码审计-Fuzz学习
字数 2049 2025-09-01 11:26:17
AFL源码审计与Fuzz学习指南
1. AFL概述
American Fuzzy Lop (AFL)是一款基于覆盖制导的模糊测试工具,其核心设计原则是速度、可靠性和易用性。AFL通过轻量级插装技术获取程序执行路径信息,指导测试用例的变异和选择。
1.1 基本工作原理
AFL通过以下机制实现高效的模糊测试:
- 覆盖率测量:捕获分支(边缘)覆盖率和粗略的分支执行次数
- 路径反馈:通过共享内存记录执行路径的元组信息
- 进化算法:基于反馈信息逐步优化测试用例
2. AFL插桩机制
2.1 插桩类型
AFL支持两种插桩方式:
-
汇编层插桩:在汇编代码中寻找条件跳转指令插入探测代码
- 优点:直接操作底层代码
- 缺点:需要针对不同架构实现,不够通用
-
LLVM PASS插桩:通过Clang/LLVM在中间语言层面插入探测代码
- 优点:跨平台,更通用
- 实现方式:使用LLVM的Pass机制
2.2 插桩实现细节
插桩代码的核心逻辑:
cur_location = <随机生成的位置标识>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
shared_mem[]:64KB共享内存区域,记录(branch_src, branch_dst)元组执行次数- 位移操作:保留元组方向性(A^B ≠ B^A)和循环身份(A^A ≠ B^B)
3. AFL核心组件分析
3.1 afl-as源码分析
主要功能:
- 初始化随机数种子
- 实现汇编指令插桩
- 修改as参数
- 生成可执行文件并清理临时文件
关键流程:
- 确定as名称(GNU as)
- 检查临时目录(TMPDIR → TEMP → TMP → /tmp)
- 参数拷贝(原始命令行参数)
- 识别和检查输入文件
- 创建插桩后内容
3.2 ForkServer机制
优化性能的关键设计:
- 避免重复的execve()、链接和libc初始化
- 利用写时复制(copy-on-write)克隆已初始化的进程镜像
- 性能提升:通常1.5-2倍
实现方式:
- 在第一个插桩函数处停止
- 等待afl-fuzz的命令
- 通过管道通信控制子进程执行
3.3 变异策略
AFL采用多种变异策略组合:
确定性变异阶段:
-
按位翻转(bitflip):1-4位翻转
- 实现:
FLIP_BIT逐位翻转 +common_fuzz_stuff测试 - 辅助算法:检测变异数据是否可通过按位翻转得到
- 实现:
-
算术增减(arithmetic):
- 8位、16位、32位算术运算
- 包括加减操作
-
特殊值测试(interesting):
- 插入预定义的边界值和特殊值
- 例如:0, 1, INT_MAX等
非确定性变异阶段:
-
字典替换(DICTIONARY):
- 使用用户提供的字典条目
- 自动识别语法标记
-
随机变异(havoc):
- 包括位翻转、设置有趣值、增删字节等10多种操作
- 支持多种变异方法组合
-
拼接(SPLICING):
- 将当前输入与其他输入文件拼接
- 作为其他策略无效时的最后手段
4. AFL使用实践
4.1 libxml2模糊测试流程
-
编译:
AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ \ CXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" \ ./configure --disable-shared make -
准备输入文件:
- 使用简单有效的XML样本作为种子
- 例如:
<root></root>
-
执行模糊测试:
afl-fuzz -i seeds -o output -x xml.dict -M master -- ./xmllint @@ afl-fuzz -i seeds -o output -x xml.dict -S slave1 -- ./xmllint @@
4.2 关键参数说明
-M master:主节点,负责探索新路径-S slave:从节点,挖掘已有路径的深层bug-x xml.dict:指定领域特定字典@@:表示输入文件占位符
5. AFL高级特性
5.1 覆盖率引导优化
AFL通过以下方式优化测试效率:
-
效应器映射:识别输入文件中影响执行路径的关键区域
-
virgin_bits追踪:
- 记录尚未探索到的路径(01)
- 新路径会使virgin_bits变为01
- 已探索路径标记为10
-
队列精简:
- 选择覆盖所有元组的最小测试用例集
- 基于执行延迟和文件大小评分
5.2 崩溃处理
-
去重机制:
- 崩溃跟踪包含新元组
- 崩溃跟踪缺少之前所有故障共有的元组
-
崩溃调查模式:
- 仅保留导致崩溃的变异
- 探索崩溃程序的状态
6. AFL扩展开发
6.1 测试用例统计扩展
通过afl_postprocess函数实现:
-
编译为动态库:
gcc -fPIC -shared work.c -o mypost.so -
运行时加载:
AFL_POST_LIBRARY=./mypost.so afl-fuzz -i input -o output -- ./target
6.2 自定义变异算子
添加步骤:
- 修改
havoc函数中的变异算子 - 将定制算子编号为15-16
- 原有词典相关算子移至17-18
- 重新编译AFL
7. 性能优化技巧
-
持久模式:单个进程中测试多个输入,减少fork()开销
-
延迟初始化:跳过不必要的初始化代码
-
输入裁剪:
afl-tmin:最小化崩溃样本- 内置修剪器:移除不影响执行路径的数据块
-
并行化:
- 多实例协同工作
- 主从架构分工
8. 白皮书核心要点
- 设计哲学:实践验证的简单可靠方案组合
- 覆盖率测量:分支覆盖率+粗略执行计数
- 新行为检测:基于元组映射的快速比较
- 输入队列演化:保留所有发现新行为的测试用例
- 精简策略:选择最优测试用例子集
- 二进制插桩:QEMU模式实现黑盒测试
通过深入理解AFL的这些核心机制和技术细节,安全研究人员可以更有效地使用和定制AFL进行漏洞挖掘工作。