├── .gitignore ├── README.md ├── babyheap ├── build.sh ├── kp_src ├── dumper.cc ├── mutator.cc └── out.proto ├── pre_run.sh └── workdir └── fuzz_input └── input1.bin /.gitignore: -------------------------------------------------------------------------------- 1 | AFLplusplus/ 2 | afl-libprotobuf-mutator/ 3 | workdir/fuzz_output* 4 | target 5 | .vscode/ 6 | .gdb* 7 | *.bin 8 | 9 | **/*.o 10 | **/*.bak 11 | **/*.txt 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # protobuf_ctf_fuzz 2 | 3 | ## 一、简介 4 | 5 | 通过 protobuf + AFLplusplus 进行传统 ctf fuzz。 6 | 7 | 请参考[这篇博文](https://kiprey.github.io/2021/09/protobuf_ctf_fuzz/) 来了解具体细节。 8 | 9 | > 注意:**该程序为学习时所编写,运行可能不太稳定**,如果在使用过程中存在问题,请优先尝试使用 GDB 调试或审计代码来解决。 10 | > 11 | > 最好在**有 AFL 研究基础**的情况下去玩耍该项目。 12 | 13 | 介于 AFL-fuzz++ 的快速迭代,新版的 AFL-fuzz++ 可能对该项目不支持。请使用 **2021年10月左右** 版本的 AFL-fuzz++ 14 | 15 | ## 二、构建与运行 16 | 17 | 构建很简单,只需一行命令即可: 18 | 19 | > **网络一定一定一定要好!!!** 20 | > 21 | > 否则还是一条一条的粘贴 ./build.sh 中的命令运行,确保每条命令都成功吧(笑) 22 | 23 | ```bash 24 | sudo ./build.sh 25 | ``` 26 | 27 | 构建好后,将自定义 protobuf 放入 `kp_src/out.proto` 中,同时修改对应的 `kp_src/mutate.cc` 以及 `kp_src/dump.cc`,最后执行以下脚本以更新被修改的部分: 28 | 29 | ```bash 30 | source ./pre_run.sh 31 | ``` 32 | 33 | > **每次修改**完 `kp_src/` 文件夹下的代码后,或者新开一个终端准备跑 fuzz 前,均需执行`./pre_run.sh`。 34 | 35 | 之后自己准备 workdir 以及 fuzz_input,然后跑以下命令以启动 fuzz: 36 | 37 | > 语料的准备,或许可以修改 `kp_src/dumper.cc` 并借助 `afl-libprotobuf-mutator/dumper` 来生成。 38 | 39 | ```bash 40 | # 此时工作目录为:protobuf_ctf_fuzz/ 41 | AFLplusplus/afl-fuzz -i workdir/fuzz_input -o workdir/fuzz_output -Q -- 42 | ``` 43 | 44 | ## 三、例子 45 | 46 | 根目录下的 `babyheap` 文件作为例子用的 CTF 题目,其 protobuf 描述以及对应的 dumper 和 mutate 代码已经预置于 `kp_src`中。 47 | 48 | ## 四、可改进的地方 49 | 50 | 1. libprotobuf-mutator 的变异效果一般,最好手动改进一下 51 | 2. 需要实现一下 trim 逻辑,防止样例爆炸 52 | -------------------------------------------------------------------------------- /babyheap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/protobuf_ctf_fuzz/9c68621f0617c8b2b00ec7b7b3bdf516590a30d3/babyheap -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 安装依赖 4 | git clone git@github.com:AFLplusplus/AFLplusplus.git 5 | git clone git@github.com:thebabush/afl-libprotobuf-mutator.git 6 | 7 | sudo apt-get update 8 | sudo apt-get install -y ninja-build build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools 9 | # try to install llvm 11 and install the distro default if that fails 10 | # sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang 11 | # sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev 12 | 13 | # 复制修改的代码至 libprotobuf 14 | cp kp_src/out.proto afl-libprotobuf-mutator/gen/out.proto 15 | cp kp_src/dumper.cc afl-libprotobuf-mutator/src/dumper.cc 16 | cp kp_src/mutator.cc afl-libprotobuf-mutator/src/mutator.cc 17 | 18 | # 构建 libprotobuf 19 | pushd afl-libprotobuf-mutator 20 | chmod +x build.sh 21 | ./build.sh 22 | make 23 | popd 24 | 25 | # 构建 AFL 26 | pushd AFLplusplus 27 | make distrib 28 | popd 29 | -------------------------------------------------------------------------------- /kp_src/dumper.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" 8 | 9 | #include "gen/out.pb.h" 10 | 11 | #include "mutator.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | inline std::string slurp(const std::string& path) { 18 | std::ostringstream buf; 19 | std::ifstream input (path.c_str()); 20 | buf << input.rdbuf(); 21 | return buf.str(); 22 | } 23 | 24 | extern "C" { 25 | void *afl_custom_init(void *afl, unsigned int seed); 26 | size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, 27 | unsigned char *add_buf, size_t add_buf_size, size_t max_size); 28 | size_t afl_custom_post_process(void* data, uint8_t *buf, size_t buf_size, uint8_t **out_buf); 29 | void afl_custom_deinit(void *data); 30 | } 31 | 32 | int main(int argc, char *argv[]) { 33 | menuctf::ChoiceList msg; 34 | 35 | if (argc == 2) { 36 | std::string data = slurp(argv[1]); 37 | if(!protobuf_mutator::libfuzzer::LoadProtoInput(true, (const uint8_t *)data.c_str(), data.size(), &msg)) { 38 | printf("[afl_custom_post_process] LoadProtoInput Error\n"); 39 | abort(); 40 | } 41 | 42 | // 测试变异逻辑 43 | int rand_fd; 44 | if((rand_fd = open("/dev/random", O_RDONLY)) < 0) 45 | abort(); 46 | unsigned int seed; 47 | read(rand_fd, &seed, sizeof(seed)); 48 | close(rand_fd); 49 | 50 | void* init_data = afl_custom_init(nullptr, seed); 51 | for(int i = 0; i < 30; i++) { 52 | uint8_t *out_buf = nullptr; 53 | size_t new_size = afl_custom_fuzz(init_data, (uint8_t*)data.c_str(), data.size(), 54 | &out_buf, nullptr, 0, data.size() + 100); 55 | uint8_t *new_str = nullptr; 56 | size_t new_str_size = afl_custom_post_process(init_data, out_buf, new_size, &new_str); 57 | std::string new_str_str((char*)new_str, new_str_size); 58 | std::cout << i << " =============== " << std::endl; 59 | std::cout << new_str_str << std::endl << std::endl; 60 | } 61 | afl_custom_deinit(init_data); 62 | } else { 63 | // alloc 12 "[menuctf::AllocChoice]" 64 | { 65 | auto choice = new menuctf::AllocChoice(); 66 | choice->set_size(12); 67 | choice->set_content("[menuctf::AllocChoice]"); 68 | 69 | msg.add_choice()->set_allocated_alloc_choice(choice); 70 | } 71 | 72 | // update 2 20 "[menuctf::UpdateChoice]" 73 | { 74 | auto choice = new menuctf::UpdateChoice(); 75 | choice->set_idx(2); 76 | choice->set_size(20); 77 | choice->set_content("[menuctf::UpdateChoice]"); 78 | 79 | msg.add_choice()->set_allocated_update_choice(choice); 80 | } 81 | 82 | // DeleteChoice 3 83 | { 84 | auto choice = new menuctf::DeleteChoice(); 85 | choice->set_idx(3); 86 | 87 | msg.add_choice()->set_allocated_delete_choice(choice); 88 | } 89 | 90 | // ViewChoice 4 91 | { 92 | auto choice = new menuctf::ViewChoice(); 93 | choice->set_idx(4); 94 | 95 | msg.add_choice()->set_allocated_view_choice(choice); 96 | } 97 | 98 | // ExitChoice 99 | { 100 | auto choice = new menuctf::ExitChoice(); 101 | 102 | msg.add_choice()->set_allocated_exit_choice(choice); 103 | } 104 | 105 | std::ofstream output_file("output.bin", std::ios::binary); 106 | // 这里保存的 Serialize 必须使用 Partial 保存, 107 | msg.SerializePartialToOstream(&output_file); 108 | output_file.close(); 109 | } 110 | 111 | // std::cout << "msg DebugString: " << msg.DebugString() << std::endl; 112 | std::stringstream stream; 113 | ProtoToDataHelper(stream, msg); 114 | std::cout << stream.str() << std::endl; 115 | 116 | return 0; 117 | } 118 | -------------------------------------------------------------------------------- /kp_src/mutator.cc: -------------------------------------------------------------------------------- 1 | #include "mutator.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "google/protobuf/descriptor.h" 7 | #include "google/protobuf/descriptor.pb.h" 8 | #include "google/protobuf/reflection.h" 9 | 10 | #include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" 11 | 12 | #include "gen/out.pb.h" 13 | #include "libprotobuf-mutator/src/mutator.h" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | void ProtoToDataHelper(std::stringstream &out, const google::protobuf::Message &msg) { 21 | const google::protobuf::Descriptor *desc = msg.GetDescriptor(); 22 | const google::protobuf::Reflection *refl = msg.GetReflection(); 23 | 24 | const unsigned fields = desc->field_count(); 25 | // std::cout << msg.DebugString() << std::endl; 26 | for (unsigned i = 0; i < fields; ++i) { 27 | const google::protobuf::FieldDescriptor *field = desc->field(i); 28 | 29 | // 对于单个 choice 30 | if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { 31 | // 如果当前是 choice list 32 | if (field->is_repeated()) { 33 | const google::protobuf::RepeatedFieldRef &ptr = refl->GetRepeatedFieldRef(msg, field); 34 | // 将每个 choice 打出来 35 | for (const auto &child : ptr) { 36 | ProtoToDataHelper(out, child); 37 | out << "\n"; 38 | } 39 | // 如果当前是某个子 choice 40 | } else if (refl->HasField(msg, field)) { 41 | // 获取到某一个 choice 的 message 42 | const google::protobuf::Message &child = refl->GetMessage(msg, field); 43 | 44 | // 输出其 choice ID 45 | std::string choice_typename = child.GetDescriptor()->name(); 46 | if(choice_typename == "AllocChoice") 47 | out << "1 "; 48 | else if(choice_typename == "UpdateChoice") 49 | out << "2 "; 50 | else if(choice_typename == "DeleteChoice") 51 | out << "3 "; 52 | else if(choice_typename == "ViewChoice") 53 | out << "4 "; 54 | else if(choice_typename == "ExitChoice") 55 | out << "5 "; 56 | else 57 | abort(); 58 | 59 | // 输出剩余的 field 60 | ProtoToDataHelper(out, child); 61 | } 62 | } 63 | // 对于单个 field 64 | else if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT32) { 65 | out << refl->GetInt32(msg, field); 66 | if(i < fields - 1) 67 | out << " "; 68 | } 69 | else if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_STRING) { 70 | out << refl->GetString(msg, field); 71 | if(i < fields - 1) 72 | out << " "; 73 | } 74 | else { 75 | abort(); 76 | } 77 | 78 | } 79 | } 80 | 81 | // Apparently the textual generator kinda breaks? 82 | DEFINE_BINARY_PROTO_FUZZER(const menuctf::ChoiceList &root) { 83 | std::stringstream stream; 84 | ProtoToDataHelper(stream, root); 85 | std::string data = stream.str(); 86 | std::replace(data.begin(), data.end(), '\n', '|'); 87 | puts(("ProtoToDataHelper: " + data).c_str()); 88 | puts("===================================================================================================="); 89 | // std::string dbg = root.DebugString(); 90 | // std::replace(dbg.begin(), dbg.end(), '\n', ' '); 91 | } 92 | 93 | // AFLPlusPlus interface 94 | extern "C" { 95 | void *afl_custom_init(void *afl, unsigned int seed) { 96 | #pragma unused (afl) 97 | 98 | auto mutator = new protobuf_mutator::Mutator(); 99 | mutator->Seed(seed); 100 | return mutator; 101 | } 102 | 103 | void afl_custom_deinit(void *data) { 104 | protobuf_mutator::Mutator *mutator = (protobuf_mutator::Mutator*)data; 105 | delete mutator; 106 | } 107 | 108 | // afl_custom_fuzz 109 | size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, 110 | unsigned char *add_buf, size_t add_buf_size, size_t max_size) { 111 | #pragma unused (add_buf) 112 | #pragma unused (add_buf_size) 113 | 114 | static uint8_t *saved_buf = nullptr; 115 | 116 | assert(buf_size <= max_size); 117 | 118 | uint8_t *new_buf = (uint8_t *) realloc((void *)saved_buf, max_size); 119 | if (!new_buf) 120 | abort(); 121 | saved_buf = new_buf; 122 | 123 | // memcpy(new_buf, buf, buf_size); 124 | 125 | protobuf_mutator::Mutator *mutator = (protobuf_mutator::Mutator*)data; 126 | 127 | menuctf::ChoiceList msg; 128 | if (!protobuf_mutator::libfuzzer::LoadProtoInput(true, buf, buf_size, &msg)) 129 | abort(); 130 | 131 | // 不用合并两个 Message 的 CrossOver 函数,因为它的变异效果有亿点点拉胯 132 | mutator->Mutate(&msg, max_size); 133 | 134 | std::string out_str; 135 | msg.SerializePartialToString(&out_str); 136 | 137 | memcpy(new_buf, out_str.c_str(), out_str.size()); 138 | *out_buf = new_buf; 139 | return out_str.size(); 140 | } 141 | 142 | size_t afl_custom_post_process(void* data, uint8_t *buf, size_t buf_size, uint8_t **out_buf) { 143 | #pragma unused (data) 144 | // new_data is never free'd by pre_save_handler 145 | // I prefer a slow but clearer implementation for now 146 | 147 | static uint8_t *saved_buf = NULL; 148 | 149 | menuctf::ChoiceList msg; 150 | std::stringstream stream; 151 | // 如果加载成功 152 | if (protobuf_mutator::libfuzzer::LoadProtoInput(true, buf, buf_size, &msg)) 153 | ProtoToDataHelper(stream, msg); 154 | else 155 | // 必须保证成功 156 | abort(); 157 | const std::string str = stream.str(); 158 | 159 | uint8_t *new_buf = (uint8_t *) realloc((void *)saved_buf, str.size()); 160 | if (!new_buf) { 161 | *out_buf = buf; 162 | return buf_size; 163 | } 164 | *out_buf = saved_buf = new_buf; 165 | 166 | memcpy((void *)new_buf, str.c_str(), str.size()); 167 | 168 | return str.size(); 169 | } 170 | 171 | int32_t afl_custom_init_trim(void *data, uint8_t *buf, size_t buf_size) { 172 | /// NOTE: disable trim 173 | return 0; 174 | } 175 | 176 | size_t afl_custom_trim(void *data, uint8_t **out_buf) { 177 | /// NOTE: unreachable 178 | return 0; 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /kp_src/out.proto: -------------------------------------------------------------------------------- 1 | // See README.txt for information and build instructions. 2 | // 3 | // Note: START and END tags are used in comments to define sections used in 4 | // tutorials. They are not part of the syntax for Protocol Buffers. 5 | // 6 | // To get an in-depth walkthrough of this file and the related examples, see: 7 | // https://developers.google.com/protocol-buffers/docs/tutorials 8 | 9 | // [START declaration] 10 | syntax = "proto2"; 11 | package menuctf; 12 | 13 | // [END declaration] 14 | 15 | // [START messages] 16 | message AllocChoice { 17 | //required int32 choice_id = 1 [default=1]; 18 | required int32 size = 2; 19 | required bytes content = 3; 20 | } 21 | 22 | message UpdateChoice { 23 | //required int32 choice_id = 1 [default=2]; 24 | required int32 idx = 2; 25 | required int32 size = 3; 26 | required bytes content = 4; 27 | } 28 | 29 | message DeleteChoice { 30 | //required int32 choice_id = 1 [default=3]; 31 | required int32 idx = 2; 32 | } 33 | 34 | message ViewChoice { 35 | //required int32 choice_id = 1 [default=4]; 36 | required int32 idx = 2; 37 | } 38 | 39 | message ExitChoice { 40 | //required int32 choice_id = 1 [default=5]; 41 | } 42 | 43 | // Our address book file is just one of these. 44 | message ChoiceList { 45 | message Choice { 46 | oneof the_choice{ 47 | AllocChoice alloc_choice = 1; 48 | UpdateChoice update_choice = 2; 49 | DeleteChoice delete_choice = 3; 50 | ViewChoice view_choice = 4; 51 | ExitChoice exit_choice = 5; 52 | } 53 | } 54 | repeated Choice choice = 1; 55 | } 56 | // [END messages] 57 | -------------------------------------------------------------------------------- /pre_run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 复制修改的代码至 libprotobuf 3 | cp kp_src/out.proto afl-libprotobuf-mutator/gen/out.proto 4 | cp kp_src/dumper.cc afl-libprotobuf-mutator/src/dumper.cc 5 | cp kp_src/mutator.cc afl-libprotobuf-mutator/src/mutator.cc 6 | 7 | # 由于已经构建好了,因此只需 make 即可 8 | pushd afl-libprotobuf-mutator 9 | make 10 | popd 11 | 12 | # 配置环境变量 13 | export AFL_CUSTOM_MUTATOR_ONLY=1 14 | export AFL_CUSTOM_MUTATOR_LIBRARY=$(pwd)/afl-libprotobuf-mutator/libmutator.so 15 | export AFL_USE_QASAN=1 16 | 17 | # 输出信息 18 | echo '==========================================================================================================' 19 | echo 'Just prepare fuzz_input and run the command to start fuzz: ' 20 | echo ' AFLplusplus/afl-fuzz -i workdir/fuzz_input -o workdir/fuzz_output -Q -- ' 21 | echo ' ' 22 | echo 'e.g. AFLplusplus/afl-fuzz -i workdir/fuzz_input -o workdir/fuzz_output -Q -- ./babyheap ' 23 | echo '==========================================================================================================' -------------------------------------------------------------------------------- /workdir/fuzz_input/input1.bin: -------------------------------------------------------------------------------- 1 | 2 |  3 |  [menuctf::AllocChoice] 4 | "[menuctf::UpdateChoice] 5 |  6 | " 7 | * --------------------------------------------------------------------------------