前言

winaflaflwindows 的移植版, winafl 使用 dynamorio 来统计代码覆盖率,并且使用共享内存的方式让 fuzzer 知道每个测试样本的覆盖率信息。本文主要介绍 winafl 不同于 afl 的部分,对于 afl 的变异策略等部分没有介绍,对于 afl 的分析可以看

https://paper.seebug.org/496/#arithmetic

源码分析

winafl 主要分为两个部分 afl-fuzz.cwinafl.c , 前者是 fuzzer 的主程序 ,后面的是收集程序运行时信息的 dynamorio 插件的源码。

afl-fuzz

main

winafl 的入口时 afl-fuzz.c , 其中的 main 函数的主要代码如下

int main(int argc, char** argv) {

  // 加载变异数据修正模块
  setup_post();
  if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); // MAP_SIZE --> 0x00010000
  setup_shm();  // 设置共享内存
  init_count_class16();

  setup_dirs_fds(); // 设置模糊测试过程中的文件存放位置
  read_testcases();  // 读取测试用例到队列

  // 首先跑一遍所有的测试用例, 记录信息到样本队列
  perform_dry_run(use_argv);

  // 模糊测试主循环
  while (1) {
    u8 skipped_fuzz;
    // 每次循环从样本队列里面取测试用例
    cull_queue();

    // 对测试用例进行测试
    skipped_fuzz = fuzz_one(use_argv);

    queue_cur = queue_cur->next;
    current_entry++;
  }
}
  • 首先设置一些 fuzz 过程中需要的状态值,比如共享内存、输入输出位置。
  • 然后通过 perform_dry_run 把提供的所有测试用例让目标程序跑一遍,同时统计执行过程中的覆盖率信息。
  • 之后就开始进行模糊测试的循环,每次取样本出来,然后交给 fuzz_one 对该样本进行 fuzz .

post_handler

该函数里面最重要的就是 fuzz_one 函数, 该函数的作用是完成一个样本的模糊测试,这里面实现了 afl 中的模糊测试策略,使用这些测试策略生成一个样本后,使用采用 common_fuzz_stuff 函数来让目标程序执行测试用例。common_fuzz_stuff 的主要代码如下

static u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) {

  u8 fault;

  // 如果提供了数据修正函数,则调用
  if (post_handler) {

    out_buf = post_handler(out_buf, &len);
    if (!out_buf || !len) return 0;

  }

  write_to_testcase(out_buf, len);

  // 让目标程序执行测试用例,并返回执行结果
  fault = run_target(argv, exec_tmout);

函数首先会判断是否提供了 post_handler , 如果提供了 post_handler 就会使用提供的 post_handler 对变异得到的测试数据进行处理, post_handler 函数指针在 setup_post 函数中设置。

static void setup_post(void) {
    HMODULE dh;
    u8* fn = getenv("AFL_POST_LIBRARY"); // 通过环境变量获取 post_handler  所在 dll 的路径
    u32 tlen = 6;

    if (!fn) return;
    ACTF("Loading postprocessor from '%s'...", fn);
    dh = LoadLibraryA(fn);
    if (!dh) FATAL("%s", dlerror());
    post_handler = (u8* (*)(u8*,u32*))GetProcAddress(dh, "afl_postprocess"); // 加载dll 获取函数地址
    if (!post_handler) FATAL("Symbol 'afl_postprocess' not found.");

    /* Do a quick test. It's better to segfault now than later =) */
    post_handler("hello", &tlen);
    OKF("Postprocessor installed successfully.");
}

该函数首先从 AFL_POST_LIBRARY 环境变量里面拿到 post_handler 所在 dll 的路径, 然后设置 post_handlerdll 里面的 afl_postprocess 函数的地址。该函数在 fuzzer 运行的开头会调用。 post_handler 的定义如下

static u8* (*post_handler)(u8* buf, u32* len);
参数: buf 输入内存地址,  len 输入内存的长度
返回值: 指向修正后的内存的地址

所以 afl_postprocess 需要接收两个参数, 然后返回一个指向修正后的内存的地址。post_handler 这个机制用于对测试数据的格式做简单的修正,比如计算校验和,计算文件长度等。

run_target

post_handler 这一步过后,会调用 write_to_testcase 先把测试用例写入文件,默认情况下测试用例会写入 .cur_input (用户可以使用 -f 指定)

out_file = alloc_printf("%s\\.cur_input", out_dir);

然后调用 run_target 让目标程序处理测试用例,其主要代码如下

static u8 run_target(char** argv, u32 timeout) {

  // 如果进程还存活就不去创建新的进程
  if(!is_child_running()) {
    destroy_target_process(0);
    create_target_process(argv);  // 创建进程并且使用 dynamorio 监控
    fuzz_iterations_current = 0;
  }

  if (custom_dll_defined)
      process_test_case_into_dll(fuzz_iterations_current);

  child_timed_out = 0;
  memset(trace_bits, 0, MAP_SIZE);

  result = ReadCommandFromPipe(timeout);
  if (result == 'K')
  {
      //a workaround for first cycle in app persistent mode
      result = ReadCommandFromPipe(timeout);
  }

  // 当 winafl.dll 插桩准备好以后, 会通过命名管道发送 P 
  if (result != 'P')
  {
      FATAL("Unexpected result from pipe! expected 'P', instead received '%c'\n", result);
  }

  // 让 winafl.dll 那端开始继续执行
  WriteCommandToPipe('F');

  result = ReadCommandFromPipe(timeout); 
  // 接收到 K 就表示该用例运行正常
  if (result == 'K') return FAULT_NONE;
点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖