Android(八)libFuzzer
libFuzzer 是一个
in-process
,coverage-guided
,evolutionary
的 fuzz
引擎,是 LLVM
项目的一部分。libFuzzer
和 要被测试的库 链接在一起,通过一个模糊测试入口点(目标函数),把测试用例喂给要被测试的库。fuzzer
会跟踪哪些代码区域已经测试过,然后在输入数据的语料库上进行变异,来使代码覆盖率最大化。代码覆盖率的信息由 LLVM
的SanitizerCoverage
插桩提供。
Android上使用libFuzzer需要编译插桩版本,详情参考:Android(七)源码编译
0x1 编写fuzzer工具
- 在 Android 源代码树中创建一个目录
$ mkdir tools/fuzzers/fuzz_me_fuzzer
- 在上面目录下创建测试代码
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; }
- 在该目录下创建编译配置文件
Android.mk
,内容如下:
实现这个目的所需的大部分逻辑都包含在 BUILD_FUZZ_TEST 宏(在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/core/fuzz_test.mk
中进行定义) - 在 Android 源码根目录编译fuzzer工具
编译完成后,fuzzer工具生成的默认位置为$ make -j8 fuzz_me_fuzzer SANITIZE_TARGET="address coverage"
out/target/product/sailfish/data/nativetest/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer
0x2 使用fuzzer工具
- 将fuzzer工具上传到设备上
$ adb root $ adb shell mkdir -p /data/tmp/fuzz_me_fuzzer/corpus $ adb push ./fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/
- 运行fuzzer工具
$ adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/corpus
- libFuzzer 输出结果
在本次测试代码中,崩溃是由第 10 行中的 fuzz_me_fuzzer.cpp 导致的:
详细理解 libFuzzer 输出,可参考LibFuzzer 文档Data[3] == 'Z'; //如果数据长度为 3,会导致出界读取错误。
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
#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 评论:
发表评论