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界面解析

  1. Process Timing:Fuzzer运行时长、距离最近发现的路径/崩溃/挂起的时间
  2. cycle progress:输入队列进度,当前测试用例序号
  3. Stage progress:当前测试策略、执行次数、平均速度
  4. fuzzing strategy yields:变异策略的最新行为和结果
  5. Overall results:当前状态(循环队列次数、总路径数、崩溃次数、挂起次数)
  6. map coverage
    • map density:使用的位图大小占比
    • count coverage:位图中每个被命中字节平均改变的位数(1-8)
  7. findings in depth:青睐的测试用例数、覆盖新路径测试用例数、总崩溃数、总超时数
  8. 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函数流程

  1. 通过find_as()寻找afl-as插桩器位置
  2. 通过edit_params()修改下游编译参数
  3. 使用execvp执行下游编译器

find_as()逻辑

  1. 检查AFL_PATH环境变量
  2. 在argv[0]所在目录查找
  3. 最后通过编译时的AFL_PATH查找

edit_params()逻辑

  1. 分析argv[0]确定调用的编译器类型
  2. 复制argv[1]开始的参数
  3. 覆盖-B参数为as_path,使编译时使用afl-as替换原生as

2. afl-as插桩器

afl-as是GNU汇编器(as)的包装器,主要功能:

  1. 预处理GCC/clang生成的汇编文件
  2. 注入来自afl-as.h的插桩代码
  3. 自动被afl-gcc/afl-clang调用

注意:不会对手写汇编代码插桩(.s文件或asm块)

主要函数:

main函数流程

  1. 初始化随机种子
  2. 修改as参数
  3. 在汇编上插桩
  4. 调用as编译

edit_params()逻辑

  1. 确定as名称(默认GNU as,可被AFL_AS覆盖)
  2. 复制原始argv参数
  3. 设置临时文件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()工作流程

  1. 检查共享内存是否已映射
  2. 未映射则调用__afl_setup初始化
  3. 已映射则调用__afl_store记录执行信息

__afl_setup流程

  1. 检查__afl_setup_failure
  2. 检查__afl_global_area_ptr
  3. 获取__AFL_SHM_ID环境变量
  4. 使用shmat映射共享内存
  5. 进入__afl_forkserver

fork server机制

  • 使用管道(198/199)通信
  • 主进程循环:fork子进程→报告pid→等待子进程结束
  • 子进程恢复原始执行流程
  • 避免频繁execve,提高fuzz效率

五、AFL覆盖率机制

AFL使用基于边界(edge)的覆盖率反馈机制:

  1. 每个基本块开头插入桩代码
  2. 将源基本块和目的基本块组合成元组(tuple)
  3. 通过记录tuple信息实现边界覆盖率统计
  4. 执行次数被划分为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
};
  1. 通过比较trace_bits的hash值判断是否发现新路径

六、关键点总结

  1. 编译插桩:afl-gcc包装编译器,afl-as实现汇编级插桩
  2. 覆盖率统计:基于边界(edge)的tuple记录方式
  3. fork server:高效执行目标程序的核心机制
  4. 共享内存:通过__AFL_SHM_ID环境变量传递共享内存ID
  5. 变异策略:遗传算法驱动测试用例生成
  6. 反馈机制:通过执行路径变化指导fuzzing方向

七、参考资源

  1. AFL中使用的环境变量以及状态栏、fuzzer_stats文件、plot_data文件中各字段的含义
  2. AFL白皮书
  3. experimental/clang_asm_normalize/(处理手写汇编的解决方案)
AFL源码分析与使用指南 一、AFL简介 American Fuzzy Lop (AFL)是由Google安全工程师Michał Zalewski开发的一款开源fuzzing测试工具,具有以下特点: 使用编译时插桩技术 采用遗传算法自动发现触发目标程序漏洞的测试用例 显著提高测试代码的功能覆盖率 项目地址:google/AFL: american fuzzy lop - a security-oriented fuzzer 二、AFL基本使用 1. 安装AFL 2. 插装编译测试 编译命令: 3. 创建种子文件夹 4. 开始fuzz 5. 常见问题解决 core_ pattern报错 : 内存设置问题 : 三、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()调用 使用独立栈保存寄存器状态 核心逻辑: 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: 通过比较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/(处理手写汇编的解决方案)