Fuzz Server With AFL
字数 1374 2025-08-24 07:48:09

AFL 持久化模式模糊测试服务器应用指南

问题背景

AFL (American Fuzzy Lop) 是一个强大的模糊测试工具,但其标准工作模式存在以下限制:

  1. 只能从 stdin 或文件获取输入进行变异
  2. 网络应用通常使用 socket 或其他网络通信方式
  3. 每次测试都会重新启动程序,对于启动时间较长的服务器应用效率低下

传统解决方案是为特定程序编写 harness 来测试部分代码,但这种方法:

  • 需要对源码有深入理解
  • 覆盖率较低

解决方案:持久化模式(Persistent Mode)

AFL 标准工作流程

  1. 创建新进程
  2. 提供测试用例
  3. 监视直到进程结束
  4. 重复上述过程

Forkserver 机制

AFL 引入 forkserver 机制来优化:

  • 新进程通过 fork 产生
  • 利用 copy-on-write 机制提高性能
  • 但每次测试仍需要 fork 操作

持久化模式原理

  1. 适用于 API 无状态或可重置到初始状态的情况
  2. 测试用例在进程内部生成
  3. 通过自定义的单进程循环反馈到待测试 API
  4. 效率提升约 10 倍

注意事项

  • 可能因内存泄漏或 DoS 而失败
  • 自 AFL-1.81b 开始支持

持久化模式工作机制

  1. AFL 将测试用例提供给一个长期存活的进程
  2. 该进程读取输入并传递给待测试 API
  3. 通过终止进程通知 fuzzer 测试完成
  4. 父进程恢复时,自定义进程循环回到起点

简单示例 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();
  }
}

持久化模式关键控制点

  1. AFL 何时 fork 目标进程
  2. AFL 何时提供新的测试用例
  3. 每轮循环开始时必须重置目标进程状态

服务器应用适配方案

标准服务器实现

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 服务器

  1. 使用配置文件:./tests-fuzz/knotd_wrap/knot_stdio.conf
  2. 创建工作目录(根据配置指定)
  3. 启动服务器: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_packet
  • fuzz_dnamr_from_str
  • fuzz_dname_to_str
  • fuzz_zscanner

这些测试点都提供了函数包装器,可以从文件获取内容进行模糊测试。项目还保留了 knotd_wrap,重写了 UDP 和 TCP 的 recv 方法以便从 stdin 读取。

关键总结

  1. 持久化模式优势:显著提高测试效率,避免频繁进程启动开销
  2. 适配要点
    • 确保状态可重置
    • 正确处理输入/输出
    • 正确通知 fuzzer
  3. 实现方式:通过添加 Shim 代码段修改目标程序
  4. 适用场景:网络服务器、长时间运行的应用等

通过这种方法,可以有效地将 AFL 应用于网络服务器等传统上难以直接测试的目标,大大提高模糊测试的效率和覆盖率。

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 持久化模式关键控制点 AFL 何时 fork 目标进程 AFL 何时提供新的测试用例 每轮循环开始时必须重置目标进程状态 服务器应用适配方案 标准服务器实现 适配 AFL 持久化模式修改 Knot DNS 实战案例 目标分析 代码位置: src/knot/server/udp-handler.c 关键函数: udp_master 主要逻辑:循环等待 socket 事件,接收并处理 UDP 数据包 修改方案(三个 Shim) Shim1:定义和初始化变量 在主循环之前添加: Shim2:从文件获取测试样例 在事件等待循环中添加: Shim3:通知 fuzzer 在处理事件后添加: 配置和编译目标 运行 Knot 服务器 使用配置文件: ./tests-fuzz/knotd_wrap/knot_stdio.conf 创建工作目录(根据配置指定) 启动服务器: src/knotd 测试用例最小化 启动持久化模式模糊测试 其他测试点 Knot DNS 项目中还提供了其他 AFL 测试点: fuzz_packet fuzz_dnamr_from_str fuzz_dname_to_str fuzz_zscanner 这些测试点都提供了函数包装器,可以从文件获取内容进行模糊测试。项目还保留了 knotd_wrap ,重写了 UDP 和 TCP 的 recv 方法以便从 stdin 读取。 关键总结 持久化模式优势 :显著提高测试效率,避免频繁进程启动开销 适配要点 : 确保状态可重置 正确处理输入/输出 正确通知 fuzzer 实现方式 :通过添加 Shim 代码段修改目标程序 适用场景 :网络服务器、长时间运行的应用等 通过这种方法,可以有效地将 AFL 应用于网络服务器等传统上难以直接测试的目标,大大提高模糊测试的效率和覆盖率。