AFL(American Fuzzy Lop)源码详细解读(5)

感谢大佬们的文章:
https://bbs.pediy.com/thread-265973.htm
https://eternalsakura13.com/2020/08/23/afl/#more
这篇记录 afl-as.c 的代码

afl-as.c

首先,需要知道的是我们平常在用gcc时,其实是经过了预处理、编译、汇编、链接这几个阶段的。汇编就是将汇编代码转换为二进制代码的过程,这需要一个汇编器来完成,Linux常用的汇编器是as。
插桩过程:先调用afl-gcc,这是对gcc的一个简单封装,其中有一项工作是查找AFL文件夹下的as,这个as是个假的as,是指向afl-as的符号链接(软连接),并且-B 指定优先执行这个假的as。所以经过预处理和编译两个阶段后,在汇编阶段实际上是执行了afl-as(as的封装),在afl-as中封装了插桩功能,在执行真正的as命令前会将插桩好的汇编程序传给真正的as。

1. main主函数
  • 获取环境变量 AFL_INST_RATIO ,赋值给 inst_ratio_str,该变量表示插桩密度

  • 时间和时区结构体

  • 获取__AFL_CLANG_MODE环境变量并归一化 ,赋值给clang_mode

  • 如果没有AFL_QUIET 环境变量,打印信息

  • 有AFL_QUIET环境变量, 置 be_quiet = 1

  • 如果传入参数少于2,打印信息并退出

  • 获取时间和时区

  • 设置随即种子rand_seed,秒为单位的时间异或毫秒为单位的时间再异或进程ID

  • 调用edit_params函数进行参数处理

  • 打印出参数来看一下

    // 输入 
    afl-gcc test.c -o a.out
    // 输出
    arg0: as
    arg1: --64
    arg2: -o
    arg3: /tmp/cc1Twew.o
    arg4: /tmp/.afl-33240-1653576534.s
    arg5: (null)
    arg6: (null)
    arg7: (null)
    
  • 如果inst_ratio_str不为0

    • 如果输入格式不正确,或是大于100,抛出异常。就是检测 inst_ratio_str 的值是否合法范围内
  • 如果获取到环境变量 __AFL_AS_LOOPCHECK,抛出异常。去掉传入参数中的 .

  • 设置环境变量 __AFL_AS_LOOPCHECK 为1

  • 读取环境变量AFL_USE_ASAN和AFL_USE_MSAN的值,如果其中有一个为1

    • sanitizer = 1
    • inst_ratio 除以 3,该变量表示插桩密度,通常情况下是100%。这是因为在进行ASAN的编译时,AFL无法识别出ASAN特定的分支,导致插入很多无意义的桩代码,所以直接暴力地将插桩概率/3
  • 调用 add_instrumentation函数进行插桩

  • fork一个子进程调用execvp,进行编译。这其实是因为我们的execvp执行的时候,会用as_params[0]来完全替换掉当前进程空间中的程序,如果不通过子进程来执行实际的as,那么后续就无法在执行完实际的as之后,还能unlink掉modified_file

  • 如果pid < 0 ,fork失败

  • 调用 waitpid 等待子进程结束,status中存放导致子进程返回的状态信息

  • 如果没有 AFL_KEEP_ASSEMBLY 环境变量,删除文件

2. edit_params() 处理参数
  • 获取环境变量 TMPDIR ,赋值给 tmp_dir,获取环境变量 AFL_AS ,赋值给 afl_as
  • 没有环境变量 TMPDIR,查看是否有环境变量 TEMP,没有则查看环境变量 TMP,还没有就将 tmp_dir赋值为 “/tmp”
  • cc_params 分配空间
  • 如果afl_as不为空,就设置as_params[0]afl_as,否则设置为as
  • as_params[argc] = 0; 最后一个参数设置为0(没看懂)
  • 进入循环,从argv[1]遍历到argv[argc-2],放入 cc_params 数组中。如果传入的参数中有 --64,置use_64bit = 1,如果传入的参数中有 --32置use_64bit = 0。
  • input_file 为 argv的最后一个参数、
  • 如果input_file的第一个字符为 ‘-’
    • 如果input_file为”-version“
      • just_version 为1
      • modified_file = input_file
      • 跳转到wrap_things_up
    • 如果input_file不是”-version“,并且 - 后面还有其他值,则抛出异常,告知用户使用错误
    • 否则input_file = NULL
  • 如果input_file的第一个字符不为 ‘-’
    • 如果input_file的前几个字符与tmp_dir 不相同,并且input_file的前9个字符与 /var/tmp/ 不相同,并且input_file的前5个字符与 /tmp/ 不相同
      • pass_thru 为1
  • modified_file 置为 tmp_dir/afl-getpid()-(u32)time(NULL),注意这里只是给了个文件名,真正的文件会在add_instrumentation 中生成。
  • cc_params 最后一个参数为modified_file
  • 设置结束标志
3. add_instrumentation 处理输入文件并插桩
  • 如果input_file不为null
    • 只读方式打开input_file,fd赋值给 inf,如果打开失败,抛出异常
  • 如果input_file为null,inf设置为标准输入
  • 创建modified_file文件,fd赋值给 outfd,如果创建失败,抛出异常
  • 进一步验证该文件是否可写,不可写抛出异常
  • 进入while循环,读取 inf 指向文件的每一行到 line 数组,每行最多 MAX_LINE = 8192个字节(含末尾的‘\0’)
    • 如果pass_thru为0,并且skip_intel为0,并且skip_app为0,并且instr_ok不为0,并且instrument_next不为0,并且以’\t’ + 字母 开头(instr_ok是一个flag,是否位于text段)
      • outf指向的文件(即modified_file)中写入 trampoline_fmt_64桩代码并插入随机数
      • instrument_next置0
      • ins_lines插桩行数加一
    • 将原有汇编代码写入outf
    • 如果pass_thru 为1,直接进入下一次循环
    • 如果以 '\t.'开头
      • 如果clang_mode为0,且instr_ok不为0,且 \t. 后面跟的是 p2align 指令,且 p2align 后跟了一个数字,且后又跟了一个 ‘\n’(检查是否为 p2align 指令)
        • 则置skip_next_label为1
      • 如果line的值为\t.[text\n|section\t.text|section\t__TEXT,__text|section __TEXT,__text]...其中之一,则设置instr_ok为1,直接continue(表示位于.text段,指令段。具体可阅读https://www.cnblogs.com/skullboyer/p/10979704.html)
      • 如果不是上面的几种情况,且line的值为\t.[section\t|section |bss\n|data\n]...,则设置instr_ok为0,直接continue(表示不位于.text段,不执行插桩)
    • 如果line中包含 “.code”
      • 如果包含的是 “.code32”,skip_csect置1,不插桩
      • 如果包含的是 “.code64”,skip_csect置0。插桩
    • 如果包含".intel_syntax"即intel汇编(一般为window),skip_intel置1,不插桩
    • 如果包含".att_syntax"即att汇编(一般为Linux),skip_intel置0,插桩
    • 如果line中第一个字符或第二个字符为 ‘#’(跳过ad-hoc __asm__块)
      • 如果是"#APP",skip_app置1,不插桩
      • 如果是"#NO_APP",skip_app置0,插桩
    • 如果skip_intel不为0,或skip_app不为0,或skip_ceset不为0,或instr_ok为0,或第一个字符为’#‘,或第一个字符为’ ’
      • continue
    • 如果第一个字符为 ‘\t’
      • 如果第二个字符为 ‘j’,并且第三个字符不为 ‘m’(这是在判断是否是跳转指令,但是好像不包括无条件跳转jmp),且随机数小于inst_ratio插桩密度
        • 调用 fprintf 将桩代码以及随机数(块的标识符,应该会产生碰撞,即多个块的标识符相同)写入代码段中
        • ins_lines插桩计数加1
      • 否则,continue
    • 如果line中有’:’
      • 如果第一个字符为’.’
        • 如果第三个字符是数字(.L0,GCC的分支标志) 或者 clang模式并且第二到第四个字符为"LBB"(.LBB,clang的分支标志) 并且 随机数小于inst_ratio插桩密度
          • 如果skip_next_label不为0, 置instrument_next = 1,表示要进行插桩
          • 否则skip_next_label置0
      • 否则,即如果第一个字符不为’.'(说明是函数标志)
        • instrument_next = 1
  • 如果ins_lines不为0
    • main_payload_64 插入到代码最后
  • 关闭资源
  • 如果不是Quite 模式
    • 如果没有插入任何桩代码,警告
    • 否则,打印插桩信息

注:在while循环中总共有两处插桩的代码,while循环开头的是deferred mode (延迟模式)下的插桩代码,用于.Lfunc_begin0这样的插桩处理。另一处是正常逻辑的插桩代码。这两处插桩代码只是简单地将 保存寄存器状态、插入随机数、调用、恢复寄存器状态 这些操作插入到每个块中。可以注意到循环结束后,插入了main_payload_64代码,这部分代码中定义了要调用的函数等更多操作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值