libfuzzer模糊测试理论实践以及在IOT嵌入式下场景迁移 2020-09-12 11:14:31 Steven Xeldax ## 目录 1. libfuzzer理论篇 2. libfuzzer实践篇 3. ARM架构libfuzzer使用 4. 参考资料 ## 理论篇 ### libfuzzer简介 libFuzzer是一个in-process,covergae-guided,evolutionary的fuzz引擎,是llvm项目的一部分。libFuzzer要和被测试的库连接在一起通过一个模糊测试入口点或者说是目标函数。把测试用例喂给需要被测试的库。fuuzer会跟踪到哪些代码区域已经测试过,然后在输入数据的语料库上进行变异,来使代码覆盖率最大化。代码覆盖率的信息由llvm的SanitizerCoverage插桩提供。 ### 关于fuzz的种类 Generation Based:通过对目标协议或文件格式建模的方法,从零开始产生测试用例,没有先前的状态; Mulation Based: 基于一些规则,从已有的数据样本或存在的状态变异而来; Evolutionary: 包含上述两种,同时会根据代码覆盖率的回馈进行变异。 ### 基本流程 如果我们需要fuzz一个程序,找到一个入口函数,然后编译生成fuzz程序进行模糊测试 ``` extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ....... ....... } ``` 我们可以拿到libfuzzer自己生成的测试数据,我们的任务就是把这些测试数据传入到目标程序中,让程序来处理测试数据,同时要尽可能多的出发代码逻辑。   ## 实践篇 ### libfuzzer安装 ibfuzzer是基于clang编译器为基础的fuzz工具,因此在使用时候必须安装clang llvm项目。 有三种方法安装: ● apt-get install clang ● 前往llvm项目下载静态编译文件(https://releases.llvm.org/download.html) ● 源码手动编译 这边讲解下如何手动安装 https://github.com/Dor1s/libfuzzer-workshop  下载后运行checkout_build_install_llvm.sh 运行就可以源码安装llvm工具链。 之后cd进libFuzzer运行build.sh就会生成libfuzzer.a,往后在编译生成fuzz程序时候带上这个依赖库就行。 ### helloword 目标代码: ``` #include <stdint.h> #include <stddef.h> bool FuzzMe(const uint8_t *Data, size_t DataSize) { return DataSize >= 3 && Data[0] == 'F' && Data[1] == 'U' && Data[2] == 'Z' && Data[3] == 'Z'; // :‑< } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { FuzzMe(Data, Size); return 0; } ``` 生成fuzz程序 > clang++ -g -fsanitize=address,fuzzer ./helloworld.cc -fprofile-instr-generate -fcoverage-mapping ``` -g 保留调试信息 -fsanitize 增加fuzz的漂白剂 -fcoverage-mapping 生成覆盖率地图 -fprofile-instr-generate 生存 instr中间文件 ``` 运行fuzz程序得到crash结果  查看代码覆盖率 > llvm-profdata merge -sparse default.profraw -o asn1.prodata > llvm-cov show ./a.out --instr-profile asn1.prodata -format text  ### freeimages 下载源代码 wget https://downloads.sourceforge.net/freeimage/FreeImage3180.zip 编译(黑盒) ``` cd Freeimages make ``` 这种不推荐,因为编译用的是gcc,libfuzzer使用的不是很好建议还是使用clang带上fuzzer参数进行百衲衣 编译(clang) > CC="clang -g -fsanitize=address,fuzzer-no-link" make -j4 编写fuzz入口点 ``` #include <cstddef> #include <cstdint> #include <cstdlib> #include <vector> #include <FreeImage.h> namespace { // Returns true if the format should be attempted to loaded from memory. bool SafeToLoadFromMemory(FREE_IMAGE_FORMAT fif) { // For now, just load if it is a BMP. Future heuristics may need to be based // on the expected size in different formats for memory regions to avoid OOMs. return fif == FIF_BMP; } } // namespace extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { static bool initialized = false; if (!initialized) { FreeImage_Initialise(); } if (size > 100 * 1000) { return 0; } std::vector<uint8_t> fuzzer_data_vector(data, data + size); FIMEMORY* fiMem = FreeImage_OpenMemory( reinterpret_cast<unsigned char*>(fuzzer_data_vector.data()), fuzzer_data_vector.size()); FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(fiMem, 0); if (SafeToLoadFromMemory(fif)) { FIBITMAP* fiBitmap = FreeImage_LoadFromMemory(fif, fiMem); FreeImage_Unload(fiBitmap); } FreeImage_CloseMemory(fiMem); return 0; } ``` 编译入口点文件 ``` clang++ -g -fsanitize=address,fuzzer -fprofile-instr-generate -fcoverage-mapping ./fuzz.cc -I FreeImage/Source/ ./FreeImage/libfreeimage.a ``` 运行 黑盒下 ./a.out  clang下  ### openssl ``` $ 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-cmp,trace-gep,trace-div" -j$(nproc) ``` fuzzer入口点文件 ``` #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; } ``` 编译fuzz ``` clang++ -g fuzz.cc -O2 -fno-omit-frame-pointer -fsanitize=address -fsanitize-coverage=trace-cmp,trace-gep,trace-div -Iopenssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a ../../libFuzzer/libFuzzer.a ``` or ``` clang++ -g fuzz.cc -O2 -fno-omit-frame-pointer -fsanitize=address,fuzzer -fsanitize-coverage=trace-cmp,trace-gep,trace-div -Iopenssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a ``` ## ARM架构libfuzzer使用 1. 一个arm架构的libfuzzer.a 2. make编译项目的时候也要生成arm架构的 3. 生成入口文件的时候也要指定arm架构  使用clang交叉编译需要安装arm gcc版本的交叉编译链,以及对应的文件头和各种依赖 ``` apt install g++-4.8-aarch64-linux-gnu gcc-4.8-aarch64-linux-gnu g++-4.8-multilib gcc-4.8-multilib ``` > clang -target aarch64-linux-gnueabi --sysroot=/usr/aarch64-linux-gnueabi 1.c 然后就可以编译了  ### aarch64 libfuzzer 要交叉编译libfuzz首先要使用clang编译工具交叉编译libfuzzer.a 这里距离aarch64-linux-gnu交叉编译的方式 首先确定aarch64-linux-gnu交叉编译工具链已经装入系统中  下载libfuzzer https://github.com/Dor1s/libfuzzer-workshop 修改编译脚本支持aarch64-linux-gnu方式: ``` #!/bin/sh LIBFUZZER_SRC_DIR=$(dirname $0) CXX="${CXX:-clang}" for f in $LIBFUZZER_SRC_DIR/*.cpp; do $CXX -g -O2 -fno-omit-frame-pointer -std=c++11 $f -c -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -I /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu/& done wait rm -f libFuzzer.a ar ru libFuzzer.a Fuzzer*.o ```  得到libfuzz.a 然后选择需要fuzz的项目 在CC中加入 ``` -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -I /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu/ ``` 来支持交叉编译 -target指定了交叉编译的目标 --sysroot是交叉编译环境的根路径 -I 是c++头文件的地址 以openssl libfuzzer选择为例子(https://github.com/Dor1s/libfuzzer-workshop/tree/master/lessons/05) ``` tar xzf openssl1.0.1f.tgz cd openssl1.0.1f/ ./config no-asm 这边我选择不要内联汇编不知道如何生成不是x86的汇编 ``` ``` make clean make CC="clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -I /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu/ -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div" -j$(nproc) ``` 编译好之后我们前往入口文件去编译链接libfuzzer.a ``` clang++ -g openssl_fuzzer.cc -O2 -fno-omit-frame-pointer -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div -Iopenssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a ../../libFuzzer/Fuzzer/libFuzzer.a -o openssl_fuzzer -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -I /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu/ -v ``` 编译时候会遇到这种错误  一般是缺少对应架构的clang compare rt,直接安装一下就可以 这边我选择比较快的方法下载一个静态的安装包,把libclang_rt.asan-aarch64这种以aarch结尾的复制到系统环境中。 最终编译 ``` clang++ -g openssl_fuzzer.cc -O2 -fno-omit-frame-pointer -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div -Iopenssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a ../../libFuzzer/Fuzzer/libFuzzer.a -o openssl_fuzzer -target aarch64-linux-gnu -I /usr/aarch64-linux-gnu/include/ -I /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu/ -I /root/Temp/arrachclang/clang+llvm-9.0.0-aarch64-linux-gnu/lib/clang/9.0.0/lib/linux/ -v ```  之后把该文件丢进板子操作就可以\ ## 参考资料 ``` https://llvm.org/docs/HowToCrossCompileLLVM.html https://medium.com/@wolfv/cross-compiling-arm-on-travis-using-clang-and-qemu-2b9702d7c6f3 https://archive.fosdem.org/2018/schedule/event/crosscompile/attachments/slides/2107/export/events/attachments/crosscompile/slides/2107/How_to_cross_compile_with_LLVM_based_tools.pdf https://myfzy.top/2020/06/03/libfuzzer/ ```