原文链接:【表哥有话说 第72期】honggfuzz 学习笔记
相关概念
Feedback-driven fuzzing
中文直译就是反馈驱动型模糊测试。通过提高 Code coverage (代码覆盖率),来进行变异样本,增加发现漏洞的概率。 Persistent fuzzing中文直译就是持续型模糊测试。之前的 Fuzzer 都是不断 fork() 出新进程,然后再将变异生成的样本放入程序中,最后查看结果,这样会消耗一定的系统资源。所以 honggfuzz 就实现了这个模式,只要在一个循环中不断的将变异样本输入到下面这个函数中,然后看函数执行的结果,就可以减少 fork() 的使用了。 - int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
- TestAPI(buf, len);
- return 0;
- }
复制代码
编译也很简单,使用 honggfuzz 封装好的 clang 就可以生成可以运行的程序了。- # honggfuzz 根目录下
- hfuzz_cc/hfuzz-clang test.c -o test # 编译
- honggfuzz -P -- ./test # 进行模糊测试
复制代码
Sanitizer直译就是消毒剂,在 Fuzz 时候可以使用 Address Sanitizer 辅助排查内存使用的问题。例如 use-after-free,heap overflow。当然还有其他的消毒剂可以配合使用。 Dict 和 Corpus中文直译就是字典和资料,这些可以辅助样本的生成。 环境搭建这次使用实机上的 Debian Testing 作为开发环境。首先用 tmpfs 建立 ramdisk,因为 Fuzz 中内存读写非常大。 - mkdir /mnt/ramdisk
- mount -t tmpfs -o size=2048M tmpfs /mnt/ramdisk
复制代码 然后按照 Usage 安装 binutilslibunwind-dev,编译 honggfuzz。
- apt install binutils binutils-dev libunwind-dev
- git clone https://github.com/google/honggfuzz --depth=1
- cd honggfuzz
- make -j$(nproc)
复制代码
实战:OpenSSL HeartBleed首先编译一份有漏洞的 OpenSSL 库,主要是启用了消毒剂-fsanitize,代码覆盖率检测-fsanitize-coverage,还有调试信息-g。 - tar xzf openssl1.0.1f.tgz
- cd openssl1.0.1f/
- ./config
- make clean
- make CC="clang -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div" -j$(nproc)
复制代码 接下来实现 Fuzzer 的框架。从 libfuzzer-workshop 复制来了相应的文件,可以看到代码主要是实现了 SSL 服务端。注意代码中使用了私钥文件得自己生成。
- / Copyright 2016 Google Inc. All Rights Reserved.
- // Licensed under the Apache License, Version 2.0 (the "License");
- #include <openssl/ssl.h>
- #include <openssl/err.h>
- #include <assert.h>
- #include <stdint.h>
- #include <stddef.h>
- #ifndef CERT_PATH
- # define CERT_PATH
- #endif
- SSL_CTX *Init() {
- SSL_library_init();
- SSL_load_error_strings();
- ERR_load_BIO_strings();
- OpenSSL_add_all_algorithms();
- SSL_CTX *sctx;
- assert (sctx = SSL_CTX_new(TLSv1_method()));
- /* These two file were created with this command:
- openssl req -x509 -newkey rsa:512 -keyout server.key \
- -out server.pem -days 9999 -nodes -subj /CN=a/
-
- assert(SSL_CTX_use_certificate_file(sctx, CERT_PATH "server.pem",
- SSL_FILETYPE_PEM));
- */
- assert(SSL_CTX_use_PrivateKey_file(sctx, CERT_PATH "server.key",
- SSL_FILETYPE_PEM));
- return sctx;
- }
- extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
- static SSL_CTX *sctx = Init();
- SSL *server = SSL_new(sctx);
- BIO *sinbio = BIO_new(BIO_s_mem());
- BIO *soutbio = BIO_new(BIO_s_mem());
- SSL_set_bio(server, sinbio, soutbio);
- SSL_set_accept_state(server);
- BIO_write(sinbio, Data, Size);
- SSL_do_handshake(server);
- SSL_free(server);
- return 0;
- }
复制代码 然后写个编译脚本辅助编译,主要操作就是将 OpenSSL,libstdc++,pthread 和我们写的 Fuzzer 使用 hfuzz_cc/hfuzz-clang 进行链接。当然还启用了消毒剂 -fsanitize,代码覆盖率检测 -fsanitize-coverage,还有调试信息 -g。
- set -x
- FUZZER_SRC="openssl_fuzzer.cc"
- HFUZZ_SRC="/data/remote/honggfuzz"
- OPENSSL_SRC="openssl1.0.1f"
- CC="$HFUZZ_SRC/hfuzz_cc/hfuzz-clang"
- COMMON_FLAGS="-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE \
- -DBORINGSSL_UNSAFE_FUZZER_MODE \
- -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
- -DBN_DEBUG \
- -DLIBRESSL_HAS_TLS1_3 \
- -O3 \
- -g \
- -DFuzzerInitialize=LLVMFuzzerInitialize \
- -DFuzzerTestOneInput=LLVMFuzzerTestOneInput \
- -I./$OPENSSL_SRC/include -I$HFUZZ_SRC"
- COMMON_LDFLAGS="-lpthread -lz"
- SAN_COMPILE="-fsanitize=address"
- SAN_COV="-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div"
- $CC $FUZZER_SRC $COMMON_FLAGS $COMMON_LDFLAGS $SAN_COMPILE $SAN_COV \
- -I$OPENSSL_SRC/include $OPENSSL_SRC/libssl.a $OPENSSL_SRC/libcrypto.a \
- -lstdc++ \
- -o openssl_fuzzer
复制代码 接下来就是跑 Fuzzer 了。
- $ $HONGGFUZZ_SRC/honggfuzz --input corpus_server --persistent --workspace out -- ./openssl_fuzzer
- ------------------------[ 0 days 00 hrs 00 mins 04 secs ]----------------------
- Iterations : 78093 [78.09k]
- Mode [3/3] : Feedback Driven Mode
- Target : ./openssl_fuzzer
- Threads : 4, CPUs: 8, CPU%: 436% [54%/CPU]
- Speed : 19169/sec [avg: 19523]
- Crashes : 18 [unique: 1, blacklist: 0, verified: 0]
- Timeouts : 0 [1 sec]
- Corpus Size : 165, max: 131072 bytes, init: 1944 files
- Cov Update : 0 days 00 hrs 00 mins 01 secs ago
- Coverage : edge: 1361/44726 [3%] pc: 86 cmp: 34115
- ---------------------------------- [ LOGS ] ------------------/ honggfuzz 2.3 /-
- Crash (dup): 'out/SIGABRT.PC.436767.STACK.18647aae6e.CODE.-6.ADDR.0.INSTR.lea____-0x840(%rbp),%rdx.f
- uzz' already exists, skipping
- [2020-08-13T19:30:44+0800][W][55426] arch_checkWait():232 Persistent mode: pid=55456 exited with sta
- tus: SIGNALED, signal: 6 (Aborted)
- Persistent mode: Launched new persistent pid=55466
- Crash (dup): 'out/SIGABRT.PC.436767.STACK.18647aae6e.CODE.-6.ADDR.0.INSTR.lea____-0x840(%rbp),%rdx.f
- uzz' already exists, skipping
- [2020-08-13T19:30:44+0800][W][55425] arch_checkWait():232 Persistent mode: pid=55464 exited with sta
- tus: SIGNALED, signal: 6 (Aborted)
- Persistent mode: Launched new persistent pid=55468
- Signal 2 (Interrupt) received, terminating
- Terminating thread no. #2, left: 3
- Terminating thread no. #1, left: 2
- Terminating thread no. #3, left: 1
- Terminating thread no. #0, left: 0
- Summary iterations:78101 time:4 speed:19525 crashes_count:18 timeout_count:0 new_units_added:4 slowe
- st_unit_ms:269 guard_nb:44726 branch_coverage_percent:3 peak_rss_mb:370
复制代码
然后查看生成的样本,我们设置工作目录为 out,可以在这个文件夹下发现 .log 结尾的文件,记录消毒剂的运行情况,还有 .fuzz 结尾的文件存放着输入样例,还有 HONGGFUZZ.REPORT.TXT 存放着所有的崩溃情况。 将样本输入到程序中,可以发现 tls1_process_heartbeat() 这个函数出现了使用 memcpy() 造成的崩溃,即发现了漏洞。 - $ ./openssl_fuzzer < /mnt/ramdisk/out/SIGABRT.PC.436767.STACK.18647aae6e.CODE.-6.ADDR.0.INSTR.lea____-0x840\(%rbp\),%rdx.fuzz
- Accepting input from '[STDIN]'
- Usage for fuzzing: honggfuzz -P [flags] -- ./openssl_fuzzer
- =================================================================
- ==55483==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000009748 at pc 0x000000436767 bp 0x7fff50095080 sp 0x7fff50094840
- READ of size 61684 at 0x629000009748 thread T0
- #0 0x436766 in memcpy (/data/remote/openssl_fuzz/openssl_fuzzer+0x436766)
- #1 0x4ccc30 in tls1_process_heartbeat /data/remote/openssl1.0.1f/ssl/t1_lib.c:2586:3
- #2 0x5176c8 in ssl3_read_bytes /data/remote/openssl1.0.1f/ssl/s3_pkt.c:1092:4
- #3 0x519314 in ssl3_get_message /data/remote/openssl1.0.1f/ssl/s3_both.c:457:7
- #4 0x4fb244 in ssl3_get_client_hello /data/remote/openssl1.0.1f/ssl/s3_srvr.c:941:4
- #5 0x4f7d05 in ssl3_accept /data/remote/openssl1.0.1f/ssl/s3_srvr.c:357:9
- #6 0x4c6f42 in LLVMFuzzerTestOneInput /data/remote/openssl_fuzz/openssl_fuzzer.cc:40:3
- #7 0x7007b1 in HonggfuzzMain (/data/remote/openssl_fuzz/openssl_fuzzer+0x7007b1)
- #8 0x7fa9723f6cc9 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26cc9)
- #9 0x41f7e9 in _start (/data/remote/openssl_fuzz/openssl_fuzzer+0x41f7e9)
- 0x629000009748 is located 0 bytes to the right of 17736-byte region [0x629000005200,0x629000009748)
- allocated by thread T0 here:
- #0 0x4976ed in malloc (/data/remote/openssl_fuzz/openssl_fuzzer+0x4976ed)
- #1 0x534676 in CRYPTO_malloc /data/remote/openssl1.0.1f/crypto/mem.c:308:8
- #2 0x51a217 in freelist_extract /data/remote/openssl1.0.1f/ssl/s3_both.c:708:12
- #3 0x51a217 in ssl3_setup_read_buffer /data/remote/openssl1.0.1f/ssl/s3_both.c:770:10
- #4 0x51a217 in ssl3_setup_buffers /data/remote/openssl1.0.1f/ssl/s3_both.c:827:7
- #5 0x4f8aac in ssl3_accept /data/remote/openssl1.0.1f/ssl/s3_srvr.c:292:9
- #6 0x4c6f42 in LLVMFuzzerTestOneInput /data/remote/openssl_fuzz/openssl_fuzzer.cc:40:3
- #7 0x7007b1 in HonggfuzzMain (/data/remote/openssl_fuzz/openssl_fuzzer+0x7007b1)
- SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/remote/openssl_fuzz/openssl_fuzzer+0x436766) in memcpy
- Shadow bytes around the buggy address:
- 0x0c527fff9290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0x0c527fff92a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0x0c527fff92b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0x0c527fff92c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0x0c527fff92d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- =>0x0c527fff92e0: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
- 0x0c527fff92f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
- 0x0c527fff9300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
- 0x0c527fff9310: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
- 0x0c527fff9320: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
- 0x0c527fff9330: 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
- Shadow gap: cc
- ==55483==ABORTING
复制代码
实战:PCRE2和 OpenSSL 的 Fuzz 类似,先实现测试框架。 - // Copyright 2016 Google Inc. All Rights Reserved.
- // Licensed under the Apache License, Version 2.0 (the "License");
- #include <stdint.h>
- #include <stddef.h>
- #include <string>
- #include "pcre2posix.h"
- using std::string;
- extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) {
- if (size < 1) return 0;
- regex_t preg;
- string str(reinterpret_cast<const char*>(data), size);
- string pat(str);
- int flags = data[size/2] - 'a'; // Make it 0 when the byte is 'a'.
- if (0 == regcomp(&preg, pat.c_str(), flags)) {
- regmatch_t pmatch[5];
- regexec(&preg, str.c_str(), 5, pmatch, 0);
- regfree(&preg);
- }
- return 0;
- }
复制代码 编译 PCRE2 和 Fuzzer,然后链接起来。
- set -x
- FUZZER_SRC="pcre2_fuzzer.cc"
- HFUZZ_SRC="/data/remote/honggfuzz"
- PCRE2_SRC="pcre2-10.00"
- CC="$HFUZZ_SRC/hfuzz_cc/hfuzz-clang"
- COMMON_FLAGS="-O3 \
- -g \
- -DFuzzerInitialize=LLVMFuzzerInitialize \
- -DFuzzerTestOneInput=LLVMFuzzerTestOneInput \
- -I./$PCRE2_SRC/include -I$HFUZZ_SRC"
- COMMON_LDFLAGS="-lpthread -lz"
- SAN_COMPILE="-fsanitize=address"
- SAN_COV="-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div"
- $CC $FUZZER_SRC $COMMON_FLAGS $COMMON_LDFLAGS $SAN_COMPILE $SAN_COV \
- -I$PCRE2_SRC/src \
- -Wl,--whole-archive $PCRE2_SRC/.libs/*.a -Wl,-no-whole-archive \
- -lstdc++ \
- -o pcre2_fuzzer
复制代码 在 Fuzz 时可以准备字典和资料进行辅助。
- "?"
- "abc"
- "()"
- "[]"
- "abc|def"
- "abc|def|ghi"
- "^xxx[ DISCUZ_CODE_11 ]quot;
- "ab\\b\\d\\bcd"
- "\\w|\\d"
- "a*?"
- "abc+"
- "abc+?"
- #...
复制代码 跑了应该有 30 分钟吧,生成了差不多 60 个崩溃事例,例如下面两句错误的正则都会导致程序崩溃。
- (?(?=a){0,10}a)
- (|)(?(?!a)?aa)\d*
复制代码
拓展阅读
Double-Free RCE in VLC. A honggfuzz how-to(https://www.pentestpartners.com/ ... a-honggfuzz-how-to/) fuzzing-openssh-daemon-using-afl(http://www.vegardno.net/2017/03/ ... emon-using-afl.html) peruvian-were-rabbit(https://countuponsecurity.com/tag/peruvian-were-rabbit/) Dor1s/libfuzzer-workshop(https://github.com/Dor1s/libfuzzer-workshop) google/fuzzing(https://github.com/google/fuzzing) refshonggfuzz(https://github.com/google/honggfuzz) honggfuzz漏洞挖掘技术深究系列(https://bbs.pediy.com/thread-247954.htm) AFL(American Fuzzy Lop)实现细节与文件变异(https://paper.seebug.org/496/)
|