AFL原码分析----fuzz准备和UI显示
字数 2970 2025-08-06 18:08:09

AFL源码分析:Fuzz准备和UI显示

1. 环境准备和编译

1.1 测试程序准备

使用开源png处理库编写测试程序:

#include <stdio.h>
#include "ok_png.h"

int main(int _argc, char **_argv) {
    FILE *file = fopen(_argv[1], "rb");
    ok_png image = ok_png_read(file, OK_PNG_COLOR_FORMAT_BGRA | OK_PNG_PREMULTIPLIED_ALPHA);
    fclose(file);
    
    if (image.data) {
        printf("Got image! Size: %li x %li\n", (long)image.width, (long)image.height);
        free(image.data);
    }
    return 0;
}

1.2 编译命令

使用afl-gcc编译测试程序:

/home/tamako/Desktop/FUZZ/AFL_debug/AFLcpp/afl-gcc -g -o fuzz_png test.c ok_png.c ok_png.h

1.3 AFL参数设置

-i /home/tamako/Desktop/FUZZ/AFL_debug/work_dir/fuzz_in
-o /home/tamako/Desktop/FUZZ/AFL_debug/work_dir/fuzz_out
-m none
-t 500+
-- /home/tamako/Desktop/FUZZ/AFL_debug/work_dir/fuzzbuild/other/ok-file-formats/fuzz_png @@

2. 主函数流程分析

2.1 初始化和参数处理

  1. 初始化随机数种子
  2. 使用while循环和switch结构处理参数
  3. 设置信号处理函数setup_signal_handlers
  4. 检查ASAN_OPTIONS和MASN_OPTIONS的合法性check_asan_opts

2.2 模式设置和系统检查

2.2.1 主从模式处理

fix_up_sync函数处理-M/-S主从模式:

  • 如果指定了sync_id,更新out_dir和sync_dir的值
  • 设置sync_dir的值为out_dir
  • 设置out_dir的值为out_dir/sync_id

2.2.2 其他初始化函数

  • save_line: 将参数转移到堆上存储,指针保存在全局变量orig_cmdline中
  • fix_up_banner: 设置UI显示的标题
  • check_if_tty: 检查程序是否在tty终端运行
  • get_core_count: 获取CPU核心数
  • bind_to_free_cpu: 绑定进程到空闲CPU(如果定义了HAVE_AFFINITY)
  • check_crash_handling: 检查崩溃处理设置
  • check_cpu_governor: 检查CPU调节器设置

3. 关键功能模块

3.1 后处理设置setup_post

static void setup_post(void) {
    void *dh;
    u8 *fn = getenv("AFL_POST_LIBRARY");
    u32 tlen = 6;
    
    if (!fn) return;
    
    ACTF("Loading postprocessor from '%s'...", fn);
    dh = dlopen(fn, RTLD_NOW);
    if (!dh) FATAL("%s", dlerror());
    
    post_handler = dlsym(dh, "afl_postprocess");
    if (!post_handler) FATAL("Symbol 'afl_postprocess' not found.");
    
    /* 测试调用 */
    post_handler("hello", &tlen);
    OKF("Postprocessor installed successfully.");
}
  • 如果定义了AFL_POST_LIBRARY环境变量,加载指定的动态库
  • 查找并测试调用afl_postprocess函数
  • 为后续fuzz操作提供hook点

3.2 共享内存设置setup_shm

EXP_ST void setup_shm(void) {
    u8 *shm_str;
    
    if (!in_bitmap) 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);
    if (shm_id < 0) PFATAL("shmget() failed");
    
    atexit(remove_shm);
    shm_str = alloc_printf("%d", shm_id);
    
    if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1);
    ck_free(shm_str);
    
    trace_bits = shmat(shm_id, NULL, 0);
    if (trace_bits == (void *)-1) PFATAL("shmat() failed");
}
  • 初始化virgin_bits、virgin_tmout和virgin_crash为255
  • 创建共享内存段,大小为MAP_SIZE
  • 注册atexit处理函数remove_shm
  • 如果不是dumb模式,设置环境变量SHM_ENV_VAR
  • 将共享内存附加到进程地址空间,返回指针trace_bits

3.3 路径计数分类init_count_class16

EXP_ST void init_count_class16(void) {
    u32 b1, b2;
    
    for (b1 = 0; b1 < 256; b1++)
        for (b2 = 0; b2 < 256; b2++)
            count_class_lookup16[(b1 << 8) + b2] = 
                (count_class_lookup8[b1] << 8) | count_class_lookup8[b2];
}
  • 基于count_class_lookup8初始化count_class_lookup16
  • count_class_lookup8定义:
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
};
  • 目的:减少因命中次数不同导致的路径差异,将相近的执行次数归为同一类

3.4 输入输出目录处理setup_dirs_fds

  • 处理主从模式下的目录结构
  • 创建输出目录并设置权限为0700
  • 处理恢复模式(in_place_resume)的特殊情况
  • 创建必要的子目录和文件(queue, queue/.stat等)

3.5 测试用例读取read_testcases

  1. 检查in_dir/queue目录是否存在
  2. 使用scandir和alphasort扫描输入目录
  3. 如果设置了shuffle_queue,对文件列表进行随机排序
  4. 过滤无效文件(如readme.txt)
  5. 检查文件大小(不超过1MB)
  6. 检查是否已完成确定性fuzzing(passed_det)
  7. 调用add_to_queue将测试用例加入队列

3.6 队列管理add_to_queue

struct queue_entry {
    u8* fname;                  /* 文件名 */
    u32 len;                    /* 文件大小 */
    u32 depth;                  /* 路径深度 */
    u8 passed_det;              /* 确定性fuzzing标志 */
    struct queue_entry *next;   /* 链表指针 */
    struct queue_entry *next_100; /* 每100个一组的指针 */
    /* 其他字段... */
};
  • 使用头插法维护队列
  • 每100个测试用例设置一个next_100指针,便于快速遍历
  • 维护计数器:queued_paths, pending_not_fuzzed等

4. Fuzz核心流程

4.1 初始运行perform_dry_run

perform_dry_run(use_argv);  // 主要fuzz函数
cull_queue();               // 根据运行效果排序种子
show_init_stats();          // 初始UI显示
seek_to = find_start_position();
write_stats_file(0, 0, 0);  // 状态文件写入
save_auto();                // 保存自动提取的token

4.2 校准测试用例calibrate_case

  1. 判断是否是第一次运行该用例
  2. 设置超时时间和校准轮次(默认为8次)
  3. 初始化forkserver(如果不是dumb模式)
  4. 执行多次运行:
    • write_to_testcase: 写入测试用例
    • run_target: 运行目标程序
    • 计算路径hash hash32(trace_bits, MAP_SIZE, HASH_CONST)
    • 检查新路径has_new_bits(virgin_bits)
  5. 处理可变行为:
    • 比较首次运行和后续运行的trace_bits差异
    • 标记可变测试用例mark_as_variable
  6. 更新bitmap分数update_bitmap_score

4.3 目标程序运行run_target

  1. 清空共享内存trace_bits
  2. dumb模式或no_forkserver时:
    • 直接fork子进程执行目标程序
    • execv失败时写入EXEC_FAIL_SIG
  3. 正常模式:
    • 通过管道与forkserver通信
    • 读取子进程PID和状态码
  4. 对路径执行次数进行分类classify_counts

4.4 路径评分更新update_bitmap_score

  1. 计算fav_factor = exec_us * len
  2. 遍历trace_bits:
    • 跳过未被覆盖的路径
    • 比较当前测试用例和top_rated的fav_factor
    • 更新更优的测试用例到top_rated数组
  3. 必要时压缩trace_bits到trace_mini
static void minimize_bits(u8 *dst, u8 *src) {
    u32 i = 0;
    while (i < MAP_SIZE) {
        if (*(src++)) dst[i >> 3] |= 1 << (i & 7);
        i++;
    }
}

5. 关键数据结构

5.1 共享内存相关

  • trace_bits: 共享内存指针,记录路径覆盖情况
  • virgin_bits: 记录未被fuzz覆盖的路径
  • virgin_tmout: 超时相关路径
  • virgin_crash: 崩溃相关路径

5.2 队列相关

  • queue: 测试用例队列头指针
  • queue_top: 队列尾指针
  • q_prev100: 每100个测试用例的指针
  • queued_paths: 队列中测试用例总数
  • pending_not_fuzzed: 待fuzz的测试用例数

5.3 路径分类

  • count_class_lookup8: 8位路径计数分类
  • count_class_lookup16: 16位路径计数分类
  • top_rated: 每个路径字节的最佳测试用例

6. 关键算法

6.1 新路径检测has_new_bits

  1. 8字节一组处理trace_bits和virgin_map
  2. 检查current & virgin是否为非零
  3. 判断是否发现全新路径(ret=2)或路径次数变化(ret=1)
  4. 更新virgin_map: *virgin &= ~*current
  5. 如果virgin_map是virgin_bits且发现新路径,设置bitmap_changed=1

6.2 路径压缩minimize_bits

  • 将MAP_SIZE位的trace_bits压缩为MAP_SIZE/8字节
  • 使用位操作高效存储路径信息

7. 总结

本文详细分析了AFL的fuzz准备阶段和UI显示相关源码,包括:

  1. 环境准备和参数处理流程
  2. 共享内存和关键数据结构初始化
  3. 测试用例队列管理机制
  4. 初始校准运行的核心算法
  5. 路径覆盖检测和评分系统

这些组件共同构成了AFL高效fuzzing的基础,理解这些机制对于后续的AFL定制和优化至关重要。

AFL源码分析:Fuzz准备和UI显示 1. 环境准备和编译 1.1 测试程序准备 使用开源png处理库编写测试程序: 1.2 编译命令 使用afl-gcc编译测试程序: 1.3 AFL参数设置 2. 主函数流程分析 2.1 初始化和参数处理 初始化随机数种子 使用while循环和switch结构处理参数 设置信号处理函数 setup_signal_handlers 检查ASAN_ OPTIONS和MASN_ OPTIONS的合法性 check_asan_opts 2.2 模式设置和系统检查 2.2.1 主从模式处理 fix_up_sync 函数处理-M/-S主从模式: 如果指定了sync_ id,更新out_ dir和sync_ dir的值 设置sync_ dir的值为out_ dir 设置out_ dir的值为out_ dir/sync_ id 2.2.2 其他初始化函数 save_line : 将参数转移到堆上存储,指针保存在全局变量orig_ cmdline中 fix_up_banner : 设置UI显示的标题 check_if_tty : 检查程序是否在tty终端运行 get_core_count : 获取CPU核心数 bind_to_free_cpu : 绑定进程到空闲CPU(如果定义了HAVE_ AFFINITY) check_crash_handling : 检查崩溃处理设置 check_cpu_governor : 检查CPU调节器设置 3. 关键功能模块 3.1 后处理设置 setup_post 如果定义了AFL_ POST_ LIBRARY环境变量,加载指定的动态库 查找并测试调用 afl_postprocess 函数 为后续fuzz操作提供hook点 3.2 共享内存设置 setup_shm 初始化virgin_ bits、virgin_ tmout和virgin_ crash为255 创建共享内存段,大小为MAP_ SIZE 注册atexit处理函数 remove_shm 如果不是dumb模式,设置环境变量SHM_ ENV_ VAR 将共享内存附加到进程地址空间,返回指针trace_ bits 3.3 路径计数分类 init_count_class16 基于count_ class_ lookup8初始化count_ class_ lookup16 count_ class_ lookup8定义: 目的:减少因命中次数不同导致的路径差异,将相近的执行次数归为同一类 3.4 输入输出目录处理 setup_dirs_fds 处理主从模式下的目录结构 创建输出目录并设置权限为0700 处理恢复模式(in_ place_ resume)的特殊情况 创建必要的子目录和文件(queue, queue/.stat等) 3.5 测试用例读取 read_testcases 检查in_ dir/queue目录是否存在 使用scandir和alphasort扫描输入目录 如果设置了shuffle_ queue,对文件列表进行随机排序 过滤无效文件(如readme.txt) 检查文件大小(不超过1MB) 检查是否已完成确定性fuzzing(passed_ det) 调用 add_to_queue 将测试用例加入队列 3.6 队列管理 add_to_queue 使用头插法维护队列 每100个测试用例设置一个next_ 100指针,便于快速遍历 维护计数器:queued_ paths, pending_ not_ fuzzed等 4. Fuzz核心流程 4.1 初始运行 perform_dry_run 4.2 校准测试用例 calibrate_case 判断是否是第一次运行该用例 设置超时时间和校准轮次(默认为8次) 初始化forkserver(如果不是dumb模式) 执行多次运行: write_to_testcase : 写入测试用例 run_target : 运行目标程序 计算路径hash hash32(trace_bits, MAP_SIZE, HASH_CONST) 检查新路径 has_new_bits(virgin_bits) 处理可变行为: 比较首次运行和后续运行的trace_ bits差异 标记可变测试用例 mark_as_variable 更新bitmap分数 update_bitmap_score 4.3 目标程序运行 run_target 清空共享内存trace_ bits dumb模式或no_ forkserver时: 直接fork子进程执行目标程序 execv失败时写入EXEC_ FAIL_ SIG 正常模式: 通过管道与forkserver通信 读取子进程PID和状态码 对路径执行次数进行分类 classify_counts 4.4 路径评分更新 update_bitmap_score 计算fav_ factor = exec_ us * len 遍历trace_ bits: 跳过未被覆盖的路径 比较当前测试用例和top_ rated的fav_ factor 更新更优的测试用例到top_ rated数组 必要时压缩trace_ bits到trace_ mini 5. 关键数据结构 5.1 共享内存相关 trace_bits : 共享内存指针,记录路径覆盖情况 virgin_bits : 记录未被fuzz覆盖的路径 virgin_tmout : 超时相关路径 virgin_crash : 崩溃相关路径 5.2 队列相关 queue : 测试用例队列头指针 queue_top : 队列尾指针 q_prev100 : 每100个测试用例的指针 queued_paths : 队列中测试用例总数 pending_not_fuzzed : 待fuzz的测试用例数 5.3 路径分类 count_class_lookup8 : 8位路径计数分类 count_class_lookup16 : 16位路径计数分类 top_rated : 每个路径字节的最佳测试用例 6. 关键算法 6.1 新路径检测 has_new_bits 8字节一组处理trace_ bits和virgin_ map 检查current & virgin是否为非零 判断是否发现全新路径(ret=2)或路径次数变化(ret=1) 更新virgin_ map: *virgin &= ~*current 如果virgin_ map是virgin_ bits且发现新路径,设置bitmap_ changed=1 6.2 路径压缩 minimize_bits 将MAP_ SIZE位的trace_ bits压缩为MAP_ SIZE/8字节 使用位操作高效存储路径信息 7. 总结 本文详细分析了AFL的fuzz准备阶段和UI显示相关源码,包括: 环境准备和参数处理流程 共享内存和关键数据结构初始化 测试用例队列管理机制 初始校准运行的核心算法 路径覆盖检测和评分系统 这些组件共同构成了AFL高效fuzzing的基础,理解这些机制对于后续的AFL定制和优化至关重要。