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 初始化和参数处理
- 初始化随机数种子
- 使用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
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
- 检查in_dir/queue目录是否存在
- 使用scandir和alphasort扫描输入目录
- 如果设置了shuffle_queue,对文件列表进行随机排序
- 过滤无效文件(如readme.txt)
- 检查文件大小(不超过1MB)
- 检查是否已完成确定性fuzzing(passed_det)
- 调用
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
- 判断是否是第一次运行该用例
- 设置超时时间和校准轮次(默认为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
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
- 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定制和优化至关重要。