afl-fuzz.c main函数简单流程

afl-fuzz.c main函数简单流程(关键步骤省略)

预处理

  • 打印afl程序信息;
1
SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
  • 检查文件是否存在;
1
doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH;   
  • 设置一个种子,方便后续随机量的设置;
1
2
gettimeofday(&tv, &tz);
srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());
  • 根据命令行初始化并检查一些参数;

    1. i:输入语料文件夹,对应变量in_dir
    2. o:fuzz输出文件夹,对应变量out_dir
    3. M,S:多线程模式下的Master和Server,Master会采取强制确定性变异,然后进行随进行变异,而Server会采取dumb mode进行fuzz,即不进行确定性变异;
    4. f:用来进行fuzz的文件,对应变量out_file
    5. x:确定性变异阶段的字典(?),对应变量extras_dir
    6. t:目标程序运行case的限制时间,对应变量timeout_given
    7. m:目标程序运行内存的限制,对应变量mem_limit
    8. d:跳过确定性变异,对应变量skip_deterministic
    9. B:指定fuzz_bitmap来跳过该case;
    10. C:crash mode;
    11. n:dumb mode(随机模式,且不进行插桩);
    12. T:text banner;
    13. Q;QEMU mode;
  • 设置信号句柄sa

1
setup_signal_handlers();
  • 检查ASAN设置:
1
check_asan_opts();
  • 修正fuzzer ID:
1
if (sync_id) fix_up_sync();
  • 检查输入输出文件夹:
1
2
if (!strcmp(in_dir, out_dir))
FATAL("Input and output directories can't be the same");
  • 若设置了dumb mode则检查与其冲突的模式:
1
2
3
4
5
6
if (dumb_mode) {

if (crash_mode) FATAL("-C and -n are mutually exclusive");
if (qemu_mode) FATAL("-Q and -n are mutually exclusive");

}
  • 一些环境变量的设置以及检查。

  • 保存命令行:

1
save_cmdline(argc, argv);
  • 调整banner的展示效果(将fuzzer id写上):
1
fix_up_banner(argv[optind]);
  • 检查是否在TTY终端:
1
check_if_tty();
  • 查询/proc/stat得知内核信息并打印:
1
get_core_count();
  • 构建绑定到特定核心的进程列表:
1
2
3
#ifdef HAVE_AFFINITY
bind_to_free_cpu();
#endif /* HAVE_AFFINITY */
  • 检查crash的转存以及CPU的管理者(主要通过检查/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor这类的设备文件):
1
2
check_crash_handling(); 
check_cpu_governor();
  • 检查环境变量AFL_POST_LIBRARY,该环境变量用于对变异后的testcases格式进行修正,如计算校验和等:
1
setup_post();

使用后会进行如下编译:

1
gcc -shared -Wall -O3 post_library.so.c -o post_library.so
  • 设置共享内存块以及virgin_bits(用来记录总分支路径信息:Regions yet untouched by fuzzing):
1
setup_shm();
  • 扩展路径记录的表:
1
init_count_class16();

喂数据

  • 设置输出文件夹和文件描述符:
1
setup_dirs_fds();
  • 读取测试用例进入队列中,包括对输入文件夹权限、内容等的检查:
1
read_testcases();
  • 自动载入token,即检查输入文件夹中是否有token文件夹,有则载入。这里查阅资料后:
    • 使用token是用来执行bitflip时降低消耗资源的一种策略,即将一个token代表一系列变异后覆盖路径未变化的语料。
1
load_auto();
  • 在输出文件中为输入文件(变异后的)创建硬链接,以”id:”开头:# define CASE_PREFIX "id:",并根据其进行适当的调整:
1
pivot_inputs();
  • 若存在token文件夹则载入该文件夹:
1
if (extras_dir) load_extras(extras_dir);
  • 若没有设置”-t”的参数,则找到一个合理的”timeout”值,以防止不断地收缩这个”timeout”值:
1
if (!timeout_given) find_timeout();
  • 若未设置输出文件夹,则为输出数据设置输出文件夹:
1
if (!out_file) setup_stdio_file();
  • 检查目标程序的存在,且不是一个shell脚本,通过检查ELF头来判断是否为一个ELF文件,可以通过设置”AFL_SKIP_BIN_CHECK”环境变量来跳过该项检查:
1
check_binary(argv[optind]);
  • 获取当前时间为开始时间:
1
start_time = get_cur_time();
  • 判断是否为qemu模式的fuzz:
1
2
3
4
if (qemu_mode)
use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind);
else
use_argv = argv + optind;

执行fuzz(具体步骤后续详细分析)

  • dry run,将种子文件直接作为输入文件喂给目标程序:
1
perform_dry_run(use_argv);
  • 从队列中找到最合适的testcase,赋值给top_rated[],并且设置q->favored:
1
cull_queue();
  • 处理完初始语料后显示提示信息:
1
show_init_stats();
  • 找到队列开始的位置:
1
seek_to = find_start_position();
  • 更新统计信息文件:
1
write_stats_file(0, 0, 0);
  • 自动保存token:
1
save_auto();
  • contrl+C,结束fuzz:
1
if (stop_soon) goto stop_fuzzing;
  • 不在TTy终端也结束fuzz:
1
2
3
4
5
  if (!not_on_tty) {
sleep(4);
start_time += 4000;
if (stop_soon) goto stop_fuzzing;
}
  • fuzz_one循环:
1
2
3
while(1){
......
}
  • 收尾工作,包括关闭文件描述符,销毁队列、token文件夹等:
1
2
3
4
5
6
7
8
9
10
11
fclose(plot_file);
destroy_queue();
destroy_extras();
ck_free(target_path);
ck_free(sync_id);

alloc_report();

OKF("We're done here. Have a nice day!\n");

exit(0);

参考链接:

https://www.jianshu.com/p/487f5e451325

https://blog.csdn.net/wxh0000mm/article/details/108828040

https://bbs.pediy.com/thread-218671.htm