AFL源码分析系列(二)-- afl-gcc

安全入门
2022-08-09 17:34
24833

写在前面

接下来是 afl 的编译,分成两个部分 gcc 编译部分和 llvm 模式,首先来介绍 gcc 部分。

afl-gcc

1. 文件描述

afl-gcc是gcc的一个wrapper,能够实现对一些关键节点进行插桩,利用插桩代码来记录程序的执行路径等信息。该文件的主要作用是进行源码编译,并在编译前对输入的参数进行处理。

2. 文件架构

文件涉及的头文件调用关系如下:

大部分属于标准库中的头文件,AFL自定义头文件有alloc-inl.hdebug.hconfig.htypes.h 总计4个,在后面涉及到的源码部分再对这4个头文件进行展开详解。

afl-gcc.c 文件主要包含三个函数:mainfind_asedit_params。函数的主要作用如下所示:

3. 函数源码分析

1. 部分关键变量

首先看下几个全局变量:

static u8*  as_path;                /* Path to the AFL 'as' wrapper      */
static u8** cc_params;              /* Parameters passed to the real CC  */
static u32  cc_par_cnt = 1;         /* Param count, including argv0      */
static u8   be_quiet,               /* Quiet mode                        */
            clang_mode;             /* Invoked as afl-clang*?            */

u8uint8_t 类型。

as_path 存放AFL的as封装的路径,cc_params 是传递给真正的cc的参数,cc_par_cnt 是传递给cc的参数的个数,be_quiet是AFL是否使用quiet模式,clang_mode是是否使用afl-clang进行编译。

2. main函数

main 函数的主要逻辑是进行简单的程序启动设置,其核心是调用find_as(argv[0])edit_params(argc, argv)

/* Main entry point */

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

  if (isatty(2) && !getenv("AFL_QUIET")) {

    SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");

  } else be_quiet = 1;

  if (argc < 2) {

    SAYF("\n"
         "This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
         "for gcc or clang, letting you recompile third-party code with the required\n"
         "runtime instrumentation. A common use pattern would be one of the following:\n\n"

         "  CC=%s/afl-gcc ./configure\n"
         "  CXX=%s/afl-g++ ./configure\n\n"

         "You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
         "Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
         BIN_PATH, BIN_PATH);

    exit(1);

  }

  find_as(argv[0]);

  edit_params(argc, argv);

  printf("\n");
  for (int i =0 ;i < sizeof(cc_params); i++){
      printf("\tag%d: %s\n", i, cc_params[i]);
  }

  execvp(cc_params[0], (char**)cc_params);

  FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);

  return 0;

}

这里我们添加一个 for 循环来打印出 main 处理的各个参数,就可以看到程序处理时真正完整的命令。

3. find_as函数

该函数主要用于查找对应的汇编器as,流程如下:

  • 第一步,获取环境变量 AFL_PATH。如果成功,则调用 alloc_printf("%s/as", afl_path) 动态分配内存空间来存储路径,并确保这片内存可访问,然后进行free操作,最后return。如果不可访问,则直接free掉。

  • 如果获取 AFL_PATH 失败,则匹配 argv[0] ,然后通过 ck_strdup 来提取当前路径 dir, 确保 {dir}/afl-as 可访问后,赋值给 as_path,然后free,最后return。不可访问则直接return。

  • 前面的方法都失败,会尝试直接去找as,如果再失败就输出错误信息然后程序退出。

函数使用了3个判断来实现as的查找流程,可以看到 AFL_PATH 的环境变量的优先级是最高的,在AFL的官方文档中,也是建议去设置 AFL_PATH 环境变量,这样可以确保编译时不会出问题。所以在使用AFL前,可以先设置一下 AFL_PATH 这个环境变量。

4. edit_params函数

该函数主要是把 argv的内容copy到 cc_params 中,并做必要的处理。函数的整体流程如下:

  • 首先,调用 ck_alloccc_params 进行内存分配,长度为 (argc + 128)*sizeof(u8*);
  • 查找最后一个 / 来确认使用的对应的编译器(比如afl-gcc,afl-clang),将名称保存在 name 中;
  • name 的值和 afl-clang 对比:
    • 如果相同,设置 clang_mode 值为1,设置环境变量 CLANG_ENV_VAR 值为1,说明使用的是 clang 编译器:
      • 比较 nameafl-clang++ ,相同则获取环境变量 AFL_CXX 的值,成功后赋值给 cc_params[0],说明使用的编译器是 afl-clang++,失败设置为 clang++;不相同则获取环境变量 AFL_CC 的值,成功后赋值给 cc_params[0],说明使用的编译器是 afl-clang,失败设置为 clang
    • 如果不相同,比较 nameafl-g++
      • 相同则获取环境变量 AFL_CXX 的值,成功后赋值给 cc_params[0],说明使用的编译器是 afl-g++,失败设置为 g++;不相同则获取环境变量 AFL_CC 的值,成功后赋值给 cc_params[0],说明使用的编译器是 afl-gcc,失败设置为 gcc
  • 通过 while(--argc) 循环来遍历所有的 argv 参数进行处理,并放入 cc_params
    • 跳过 -B/integreated-as/pipe 选项;
    • 存在 -fsanitize=address/memory,则设置 asan_set = 1
    • 存在 FORTIFY_SOURCE ,则设置 fortify_set = 1
    • cc_params[cc_par_cnt++] = cur
  • 其他参数选项设置:
    • 获取之前计算的 as_path ,设置成 -B as_path,-B选项用于设置编译器的搜索路径;
    • 如果是clang模式,则追加 -no-integrated-as 选项;
    • 获取环境变量 AFL_HARDEN,如果存在则追加 -fstack-protector-all,如果没有设置 fortify_set 则追加 -D_FORTIFY_SOURCE=2。该环境变量是一个自动添加代码强化的选项,添加上该选析那个后,可以捕获一些non-crashing类型的内存错误;
    • 检查 asan_set是否进行了设置,如果进行了设置, 则设置 AFL_USE_ASAN 环境变量为1;如果没有设置,获取环境变量 AFL_USE_ASAN,成功则追加 -U_FORTIFY_SOURCE-fsanitize=address 参数选项;如果前面两种都没有,再获取环境变量 AFL_USE_MSAN,成功则追加 -U_FORTIFY_SOURCE-fsanitize=memory 参数选项。
    • 获取环境变量 AFL_DONT_OPTIMIZE,如果不存在,则设置 -g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
    • 获取环境变量 AFL_NO_BUILTIN ,如果存在,则设置 -fno-builtin-strcmp -fno-builtin-strncmp -fno-builtin-strcasecmp -fno-builtin-strncasecmp -fno-builtin-memcmp -fno-builtin-strstr -fno-builtin-strcasestr
  • 最后设置 cc_params[cc_par_cnt] = NULL,结束对 cc_params 的编辑。

可以看到,edit_params 主要是根据环境变量的设置对一些编译参数进行设置,基本涵盖了所有的编译情况。不同的情况下,会有不同的编译选项被添加。

分享到

参与评论

0 / 200

全部评论 2

zebra的头像
学习大佬思路
2023-03-19 12:14
Hacking_Hui的头像
学习了
2023-02-01 14:20
投稿
签到
联系我们
关于我们