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

 

1 条评论:

  1. taimen:/data/local/tmp # ./afl-fuzz -i in -o out -m none -- ./afl-fuzzme @@
    afl-fuzz++3.13a based on afl by Michal Zalewski and a large online community
    [+] afl++ is maintained by Marc "van Hauser" Heuse, Heiko "hexcoder" Ei遞eldt, Andrea Fioraldi and Dominik Maier
    [+] afl++ is open source, get it at https://github.com/AFLplusplus/AFLplusplus
    [+] NOTE: This is v3.x which changes defaults and behaviours - see README.md
    [+] No -M/-S set, autoconfiguring for "-S default"
    [*] Getting to work...
    [+] Using exponential power schedule (FAST)
    [+] Enabled testcache with 50 MB
    [*] Checking core_pattern...
    [*] Checking CPU scaling governor...
    [+] You have 8 CPU cores and 1 runnable tasks (utilization: 12%).
    [+] Try parallel jobs - see docs/parallel_fuzzing.md.
    [*] Setting up output directories...
    [*] Checking CPU core loadout...
    [+] Found a free CPU core, try binding to #7.
    [*] Scanning 'in'...
    [+] Loaded a total of 1 seeds.
    [*] Creating hard links for all input files...
    [*] Validating target binary...

    [-] Looks like the target binary is not instrumented! The fuzzer depends on
    compile-time instrumentation to isolate interesting test cases while
    mutating the input data. For more information, and for tips on how to
    instrument binaries, please see docs/README.md.

    When source code is not available, you may be able to leverage QEMU
    mode support. Consult the README.md for tips on how to enable this.
    (It is also possible to use afl-fuzz as a traditional, non-instrumented fuzzer.
    For that, you can use the -n option - but expect much worse results.)

    [-] PROGRAM ABORT : No instrumentation detected
    Location : check_binary(), AFLplusplus/src/afl-fuzz-init.c:2719
    -----------------------------------------------------------------------------------------------------------------
    你好,按照您的方式 还是有点问题,能提供一点解决思路吗,非常感谢!

    回复删除