时时勤拂拭,勿使惹尘埃

TOC

Categories

Android(八)libFuzzer


libFuzzer 是一个in-processcoverage-guidedevolutionaryfuzz 引擎,是 LLVM 项目的一部分。
libFuzzer 和 要被测试的库 链接在一起,通过一个模糊测试入口点(目标函数),把测试用例喂给要被测试的库。
fuzzer会跟踪哪些代码区域已经测试过,然后在输入数据的语料库上进行变异,来使代码覆盖率最大化。代码覆盖率的信息由 LLVMSanitizerCoverage 插桩提供。
Android上使用libFuzzer需要编译插桩版本,详情参考:Android(七)源码编译

0x1 编写fuzzer工具

  1. 在 Android 源代码树中创建一个目录
    $ mkdir tools/fuzzers/fuzz_me_fuzzer
    
  2. 在上面目录下创建测试代码fuzz_me_fuzzer.cc,内容如下:
    #include <stdint.h>
    #include <stddef.h>
    //漏洞函数。使用 libFuzzer 编写模糊测试目标,模糊测试目标是一个函数
    //该函数可接收指定大小的 blob 数据,并将其传递给要接受模糊测试的函数
    //这个函数有两个参数,第一个参数 data 是 uint8_t* 类型的,说明 data 应该是指向了一个缓冲区
    //size 应该是缓冲区的大小,如果 size >=3 , 会访问 data[3] , 越界访问了
    bool FuzzMe(const uint8_t *Data, size_t DataSize) {
    return DataSize >= 3 &&
           Data[0] == 'F' &&
           Data[1] == 'U' &&
           Data[2] == 'Z' &&
           Data[3] == 'Z';  // ← 如果数据长度为 3,会导致越界读取错误
    }
    //针对测试函数的基本模糊测试工具
    extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
    FuzzMe(buf, len);
    return 0;
    }
    
  3. 在该目录下创建编译配置文件Android.mk,内容如下:
    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_SRC_FILES := fuzz_me_fuzzer.cpp
    LOCAL_CFLAGS += -Wno-multichar -g -O0
    LOCAL_MODULE_TAGS := optional
    LOCAL_CLANG := true
    LOCAL_MODULE:= fuzz_me_fuzzer
    include $(BUILD_FUZZ_TEST)
    
    实现这个目的所需的大部分逻辑都包含在 BUILD_FUZZ_TEST 宏(在 build/core/fuzz_test.mk中进行定义)
  4. 在 Android 源码根目录编译fuzzer工具
    $ make -j8 fuzz_me_fuzzer SANITIZE_TARGET="address coverage"
    
    编译完成后,fuzzer工具生成的默认位置为out/target/product/sailfish/data/nativetest/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer

0x2 使用fuzzer工具

  1. 将fuzzer工具上传到设备上
    $ adb root
    $ adb shell mkdir -p /data/tmp/fuzz_me_fuzzer/corpus
    $ adb push ./fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/
    
  2. 运行fuzzer工具
    $ adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/corpus
    
  3. libFuzzer 输出结果
    在本次测试代码中,崩溃是由第 10 行中的 fuzz_me_fuzzer.cpp 导致的:
    Data[3] == 'Z';  //如果数据长度为 3,会导致出界读取错误。
    
    详细理解 libFuzzer 输出,可参考LibFuzzer 文档
    INFO: Seed: 2720633294
    INFO: Loaded 3 modules   (18724 guards): 18616 [0xf720f738, 0xf7221a18), 101 [0xd57ae060, 0xd57ae1f4), 7 [0xb63c1000, 0xb63c101c),
    INFO:        0 files found in /data/tmp/fuzz_me_fuzzer/corpus
    INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
    INFO: A corpus is not provided, starting from an empty corpus
    #2 INITED cov: 265 ft: 5 corp: 1/1b exec/s: 0 rss: 33Mb
    #7 NEW    cov: 271 ft: 6 corp: 2/52b exec/s: 0 rss: 34Mb L: 51/51 MS: 5 CopyPart-ShuffleBytes-ChangeBit-ShuffleBytes-InsertRepeatedBytes-
    #38 REDUCE cov: 275 ft: 6 corp: 2/50b exec/s: 0 rss: 34Mb L: 49/49 MS: 1 EraseBytes-
    #39 REDUCE cov: 275 ft: 6 corp: 2/33b exec/s: 0 rss: 34Mb L: 32/32 MS: 2 EraseBytes-EraseBytes-
    #123 REDUCE cov: 275 ft: 6 corp: 2/22b exec/s: 0 rss: 35Mb L: 21/21 MS: 1 EraseBytes-
    #171 REDUCE cov: 275 ft: 6 corp: 2/18b exec/s: 0 rss: 35Mb L: 17/17 MS: 4 CMP-ChangeBinInt-CopyPart-EraseBytes- DE: "\x01\x00\x00\x00\x00\x00\x00\x00"-
    #191 REDUCE cov: 275 ft: 6 corp: 2/12b exec/s: 0 rss: 36Mb L: 11/11 MS: 4 ChangeByte-ChangeByte-ChangeBinInt-EraseBytes-
    #213 REDUCE cov: 275 ft: 6 corp: 2/7b exec/s: 0 rss: 36Mb L: 6/6 MS: 1 EraseBytes-
    #214 REDUCE cov: 275 ft: 6 corp: 2/6b exec/s: 0 rss: 36Mb L: 5/5 MS: 2 EraseBytes-EraseBytes-
    #229 REDUCE cov: 275 ft: 6 corp: 2/4b exec/s: 0 rss: 36Mb L: 3/3 MS: 2 CopyPart-EraseBytes-
    #4993 REDUCE cov: 279 ft: 7 corp: 3/7b exec/s: 0 rss: 44Mb L: 3/3 MS: 1 ChangeByte-
    #19054 NEW    cov: 280 ft: 8 corp: 4/91b exec/s: 0 rss: 60Mb L: 84/84 MS: 2 CopyPart-InsertRepeatedBytes-
    #19068 REDUCE cov: 280 ft: 8 corp: 4/72b exec/s: 0 rss: 60Mb L: 65/65 MS: 1 EraseBytes-
    #19076 REDUCE cov: 280 ft: 8 corp: 4/44b exec/s: 0 rss: 60Mb L: 37/37 MS: 4 InsertByte-CrossOver-CrossOver-EraseBytes-
    #19084 REDUCE cov: 280 ft: 8 corp: 4/30b exec/s: 0 rss: 60Mb L: 23/23 MS: 2 ChangeByte-EraseBytes-
    #19148 REDUCE cov: 280 ft: 8 corp: 4/19b exec/s: 0 rss: 60Mb L: 12/12 MS: 1 EraseBytes-
    #19208 REDUCE cov: 280 ft: 8 corp: 4/17b exec/s: 0 rss: 60Mb L: 10/10 MS: 1 EraseBytes-
    #19519 REDUCE cov: 280 ft: 8 corp: 4/15b exec/s: 0 rss: 60Mb L: 8/8 MS: 2 ShuffleBytes-EraseBytes-
    #19520 REDUCE cov: 280 ft: 8 corp: 4/13b exec/s: 0 rss: 60Mb L: 6/6 MS: 3 ShuffleBytes-EraseBytes-EraseBytes-
    #19743 REDUCE cov: 280 ft: 8 corp: 4/12b exec/s: 0 rss: 60Mb L: 5/5 MS: 1 EraseBytes-
    #19748 REDUCE cov: 280 ft: 8 corp: 4/11b exec/s: 0 rss: 60Mb L: 4/4 MS: 1 EraseBytes-
    #19758 REDUCE cov: 280 ft: 8 corp: 4/10b exec/s: 0 rss: 60Mb L: 3/3 MS: 1 EraseBytes-
    =================================================================
    ==27066==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xd523c1b3 at pc 0xb638e1c8 bp 0xffafed60 sp 0xffafed5c
    READ of size 1 at 0xd523c1b3 thread T0
     #0 0xb638e1c7  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x51c7)
     #1 0xb638e25f  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x525f)
     #2 0xb639dd87  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x14d87)
     #3 0xb639d873  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x14873)
     #4 0xb639ec47  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x15c47)
     #5 0xb639f4bb  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x164bb)
     #6 0xb6397de7  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0xede7)
     #7 0xb639368f  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0xa68f)
     #8 0xf6fd2e6d  (/system/lib/libc.so+0x8ae6d)
     #9 0xb638dedd  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x4edd)
    0xd523c1b3 is located 0 bytes to the right of 3-byte region [0xd523c1b0,0xd523c1b3)
    allocated by thread T0 here:
     #0 0xf6a42177  (/system/lib/libclang_rt.asan-arm-android.so+0xbd177)
     #1 0xb639dc4b  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x14c4b)
     #2 0xb639d873  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x14873)
     #3 0xb639ec47  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x15c47)
     #4 0xb639f4bb  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x164bb)
     #5 0xb6397de7  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0xede7)
     #6 0xb639368f  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0xa68f)
     #7 0xf6fd2e6d  (/system/lib/libc.so+0x8ae6d)
    SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x51c7)
    Shadow bytes around the buggy address:
    0xf13c77e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c77f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7820: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fd
    =>0xf13c7830: fa fa fd fa fa fa[03]fa fa fa fa fa fa fa fa fa
    0xf13c7840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7860: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7870: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    0xf13c7880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    Shadow byte legend (one shadow byte represents 8 application bytes):
    Addressable:           00
    Partially addressable: 01 02 03 04 05 06 07
    Heap left redzone:       fa
    Freed heap region:       fd
    Stack left redzone:      f1
    Stack mid redzone:       f2
    Stack right redzone:     f3
    Stack after return:      f5
    Stack use after scope:   f8
    Global redzone:          f9
    Global init order:       f6
    Poisoned by user:        f7
    Container overflow:      fc
    Array cookie:            ac
    Intra object redzone:    bb
    ASan internal:           fe
    Left alloca redzone:     ca
    Right alloca redzone:    cb
    ==27066==ABORTING
    MS: 1 ChangeBinInt-; base unit: 9d447627131a2fa79c753457599a7adc3ef03146
    0x46,0x55,0x5a,
    FUZ
    artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
    Base64: RlVa
    

0x3 输出详解

1. 分析输出

fuzzer工具从随机种子开始,重新运行fuzz_me_fuzzer -seed= 2720633294可以获得相同的结果
INFO: Seed: 2720633294
默认情况下,libFuzzer所有输入都是4096字节或更小。要更改使用-max_len=N或运行非空种子语料库
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
libFuzzer尝试了至少19758个输入#19758并发现了总共10个字节的4个输入corp: 4/10b,它们共同覆盖了280个覆盖点cov: 280。这里将覆盖点视为代码中的基本块
#19758    REDUCE cov: 280 ft: 8 corp: 4/10b exec/s: 0 rss: 60Mb L: 3/3 MS: 1 EraseBytes-
在其中一个输入上,AddressSanitizer检测到heap-buffer-overflow错误并中止执行。
==27066==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xd523c1b3 at pc 0xb638e1c8 bp 0xffafed60 sp 0xffafed5c
READ of size 1 at 0xd523c1b3 thread T0
    #0 0xb638e1c7  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x51c7)
    #1 0xb638e25f  (/data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer+0x525f)

2. 重现崩溃

运行fuzzer工具输出崩溃后,导致问题的输入会保存到语料库中,并被指定一个 ID,标记在输出log倒数第二行。在本例输出中,ID 为 crash-0eb8e4ed029b774d80f2b66408203801cb982a60
artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
但按照官方描述的指令执行,是不会生成该crash-*文件的,而是在corpus目录下生成其他参数的文件(暂不明参数来源)
$ adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/corpus
其实直接执行即可在/data/tmp/fuzz_me_fuzzer/目录下生成crash-*文件
$ adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer
重现崩溃可以使用:
$ adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer crash-*

0 评论:

发表评论