libprotobuf-mutator fuzzing learning

前言

本文章为笔者学习 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
首先需要明确该函数的功能:

  • 需要用 LibFuzzerprotobuf 中的数据进行变异,而 libprotobuf-matutorlibfuzzer_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-mutatorprotobuf 数据进行变异,然后将其转换为字节流,最后交给 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.alibprotobuf-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 其是随意变化的,那么如果我们的程序必须要求 sFUZZ 或者 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-matutorprotobuf 的数据变异完成后,会调用该函数,那么我们就可以在将输入传入测试目标前对 s 字段做限制。

这里来看下是如果注册 PostProcessor 的:函数原型也是在 libfuzzer_macro.h 文件中

void RegisterPostProcessor(
    const protobuf::Descriptor* desc,
    std::function<void(protobuf::Message* message, unsigned int seed)>
        callback);
  • 一参:protobufmessage 描述符,调研 descriptor() 函数即可
  • 二参:回调函数,即 PostProcessor
    • 一参:protobufmessage 结构元素
    • 二参:伪随机数 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_initafl_custom_deinitafl_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.protovuln.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(收费的)

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值