ToB企服应用市场:ToB评测及商务社交产业平台

标题: 模糊测试工具AFL源码浅析 [打印本页]

作者: 立聪堂德州十三局店    时间: 2022-10-25 23:22
标题: 模糊测试工具AFL源码浅析
前言

AFL是一款著名的模糊测试的工具,最近在阅读AFL源码,记录一下,方便以后查阅。
环境

[img=720,514.601226993865]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527686.png[/img]
afl-gcc.c

使用gdb加载afl-gcc,并使用set arg -o test test.c设置参数
[img=720,392.3562753036437]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527688.png[/img]
find_as函数

  1. u8 *afl_path = getenv("AFL_PATH");
  2. ...
  3. if (afl_path) {
  4.     tmp = alloc_printf("%s/as", afl_path); //将AFL所在路径与字符as进行拼接
  5.     if (!access(tmp, X_OK)) { //函数用来判断指定的文件或目录是否有可执行权限,若指定方式有效则返回0,否则返回-1
  6.       as_path = afl_path;
  7.       ck_free(tmp);
  8.       return;
  9.     }
  10.     ck_free(tmp);
  11.   }
  12.   slash = strrchr(argv0, '/'); //在参数argv0所指向的字符串中搜索最后一次出现字符'/'
  13.   if (slash) {
  14.     u8 *dir;
  15.     *slash = 0;
  16.     dir = ck_strdup(argv0);
  17.     *slash = '/';
  18.     tmp = alloc_printf("%s/afl-as", dir); //将当前AFL所在的路径跟afl-as进行拼接
  19.     if (!access(tmp, X_OK)) {
  20.       as_path = dir;
  21.       ck_free(tmp);
  22.       return;
  23.     }
  24. ...
复制代码
 
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
edit_params函数

  1.   ...
  2.   cc_params = ck_alloc((argc + 128) * sizeof(u8*));
  3.   name = strrchr(argv[0], '/'); //获取可执行文件名称
  4.   if (!name) name = argv[0]; else name++; /*跳过路径符'/' */
  5.   if (!strncmp(name, "afl-clang", 9)) { //判断编译器是否为clang
  6.       ...
  7.   }
  8.   else {
  9.     if (!strcmp(name, "afl-g++")) {
  10.       u8* alt_cxx = getenv("AFL_CXX");
  11.       cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++";
  12.     } else if (!strcmp(name, "afl-gcj")) {
  13.       u8* alt_cc = getenv("AFL_GCJ");
  14.       cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj";
  15.     } else {
  16.       u8* alt_cc = getenv("AFL_CC");
  17.       cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc"; //如环境变量没写入AFL_CC则默认使用gcc
  18.     }
  19.   }
  20.   while (--argc) {
  21.     u8* cur = *(++argv); //读取下一个参数
  22.     if (!strncmp(cur, "-B", 2)) { //若参数是-B
  23.       if (!be_quiet) WARNF("-B is already set, overriding"); //用于设置编译器的搜索路径
  24.       if (!cur[2] && argc > 1) { argc--; argv++; }//继续读取下一个参数
  25.       continue;
  26.     }
  27.     if (!strcmp(cur, "-integrated-as")) continue;
  28.     if (!strcmp(cur, "-pipe")) continue;
  29. #if defined(__FreeBSD__) && defined(__x86_64__)
  30.     if (!strcmp(cur, "-m32")) m32_set = 1;
  31. #endif
  32.     if (!strcmp(cur, "-fsanitize=address") ||
  33.         !strcmp(cur, "-fsanitize=memory")) asan_set = 1; //内存访问的错误
  34.     if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;//缓冲区溢出问题的检查
  35.     cc_params[cc_par_cnt++] = cur; //cc_params用于存放的参数
  36.   }
  37.   cc_params[cc_par_cnt++] = "-B"; //参数-B
  38.   cc_params[cc_par_cnt++] = as_path; //afl-as的路径
  39.   if (clang_mode)
  40.     cc_params[cc_par_cnt++] = "-no-integrated-as";
  41.   if (getenv("AFL_HARDEN")) {
  42.     cc_params[cc_par_cnt++] = "-fstack-protector-all"; //canary保护
  43.     if (!fortify_set)
  44.       cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
  45.   }
  46.   if (asan_set) {
  47.     /* Pass this on to afl-as to adjust map density. */
  48.     setenv("AFL_USE_ASAN", "1", 1);
  49.   } else if (getenv("AFL_USE_ASAN")) {
  50.     if (getenv("AFL_USE_MSAN"))
  51.       FATAL("ASAN and MSAN are mutually exclusive");
  52.     if (getenv("AFL_HARDEN"))
  53.       FATAL("ASAN and AFL_HARDEN are mutually exclusive");
  54.     cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
  55.     cc_params[cc_par_cnt++] = "-fsanitize=address";
  56.   } else if (getenv("AFL_USE_MSAN")) {
  57.     if (getenv("AFL_USE_ASAN"))
  58.       FATAL("ASAN and MSAN are mutually exclusive");
  59.     if (getenv("AFL_HARDEN"))
  60.       FATAL("MSAN and AFL_HARDEN are mutually exclusive");
  61.     cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
  62.     cc_params[cc_par_cnt++] = "-fsanitize=memory";
  63.   }
  64.   ...
  65.       cc_params[cc_par_cnt++] = "-g";
  66.   ...
  67.     cc_params[cc_par_cnt++] = "-O3";
  68.     cc_params[cc_par_cnt++] = "-funroll-loops";
  69.     /* Two indicators that you're building for fuzzing; one of them is
  70.        AFL-specific, the other is shared with libfuzzer. */
  71.     cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
  72.     cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
  73.   }
  74.   if (getenv("AFL_NO_BUILTIN")) {
  75.     cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
  76.     cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
  77.     cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
  78.     cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
  79.     cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
  80.     cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
  81.     cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
  82.   }
  83.   cc_params[cc_par_cnt] = NULL;
  84. }
复制代码
通过edit_params函数后
[img=720,301.72062904717853]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527689.png[/img]
可以传递给编译器的参数增加了-B . -g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1这几项
main函数

  1.   /*
  2.     isatty函数用于判断文件描述词是否是为终端机
  3.     获取AFL_QUIET的环境变量
  4.   */
  5.   if (isatty(2) && !getenv("AFL_QUIET")) { //判断是否静默模式
  6.     /*
  7.       #ifdef MESSAGES_TO_STDOUT
  8.       #  define SAYF(x...)    printf(x)
  9.       #else
  10.       #  define SAYF(x...)    fprintf(stderr, x)
  11.       #endif
  12.     */
  13.     SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
  14.   } else be_quiet = 1;
  15.   if (argc < 2) { //参数个数小于两个
  16.     SAYF("\n"
  17.          "This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
  18.          "for gcc or clang, letting you recompile third-party code with the required\n"
  19.          "runtime instrumentation. A common use pattern would be one of the following:\n\n"
  20.          "  CC=%s/afl-gcc ./configure\n"
  21.          "  CXX=%s/afl-g++ ./configure\n\n"
  22.          "You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
  23.          "Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
  24.          BIN_PATH, BIN_PATH);
  25.     exit(1);
  26.   }
  27.   find_as(argv[0]); //用于寻找as所在路径
  28.   edit_params(argc, argv);//用于获取编译参数
  29.   execvp(cc_params[0], (char**)cc_params);//启动gcc或其他编译器
复制代码
 
大致流程图
[img=720,237.33333333333334]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527690.png[/img]
afl-gcc可以看作是劫持了gcc的一个程序,从而修改as的路径(为了后续的插桩做准备),并且添加所有fuzzing所需要的参数再传入实际的编译器中去(这里以gcc作为例子)
afl-as.c

edit_params函数

afl-as.c的edit_params函数比较简单
  1.   u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); //afl-as的地址
  2.   ...
  3.   as_params = ck_alloc((argc + 32) * sizeof(u8*)); //给参数分配空间
  4.   as_params[0] = afl_as ? afl_as : (u8*)"as";
  5.   as_params[argc] = 0; //截断符
  6.   ...
  7.   //用于记录文件是64位还是32位
  8.   for (i = 1; i < argc - 1; i++) {
  9.     if (!strcmp(argv[i], "--64")) use_64bit = 1;
  10.     else if (!strcmp(argv[i], "--32")) use_64bit = 0;  
  11.   ...
  12.     if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&
  13.         strncmp(input_file, "/var/tmp/", 9) &&
  14.         strncmp(input_file, "/tmp/", 5)) pass_thru = 1; //汇编文件需要放在临时目录下,否则后续无法对文件进行插桩
  15.   }
  16.   modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),
  17.                                (u32)time(NULL)); //随机生成文件名,作为插桩的目标文件
  18.   ...
  19.   as_params[as_par_cnt++] = modified_file; //将待修改的文件名作为汇编器的参数
  20.   as_params[as_par_cnt]   = NULL;
复制代码
add_instrumentation函数

add_instrumentation函数是插桩的关键函数
  1.    ...
  2.    if (input_file) { //需要编译的文件
  3.     inf = fopen(input_file, "r");
  4.     if (!inf) PFATAL("Unable to read '%s'", input_file);
  5.   } else inf = stdin;
  6.   outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); //打开存放插桩后的文件
  7.   if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);
  8.   outf = fdopen(outfd, "w");
  9.   if (!outf) PFATAL("fdopen() failed");  
  10.   while (fgets(line, MAX_LINE, inf)) { //对需要汇编的文件进行一行一行的扫描
  11.     /* In some cases, we want to defer writing the instrumentation trampoline
  12.        until after all the labels, macros, comments, etc. If we're in this
  13.        mode, and if the line starts with a tab followed by a character, dump
  14.        the trampoline now. */
  15.     //isalpha是一种函数:判断字符ch是否为英文字母
  16.     //#  define R(x) (random() % (x))
  17.     if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
  18.         instrument_next && line[0] == '\t' && isalpha(line[1])) {
  19.       fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
  20.               R(MAP_SIZE)); //将插桩代码写入改写文件中,trampoline_fmt_64为64位程序的插桩代码,trampoline_fmt_32为32位程序的插桩代码
  21.       instrument_next = 0;
  22.       ins_lines++; //总共插桩了多少处地方
  23.     }
  24.     ...
  25.     if (line[0] == '\t' && line[1] == '.') {
  26.       /* OpenBSD puts jump tables directly inline with the code, which is
  27.          a bit annoying. They use a specific format of p2align directives
  28.          around them, so we use that as a signal.
  29.         OpenBSD为一个类unix的操作系统
  30.        */
  31.       if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
  32.           isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; //跳转到下一个标签
  33.    
  34.       if (!strncmp(line + 2, "text\n", 5) ||
  35.           !strncmp(line + 2, "section\t.text", 13) ||
  36.           !strncmp(line + 2, "section\t__TEXT,__text", 21) ||
  37.           !strncmp(line + 2, "section __TEXT,__text", 21)) {
  38.         instr_ok = 1; //只要是text段就是我们应该插桩的段
  39.         continue;
  40.       }
  41.       if (!strncmp(line + 2, "section\t", 8) ||
  42.           !strncmp(line + 2, "section ", 8) ||
  43.           !strncmp(line + 2, "bss\n", 4) ||
  44.           !strncmp(line + 2, "data\n", 5)) {
  45.         instr_ok = 0; //不需要插桩的段
  46.         continue;
  47.       }
  48.     }   
  49.     ...
  50.     if (line[0] == '\t') {//检测jnz等分支指令
  51.       if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { //绝对跳转jmp不进行插桩处理
  52.         fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
  53.                 R(MAP_SIZE)); //给分支跳转指令进行插桩
  54.         ins_lines++; //插桩的指令数
  55.       }
  56.       continue; //插桩完直接跳过
  57.     }
  58.     ...
  59.     if (strstr(line, ":")) { //检测标签
  60.       if (line[0] == '.') {
  61.         /* Apple: .L<num> / .LBB<num> */
  62.   
  63.         if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) //分支标签
  64.             && R(100) < inst_ratio) {
  65.             
  66.           ...
  67.           if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;//若该标签不需要跳转则记录下来,该标签需要插桩
  68.         }
  69.       } else { //函数标签
  70.         /* Function label (always instrumented, deferred mode). */
  71.         instrument_next = 1;//函数标签都需要进行插桩
  72.    
  73.       }
  74.     }
  75.   }
  76.     if (ins_lines)
  77.     fputs(use_64bit ? main_payload_64 : main_payload_32, outf); //若进行插桩处理则需要插入main_payload_64
复制代码
这里重点关注一下插桩的位置
函数标签处的插桩如下图所示,插桩的位置是函数第一条指令的上方进行插桩
[img=720,345.69948186528495]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527691.png[/img]
扫描到分支跳转指令,则直接在跳转指令下方进行插桩处理,如下图所示
[img=720,341.59678345778286]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527692.png[/img]
.L为本地标签,afl-as.c也会扫描该标签并进行插桩处理,可以看到跳转指令的目的地地址就是以.L,因此.L可以认为分支的起始位置,与函数标签一样,会在第一条指令上方进行插桩处理
[img=720,417.4755043227666]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527693.png[/img]
main函数

main函数主要经过edit_params函数修改了传入汇编器的参数,并且对汇编文件进行插桩处理,最后使用execvp函数启动汇编器进行汇编处理
  1.   ...
  2.   gettimeofday(&tv, &tz);
  3.   rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();//随机种子
  4.   srandom(rand_seed);//通过种子生成随机数
  5.   edit_params(argc, argv); //加载参数,并在/tmp/目录下生成临时的汇编文件
  6.   if (inst_ratio_str) {
  7.     if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100)
  8.       FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)");
  9.   }
  10.   if (getenv(AS_LOOP_ENV_VAR))
  11.     FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");
  12.   setenv(AS_LOOP_ENV_VAR, "1", 1);
  13.   /* When compiling with ASAN, we don't have a particularly elegant way to skip
  14.      ASAN-specific branches. But we can probabilistically compensate for
  15.      that... */
  16.   if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) {
  17.     sanitizer = 1;
  18.     inst_ratio /= 3;
  19.   }
  20.   if (!just_version) add_instrumentation();//对文件进行插桩处理
  21.   if (!(pid = fork())) {
  22.     execvp(as_params[0], (char**)as_params);//将插桩后的文件传入汇编器中
  23.     FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);
  24. ...
复制代码
传入汇编器的参数情况
[img=720,150.69767441860466]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251529661.png[/img]
大致流程图
[img=720,382.22222222222223]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202210251527694.png[/img]
afl-as相当于劫持了as从而修改汇编的文件名以及对相应的汇编文件进行插桩处理
afl-as.h

该文件放置了插桩需要的代码如trampoline_fmt_64、trampoline_fmt_32、main_payload_64以及main_payload_32,这些代码结合fuzzing过程有关。
总结

afl-gcc与afl-as可以看作是劫持了编译器,将fuzzing相关的参数设置好并对编译文件进行相应的插桩后再调用实际的编译器。
更多靶场实验练习、网安学习资料,请点击这里>>
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4