AFL源码学习(一)
字数 2205 2025-08-22 12:23:30
AFL源码分析与使用指南
一、AFL简介
American Fuzzy Lop (AFL)是由Google安全工程师Michał Zalewski开发的一款开源fuzzing测试工具,具有以下特点:
- 使用编译时插桩技术
- 采用遗传算法自动发现触发目标程序漏洞的测试用例
- 显著提高测试代码的功能覆盖率
- 项目地址:google/AFL: american fuzzy lop - a security-oriented fuzzer
二、AFL基本使用
1. 安装AFL
make
sudo make install
2. 插装编译测试
// test.c示例
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(int argc, char *argv[]) {
int size;
scanf("%d", &size);
printf("size:%d\n", size);
char *buf = (char *)malloc(size);
read(0, buf, 0x200);
puts(buf);
return 0;
}
编译命令:
afl-gcc -g -o test test.c
3. 创建种子文件夹
mkdir seeds
echo "123" > seeds/any_seed
4. 开始fuzz
afl-fuzz -i seeds -o fuzz_out ./test
5. 常见问题解决
core_pattern报错:
sudo su
echo core >/proc/sys/kernel/core_pattern
内存设置问题:
afl-fuzz -i seeds -o fuzz_out -m none ./test
三、AFL界面解析
- Process Timing:Fuzzer运行时长、距离最近发现的路径/崩溃/挂起的时间
- cycle progress:输入队列进度,当前测试用例序号
- Stage progress:当前测试策略、执行次数、平均速度
- fuzzing strategy yields:变异策略的最新行为和结果
- Overall results:当前状态(循环队列次数、总路径数、崩溃次数、挂起次数)
- map coverage:
- map density:使用的位图大小占比
- count coverage:位图中每个被命中字节平均改变的位数(1-8)
- findings in depth:青睐的测试用例数、覆盖新路径测试用例数、总崩溃数、总超时数
- path geometry:执行路径信息
四、AFL源码分析
1. afl-gcc分析
afl-gcc是gcc/g++/clang/clang++的包装器,主要功能:
- 设置编译参数并调用下游编译器
- 需要知道afl-as的路径(默认/usr/local/lib/afl/或通过AFL_PATH指定)
- 支持AFL_HARDEN(添加安全编译选项)和AFL_USE_ASAN(使用ASan)
- 可通过AFL_CC/AFL_CXX指定非标准编译器
主要函数:
main函数流程:
- 通过find_as()寻找afl-as插桩器位置
- 通过edit_params()修改下游编译参数
- 使用execvp执行下游编译器
find_as()逻辑:
- 检查AFL_PATH环境变量
- 在argv[0]所在目录查找
- 最后通过编译时的AFL_PATH查找
edit_params()逻辑:
- 分析argv[0]确定调用的编译器类型
- 复制argv[1]开始的参数
- 覆盖-B参数为as_path,使编译时使用afl-as替换原生as
2. afl-as插桩器
afl-as是GNU汇编器(as)的包装器,主要功能:
- 预处理GCC/clang生成的汇编文件
- 注入来自afl-as.h的插桩代码
- 自动被afl-gcc/afl-clang调用
注意:不会对手写汇编代码插桩(.s文件或asm块)
主要函数:
main函数流程:
- 初始化随机种子
- 修改as参数
- 在汇编上插桩
- 调用as编译
edit_params()逻辑:
- 确定as名称(默认GNU as,可被AFL_AS覆盖)
- 复制原始argv参数
- 设置临时文件modified_file(路径为/tmp/.afl-pid-time.s)
插桩实现:
- 在分支处插入_afl_maybe_log()调用
- 使用独立栈保存寄存器状态
- 核心逻辑:
cur_location = <COMPILE_TIME_RANDOM>; shared_mem[cur_location ^ prev_location]++; prev_location = cur_location >> 1;
3. 共享内存与fork server机制
__afl_maybe_log()工作流程:
- 检查共享内存是否已映射
- 未映射则调用__afl_setup初始化
- 已映射则调用__afl_store记录执行信息
__afl_setup流程:
- 检查__afl_setup_failure
- 检查__afl_global_area_ptr
- 获取__AFL_SHM_ID环境变量
- 使用shmat映射共享内存
- 进入__afl_forkserver
fork server机制:
- 使用管道(198/199)通信
- 主进程循环:fork子进程→报告pid→等待子进程结束
- 子进程恢复原始执行流程
- 避免频繁execve,提高fuzz效率
五、AFL覆盖率机制
AFL使用基于边界(edge)的覆盖率反馈机制:
- 每个基本块开头插入桩代码
- 将源基本块和目的基本块组合成元组(tuple)
- 通过记录tuple信息实现边界覆盖率统计
- 执行次数被划分为8个bucket:
static const u8 count_class_lookup8[256] = {
[0] = 0, [1] = 1, [2] = 2, [3] = 4,
[4...7] = 8, [8...15] = 16,
[16...31] = 32, [32...127] = 64,
[128...255] = 128
};
- 通过比较trace_bits的hash值判断是否发现新路径
六、关键点总结
- 编译插桩:afl-gcc包装编译器,afl-as实现汇编级插桩
- 覆盖率统计:基于边界(edge)的tuple记录方式
- fork server:高效执行目标程序的核心机制
- 共享内存:通过__AFL_SHM_ID环境变量传递共享内存ID
- 变异策略:遗传算法驱动测试用例生成
- 反馈机制:通过执行路径变化指导fuzzing方向
七、参考资源
- AFL中使用的环境变量以及状态栏、fuzzer_stats文件、plot_data文件中各字段的含义
- AFL白皮书
- experimental/clang_asm_normalize/(处理手写汇编的解决方案)