Fuzz Server With AFL
字数 1374 2025-08-24 07:48:09
AFL 持久化模式模糊测试服务器应用指南
问题背景
AFL (American Fuzzy Lop) 是一个强大的模糊测试工具,但其标准工作模式存在以下限制:
- 只能从 stdin 或文件获取输入进行变异
- 网络应用通常使用 socket 或其他网络通信方式
- 每次测试都会重新启动程序,对于启动时间较长的服务器应用效率低下
传统解决方案是为特定程序编写 harness 来测试部分代码,但这种方法:
- 需要对源码有深入理解
- 覆盖率较低
解决方案:持久化模式(Persistent Mode)
AFL 标准工作流程
- 创建新进程
- 提供测试用例
- 监视直到进程结束
- 重复上述过程
Forkserver 机制
AFL 引入 forkserver 机制来优化:
- 新进程通过 fork 产生
- 利用 copy-on-write 机制提高性能
- 但每次测试仍需要 fork 操作
持久化模式原理
- 适用于 API 无状态或可重置到初始状态的情况
- 测试用例在进程内部生成
- 通过自定义的单进程循环反馈到待测试 API
- 效率提升约 10 倍
注意事项:
- 可能因内存泄漏或 DoS 而失败
- 自 AFL-1.81b 开始支持
持久化模式工作机制
- AFL 将测试用例提供给一个长期存活的进程
- 该进程读取输入并传递给待测试 API
- 通过终止进程通知 fuzzer 测试完成
- 父进程恢复时,自定义进程循环回到起点
简单示例 harness
int main(int argc, char **argv) {
while (__AFL_LOOP(1000)) {
/* Reset state. */
memset(buf, 0, 100);
/* Read input data. */
read(0, buf, 100);
/* Parse it in some vulnerable way. */
if (buf[0] != 'p') puts("error 1");
else if (buf[1] != 'w') puts("error 2");
else if (buf[2] != 'n') puts("error 3");
else abort();
}
}
持久化模式关键控制点
- AFL 何时 fork 目标进程
- AFL 何时提供新的测试用例
- 每轮循环开始时必须重置目标进程状态
服务器应用适配方案
标准服务器实现
while(go):
req = get_request()
process(req)
适配 AFL 持久化模式修改
while(go):
put_request(read(file)) # AFL 提供输入
req = get_request()
process(req)
notify_fuzzer() # 通知 AFL
Knot DNS 实战案例
目标分析
- 代码位置:
src/knot/server/udp-handler.c - 关键函数:
udp_master - 主要逻辑:循环等待 socket 事件,接收并处理 UDP 数据包
修改方案(三个 Shim)
Shim1:定义和初始化变量
在主循环之前添加:
#ifdef KNOT_AFL_PERSISTENT_SHIM
/* For AFL persistent mode fuzzing shim */
/* Initialize variables for fuzzing */
size_t insize;
struct sockaddr_in servaddr;
int udp_socket;
char *env_dest_ip = getenv("KNOT_AFL_DEST_IP");
char *env_dest_port = getenv("KNOT_AFL_DEST_PORT");
int dest_port = env_dest_port ? strtol(env_dest_port, NULL, 10) : 9090;
char *dest_ip = env_dest_ip ? env_dest_ip : "127.0.0.1";
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(dest_ip);
servaddr.sin_port = htons(dest_port);
char buf[5120];
#endif
Shim2:从文件获取测试样例
在事件等待循环中添加:
#ifdef KNOT_AFL_PERSISTENT_SHIM
/* For AFL persistent mode fuzzing shim */
/* Read fuzzed packet from stdin and send to socket */
if (getenv("KNOT_AFL_STDIN") || getenv("KNOT_AFL_CMIN") || getenv("AFL_PERSISTENT")) {
memset(buf, 0, 5120);
insize = read(0, buf, 5120); // read fuzz case from stdin
udp_socket = handler->server->ifaces->fd_udp[0];
sendto(udp_socket, buf, insize, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
}
#endif
Shim3:通知 fuzzer
在处理事件后添加:
#ifdef KNOT_AFL_PERSISTENT_SHIM
/* For AFL persistent mode fuzzing shim */
/* Signal AFL to fuzz input and continue execution */
if (getenv("AFL_PERSISTENT")) {
raise(SIGSTOP);
} else if (getenv("KNOT_AFL_CMIN")) {
exit(0);
}
#endif
配置和编译目标
CC=~/path-afl/afl-clang-fast CFLAGS='-DKNOT_AFL_PERSISTENT_SHIM' ./configure --disable-shared
make
运行 Knot 服务器
- 使用配置文件:
./tests-fuzz/knotd_wrap/knot_stdio.conf - 创建工作目录(根据配置指定)
- 启动服务器:
src/knotd
测试用例最小化
KNOT_AFL_CMIN=1 ~/path-afl/afl-cmin -i in -o cmin -- ./src/knot -c my_config.config
启动持久化模式模糊测试
AFL_PERSISTENT=1 ~/path-afl/afl-fuzz -i cmin -o out -- ./src/knot -c my_config.conf
其他测试点
Knot DNS 项目中还提供了其他 AFL 测试点:
fuzz_packetfuzz_dnamr_from_strfuzz_dname_to_strfuzz_zscanner
这些测试点都提供了函数包装器,可以从文件获取内容进行模糊测试。项目还保留了 knotd_wrap,重写了 UDP 和 TCP 的 recv 方法以便从 stdin 读取。
关键总结
- 持久化模式优势:显著提高测试效率,避免频繁进程启动开销
- 适配要点:
- 确保状态可重置
- 正确处理输入/输出
- 正确通知 fuzzer
- 实现方式:通过添加 Shim 代码段修改目标程序
- 适用场景:网络服务器、长时间运行的应用等
通过这种方法,可以有效地将 AFL 应用于网络服务器等传统上难以直接测试的目标,大大提高模糊测试的效率和覆盖率。