文章目录
前言
本文章为笔者学习 libprotobuf-mutator_fuzzing_learning 的学习记录,如果读者对此感兴趣,可以自行跟着该项目完成对应实验
环境搭建
关于 AFL/LibFuzzer
的环境搭建就不多说了,参考我之前的文章,这里仅仅说下 libprotobuf-mutator
环境的搭建:
// 安装依赖
sudo apt-get install protobuf-compiler libprotobuf-dev binutils cmake ninja-build liblzma-dev libz-dev pkg-config autoconf libtool clang
// 这里 cmake 版本要 3.24+ (至少我这里是这样的)
// 所以这里直接编译安装特定版本的 cmake
curl -LO https://cmake.org/files/v3.24/cmake-3.24.0.tar.gz
tar -zxvf cmake-3.24.0.tar.gz
cd cmake-3.24.0
./bootstrap
make -j4
sudo make install
// 注:这里直接切换下版本,这样就不需要 cmake 版本在 3.24+ 了
// 而且直接拉取主分支的话,后面存在一些问题
// 拉取源码
git clone https://github.com/google/libprotobuf-mutator.git
cd libprotobuf-mutator/
// 编译安装
// 在 libprotobuf-mutator/ 目录下创建一个 build 文件夹用来存放 cmake 文件和编译后的文件
mkdir build
cd build
// 指定 DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON 安装相应版本的 protobuf
cmake .. -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON
// 使用 ninja 进行编译安装,最好挂下代理
ninja check (可以不执行,时间太长了)
ninja
sudo ninja install
//编译完成后可以在 libprotobuf-mutator/build/external.protobuf/bin 目录下找到 protoc 文件
// libprotobuf-mutator/build/src 目录下找到库文件
如果安装失败多半是网络问题,多安几次
Simple protobuf example
这里写一个简单的 protobuf
如果对
protobuf
不是很熟悉的可以参考 Protocol Buffer Basics: C++
// test.proto
syntax = "proto2";
package Test;
message TestMessage {
required uint32 a = 1;
required string b = 2;
}
然后使用 protoc
编译生成 test.pb.h/test.pb.c
:
最后写个 demo
调用测试一下:
// clang++ -std=c++11 test.pb.cc demo.cc -o demo `pkg-config --cflags --libs protobuf`
#include <bits/stdc++.h>
#include "test.pb.h"
using namespace std;
using namespace Test;
int main() {
TestMessage tm;
tm.set_a(10);
tm.set_b("XiaozaYa");
cout << tm.b() << endl << tm.a() << endl;
return 0;
}
结果如下:
Combine libprotobuf-mutator with libfuzzer【大坑】
注:这里的 libprotobuf-mutator
请切换到实验指定版本分支,不然后面编译存在链接问题
搞了我小半天,直接拉取的仓库是最新版本的,后面编译的时候一直存在链接错误
编写目标库 hardness.cc
#include <bits/stdc++.h>
extern "C" int FuzzTest(const uint8_t* data, size_t size) {
if (data[0] == '\x01') {
__builtin_trap();
}
return 0;
}
该函数的效果是:当输入 data
的第一个字符为 \x01
时,程序中断退出
编写 fuzzer
程序 lpm_libfuzz.cc
首先需要明确该函数的功能:
- 需要用
LibFuzzer
对protobuf
中的数据进行变异,而libprotobuf-matutor
的libfuzzer_macro.h
中存在处理proto
序列化数据的方法,直接调用即可 protobuf
中的数据是以二进制流的形式进行存储传递的,而测试目标需要的是字节流,所以这里需要ProtoToData
函数将其转换为字节流- 调用
harness
中的FuzzTest
函数进行测试
#include <bits/stdc++.h>
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "test.pb.h"
using namespace std;
using namespace test;
// ProtoToData 将 protobuf 二进制数据转换为字符序列
string ProtoToData(const TestMessage& tm) {
stringstream all;
const auto& xx = tm.x();
const auto& ss = tm.s();
all.write((const char*)&xx, sizeof(xx));
if (ss.size()) {
all.write(ss.c_str(), ss.size());
}
string res = all.str();
if (ss.size() && res.size()) {
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}
extern "C" int FuzzTest(const uint8_t* data, size_t size);
// 该宏是由 libfuzzer_macro.h 定义的
DEFINE_PROTO_FUZZER(const TestMessage& tm) {
auto s = ProtoToData(tm);
FuzzTest((const uint8_t*)s.data(), s.size());
}
所以整个逻辑就是:libprotobuf-mutator
对 protobuf
数据进行变异,然后将其转换为字节流,最后交给 LibFuzzer
,其会调用 FuzzTest
进行测试
编译链接:这里可以用以下两条指令,也可以直接用实验提供的 Makefile
clang++ -g -fsanitize=fuzzer,address -c -DLLVMFuzzerTestOneInput=FuzzTest harness.cc
clang++ -g -fsanitize=fuzzer,address -o lpm_libfuzz harness.o test.pb.cc lpm_libfuzz.cc /home/xiaozaya/fuzz/libprotobuf-mutator/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a /home/xiaozaya/fuzz/libprotobuf-mutator/build/src/libprotobuf-mutator.a /home/xiaozaya/fuzz/libprotobuf-mutator/build/external.protobuf/lib/libprotobufd.a -I/home/xiaozaya/fuzz/libpr
otobuf-mutator/build/external.protobuf/include/ -I/home/xiaozaya/fuzz/libprotobuf-mutator/
先来说一下第一条编译命令:
clang++ -g -fsanitize=fuzzer,address -c -DLLVMFuzzerTestOneInput=FuzzTest harness.cc
-c
:仅编译不链接,即生成harness.o
-DLLVMFuzzerTestOneInput=FuzzTEST
:此选项定义了一个预处理符号LLVMFuzzerTestOneInput
,并将其赋值为FuzzTEST
。在模糊测试中,通常会使用此选项来定义模糊测试的入口点
第二条编译命令需要注意的是:libprotobufd.a
这个静态库得放在 libprotobuf-mutator-libfuzzer.a
和 libprotobuf-mutator.a
静态库后面
运行后可以看到报了 deadly signal
错误
然后可以看下 crash
样例:
可以看到这里 x = 0x6 00 00 01
,其第最低有效字节为 \x01
(注:这里的 x
是整数,而不是字符串哈)
Combine libprotobuf-mutator with libfuzzer ( custom mutator )
现在我们回到 CTF pwn
的菜单堆题,一般情况下都需要输入一个特定的字符才能够进入下一步,比如 1
表示增加一个堆块。而在第二个实验中我们可以看到 x/s
的变异是随机的,比如这里的 s
其是随意变化的,那么如果我们的程序必须要求 s
为 FUZZ
或者 PWN
,这样程序才能往下执行。当然这里直接用第二个实验的代码也可以,毕竟 s
是有概率变异成 FUZZ
或者 PWN
字符串的,但是这会极大的减缓 Fuzz
速度。 所以这里我们可以对 s
进行一些限制。
修改实验二的 lpm_libfuzz.cc
代码:
#include <bits/stdc++.h>
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "test.pb.h"
using namespace std;
using namespace test;
string ProtoToData(const TestMessage& tm) {
stringstream all;
const auto& xx = tm.x();
const auto& ss = tm.s();
all.write((const char*)&xx, sizeof(xx));
if (ss.size()) {
all.write(ss.c_str(), ss.size());
}
string res = all.str();
if (ss.size() && res.size()) {
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}
extern "C" int FuzzTest(const uint8_t* data, size_t size);
// hasRegister 表示是否自己定义了 mutator
bool hasRegister = false;
DEFINE_PROTO_FUZZER(const TestMessage& tm) {
if (!hasRegister) {
protobuf_mutator::libfuzzer::RegisterPostProcessor (
TestMessage::descriptor(),
[](google::protobuf::Message* message, unsigned int seed) {
TestMessage* t = static_cast<TestMessage*>(message);
if (seed % 2) t->set_s("FUZZ");
else t->set_s("PWN");
}
);
hasRegister = true;
//return; // 【1】
}
auto s = ProtoToData(tm);
FuzzTest((const uint8_t*)s.data(), s.size());
}
这里主要就是设置了一个 PostProcessor
,其实就是一个回调函数,当 libprotobuf-matutor
对 protobuf
的数据变异完成后,会调用该函数,那么我们就可以在将输入传入测试目标前对 s
字段做限制。
这里来看下是如果注册 PostProcessor
的:函数原型也是在 libfuzzer_macro.h
文件中
void RegisterPostProcessor(
const protobuf::Descriptor* desc,
std::function<void(protobuf::Message* message, unsigned int seed)>
callback);
- 一参:
protobuf
的message
描述符,调研descriptor()
函数即可 - 二参:回调函数,即
PostProcessor
- 一参:
protobuf
的message
结构元素 - 二参:伪随机数
seed
- 一参:
在上述代码中,回调函数是一个 lambda
表达式,即一个匿名函数
按理说 【1】
处的这个 return
是应该存在的,因为在第一次的时候还没有设置 PostProcessor
,但是笔者发现如果加上 return
,执行时可能会出现段错误:原因未知
所以笔者建议还是在第一次不加 return
,因为其实对于 fuzz
来说是没啥影响的,当然读者可能会说如果第一次 x
就满足 data[0] = '\x01'
呢?嗯,这里读者不要忘了,在正常情况下只有 s
满足条件时才会执行后面的流程,所以其实是不存在影响的(这里是这个代码例子的问题,让读者感觉会出现第一次就满足的情况)。
Combine libprotobuf-mutator with AFL++
该实验要实现的功能为:
即我们利用 libprotobuf-mutator
自定义变异规则,然后在 AFL++
进行变异时会调用我们自定义的变异规则。
vuln.c
如下
//vuln.c
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char str[100]={};
read(0, str, 100);
int *ptr = NULL;
if( str[0] == '\x02' || str[0] == '\xe8') {
*ptr = 123;
}
return 0;
}
当满足一定条件时,会执行 *ptr = 123
,而这里的 ptr = NULL
,所以存在一个内存访问漏洞
自定义模糊测试(变异)规则
lpm_aflpp_custom_mutator.h
:
#include "/home/xiaozaya/fuzz/libprotobuf-mutator/src/mutator.h"
#include "test.pb.h"
#include <bits/stdc++.h>
extern "C" size_t afl_custom_fuzz(void* data, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size);
class MyMutator : public protobuf_mutator::Mutator {
public:
friend size_t afl_custom_fuzz(void* data, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size);
}
这里创建了一个类 MyMutator
,其继承 Mutator
基类,并声明 afl_custom_fuzz
为其友元函数,这里就可以在 afl_custom_fuzz
函数中访问该函数的私有成员和保护成员
lpm_aflpp_custom_matutor.cc
:
#include "lpm_aflpp_custom_mutator.h"
using namespace std;
using namespace test;
string ProtoToData(const TestMessage& tm) {
stringstream all;
const auto& xx = tm.x();
const auto& ss = tm.s();
all.write((const char*)&xx, sizeof(xx));
if (ss.size()) {
all.write(ss.c_str(), ss.size());
}
string res = all.str();
if (ss.size() && res.size()) {
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}
// 初始化 afl++ 的 custom_mutator 模块
extern "C" void* afl_custom_init(void* afl, unsigned int seed) {
srand(seed);
return afl;
}
extern "C" size_t afl_custom_fuzz(void* data, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size) {
static MyMutator mutator;
TestMessage input;
// 设置 a 为一个随机数
int random = rand() % 305;
input.set_x(random);
// 设置 s 为一个随机字符串
// MutateString 继承自基类 Mutator
string tmp = "";
string new_string = mutator.MutateString(tmp, 1000);
input.set_s(new_string);
const TestMessage* p = &input;
string s = ProtoToData(*p);
size_t mutated_size = s.size() <= max_size? s.size() : max_size;
uint8_t* mutated_out = new uint8_t[mutated_size+1];
memcpy(mutated_out, s.c_str(), mutated_size);
*out_buf = mutated_out;
return mutated_size;
}
extern "C" void afl_custom_deinit(void *data) {
return;
}
这里注意实现了 afl_custom_init
、afl_custom_deinit
、afl_custom_fuzz
这三个函数,其中 afl_custom_init
主要就是进行一些初始化工作,这里仅仅设置下随机数种子,然后直接返回 afl
参数;afl_custom_deinit
里面的逻辑是变异完成后需要做的一些工作,这里我们并不需要干些什么,所以直接返回;afl_custom_fuzz
就是我们自定义的变异逻辑,这里比较简单,主要就是将 x
设置为一个随机数,将 s
设置为一个随机字符串。
将 lpm_aflpp_custom_mutator.cc
编译成动态库 lpm_aflpp_custom_mutator.so
:
clang++ -fPIC -c lpm_aflpp_custom_mutator.cc test.pb.cc \
-I/home/xiaozaya/fuzz/libprotobuf-mutator/build/external.protobuf/include/ \
-I/home/xiaozaya/fuzz/libprotobuf-mutator/
clang++ -shared -Wall -O3 -o lpm_aflpp_custom_mutator.so \
test.pb.o lpm_aflpp_custom_mutator.o \
/home/xiaozaya/fuzz/libprotobuf-mutator/build/src/libprotobuf-mutator.so
插桩编译 vuln.c
:
../AFLplusplus/afl-gcc -o vuln vuln.c
准备一些种子:
启动 fuzz
:
LD_LIBRARY_PATH=/home/xiaozaya/fuzz/libprotobuf-mutator/build/src \
AFL_CUSTOM_MUTATOR_ONLY=1 AFL_CUSTOM_MUTATOR_LIBRARY=./lpm_aflpp_custom_mutator.so \
AFL_SKIP_CPUFREQ=1 \
../AFLplusplus/afl-fuzz -i ./in -o ./out ./vuln
结果如下:
然后很快就能跑出来这两个洞:一个 \x02
一个 \xe8
Handling input from AFL++ in our custom mutator
这个实验主要就是为对变异的数据进行后处理,即注册一个 PostProcessor
函数。test.proto
和 vuln.c
与上一个实验一样。
lpm_aflpp_custom_mutator_input.h
:
#include "/home/xiaozaya/fuzz/libprotobuf-mutator/src/mutator.h"
#include "test.pb.h"
#include <bits/stdc++.h>
extern "C" size_t afl_custom_fuzz(void* data, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size);
class MyMutator : public protobuf_mutator::Mutator {
public:
friend size_t afl_custom_fuzz(void* data, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size);
}
lpm_aflpp_custom_mutator_input.cc
:
#include "lpm_aflpp_custom_mutator_input.h"
using namespace std;
using namespace test;
string ProtoToData(const TestMessage& tm) {
stringstream all;
const auto& xx = tm.x();
const auto& ss = tm.s();
all.write((const char*)&xx, sizeof(xx));
if (ss.size()) {
all.write(ss.c_str(), ss.size());
}
string res = all.str();
if (ss.size() && res.size()) {
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}
// 初始化 afl++ 的 custom_mutator 模块
extern "C" void* afl_custom_init(void* afl, unsigned int seed) {
MyMutator* mutator = new MyMutator();
// 注册 PostProcessor
mutator->RegisterPostProcessor(
TestMessage::descriptor(),
[](google::protobuf::Message* message, unsigned int seed){
TestMessage* t = static_cast<TestMessage*>(message);
t->set_x(rand());
}
);
// 设置随机种子
srand(seed);
// 注意这里返回的是 mutator 而不是 afl
// 如果直接返回 afl,其是没有注册 PostProcessor 的
return mutator;
}
extern "C" size_t afl_custom_fuzz(MyMutator* mutator, uint8_t* buf,
size_t buf_size, uint8_t** out_buf, uint8_t* add_buf,
size_t add_buf_size, size_t max_size) {
TestMessage input;
// 检测是否能够正常解析 proto 结果缓冲区
bool parse_ok = input.ParseFromArray(buf, buf_size);
if (!parse_ok) {
static uint8_t* dummy = new uint8_t[10];
*out_buf = dummy;
return 0;
}
// 确保突变后的数据长度小于 max_size
mutator->Mutate(&input, max_size);
const TestMessage* p = &input;
string s = ProtoToData(*p);
size_t mutated_size = s.size() <= max_size? s.size() : max_size;
uint8_t* mutated_out = new uint8_t[mutated_size+1];
memcpy(mutated_out, s.c_str(), mutated_size);
*out_buf = mutated_out;
return mutated_size;
}
extern "C" void afl_custom_deinit(void *data) {
return;
}
将其编译成动态库:
clang++ -fPIC -c lpm_aflpp_custom_mutator_input.cc test.pb.cc \
-I/home/xiaozaya/fuzz/libprotobuf-mutator/build/external.protobuf/include/ \
-I/home/xiaozaya/fuzz/libprotobuf-mutator/
clang++ -shared -Wall -O3 -o lpm_aflpp_custom_mutator_input.so \
test.pb.o lpm_aflpp_custom_mutator_input.o \
/home/xiaozaya/fuzz/libprotobuf-mutator/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a \
/home/xiaozaya/fuzz/libprotobuf-mutator/build/src/libprotobuf-mutator.a \
/home/xiaozaya/fuzz/libprotobuf-mutator/build/external.protobuf/lib/libprotobufd.a
开始 fuzz
:
AFL_DISABLE_TRIM=1 AFL_CUSTOM_MUTATOR_ONLY=1 \
AFL_CUSTOM_MUTATOR_LIBRARY=./lpm_aflpp_custom_mutator_input.so \
AFL_SKIP_CPUFREQ=1 \
../AFLplusplus/afl-fuzz -i in -o out ./vuln
代码存在问题,效果很差
参考
https://hollk.blog.csdn.net/article/details/124784929(收费的)