物联网安全网技术丨AFL源码分析系列(一)-- afl-as

admin 2023年3月11日00:08:17评论9 views字数 9522阅读31分44秒阅读模式
物联网安全网技术丨AFL源码分析系列(一)-- afl-as



我们将首先从 afl-as 开始,这里是AFL的源码插桩部分。理论上来说应该从编译入手会更递进一些,但是插桩是发生在编译的过程中的,而且AFL的编译内容较多,先了解插桩部分会有助于编译部分的逻辑理解,也符合Fuzz的实际流程。
备注:AFL的源码分析网上已有很多公开文章,文章内容难免会有重合。我们希望大家能在这一系列中融入自己的思考,切实保证自己能体会到AFL的优秀之处。此外,如果大家有什么建议,欢迎交流。



afl-as.c

0. 效果

我们首先来观察一下使用 afl-gcc 编译的文件的样子。使用的源码如下:

#include <stdio.h>void show(int a, int b){    int  x, y, z= 0;    x = a;    y = b;    if( x > y){      z = x + y;    }else{      z = y - x;    }    printf("[+] Z is %d", z);}int main(){  show(1,3);  show(7,2);  return 0;}

使用 afl-gcc 对以上源码进行编译:

AFL_KEEP_ASSEMBLY=1 ./afl-gcc -g -o example example.c# 这里使用 AFL_KEEP_ASSEMBLY 环境变量来保留中间生成的.s汇编文件

查看生成的中间 .s 文件:

   .file    "example.c"    .text.Ltext0:    .file 0 "/home/v4ler1an/Documents/AFL_debug/AFL/cmake-build-debug/tmp" "example.c"    .section    .rodata.str1.1,"aMS",@progbits,1.LC0:    .string    "[+] Z is %d"    .text    .p2align 4    .globl    show    .type    show, @functionshow:.LVL0:.LFB23:    .file 1 "example.c"    .loc 1 3 24 view -0    .cfi_startproc    .loc 1 3 24 is_stmt 0 view .LVU1/* --- AFL TRAMPOLINE (64-BIT) --- */.align 4leaq -(128+24)(%rsp), %rspmovq %rdx,  0(%rsp)movq %rcx,  8(%rsp)movq %rax, 16(%rsp)movq $0x00001fa9, %rcxcall __afl_maybe_logmovq 16(%rsp), %raxmovq  8(%rsp), %rcxmovq  0(%rsp), %rdxleaq (128+24)(%rsp), %rsp/* --- END --- */    endbr64    .loc 1 5 2 is_stmt 1 view .LVU2.LVL1:    .loc 1 6 2 view .LVU3    .loc 1 7 2 view .LVU4    .loc 1 8 2 view .LVU5    .loc 1 9 6 is_stmt 0 view .LVU6    movl    %esi, %edx    leal    (%rdi,%rsi), %eax    subl    %edi, %edx    cmpl    %esi, %edi.LBB12:

其中的 AFL TRAMPOLINE 部分就是 afl-as.h 文件中的桩代码。我们反汇编生成的二进制文件,首先看下main函数:

物联网安全网技术丨AFL源码分析系列(一)-- afl-as
物联网安全网技术丨AFL源码分析系列(一)-- afl-as

然后是show函数:

物联网安全网技术丨AFL源码分析系列(一)-- afl-as
物联网安全网技术丨AFL源码分析系列(一)-- afl-as

然后Bindiff看下两个程序的对比:

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

我们可以明显看到非原生代码 _afl_maybe_log() ,这就是AFL插入的桩代码,我们这里只在show函数中设置了一个if语句,所以插桩逻辑十分简单,只在main函数和show函数中各进行了一次插桩。该过程由 afl-as.c 中的逻辑来完成,其核心作用就是探测、反馈程序此时的状态,这会修改程序的原执行流。我们会在后续详细解释桩代码。


1. 文件描述

afl-as是AFL使用的汇编器,这里做成wrapper主要目的是为了进行插桩,AFL的插桩逻辑都在该文件中完成,而桩代码位于 afl-as.h 头文件中。


2. 文件架构

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

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

与前面的 afl-gcc.c 文件基本相同,但多了对 afl-as.h 的包含,此外还多了几个与时间和进程相关的头文件。

afl-as.c 文件主要包含三个函数:main、edit_params 、add_instrumentation:

物联网安全网技术丨AFL源码分析系列(一)-- afl-as


3. 源码分析

①部分关键变量

static u8** as_params;          /* Parameters passed to the real 'as'   */static u8*  input_file;         /* Originally specified input file      */static u8*  modified_file;      /* Instrumented file for the real 'as'  */static u8   be_quiet,           /* Quiet mode (no stderr output)        */            clang_mode,         /* Running in clang mode?               */            pass_thru,          /* Just pass data through?              */            just_version,       /* Just show version?                   */            sanitizer;          /* Using ASAN / MSAN                    */static u32  inst_ratio = 100,   /* Instrumentation probability (%)      */            as_par_cnt = 1;     /* Number of params to 'as'             */

as_params 与 afl-gcc.c 中的 cc_params 一样,作为接收处理后的参数传递给as;input_file 是需要编译的输入文件;modified_file 是经过插桩的源码文件;接下来的几个 u8 类型变量是一些模式参数;inst_ratio 是插桩百分比,该变量可以控制在源码中的插桩密度,需要注意的是插桩越多,编译速度越慢;as_par_cnt 是最终传递给as的所有参数的总量。


②main函数

main函数主要作为程序入口,进行一些基本处理,其调用的函数关系如下:

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

主要是调用 edit_params 和 add_instrumentation 函数完成程序的主要功能。在此之外,还调用了一下系统库中的函数进行辅助处理。下面通过源码来梳理它的处理流程:

/* Main entry point */int main(int argc, char** argv) {    ... ...  u8* inst_ratio_str = getenv("AFL_INST_RATIO");    ... ...  clang_mode = !!getenv(CLANG_ENV_VAR);  if (isatty(2) && !getenv("AFL_QUIET")) {    SAYF(cCYA "afl-as " cBRI VERSION cRST " by <[email protected]>n");  } else be_quiet = 1;  if (argc < 2) {    ... ...  }  gettimeofday(&tv, &tz);  rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();  srandom(rand_seed);  edit_params(argc, argv);  if (inst_ratio_str) {    if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100)       FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)");  }  if (getenv(AS_LOOP_ENV_VAR))    FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");  setenv(AS_LOOP_ENV_VAR, "1", 1);  /* When compiling with ASAN, we don't have a particularly elegant way to skip     ASAN-specific branches. But we can probabilistically compensate for     that... */  if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) {    sanitizer = 1;    inst_ratio /= 3;  }  if (!just_version) add_instrumentation();  if (!(pid = fork())) {    // 打印处理完之后的参数    printf("n");    for (int i =0 ; i < sizeof(as_params); i++){        printf("as_params[%d]:%sn", i, as_params[i]);    }    execvp(as_params[0], (char**)as_params);    FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);  }  if (pid < 0) PFATAL("fork() failed");  if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");  if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file);  exit(WEXITSTATUS(status));}
  • 获取环境变量 AFL_INST_RATIO,赋值给 inst_ratio_str,该环境变量主要控制检测每个分支的概率,取值为0到100%,设置为0时则只检测函数入口的跳转,而不会检测函数分支的跳转;

  • 通过 “当前时间+当前进程id”的方式获取一个随机数传给sradom()函数,生成随机数种子;

  • 调用 edit_params(argc, argv) 函数进行参数处理;

  • 判断 inst_ratio_str 是否进行了设置,如果没有则设置为100;

  • 设置 AS_LOOP_ENV_VAR 环境变量的值为1,这是一个内部环境变量;

  • 读取环境变量 AFL_USE_ASAN 和 AFL_USE_MSAN 的值,如果其中有一个为1,则设置sanitizer为1,且将inst_ratio除3。因为在进行ASAN的编译时,AFL无法识别出ASAN特定的分支,导致插入很多无意义的桩代码,所以直接暴力地将插桩概率除以3;

  • 调用 add_instrumentation() 函数进行插桩;

  • fork 一个子进程来执行 execvp(as_params[0], (char**)as_params);。这里采用的是 fork 一个子进程的方式来执行插桩。这是因为 execvp 执行的时候,会用 as_params[0] 来完全替换掉当前进程空间中的程序,这样就可以在执行完成之后 unlink 掉经过插桩的 modified_file(其实就是中间产生的.s汇编文件);

  • 调用 waitpid(pid, &status, 0) 等待子进程执行结束;

  • 读取环境变量 AFL_KEEP_ASSEMBLY 的值,如果没有设置这个环境变量,就 unlink 掉 modified_file(已插完桩的文件)。设置该环境变量主要是为了防止 afl-as 删掉插桩后的汇编文件,设置为1则会保留插桩后的汇编文件。

main 函数的主要功能还是处理各种环境变量和数据,对参数的处理在 edit_params函数中,插桩功能在 add_instrumentation 函数中。main 函数把程序的执行放在了 fork 出的子进程中,这样就可以“优雅”地处理中间文件。


③edit_params函数

该函数的主要职责还是在运行真正的as之前先处理一下参数选项,最后存放在 as_params 中。此外,还会设置一下 use_64bit/modified_file 的值。

/* Examine and modify parameters to pass to 'as'. Note that the file name   is always the last parameter passed by GCC, so we exploit this property   to keep the code simple. */static void edit_params(int argc, char** argv) {  u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS");    ... ...  if (!tmp_dir) tmp_dir = getenv("TEMP");  if (!tmp_dir) tmp_dir = getenv("TMP");  if (!tmp_dir) tmp_dir = "/tmp";  as_params = ck_alloc((argc + 32) * sizeof(u8*));  as_params[0] = afl_as ? afl_as : (u8*)"as";  as_params[argc] = 0;  for (i = 1; i < argc - 1; i++) {    if (!strcmp(argv[i], "--64")) use_64bit = 1;    else if (!strcmp(argv[i], "--32")) use_64bit = 0;        ... ...    as_params[as_par_cnt++] = argv[i];  }        ... ...  input_file = argv[argc - 1];  if (input_file[0] == '-') {    if (!strcmp(input_file + 1, "-version")) {      just_version = 1;      modified_file = input_file;      goto wrap_things_up;    }    if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)");      else input_file = NULL;  } else {    if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&        strncmp(input_file, "/var/tmp/", 9) &&        strncmp(input_file, "/tmp/", 5)) pass_thru = 1;  }  modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),                               (u32)time(NULL));wrap_things_up:  as_params[as_par_cnt++] = modified_file;  as_params[as_par_cnt]   = NULL;}
  • 依次检查环境变量 TMPDIR/TEMP/TMP, 确定 tmp_dir 的路径,都没有则设置为 /tmp,获取环境变量 AFL_AS 给到 afl_as;

  • ck_alloc((argc + 32) * sizeof(u8*)) 为 as_params 分配内存空间;

  • 设置 afl-as 路径:as_params[0] = afl_as ? afl_as : (u8*)"as";

  • 设置 as_params[argc] = 0; ,as_par_cnt 初始值为1;

  • 通过一个 for 循环来检查参数中是否有 --64, 如果有则设置 use_64bit=1;如果有 --32 则设置 use_64bit=0。最后,as_params[as_par_cnt++] = argv[i];设置as_params的值为argv对应的参数值,结束for循环;

  • 设置 input_file 变量:input_file = argv[argc - 1];,把最后一个参数的值作为 input_file:

⑴如果 input_file 的首字符为-:

⑵如果后续为 -version,则 just_version = 1, modified_file = input_file,然后跳转到wrap_things_up。这里就只是做version的查询;

⑶如果后续不为 -version,抛出异常;

⑷如果 input_file 首字符不为-,比较 input_file 和 tmp_dir、/var/tmp 、/tmp/的前 strlen(tmp_dir)/9/5个字节是否相同,如果不相同,就设置 pass_thru 为1;

  • 设置modified_file的值为alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),(u32) time(NULL));,简单的说就是tmp_dir/.afl-pid-time.s这样的字符串。


④add_instrumentation函数

该函数执行了插桩操作,桩代码来自于 afl-as.h 文件中。函数源码如下:

static void add_instrumentation(void) {    ... ...  if (input_file) {    inf = fopen(input_file, "r");    if (!inf) PFATAL("Unable to read '%s'", input_file);  } else inf = stdin;  outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600);  if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);  outf = fdopen(outfd, "w");  if (!outf) PFATAL("fdopen() failed");    while (fgets(line, MAX_LINE, inf)) {    if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&        instrument_next && line[0] == 't' && isalpha(line[1])) {      fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,              R(MAP_SIZE));      instrument_next = 0;      ins_lines++;    }    fputs(line, outf);    if (pass_thru) continue;    if (line[0] == 't' && line[1] == '.') {      if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&          isdigit(line[10]) && line[11] == 'n') skip_next_label = 1;      if (!strncmp(line + 2, "textn", 5) ||          !strncmp(line + 2, "sectiont.text", 13) ||          !strncmp(line + 2, "sectiont__TEXT,__text", 21) ||          !strncmp(line + 2, "section __TEXT,__text", 21)) {        instr_ok = 1;        continue;       }      if (!strncmp(line + 2, "sectiont", 8) ||          !strncmp(line + 2, "section ", 8) ||          !strncmp(line + 2, "bssn", 4) ||          !strncmp(line + 2, "datan", 5)) {        instr_ok = 0;        continue;      }    }    if (strstr(line, ".code")) {      if (strstr(line, ".code32")) skip_csect = use_64bit;      if (strstr(line, ".code64")) skip_csect = !use_64bit;    }    if (strstr(line, ".intel_syntax")) skip_intel = 1;    if (strstr(line, ".att_syntax")) skip_intel = 0;    if (line[0] == '#' || line[1] == '#') {      if (strstr(line, "#APP")) skip_app = 1;      if (strstr(line, "#NO_APP")) skip_app = 0;    }    if (skip_intel || skip_app || skip_csect || !instr_ok ||        line[0] == '#' || line[0] == ' ') continue;    if (line[0] == 't') {      if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {        fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,                R(MAP_SIZE));        ins_lines++;      }      continue;    }... ...    if (strstr(line, ":")) {      if (line[0] == '.') {... ...        if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3)))            && R(100) < inst_ratio) {          if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;        }      } else {        instrument_next = 1;      }    }  }  if (ins_lines)    fputs(use_64bit ? main_payload_64 : main_payload_32, outf);  if (input_file) fclose(inf);  fclose(outf);  if (!be_quiet) {    if (!ins_lines) WARNF("No instrumentation targets found%s.",                          pass_thru ? " (pass-thru mode)" : "");    else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).",             ins_lines, use_64bit ? "64" : "32",             getenv("AFL_HARDEN") ? "hardened" :              (sanitizer ? "ASAN/MSAN" : "non-hardened"),             inst_ratio);   }}


由于文章字数受限

可点击下方【阅读原文】阅读全篇。


物联网安全网技术丨AFL源码分析系列(一)-- afl-as

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

分享

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

收藏

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

点赞

物联网安全网技术丨AFL源码分析系列(一)-- afl-as

在看

原文始发于微信公众号(IOTsec Zone):物联网安全网技术丨AFL源码分析系列(一)-- afl-as

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月11日00:08:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   物联网安全网技术丨AFL源码分析系列(一)-- afl-ashttps://cn-sec.com/archives/1231789.html

发表评论

匿名网友 填写信息