Custom Mutator Fuzz

本文最后更新于:2023年10月24日 晚上

Custom Mutator Fuzz

笔者这部分学习主要是参考hollk师傅以及github上的教程。

这里的自定义突变讲的是跟protobuf结合,输入符合格式的二进制数据,然后转换成符合target函数的参数形式,实现自定义变异。

LibFuzzer

定义的protobuf如下:

1
2
3
4
5
6
syntax = "proto2";

message TEST {
required uint32 a = 1;
required string b = 2;
}

使用protoc ./test.proto --cpp_out=./转换为适用于cpp的库文件。

target函数如下:

1
2
3
4
5
6
7
8
9
#include <stdint.h>
#include <stddef.h>

extern "C" int FuzzTEST(const uint8_t *data, size_t size) {
if(data[0] == '\x01') {
__builtin_trap();
}
return 0;
}

目标函数很简单,只要第一个参数data第一个字节为 ‘\01’ 即可触发crash。

这里使用如下思路编写libfuzz.cc

  1. 编写字节流转字符串流的函数
  2. 使用libprotobuf-mutator提供的api进行变异
  3. 将符合target函数的数据喂给target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "test.pb.h"

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;

// 字节流转字符串流,使用protobuf进行自定义突变的测试函数都需要此函数
std::string ProtoToData(const TEST &test_proto) {
std::stringstream all;
const auto &aa = test_proto.a();
const auto &bb = test_proto.b();
all.write((const char*)&aa, sizeof(aa));
if(bb.size() != 0) {
all.write(bb.c_str(), bb.size());
}

std::string res = all.str();
if (bb.size() != 0 && res.size() != 0) {
// set PROTO_FUZZER_DUMP_PATH env to dump the serialized 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); // our customized fuzzing function

DEFINE_PROTO_FUZZER(const TEST &test_proto) {
auto s = ProtoToData(test_proto); // convert protobuf to raw data
FuzzTEST((const uint8_t*)s.data(), s.size()); // fuzz the function
}

这里通过TEST &test_proto限制了数据类型,以实现我们自定义类型突变。然后DEFINE_PROTO_FUZZER启动突变。

编写Makefile文件进行编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TARGET=libfuzz
CXX=clang++
CXXFLAGS=-g -fsanitize=fuzzer,address
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/tools/libprotobuf-mutator/build/external.protobuf
LPM_DIR=$(HOME)/tools/libprotobuf-mutator
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a
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)

# for testing libprotobuf + libfuzzer
# compile harness first
# then link lpm_libfuzz with harness.o & static libraries
harness.o: harness.cc
$(CXX) $(CXXFLAGS) -c $(DFUZZ) $<

$(TARGET): harness.o $(TARGET).cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(PB_SRC) $(LPM_LIB) $(PROTOBUF_LIB) $(INC)

.PHONY: clean
clean:
rm $(TARGET) *.o

之后编译,即可得到目标函数。

img

直接运行libfuzz

img

几乎是一瞬间就跑出了crash,效率很高。

另外,我们注意到,上述crash的条件是第一个参数的起始字节为’\x01’即可,也就意味着我们上述定义的b字段没有其他作用,那么我们就可以用来展示自定义效果。

修改上述libfuzz.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "test.pb.h"

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;

std::string ProtoToData(const TEST &test_proto) {
std::stringstream all;
const auto &aa = test_proto.a();
const auto &bb = test_proto.b();
all.write((const char*)&aa, sizeof(aa));
if(bb.size() != 0) {
all.write(bb.c_str(), bb.size());
}

std::string res = all.str();
if (bb.size() != 0 && res.size() != 0) {
// set PROTO_FUZZER_DUMP_PATH env to dump the serialized 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); // our customized fuzzing function
bool hasRegister = false;

DEFINE_PROTO_FUZZER(const TEST &test_proto) {
/* Register post processor with our custom mutator method */
if(!hasRegister) {
protobuf_mutator::libfuzzer::RegisterPostProcessor(
TEST::descriptor(),
[](google::protobuf::Message* message, unsigned int seed) {
TEST *t = static_cast<TEST *>(message);
/* test.b will only be "FUCK" or "SHIT" */
if (seed % 2) {
t->set_b("FUCK");
}
else {
t->set_b("SHIT");
}
}
);
hasRegister = true;
return;
}

auto s = ProtoToData(test_proto);
FuzzTEST((const uint8_t*)s.data(), s.size());
}

这里我们调用了RegisterPostProcessor这个api实现自定义数据。

这个函数原型如下:

1
2
3
4
void RegisterPostProcessor(
const protobuf::Descriptor* desc,
std::function<void(protobuf::Message* message, unsigned int seed)>
callback);

这个api的功能是:为message注册了一个handle,每次突变后调用handle处理变异数据。这里呢,是将变异后的数据b设置为“FUCK”或者“SHIT”。

img

这里成功将b设置为我们自定义的数据。

AFL++

针对afl++的自定义突变主要是对afl++提供的api进行自定义。实现的效果如下图所示:(这里盗用了hollk师傅的图)。

img

这张图描述的很明显了,结合libprotobuf-mutator与afl++自定义的api生成自定义突变库*.so文件,然后运行afl++直接运行afl++即可实现自定义突变。

afl++提供的api如下:AFLplusplus/docs/custom_mutators.md at stable · AFLplusplus/AFLplusplus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void *afl_custom_init(afl_state_t *afl, unsigned int seed);
unsigned int afl_custom_fuzz_count(void *data, const unsigned char *buf, size_t buf_size);
void afl_custom_splice_optout(void *data);
size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, unsigned char *add_buf, size_t add_buf_size, size_t max_size);
const char *afl_custom_describe(void *data, size_t max_description_len);
size_t afl_custom_post_process(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf);
int afl_custom_init_trim(void *data, unsigned char *buf, size_t buf_size);
size_t afl_custom_trim(void *data, unsigned char **out_buf);
int afl_custom_post_trim(void *data, unsigned char success);
size_t afl_custom_havoc_mutation(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, size_t max_size);
unsigned char afl_custom_havoc_mutation_probability(void *data);
unsigned char afl_custom_queue_get(void *data, const unsigned char *filename);
void (*afl_custom_fuzz_send)(void *data, const u8 *buf, size_t buf_size);
u8 afl_custom_queue_new_entry(void *data, const unsigned char *filename_new_queue, const unsigned int *filename_orig_queue);
const char* afl_custom_introspection(my_mutator_t *data);
void afl_custom_deinit(void *data);

每个api的功能hollk师傅的博客,以及官方文档都阐述的很明白了。

这里我们的目标函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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;
}

这个函数很简单,当str[0]为’\x02’或者str[0]为’\xe8’时,会触发非法内存访问的crash。

afl++自定义突变部分如下:

  1. 一开始还是实现了一个数据转化函数。
  2. 对afl++提供的api进行自定义。
  3. 同样的,由于第二个参数b没用到,可以实现自定义突变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "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);

// 自定义了MyMutator类,将afl_custom_fuzz声明为自定义友元函数,使得afl_custom_fuzz可以访问protobuf_mutator::Mutator的api实现简单突变
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);
};

using std::cin;
using std::cout;
using std::endl;

static const char *commands[] = {
"GET",
"PUT",
"DEL",
};

std::string ProtoToData(const TEST &test_proto) {
std::stringstream all;
const auto &aa = test_proto.a();
const auto &bb = test_proto.b();
all.write((const char*)&aa, sizeof(aa));
if(bb.size() != 0) {
all.write(bb.c_str(), bb.size());
}

std::string res = all.str();
if (bb.size() != 0 && res.size() != 0) {
// set PROTO_FUZZER_DUMP_PATH env to dump the serialized protobuf
if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
std::ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}

// 初始化afl++自定义突变模块
extern "C" void *afl_custom_init(void *afl, unsigned int seed) {
srand(seed);
return afl;
}

// 自定义突变规则
extern "C" size_t afl_custom_fuzz(void *data, // afl state
uint8_t *buf, size_t buf_size, // input data to be mutated
uint8_t **out_buf, // output buffer
uint8_t *add_buf, size_t add_buf_size, // add_buf can be NULL
size_t max_size) {

// Here we implement our own custom mutator
static MyMutator mutator;
TEST input;
// mutate input.a ( integer )
int id = rand() % 305;
input.set_a(id);
// mutate input.b ( string ) 自定义为commands内容
std::string tmp = commands[rand() % 3];
input.set_b(tmp);
// convert input from TEST to raw data, and copy to mutated_out
const TEST *p = &input;
std::string s = ProtoToData(*p); // convert TEST to raw data
// Copy the raw data to output buffer
size_t mutated_size = s.size() <= max_size ? s.size() : max_size; // check if raw data's size is larger than 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;
}

编写的Makefile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TARGET=lpm_aflpp_custom_mutator
CXX=clang++
AFLCC=$(HOME)/tools/AFLplusplus/afl-gcc
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/tools/libprotobuf-mutator/build/external.protobuf
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a

LPM_DIR=$(HOME)/tools/libprotobuf-mutator
LPM_LIB=$(LPM_DIR)/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a $(LPM_DIR)/build/src/libprotobuf-mutator.a
# LPM_LIB=$(LPM_DIR)/build/src/libprotobuf-mutator.so
AFLPLUSPLUS_LIB = $(HOME)/tools/AFLplusplus/include
INC=-I$(PROTOBUF_DIR)/include -I$(LPM_DIR) -I$(AFLPLUSPLUS_LIB)

all: $(TARGET).so

$(TARGET).so: $(TARGET).cc $(PB_SRC)
$(CXX) -fPIC -c $^ $(INC)
$(CXX) -shared -Wall -O3 -o $@ *.o $(LPM_LIB) $(PROTOBUF_LIB)

vuln: vuln.c
$(AFLCC) -o $@ $^

.PHONY: clean
clean:
rm *.so *.o vuln

执行make。

1
2
make all	# 编译lpm_aflpp_custom_mutator.so文件
make vuln # 编译target程序

img

编译成功,接下来我们直接运行afl-fuzz进行测试。

首先,我们需要生成种子文件。这里注意种子内容格式需要与protobuf定义的格式一致。

1
2
a: 7
b: "aaaa"

定义运行afl-fuzz的shell脚本。

1
2
3
4
5
6
#!/bin/sh

AFL_CUSTOM_MUTATOR_ONLY=1 \
AFL_CUSTOM_MUTATOR_LIBRARY=/home/p2lst/Documents/fuzz/protobuf/libprotobuf-mutator_fuzzing_learning/4_libprotobuf_aflpp_custom_mutator/lpm_aflpp_custom_mutator.so \
AFL_SKIP_CPUFREQ=1 \
$HOME/tools/AFLplusplus/afl-fuzz -i ./in -o ./out ./vuln

img

几乎是一瞬间就跑出了两个crashes。

我们逐个查看crash。

img

与预期相符合。

通过上述示例,我们注意到上述mutator根本就没有处理我们输入的数据,它所做的仅仅是随机生成新的TEST测试用例而已。

接下来,我们要做的就是处理来自AFL++的输入数据,将其转换为TEST原型,并使用libprotobuf-mutator对原型进行变异。

这里我们直接修改lpm_aflpp_custom_mutator_input.cc即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "libprotobuf-mutator/src/mutator.h"
#include "test.pb.h"
#include "afl-fuzz.h"

#include <bits/stdc++.h>

class MyMutator : public protobuf_mutator::Mutator {
public:
unsigned char *mutator_out;
};

using std::cin;
using std::cout;
using std::endl;

std::string ProtoToData(const TEST &test_proto) {
std::stringstream all;
const auto &aa = test_proto.a();
const auto &bb = test_proto.b();
all.write((const char*)&aa, sizeof(aa));
if(bb.size() != 0) {
all.write(bb.c_str(), bb.size());
}

std::string res = all.str();
if (bb.size() != 0 && res.size() != 0) {
// set PROTO_FUZZER_DUMP_PATH env to dump the serialized 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" MyMutator *afl_custom_init(void *afl, unsigned int seed) {
MyMutator *mutator = new MyMutator();

mutator->mutator_out = new uint8_t[MAX_FILE];
if(mutator->mutator_out == nullptr)
{
perror("new mutator_out");
exit(-1);
}

bool hasRegister = false;
if(!hasRegister) {
protobuf_mutator::libfuzzer::RegisterPostProcessor(
TEST::descriptor(),
[](google::protobuf::Message* message, unsigned int seed) {
TEST *t = static_cast<TEST *>(message);
/* test.b will only be "FUCK" or "SHIT" */
t->set_a(rand() % 256);
if (seed % 2) {
t->set_b("FUCK");
}
else {
t->set_b("SHIT");
}
}
);
hasRegister = true;
return;
}

srand(seed);
return mutator;
}

extern "C" size_t afl_custom_fuzz(MyMutator *mutator, // return value from afl_custom_init
uint8_t *buf, size_t buf_size, // input data to be mutated
uint8_t **out_buf, // output buffer
uint8_t *add_buf, size_t add_buf_size, // add_buf can be NULL
size_t max_size) {

TEST input;
// 解析buf数组内容为input类型,从输入获取数据
bool parse_ok = input.ParseFromArray(buf, buf_size);
if(!parse_ok) {
// Invalid serialize protobuf data. Don't mutate.
// Return a dummy buffer. Also mutated_size = 0
static uint8_t *dummy = new uint8_t[10]; // dummy buffer with no data
*out_buf = dummy;
return 0;
}
// mutate the protobuf
mutator->Mutate(&input, max_size);

// Convert protobuf to raw data
const TEST *p = &input;
std::string s = ProtoToData(*p);
// Copy to a new buffer ( mutated_out )
size_t mutated_size = s.size() <= max_size ? s.size() : max_size; // check if raw data's size is larger than max_size
memcpy(mutator->mutator_out, s.c_str(), mutated_size); // copy the mutated data
// Assign the mutated data and return mutated_size
*out_buf = mutator->mutator_out;

return mutated_size;
}


extern "C" void afl_custom_deinit(MyMutator *data) {
// Honestly I don't know what to do with this...

delete []data->mutator_out;

return;
}

在这个示例中,我们使用ParseFromArray从输入数组中解析出TEST结构,并对解析出的数据进行变异。同时,使用RegisterPostProcessor为变异后的数据进行针对性的设置。同样的,由于第二个参数b没什么用,我们可以自定义为特殊字段,这里我们也设置了字段a,因为libprotobuf_mutator提供的变异策略太慢了。

这里需要注意的是,种子文件不能随便写了,必须是protobuf格式,使用程序生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "test.pb.h"
#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;

int main(int argc, char *argv[])
{
TEST t;
t.set_a(100);
t.set_b("aaaa");
std::string s;
t.SerializeToString(&s);

std::ofstream out("./in/ii");
out << s;
out.close();

return 0;
}

这里很简单,就是将符合protobuf的数据写入文件。

然后我们开始执行fuzz。

img

也是非常快的产生了两个crashes。

img

这里的crashes也符合我们的预期。

参考文档

https://blog.csdn.net/qq_41202237/category_11470526.html?utm_source=BWXQ_bottombtn&spm=1001.2101.3001.4225

https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/custom_mutators.md

https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning

https://kiprey.github.io/2021/09/protobuf_ctf_fuzz/

https://github.com/Kiprey/protobuf_ctf_fuzz/tree/main

https://protobuf.dev/reference/cpp/api-docs/google.protobuf.message/


Custom Mutator Fuzz
http://example.com/2023/10/24/Custom Mutator Fuzz/
作者
l1s00t
发布于
2023年10月24日
更新于
2023年10月24日
许可协议