libFuzzer学习

对llvm中的Fuzz工具libFuzzer的实现原理的学习笔记。

学习

https://www.secpulse.com/archives/71898.html
https://stfpeak.github.io/2017/05/24/Finding-bugs-using-libFuzzer/
http://pwn4.fun/2017/07/15/libFuzzer%E8%8F%9C%E9%B8%9F%E5%85%A5%E9%97%A8/

关键:

  1. libFuzzer简介:libFuzzer 是一个in-process,coverage-guided,evolutionary 的 fuzz 引擎,是 LLVM 项目的一部分。libFuzzer 和要被测试的库链接在一起,通过一个模糊测试入口点(目标函数),把测试用例喂给要被测试的库。fuzzer会跟踪哪些代码区域已经测试过,然后在输入数据的语料库上进行变异,来使代码覆盖率最大化。代码覆盖率的信息由 LLVM 的SanitizerCoverage 插桩提供。
  1. 关于LLVM的参考资料——

    https://blog.csdn.net/snsn1984/article/details/78788031
    https://juejin.im/entry/5874d80761ff4b006d546b2f

  2. 关于llvm的SanitizerCoverage——

    https://clang.llvm.org/docs/SanitizerCoverage.html

  1. fuzz 的种类(根据测试用例生成方式来分)——

    Generation Based :通过对目标协议或文件格式建模的方法,从零开始产生测试用例,没有先前的状态;
    Mutation Based :基于一些规则,从已有的数据样本或存在的状态变异而来;
    Evolutionary :包含了上述两种,同时会根据代码覆盖率的回馈进行变异。

  2. radamsa——一个测试用例生成器,基于变异的生成方式,根据给定的样本文件,生成各种测试用例,可以很好的集成在各个模糊测试框架中。并没有很详细的对其实现原理的解析,不太明白的地方在于:是如何根据给定的样本文件生成符合格式要求的同种测试用例的?——如有需要则阅读源码了解。

  3. 感觉libfuzzer 已经把 一个 fuzzer 的核心(样本生成引擎和异常检测系统) 给做好了, 我们需要做的是根据目标程序的逻辑,把 libfuzzer 生成的数据,交给目标程序处理。

  4. 有需要的话可以实践练习一下libfuzzer-workshop/tree/master/lessons(目前只练习了lessons04)。

  1. 简单的理解libFuzzer——如果我们要 fuzz 一个程序,找到一个入口函数,然后利用
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
.......
.......
}

接口,我们可以拿到 libfuzzer 生成的 测试数据(const uint8_t *data)以及测试数据的长度(size_t size),我们的任务就是把这些生成的测试数据传入到目标程序中, 让程序来处理测试数据, 同时要尽可能的触发更多的代码逻辑。对于测试目标中除data和size之外的参数,我们在fuzz脚本中自行构造,然后将构造的参数和libfuzzer 生成的测试数据一起传给测试目标程序。

  • 示例Fuzzer1(libfuzzer-workshop-master/lessons/04/second_fuzzer)
#include "vulnerable_functions.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
   bool verify_hash_flags[] = { false, true };
   for (auto flag : verify_hash_flags)
     VulnerableFunction2(data, size, flag);
   return 0;
 }
#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;
}

感觉用 libfuzzer 的话,我们需要做的工作就是根据目标程序的逻辑,把 libfuzzer 生成的 测试数据 传递 给 目标程序去处理, 然后在编译时采取合适的 Sanitizer 用于检测运行时出现的内存错误。比如上面就是模拟了 SSL 握手的逻辑,然后把 libfuzzer 生成的 测试数据作为握手包传递给 openssl 。

报错及解决

报错信息如下:clang: error: no such file or directory:clang++

通用类似的报错信息:clang: error: no such file or directory:xxx

产生原因:大概是在编程的时候工程目录结构改变(即文件的路径等信息和之前不同)了,编译器还按照之前的方式进行文件之间的链接,于是无法找到相应的文件结构,所以报错。

解决办法——

关闭工程,将工程下的.xcodeproj后缀的文件改成后缀为.zip;
打开.zip文件;
找到project.pbxproj这个文件,用文本编译器打开;
找到报错的文件,修改为正确的路径,如果根本不存在这个文件,就直接删除就好了;
保存后,将.zip恢复为.xcodeproj打开运行即可。

实际原因:在写命令的时候没注意,把路径搞错了,导致命令运行失败报错。

Android平台上使用libFuzzer

Android(八)libFuzzer

Android默认支持libFuzzer,但需要编译插桩版本(即AddressSanitizer版Android)才能使用,而插桩版本需要以完整userdebug编译版本为起点继续编译,故需要编译两次。官方说明参考链接如下——

通过 libFuzzer 进行模糊测试
AddressSanitizer

参考链接

https://github.com/Dor1s/libfuzzer-workshop
http://llvm.org/docs/LibFuzzer.html#dictionaries