2020年4月26日星期日

在AOSP上跑AFL的解决方案

在AOSP中跑AFL的解决方案

1. TL;DR

 

2. 背景

大约在2019年11、12月的时候,开发者 JoeyJiao 先后给 AFL 提交了两份 commit [1][2]并被AFL merge到了master分支。从此AFL终于有了官方的Android平台支持。

原生的AFL无法直接在安卓平台运行的原因是共享内存机制的差异。AFL使用的是shmat, shmget 等调用来完成内存的映射等操作,这段共享内存会被直接用于存放收集到的边路径覆盖信息并与afl-fuzz主程序进行共享,而安卓系统使用的是bionic libc,并未提供shmat等调用,而是转而使用匿名共享内存ashmem[3]

在此之前,也有人对AFL on Android进行过适配,并且都很好用,我所知最早的是2015年Adrian在afl-users论坛中贴出来的patch文件[4],而后2016年又有 ele7enxxh 在github中放出来的android-afl。不过Android 7.0之后,随着AOSP的编译方式已经由传统的make && Android.mk全面转向 soong编译系统 && Android.bp,如今直接用它们对AOSP中的代码进行Fuzz总觉得不那么顺手。

在JoeyJiao的两份提交里,首先是给AFL添加了一个 android-ashmem.h 文件,用于解决共享内存的问题,而后又提供了一个Android.bp文件,可直接使用mmm/mm/make afl-fuzz等方式将之编译成可在安卓设备上运行的可执行文件。不过关于如何具体在安卓上使用的文档并未给出,当然我们也可以在AFL的issue里看到sakura大佬的提问[6],JoeyJiao 为此也给出一个简便的guide。

在这份 guide里,对于 Android.mk 项目的支持很完善,因为mk格式的编译脚本本来就支持 LOCAL_CC, LOCAL_CXX,可以直接定义编译此工程所使用的clang/clang++的路径,不过对于An droid.bp项目,由于没法自定义clang/clang++(我又翻了一遍soong的文档以及源代码,仍未找到能自定义的地方[7][[8]),因此就需要去全局地改动 build/soong 目录下的go文件了,这种方式看起来可能不够优雅(似乎在此issue下ele7enxxh也这么觉得),Joey为此也慷慨地给出了他的patch[9]。至此,已经扫清了全部障碍。

由于某些原因,我也动过build/soong中的内容,每次动的时侯,再次编译aosp中的project就会全盘重来,所以改build中的go脚本我内心是隐隐抗拒的,毕竟,出不出得来洞是一时的事,追求极致优雅是一辈子的事情。

 

3. AFL & afl-clang-fast

3.1 afl-gcc

通常来讲,如要使用 AFL 对目标进行Fuzz,首先要使用 afl-gcc/afl-g++ 对其进行插桩。

也就是文档中给出的经典使用方法:

插桩的目的是为了对目标程序进行必要的改装,改装的方面有这么几个:

  • 插入fork server
  • 映射共享内存
  • 覆盖统计插桩

其中fork server是AFL中一个十分精妙的设计,可用于减少每次都重启目标进程导致的开销,从而大大提高fuzz的效率,当然,如果显式地使用 NO_FORKSERVER 选项,那么第一项也可以去掉;映射的共享内存用于存储运行过程中收集到的路径信息,反馈给fuzz主进程以进行决策;而在每一个基本块都插入的覆盖统计函数(__afl_maybe_log)用于记录程序的运行状态。

afl-gcc 的源码中,做的事情有三个

  • 找到编译所需的可执行文件的路径(find_as)
  • 修改编译参数(edit_params)
  • 重新执行真正的gcc (execvp)

在afl-gcc执行时,会使用一个自定义的汇编器 afl-as 对源代码进行汇编,afl-as 会向程序中插入一大段的汇编代码(位于 afl-as.h 文件中)以完成上述的插桩操作。也正是因为插入的是汇编代码,所以早期的AFL对arm平台的支持也不佳。

 

3.2 AFL LLVM mode -- with LLVM Pass

除了GCC外,AFL还支持LLVM模式,在此模式下,仍然要对目标程序进行 3.1 章节中提到的三个方面的改装。

AFL的llvm_mode 下有一个 afl-llvm-pass.so.cc 的文件,这部分代码用于在clang/clang++编译源代码的时候,加载一个定义好的 llvm pass,对每个基本块都插入覆盖统计的代码。

LLVM Pass的插桩模式是在它翻译成IR中间语言之后进行的,因此也就不用再对每个平台都进行汇编的手写工作了。

 

3.3 AFL LLVM mode -- without LLVM Pass

路径的统计不只是AFL在做,LLVM后来也有了自己的 sanitizer coverage[10],如果在编译的时候,加上了 -fsanitize-coverage=trace-pc-guard 选项,那么编译器会在模块加载时自动添加一个 __sanitizer_cov_trace_pc_guard_init 函数,并在每个基本块的起始处都插入一个 __sanitizer_cov_trace_pc_guard(&guard_variable)函数,从而自动地统计程序走过的路径。

sanitizer coverage 本来是结合 __sanitizer_cov_reset 以及 __sanitizer_cov_dump 来使用的,在运行时,程序会链接到 LLVM compiler_rt 中的runtime库进行覆盖统计。

而AFL的方法是自己在 afl-llvm-rt.o.c 里面重写了__sanitizer_cov_trace_pc_guard_init 以及 __sanitizer_cov_trace_pc_guard 函数,相当于接管了LLVM的 runtime库,以此将它变成AFL自己的路径覆盖统计器。不过这种方法也会带来一定的效率上的损失,因为 3.2 中插入的指令是 load 等基础的指令,而 trace-pc-guard 是通过插入一个 call 来完成的覆盖统计,因此据官方统计,大概会有20%左右的效率损耗[11]。

在 afl-llvm-rt 里面,有一个带 constructor 属性的 __afl_auto_init 函数。这个函数会在程序的 main 函数执行之前就执行,在这里面能够完成包括 fork server 的实现与交互、共享内存的映射、以及上文提到的两个函数的重写(用于路径的统计)等操作,加上由 trace-pc-guard 选项所带来的插桩功能,已经基本完成了需要对目标程序实施的所有改造操作。于是我再看了一下 afl-clang-fast 中的代码,已经可以确定,这个clang wrapper它可有可无。

JoeyJiao 在给 afl-llvm-rt 编写 bp 规则的时候,是将它变成了一个静态链接库,所以,要对AOSP中的project进行Fuzz,直接链这个静态库就可以了,不需要对编译过程中所使用的 clang/clang++进行任何的修改或替换操作。

也即 ... 目标程序的 build 规则简洁如下:

此外,AOSP的build系统支持一个叫做 SANITIZE_TARGET 的环境变量,当在此环境变量下加一个 coverage的时候,即可在编译过程中,自动地将 -fsantize-coverage=trace-pc-guard 选项开启。因此,编译命令简洁如下:

ps: coverage选项会和shadow call stack 缓解机制产生冲突,而scs在android10中某些项目中有启用,若仅用于测试目的,可将之手动关闭。

 

至此,我好了。

 

4. 参考链接

  1. https://github.com/google/AFL/commit/e75894a889fe854c02b9435186bd1e2927d6d490
  2. https://github.com/google/AFL/commit/077d73f7fc12b8b64e71befdb81744056640eaf1
  3. https://elinux.org/Android_Kernel_Features#ashmem
  4. https://groups.google.com/forum/#!msg/afl-users/UL46o4kTeFw/CYWKLXB0DgAJ
  5. https://github.com/ele7enxxh/android-afl
  6. https://github.com/google/AFL/issues/61
  7. https://source.android.com/setup/build
  8. https://ci.android.com/builds/submitted/6430522/linux/latest/view/soong_build.html
  9. https://gist.github.com/JoeyJiao/cca3bc6c440f7000b4969bb1ab4ccfac
  10. https://clang.llvm.org/docs/SanitizerCoverage.html
  11. https://github.com/google/AFL/tree/master/llvm_mode