这篇文章主要讲的是在Libprotobuf-mutator与LibFuzzer联合使用的基础上,加上custom mutator功能。首先需要明确的是为什么要这么做,如果你看了上一篇【Custom Mutator Fuzz】Libprotobuf + LibFuzzer联合使用就可以发现,我们虽然构造了拥有a、b两个字段的结构,但是结构中的数据是由LibFuzzer随机突变生成的。那么假设b字段只有为"FUZZ"或"PWN"两个字符的时候才能进入下一个程序分支的情况,当然LibFuzzer也可以在代码覆盖率的加持下进入下一个程序分支,但如果你通过逆向的方式已经知道了这个关键点,难道还需要等LibFuzzer跑出这两个字符串吗?这显然十分的浪费时间和资源,所以custom mutator在某些情况下还是更加有效的
编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ
PS:文章末尾有联系方式,交个朋友吧~
本文链接:https://hollk.blog.csdn.net/article/details/124725688
编写protobuf文件模糊测试系列往期回顾:
【Custom Mutator Fuzz】Libprotobuf + LibFuzzer联合使用
【Custom Mutator Fuzz】简单Protobuf使用练习
【Custom Mutator Fuzz】Protocol Buffer基础(下):C++生成代码介绍 + 配套代码
【Custom Mutator Fuzz】Protocol Buffer基础(上):proto2编写格式
【Custom Mutator Fuzz】libprotobuf-mutator安装
LibFuzzer学习(三):使用小trick
LibFuzzer学习(二):提高代码覆盖率和速度
LibFuzzer学习(一):轻松找到心脏出血漏洞
Fuzz出结果不会看?Address Sanitizer(ASan)各类溢出demo分析
Fuzzing101 Exercise 5 - LibXML2 学习笔记
Fuzzing101 Exercise 4 - LibTIFF 学习笔记
Fuzzing101 Exercise 3 - TCPdump 学习笔记
Fuzzing101 Exercise 2 - Libexif 学习笔记
Fuzzing101 Exercise 1 - Xpdf学习笔记
AFL源码分析之afl-fuzz.c详细注释(二):FUZZ执行流程(五万字警告,慎入)
AFL源码分析之afl-fuzz.c详细注释(一):初始配置(6万字警告,慎入)
AFL源码分析之afl-gcc.c详细注释
AFL源码分析之afl-as.c详细注释
免费资源:AFL-2.57b.zip(AFL源码分析章节版本)
第三个练习依然还是使用上一篇【Custom Mutator Fuzz】简单Protobuf使用练习中的proto源码:
syntax = "proto2";
message Hollk {
required uint32 a = 1;
required string b = 2;
}
这里定义了一个required的无符号整型a,和一个required的字符串类型b。接下来使用前面【Custom Mutator Fuzz】libprotobuf-mutator安装文章中已经装好了的protoc编译器编一下:
/home/hollk/libprotobuf-mutator/build/external.protobuf/bin/protoc-3.17.3.0 ./test.proto --cpp_out=./
接下来就可以在当前目录下看到hollk.pb.h和hollk.pb.cc两个文件了
编写目标库harness.cc和练习2一样,需要设计一个目标程序,向其中中输入某特定字符参数后程序中断
//harness.cc #include#include extern "C" int FuzzTEST(const uint8_t *data, size_t size) { if(data[0] == 'x01') { __builtin_trap(); } return 0; }
简单解释一下这个目标程序,整体只有一个函数FuzzTEST,并且支持C和C++混合编程,主要的参数有两个,一个是指针数组data,另外一个是size。函数内部,如果data的第一个字节为x01则整个程序中断
编写fuzzer程序lpm_libfuzz_custom_mutator.cc这里和上一篇【Custom Mutator Fuzz】Libprotobuf + LibFuzzer联合使用文章中的过程稍有不同,如果仅仅只是靠LibFuzzer按照protobuf的结构进行编译,其实没有能够诠释custom mutator的效果。因此在这篇文章编写fuzzer的时候想要加一点自定义的部分进去,也就是说玩点花的~那么首先还是需要缕清一下fuzzer程序的执行流程:
- 第一,使用LibFuzzer为protobuf中的数据结构提供变异的数据
- 第二,protobuf中的二进制流数据需要转化成字节流数据传递给被测试函数
- 第三,需要加一点自定义变异策略进去,起始向上翻一翻,可以看到目标FuzzTEST函数中判断崩溃的条件是数据起始字节为x01,这就意味着protobuf中的b字段其实并没有什么变异的必要,那么就可以考虑在b字段中做一些花样来展示自定义效果
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" //实现LibFuzzer封装头文件 #include "hollk.pb.h" //protobuf头文件 #includeusing std::cin; //初始化输入 using std::cout; //初始化输出 using std::endl; //初始化换行 std::string ProtoToData(const Hollk &hollk) { //protobuf二进制流数据转字节流数据 std::stringstream all; const auto &aa = hollk.a(); //获取a字段值存放进aa中 const auto &bb = hollk.b(); //获取b字段值存放进bb中 all.write((const char*)&aa, sizeof(aa)); //读取aa中的二进制流数据,存入all中 if(bb.size() != 0) { //如果bb中的数据长度不为0 all.write(bb.c_str(), bb.size()); //读取bb中的二进制流数据,存入all中 } std::string res = all.str(); //将all中二进制流数据转化为string类型,赋值给res if (bb.size() != 0 && res.size() != 0) { //如果bb和res中都存在数据 // 设置PROTO_FUZZER_DUMP_PATH env以便用来转储protobuf的序列化数据 if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { std::ofstream of(dump_path); of.write(res.data(), res.size()); } } return res; } extern "C" int FuzzTEST(const uint8_t* data, size_t size); // 目标函数 bool hasRegister = false; //设置一个bool型全局变量,用来判断是否已完成自定义mutator //LibFuzzer封装DEFINE_PROTO_FUZZER,出自libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h DEFINE_PROTO_FUZZER(const Hollk &hollk) { if(!hasRegister) { protobuf_mutator::libfuzzer::RegisterPostProcessor( //libprotobuf-mutator后处理器回调 Hollk::descriptor(), //一参,指向Hollk message描述符的const指针 //二参,实现自定义的回调lambda函数 //lambda函数一参:message对象,二参:Libprotobuf-mutator提供的int型伪随机数种子seed [](google::protobuf::Message* message, unsigned int seed) { Hollk *t = static_cast (message); //强制转换为Hollk message对象 // 随机奇偶,即50%概率设置b字段为"FUZZ"或者"PWN" if (seed % 2) { t->set_b("FUZZ"); } else { t->set_b("PWN"); } } ); hasRegister = true; return; } auto s = ProtoToData(hollk); // 转化为字节流 FuzzTEST((const uint8_t*)s.data(), s.size()); // 喂给目标函数FuzzTEST进行fuzz }
前面与上一篇相似的部分这里就不再过多赘述了,可以查看【Custom Mutator Fuzz】Libprotobuf + LibFuzzer联合使用这篇文章进行比对。这里主要想说一下libprotobuf-mutator提供的后处理器回调设置函数RegisterPostProcessor,函数所在文件路径为:/home/hollk/libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h。函数原型如下:
void RegisterPostProcessor(
const protobuf::Descriptor* desc,
std::function
callback);
Libprotobuf-mutator通过使用该函数在protobuf的message类型上注册后处理器回调,在Libprotobuf-mutator每次执行突变之后调用后处理器
- 一参:protobuf中message描述符,Libprotobuf-mutator将回调映射到该描述符中
- 二参:回调函数(一参:protobuf message结构元素,二参Libprotobuf-mutator提供的伪随机数)
这里的二参使用了lambda表达式的形式,首先指定元素为Hollk message对象,再通过判断伪随机数的奇偶值,即构造50%概率对b字段赋值为FUZZ或PWN字符,这样一来就做出了一个比较简单的自定义结构
编写Makefile文件TARGET=lpm_libfuzz_custom_mutator # 编译目标程序名称 CXX=clang++ # 设置编译器为clang++ CXXFLAGS=-g -fsanitize=fuzzer,address # 设置使用LibFuzzer和ASAN PB_SRC=hollk.pb.cc # 设置protobuf文件 PROTOBUF_DIR=$(HOME)/libprotobuf-mutator/build/external.protobuf # 设置protobuf目录 LPM_DIR=$(HOME)/libprotobuf-mutator # 设置Libprotobuf-mutator目录 PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a # 设置protobuf动态链接库 # 设置Libprotobuf-mutator中与LibFuzzer联动的动态链接库 LPM_LIB=$(LPM_DIR)/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a $(LPM_DIR)/build/src/libprotobuf-mutator.a INC=-I$(PROTOBUF_DIR)/include -I$(LPM_DIR) # 添加包含文件路径 DFUZZ=-DLLVMFuzzerTestOneInput=FuzzTEST # 设置模糊测试目标函数名称 all: $(TARGET) # 按顺序完成所有编译操作 harness.o: harness.cc # 将harness.cc编译成一个库文件 $(CXX) $(CXXFLAGS) -c $(DFUZZ) $< $(TARGET): harness.o $(TARGET).cc # 编译lpm_libfuzz程序 $(CXX) $(CXXFLAGS) -o $@ $^ $(PB_SRC) $(LPM_LIB) $(PROTOBUF_LIB) $(INC) .PHONY: clean clean: rm $(TARGET) *.o
这样一来我们只需要在当前目录使用make all命令,Makefile就相当于帮助我们执行了如下两条命令:
clang++ -g -fsanitize=fuzzer,address -c -DLLVMFuzzerTestOneInput=FuzzTEST harness.cc clang++ -g -fsanitize=fuzzer,address -o lpm_libfuzz harness.o lpm_libfuzz.cc hollk.pb.cc /home/hollk/libprotobuf-mutator/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a /home/hollk/libprotobuf-mutator/build/src/libprotobuf-mutator.a -I /home/hollk/libprotobuf-mutator/build/external.protobuf/include/ -I /home/hollk/libprotobuf-mutator
当然,你也可以分别单独使用make harness.o命令和make lpm_libfuzz命令单独对两个程序进行编译。使用make all命令之后,就会在当前目录中出现如下的几个文件,生成的lpm_libfuzz_custom_mutator程序就是执行的fuzzer:
执行程序可以看到LibFuzzer在尝试3次突变后发现了deadly signal类型错误(不知道什么是deadly signal类型错误,可以参考我的Fuzz出结果不会看?Address Sanitizer(ASan)各类溢出demo分析这篇文章),并将crash数据存储在了本地crash-0ec76c8f1e8099ec26bfc85609785b2d628bcaa6文件中
再看一下crash-0ec76c8f1e8099ec26bfc85609785b2d628bcaa6文件中保存的崩溃数据:
可以看到b字段按照我们定义的方式成为了PWN
问题1
可能会出现提示版本错误的问题:error: This file was generated by an older version of protoc which is
如果在做libprotobuf-mutator_fuzzing_learning练习的时候,直接使用项目练习2中的test.pb.h和test.pb.cc的话可能会出现版本错误的问题。这是因为项目中存放的这两个文件是由比较老版本的protobuf编译器进行编译的,但是在后面使用Makefile的时候就会发现,所用的动态链接库都是都是protoc-3.17.3.0版本。这就导致了版本不匹配的问题
解决方法:
/home/hollk/libprotobuf-mutator/build/external.protobuf/bin/protoc-3.17.3.0 ./test.proto --cpp_out=./
使用新的protobuf编译器重新编译.proto文件,你可能在练习2中找不到.proto文件,这里其实可以沿用练习1中的.proto文件内容。或者直接用本文开头中的proto代码
问题2
可能会出现的问题:clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是因为在编译lpm_libfuzz_custom_mutator程序时候$(LPM_LIB)和$(PROTOBUF_LIB)两个动态链接库优先级导致的,$(LPM_LIB)需要在$(PROTOBUF_LIB)前面
参考链接
https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning
https://bshastry.github.io/2019/12/27/Custom-Proto-Mutation.html
交个朋友吧~扫描下方微信二维码,一起讨论研究



