Repository: h311d1n3r/Cerberus Branch: main Commit: c31cdc500321 Files: 76 Total size: 126.2 KB Directory structure: gitextract_wdal68gi/ ├── .dockerignore ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docker/ │ ├── debian/ │ │ └── Dockerfile-10 │ ├── fedora/ │ │ └── Dockerfile-37 │ └── ubuntu/ │ ├── Dockerfile-20.04 │ └── Dockerfile-22.04 ├── src/ │ ├── algorithm/ │ │ ├── algorithm.h │ │ ├── part_hash_algorithm.cpp │ │ └── part_hash_algorithm.h │ ├── binaries/ │ │ ├── bin_extractor.h │ │ ├── bin_handler.cpp │ │ ├── bin_handler.h │ │ ├── bin_identifier.cpp │ │ ├── bin_identifier.h │ │ ├── bin_types.h │ │ ├── extractors/ │ │ │ ├── go_extractor.cpp │ │ │ ├── go_extractor.h │ │ │ ├── libelf_extractor.cpp │ │ │ ├── libelf_extractor.h │ │ │ ├── lief_extractor.cpp │ │ │ ├── lief_extractor.h │ │ │ ├── radare_extractor.cpp │ │ │ └── radare_extractor.h │ │ ├── handlers/ │ │ │ ├── elf_handler.cpp │ │ │ ├── elf_handler.h │ │ │ ├── pe_handler.cpp │ │ │ └── pe_handler.h │ │ ├── lib/ │ │ │ ├── install/ │ │ │ │ ├── go_lib_installer.cpp │ │ │ │ ├── go_lib_installer.h │ │ │ │ ├── lib_installer.h │ │ │ │ ├── rust_lib_installer.cpp │ │ │ │ └── rust_lib_installer.h │ │ │ ├── lib_manager.cpp │ │ │ └── lib_manager.h │ │ └── pe_types.h │ ├── command/ │ │ ├── command_executor.cpp │ │ └── command_executor.h │ ├── global_defs.h │ ├── langs/ │ │ ├── lang_manager.cpp │ │ ├── lang_manager.h │ │ ├── lang_types.h │ │ ├── lib_regex.cpp │ │ └── lib_regex.h │ ├── main.cpp │ ├── types/ │ │ └── value_ordered_map.h │ ├── user/ │ │ ├── dependencies/ │ │ │ ├── dependency_manager.cpp │ │ │ └── dependency_manager.h │ │ ├── local_config.cpp │ │ ├── local_config.h │ │ ├── user_prompt.cpp │ │ └── user_prompt.h │ └── utils/ │ ├── arg_parser.cpp │ ├── arg_parser.h │ ├── config.h │ ├── convert.cpp │ ├── convert.h │ ├── file_downloader.cpp │ ├── file_downloader.h │ ├── file_operations.cpp │ ├── file_operations.h │ ├── logger.cpp │ ├── logger.h │ ├── search.cpp │ └── search.h └── test/ ├── Go/ │ └── test_1/ │ ├── result.txt │ └── src/ │ ├── go.mod │ ├── go.sum │ └── test_1.go └── Rust/ └── test_1/ ├── Cargo.toml ├── result.txt └── src/ └── main.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ cmake-build*/ .cerberus*/ build/ .idea/ radare2/ Goliath/ ================================================ FILE: .github/workflows/main.yml ================================================ name: main on: push: branches: [ main ] pull_request: branches: [ main ] workflow_dispatch: jobs: check_app: runs-on: ubuntu-22.04 steps: - name: clone_project uses: actions/checkout@v3 - name: init_submodules run: git submodule update --init --recursive - name: apt_dependencies run: sudo apt update && sudo apt upgrade && sudo apt -y install libarchive-dev libcurl4-openssl-dev zlib1g-dev libelf-dev gcc-multilib g++-multilib make cmake - name: setup_radare2 run: | git clone https://github.com/radare/radare2.git cd radare2 ./sys/install.sh - name: setup_rust uses: actions-rs/toolchain@v1 with: toolchain: stable - name: setup_go uses: actions/setup-go@v4 with: go-version: '>=1.15.0' - name: setup_python uses: actions/setup-python@v4 with: python-version: '3.10' - name: compile run: mkdir build && cd build && cmake .. && make - name: append_path run: echo "`pwd`/build" >> $GITHUB_PATH - name: compile_rust_tests run: | echo "Installing Rust 32-bit architecture" rustup target install i686-unknown-linux-gnu echo "Compiling Rust tests" for i in $(seq 1 1 `ls ./test/Rust | wc -l`) do echo "Compiling Rust test n°$i" cd test/Rust/test_$i cargo build --release cargo build --release --target=i686-unknown-linux-gnu cd ../../.. done - name: run_rust_tests_32 run: | echo "Running Rust 32bit tests" for i in $(seq 1 1 `ls ./test/Rust | wc -l`) do echo "Starting Rust test n°$i" cd test/Rust/test_$i/target/i686-unknown-linux-gnu/release cerberus ./test_$i --no-prompt -output ./test_$i-syms result=$(nm test_$i-syms) cd ../../.. expected_result=$(cat 'result.txt') IFS='\n' read -ra expected_result_arr <<< $expected_result for expected_result_line in ${expected_result_arr[@]}; do if [[ $result != *$expected_result_line* ]] then echo "Failure !" echo "Content of result :" echo $result exit 1 fi done echo "Success !" cd ../../../ done - name: run_rust_tests_64 run: | echo "Running Rust 64bit tests" for i in $(seq 1 1 `ls ./test/Rust | wc -l`) do echo "Starting Rust test n°$i" cd test/Rust/test_$i/target/release cerberus ./test_$i --no-prompt -output ./test_$i-syms result=$(nm test_$i-syms) cd ../.. expected_result=$(cat 'result.txt') IFS='\n' read -ra expected_result_arr <<< $expected_result for expected_result_line in ${expected_result_arr[@]}; do if [[ $result != *$expected_result_line* ]] then echo "Failure !" echo "Content of result :" echo $result exit 1 fi done echo "Success !" cd ../../../ done - name: compile_go_tests run: | for i in $(seq 1 1 `ls ./test/Go | wc -l`) do echo "Compiling Go test n°$i" cd test/Go/test_$i/src go get ./... go build -ldflags="-s -w" -o ../test_1 test_1.go cd ../../../.. done - name: run_go_tests run: | echo "Running Go tests" for i in $(seq 1 1 `ls ./test/Go | wc -l`) do echo "Starting Go test n°$i" cd test/Go/test_1 cerberus ./test_$i --no-prompt -output ./test_$i-syms result=$(nm test_$i-syms) expected_result=$(cat 'result.txt') IFS='\n' read -ra expected_result_arr <<< $expected_result for expected_result_line in ${expected_result_arr[@]}; do if [[ $result != *$expected_result_line* ]] then echo "Failure !" echo "Content of result :" echo $result exit 1 fi done echo "Success !" cd ../../../ done ================================================ FILE: .gitignore ================================================ cmake-build*/ .cerberus*/ build/ .idea/ radare2/ Goliath/ lab/ ================================================ FILE: .gitmodules ================================================ [submodule "lib/Goliath"] path = lib/Goliath url = https://github.com/h311d1n3r/Goliath [submodule "lib/argparse"] path = lib/argparse url = https://github.com/p-ranav/argparse [submodule "lib/lief"] path = lib/lief url = https://github.com/lief-project/LIEF [submodule "lib/gzip-hpp"] path = lib/gzip-hpp url = https://github.com/mapbox/gzip-hpp ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22) project(Cerberus) set(CMAKE_CXX_STANDARD 17) find_path(libelf.h NAMES LIBELF) # will be included statically add_subdirectory(lib/lief) add_subdirectory(lib/argparse) # will be included dynamically find_package(CURL REQUIRED) find_package(ZLIB REQUIRED) find_package(LibArchive REQUIRED) include_directories(lib/argparse/include lib/lief/include lib/gzip-hpp/include ${LIBELF} src) add_executable(Cerberus src/main.cpp src/utils/logger.h src/utils/logger.cpp src/utils/arg_parser.h src/utils/config.h src/utils/arg_parser.cpp src/global_defs.h src/langs/lang_manager.h src/langs/lang_manager.cpp src/types/value_ordered_map.h src/binaries/bin_identifier.cpp src/user/user_prompt.cpp src/utils/convert.h src/binaries/bin_handler.h src/binaries/handlers/elf_handler.h src/binaries/handlers/elf_handler.cpp src/binaries/bin_extractor.h src/binaries/bin_types.h src/binaries/extractors/lief_extractor.h src/binaries/extractors/lief_extractor.cpp src/binaries/extractors/radare_extractor.h src/binaries/extractors/radare_extractor.cpp src/binaries/handlers/pe_handler.h src/binaries/handlers/pe_handler.cpp src/binaries/bin_handler.cpp src/langs/lib_regex.h src/utils/search.h src/utils/search.cpp src/langs/lang_types.h src/langs/lib_regex.cpp src/utils/convert.cpp src/binaries/lib/lib_manager.h src/binaries/lib/lib_manager.cpp src/binaries/lib/install/lib_installer.h src/binaries/lib/install/rust_lib_installer.h src/binaries/lib/install/go_lib_installer.h src/binaries/lib/install/rust_lib_installer.cpp src/binaries/lib/install/go_lib_installer.cpp src/utils/file_downloader.h src/utils/file_downloader.cpp src/utils/file_operations.h src/utils/file_operations.cpp src/user/dependencies/dependency_manager.h src/command/command_executor.h src/command/command_executor.cpp src/user/local_config.h src/user/local_config.cpp src/user/dependencies/dependency_manager.cpp src/algorithm/part_hash_algorithm.h src/algorithm/part_hash_algorithm.cpp src/algorithm/algorithm.h src/binaries/extractors/libelf_extractor.h src/binaries/extractors/libelf_extractor.cpp src/binaries/pe_types.h src/binaries/extractors/go_extractor.h src/binaries/extractors/go_extractor.cpp) target_link_libraries(Cerberus PRIVATE argparse PRIVATE LIEF::LIEF PRIVATE CURL::libcurl PRIVATE ZLIB::ZLIB PRIVATE LibArchive::LibArchive PRIVATE uuid PRIVATE elf PRIVATE stdc++fs) set_target_properties(Cerberus PROPERTIES OUTPUT_NAME cerberus) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Aurélien Tournebise Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Cerberus ## Description ### A C++ tool to unstrip Rust and Go binaries (ELF and PE) **Cerberus** is the tool you want to use to make RUST and GO static analysis a lot easier. Based on hashing and scoring systems, it can retrieve lots of symbol names. ## How does it work ? After analyzing your ELF/PE binary to find the used libraries, **Cerberus** will download and build them. Then the tool will hash (in various ways) the functions in your file and in the libraries to make matches. ## Table of contents [Installation](#install)       [Download a release](#install_release)       [Build the tool with Docker](#install_build_docker)       [Build the tool on host](#install_build_host) [How to use ?](#how)       [Syntax](#how_syntax)       [Parameters](#how_params)       [Flags](#how_flags)       [Example](#how_example) [Warning](#warning) ## Installation ### Download a release Check the [Releases](https://github.com/h311d1n3r/Cerberus/releases/) tab on the Github project and download the latest one. ### Build the tool with Docker 1. Clone the repository `git clone https://github.com/h311d1n3r/Cerberus && cd Cerberus`. 2. Initialize git dependencies : `git submodule update --init` 3. Check the available Dockerfiles under `Cerberus/docker/{OS}`. 4. Build the docker image of your choice `docker build -f ./docker/{OS}/Dockerfile-{version} .`. 5. You can run **Cerberus** from inside the docker or extract the binary on your host. This second choice needs to install the libraries listed in [this section](#install_build_host). ### Build the tool on host 1. You need to have **libarchive**, **libcurl4-openssl**, **zlib1g**, **libelf** and the **uuid-dev** libraries installed on your system. With APT just do `apt -y install libarchive-dev libcurl4-openssl-dev zlib1g-dev libelf-dev` 2. Clone the repository `git clone https://github.com/h311d1n3r/Cerberus && cd Cerberus`. 3. Initialize git dependencies : `git submodule update --init` 4. Create the build directory `mkdir build && cd build`. 5. Run CMake to configure the project `cmake ..`. 6. Run make to compile the project `make`. ## How to use ? ### Syntax `cerberus binary [-param value] [--flag]` ### Parameters `output` -> Specifies the path for the resulting ELF file. `part_hash_len` -> Specifies the length of a `part hash`. The `part hash` of a function is just a reduction of the function with a linear pace. This technique is used to prevent fixed addresses from corrupting a standard hash. Default value : 20 `part_hash_trust` -> Specifies minimum ratio of similarity between the two hashed functions to compare. The kept function will be the one with the most matches anyway. Increasing this value will reduce the number of matched functions but speed up execution time. Default value : 0.6 `min_func_size` -> The minimum length a function must be to get analyzed. Decreasing this value will increase matches but also false positives. Default value : 10 ### Flags `help` -> Displays a help message. `debug` -> Displays outputs of commands. `no-prompt` -> Automatically skips user prompts. ### Example #### Command The following command will try to unstrip the file ./rust_example into a new ELF called ./rust_example_syms. `cerberus ./rust_example -output ./rust_example_syms` #### Result Here is a comparison of the main function in the two files using Binary Ninja :

before.png

after.png

## Warning **This software must only be used to carry out lawful experiments and I am not responsible for any breach of this rule !** ================================================ FILE: docker/debian/Dockerfile-10 ================================================ FROM debian:10 WORKDIR /root RUN apt -y update RUN apt -y upgrade RUN apt -y install g++ gcc make tar wget RUN apt -y install libarchive-dev libcurl4-openssl-dev libssl-dev zlib1g-dev libelf-dev uuid-dev RUN wget https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5.tar.gz RUN tar -xzf cmake-3.27.5.tar.gz WORKDIR cmake-3.27.5 RUN ./bootstrap RUN make RUN make install WORKDIR .. RUN mkdir -p Cerberus WORKDIR Cerberus ADD ./ ./ RUN mkdir -p build WORKDIR build RUN cmake .. RUN make ================================================ FILE: docker/fedora/Dockerfile-37 ================================================ FROM fedora:37 WORKDIR /root RUN dnf -y update RUN dnf -y upgrade RUN dnf -y install g++ gcc make cmake RUN dnf -y install libarchive-devel libcurl-devel zlib-devel elfutils-libelf-devel libuuid-devel RUN mkdir -p Cerberus WORKDIR Cerberus ADD ./ ./ RUN mkdir -p build WORKDIR build RUN cmake .. RUN make ================================================ FILE: docker/ubuntu/Dockerfile-20.04 ================================================ FROM ubuntu:20.04 WORKDIR /root RUN apt -y update RUN apt -y upgrade RUN apt -y install g++ gcc make tar wget RUN apt -y install libarchive-dev libcurl4-openssl-dev libssl-dev zlib1g-dev libelf-dev uuid-dev RUN wget https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5.tar.gz RUN tar -xzf cmake-3.27.5.tar.gz WORKDIR cmake-3.27.5 RUN ./bootstrap RUN make RUN make install WORKDIR .. RUN mkdir -p Cerberus WORKDIR Cerberus ADD ./ ./ RUN mkdir -p build WORKDIR build RUN cmake .. RUN make ================================================ FILE: docker/ubuntu/Dockerfile-22.04 ================================================ FROM ubuntu:22.04 WORKDIR /root RUN apt -y update RUN apt -y upgrade RUN apt -y install g++ gcc make cmake RUN apt -y install libarchive-dev libcurl4-openssl-dev zlib1g-dev libelf-dev uuid-dev RUN mkdir -p Cerberus WORKDIR Cerberus ADD ./ ./ RUN mkdir -p build WORKDIR build RUN cmake .. RUN make ================================================ FILE: src/algorithm/algorithm.h ================================================ #ifndef CERBERUS_ALGORITHM_H #define CERBERUS_ALGORITHM_H #include #include #include #include #include class Algorithm { protected: std::ifstream* bin_file; CONFIG* config; public: Algorithm(CONFIG* config) : config(config) { bin_file = new std::ifstream(config->binary_path, std::ios::binary); } ~Algorithm() { bin_file->close(); } virtual void process_binary(std::vector>* bin_funcs) = 0; virtual void process_lib(std::string lib_path, std::vector>* lib_funcs) = 0; virtual void post_process(std::vector>* bin_funcs) = 0; }; #endif //CERBERUS_ALGORITHM_H ================================================ FILE: src/algorithm/part_hash_algorithm.cpp ================================================ #include #include using namespace std; void PartHashAlgorithm::compute_part_hash(unique_ptr& func, ifstream* file) { char data[func->end - func->start + 1]; file->seekg(func->start); file->read(data, sizeof(data)); float pace = sizeof(data) / (float)config->part_hash_len; if(pace == 0) pace = 1; func->hash = ""; for(float i = 0; i < sizeof(data); i+=pace) { func->hash += data[(uint32_t)i]; } for(uint8_t i = func->hash.size(); i < config->part_hash_len; i++) func->hash+='\x00'; func->hash = func->hash.substr(0, config->part_hash_len); } float PartHashAlgorithm::compare_part_hashes(string hash1, string hash2) { uint8_t score = 0; for(uint8_t i = 0; i < hash1.size(); i++) { if(hash1.at(i) == hash2.at(i)) score++; } return score / (float) config->part_hash_len; } void PartHashAlgorithm::process_binary(vector>* bin_funcs) { for(unique_ptr& func : *bin_funcs) { func->name = ""; if(func->end - func->start + 1 >= config->min_func_size) this->compute_part_hash(func, bin_file); funcs_by_sz[func->end - func->start + 1].push_back(move(func)); } } void PartHashAlgorithm::process_lib(string lib_path, vector>* lib_funcs) { ifstream lib_file(lib_path, ios::binary); for (unique_ptr& lib_func : *lib_funcs) { if(!lib_func->name.size()) continue; size_t lib_func_sz = lib_func->end - lib_func->start + 1; if(lib_func_sz < config->min_func_size) continue; for(unique_ptr& func : funcs_by_sz[lib_func_sz]) { this->compute_part_hash(lib_func, &lib_file); float score = compare_part_hashes(lib_func->hash, func->hash); if (score >= config->part_hash_trust) { if(func->score < score) { func->name = lib_func->name; func->score = score; } } } } lib_file.close(); } void PartHashAlgorithm::post_process(vector>* bin_funcs) { bin_funcs->clear(); for(auto& f_pair : funcs_by_sz) { for(unique_ptr& f : f_pair.second) bin_funcs->push_back(move(f)); } } ================================================ FILE: src/algorithm/part_hash_algorithm.h ================================================ #ifndef CERBERUS_PART_HASH_ALGORITHM_H #define CERBERUS_PART_HASH_ALGORITHM_H #include #include #include class PartHashAlgorithm : public Algorithm { private: std::map>> funcs_by_sz; void compute_part_hash(std::unique_ptr& func, std::ifstream* file); float compare_part_hashes(std::string hash1, std::string hash2); public: PartHashAlgorithm(CONFIG* config) : Algorithm(config) {} void process_binary(std::vector>* bin_funcs) override; void process_lib(std::string lib_path, std::vector>* lib_funcs) override; void post_process(std::vector>* bin_funcs) override; }; #endif //CERBERUS_PART_HASH_ALGORITHM_H ================================================ FILE: src/binaries/bin_extractor.h ================================================ #ifndef CERBERUS_BIN_EXTRACTOR_H #define CERBERUS_BIN_EXTRACTOR_H #include #include #include #include class BinaryExtractor { protected: std::string bin_path; BIN_TYPE type; public: BinaryExtractor(std::string bin_path, BIN_TYPE type) : bin_path(bin_path), type(type) {}; virtual BIN_ARCH extract_arch() = 0; virtual std::vector> extract_functions(BIN_ARCH arch, size_t image_base) = 0; virtual std::vector> extract_sections() = 0; }; #endif //CERBERUS_BIN_EXTRACTOR_H ================================================ FILE: src/binaries/bin_handler.cpp ================================================ #include #include #include #include #include #include #include #include using namespace std; BIN_ARCH BinaryHandler::extract_architecture() { this->arch = lief_extractor->extract_arch(); return this->arch; } void BinaryHandler::extract_image_base() { this->image_base = lief_extractor->extract_image_base(); } size_t BinaryHandler::libs_extraction() { vector lib_regex; switch(lang) { case LANG::RUST: lib_regex = rust_lib_regex; break; case LANG::GO: lib_regex = go_lib_regex; break; default: return 0; } ifstream bin_file(this->bin_path, ios::binary); bin_file.seekg(0, ios::end); size_t bin_file_sz = bin_file.tellg(); bin_file.seekg(0); size_t bin_file_off = 0; char data[2048]; while(bin_file_off < bin_file_sz) { bin_file.seekg(bin_file_off); bin_file.read(data, sizeof(data)); for (string reg: lib_regex) { vector matches = search_regex(data, sizeof(data), reg, 256); for (string match: matches) { unique_ptr lib = lib_extract_callbacks[lang](match); if (lib) { bool exists = false; for (unique_ptr &lib2: this->libs) { if (lib->name == lib2->name && lib->version == lib2->version) { exists = true; break; } } if (!exists) this->libs.push_back(move(lib)); } } } bin_file_off += 1024; } sort(this->libs.begin(), this->libs.end(), [](const unique_ptr& a, const unique_ptr& b) { return a->name < b->name; }); bin_file.close(); return this->libs.size(); } size_t BinaryHandler::libs_installation() { unique_ptr installer; switch(lang) { case RUST: installer = make_unique(this->work_dir, this->arch, this->type); break; case GO: installer = make_unique(this->work_dir, this->arch, this->type); break; default: return 0; } if(!installer->pre_install_hook(this->libs)) return 0; size_t success_ctr = 0; for(std::unique_ptr& lib : this->libs) { fcout << "$(info)Installing $(bright_magenta:b)" << lib->name << "$$(red):$$(magenta:b)" << lib->version << "$..." << endl; if(installer->install_lib(*lib.get())) { success_ctr++; fcout << "$(success)Success !" << endl; } else fcout << "$(error)Failure..." << endl; } if(!installer->post_install_hook()) return 0; return success_ctr; } size_t BinaryHandler::get_matches_sz() { for(unique_ptr& func : this->functions) if(func->name.size()) matches_sz++; return matches_sz; } void BinaryHandler::demangle_functions() { for(unique_ptr& func : this->functions) { if(func->name.size()) func->name = demangle_function_name(func->name); } } ================================================ FILE: src/binaries/bin_handler.h ================================================ #ifndef CERBERUS_BIN_HANDLER_H #define CERBERUS_BIN_HANDLER_H #include #include #include #include #include #include #include #include #include class BinaryHandler { protected: std::string bin_path; std::string work_dir; LANG lang; Algorithm* algorithm; BIN_ARCH arch; BIN_TYPE type; size_t image_base; bool stripped; std::vector> libs; std::vector> functions; size_t matches_sz = 0; LiefExtractor* lief_extractor; RadareExtractor* radare_extractor; public: BinaryHandler(std::string bin_path, std::string work_dir, LANG lang, Algorithm* algorithm, BIN_TYPE type) : bin_path(bin_path), work_dir(work_dir), lang(lang), algorithm(algorithm), type(type) { this->lief_extractor = new LiefExtractor(bin_path, type); this->radare_extractor = new RadareExtractor(bin_path, type, *this->lief_extractor); } BIN_ARCH extract_architecture(); void extract_image_base(); virtual void strip_analysis() = 0; size_t libs_extraction(); size_t libs_installation(); virtual size_t functions_analysis() = 0; virtual void functions_matching(std::string lib_path) = 0; virtual void post_matching() = 0; size_t get_matches_sz(); void demangle_functions(); virtual bool write_output(std::string output_path) = 0; bool is_stripped() { return this->stripped; } std::vector>& get_libs() { return this->libs; } std::vector>& get_functions() { return this->functions; } }; #endif //CERBERUS_BIN_HANDLER_H ================================================ FILE: src/binaries/bin_identifier.cpp ================================================ #include #include #include using namespace std; map bin_type_names { {UNKNOWN_TYPE, "Unknown"}, {ELF, "UNIX - Executable and Linkable Format (ELF)"}, {PE, "WINDOWS - Portable Executable (PE)"} }; map bin_type_from_magic = { {std::string("\x7f")+"ELF", BIN_TYPE::ELF}, {"MZ", BIN_TYPE::PE} }; BIN_TYPE identify_binary(string input_path) { ifstream input_file(input_path, ios::binary); input_file.seekg(0, ios::end); size_t input_sz = input_file.tellg(); for (const pair p : bin_type_from_magic) { input_file.seekg(0); uint8_t magic_length = p.first.length(); if(input_sz >= magic_length) { char magic_buffer[magic_length]; input_file.read(magic_buffer, magic_length); if(!strncmp(p.first.c_str(), magic_buffer, magic_length)) { input_file.close(); return p.second; } } } input_file.close(); return UNKNOWN_TYPE; } ================================================ FILE: src/binaries/bin_identifier.h ================================================ #ifndef CERBERUS_BIN_IDENTIFIER_H #define CERBERUS_BIN_IDENTIFIER_H #include #include #include extern std::map bin_type_names; extern std::map bin_type_from_magic; BIN_TYPE identify_binary(std::string input_path); #endif //CERBERUS_BIN_IDENTIFIER_H ================================================ FILE: src/binaries/bin_types.h ================================================ #ifndef CERBERUS_BIN_TYPES_H #define CERBERUS_BIN_TYPES_H #include #include struct FUNCTION { uint64_t start; uint64_t end; std::string name; std::string hash; uint8_t score = 0; size_t ordinal = 0; }; struct SECTION { uint64_t start; uint64_t end; std::string name; }; struct LIBRARY { std::string name; std::string version; }; enum BIN_TYPE { UNKNOWN_TYPE, ELF, PE, }; enum BIN_ARCH { UNKNOWN_ARCH, X86_64, X86 }; #endif //CERBERUS_BIN_TYPES_H ================================================ FILE: src/binaries/extractors/go_extractor.cpp ================================================ #include #include #include using namespace std; BIN_ARCH GoExtractor::extract_arch() { } vector> GoExtractor::extract_functions(BIN_ARCH arch, size_t image_base) { ifstream bin_file(this->bin_path, ios::binary); LIEF::PE::Section* text_sec; if(type == BIN_TYPE::PE) text_sec = this->lief_extractor.extract_text_section(); vector> funcs; COMMAND_RESULT res; executor.execute_command("go tool nm -size "+this->bin_path, &res); if(res.code) return funcs; vector lines = split_string(res.response, '\n'); for(string line : lines) { vector vals = split_string(line, ' '); vals = filter_empty_strings(vals); if(vals.size() < 4 || !isdigit(vals.at(0).at(0))) continue; unique_ptr func = make_unique(); switch(type) { case BIN_TYPE::ELF: func->start = stoull(vals.at(0), nullptr, 16) - image_base; break; case BIN_TYPE::PE: func->start = stoull(vals.at(0), nullptr, 16) - image_base - text_sec->virtual_address() + text_sec->offset(); break; } func->end = func->start + stoull(vals.at(1)) - 1; unsigned char b; bin_file.seekg(func->end); bin_file.read((char*)&b, 1); bool found_cc = false; if(b == 0xCC) found_cc = true; while(b == 0xCC) { bin_file.seekg(func->end--); bin_file.read((char*)&b, 1); } if(found_cc) func->end++; func->name = vals.at(3); if(!func->name.find("$f64") || !func->name.find("$f32")) func->name = ""; funcs.push_back(move(func)); } return funcs; } vector> GoExtractor::extract_sections() { } ================================================ FILE: src/binaries/extractors/go_extractor.h ================================================ #ifndef CERBERUS_GO_EXTRACTOR_H #define CERBERUS_GO_EXTRACTOR_H #include #include #include class GoExtractor : public BinaryExtractor { private: LiefExtractor& lief_extractor; CommandExecutor executor; public: GoExtractor(std::string bin_path, BIN_TYPE type, LiefExtractor& lief_extractor) : BinaryExtractor(bin_path, type), lief_extractor(lief_extractor), executor("./") {} BIN_ARCH extract_arch() override; std::vector> extract_functions(BIN_ARCH arch, size_t image_base) override; std::vector> extract_sections() override; }; #endif //CERBERUS_GO_EXTRACTOR_H ================================================ FILE: src/binaries/extractors/libelf_extractor.cpp ================================================ #include #include #include using namespace std; LibelfExtractor::LibelfExtractor(std::string bin_path, BIN_TYPE type) : BinaryExtractor(bin_path, type) { this->fd = open(bin_path.c_str(), O_RDONLY, 0); this->fp = fdopen(fd, "r"); this->bin = elf_begin(fd, ELF_C_READ, NULL); } LibelfExtractor::~LibelfExtractor() { elf_end(this->bin); close(fd); } BIN_ARCH LibelfExtractor::extract_arch() { } vector> LibelfExtractor::extract_functions_32(size_t image_base) { vector> funcs; Elf_Scn *scn = nullptr; Elf_Scn *sym_scn = nullptr; uint64_t str_scn_start = 0; while ((scn = elf_nextscn(this->bin, scn)) != nullptr) { Elf32_Shdr *shdr = elf32_getshdr(scn); if(!sym_scn) { if(shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM) { sym_scn = scn; if(str_scn_start) break; } } if(!str_scn_start) { if(shdr->sh_type == SHT_STRTAB) { str_scn_start = shdr->sh_addr; if(sym_scn) break; } } } if(sym_scn) { Elf_Data *data = nullptr; data = elf_getdata(sym_scn, data); Elf32_Sym *symtab = (Elf32_Sym*) data->d_buf; size_t sym_count = data->d_size / sizeof(Elf32_Sym); for (size_t i = 0; i < sym_count; i++) { if(ELF32_ST_TYPE(symtab[i].st_info) != STT_FUNC || !symtab[i].st_size) continue; unique_ptr func = make_unique(); func->start = symtab[i].st_value - image_base; func->end = func->start + symtab[i].st_size - 1; if(!str_scn_start || !symtab[i].st_value) { funcs.push_back(move(func)); continue; } char buf[1024]; fseek(this->fp, symtab[i].st_name-image_base+str_scn_start, SEEK_SET); fread(buf, 1024, 1, this->fp); func->name = string(buf); funcs.push_back(move(func)); } } return funcs; } vector> LibelfExtractor::extract_functions_64(size_t image_base) { vector> funcs; Elf_Scn *scn = nullptr; Elf_Scn *sym_scn = nullptr; uint64_t str_scn_start = 0; while ((scn = elf_nextscn(this->bin, scn)) != nullptr) { Elf64_Shdr *shdr = elf64_getshdr(scn); if(!sym_scn) { if(shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM) { sym_scn = scn; if(str_scn_start) break; } } if(!str_scn_start) { if(shdr->sh_type == SHT_STRTAB) { str_scn_start = shdr->sh_addr; if(sym_scn) break; } } } if(sym_scn) { Elf_Data *data = nullptr; data = elf_getdata(sym_scn, data); Elf64_Sym *symtab = (Elf64_Sym*) data->d_buf; size_t sym_count = data->d_size / sizeof(Elf64_Sym); for (size_t i = 0; i < sym_count; i++) { if(ELF64_ST_TYPE(symtab[i].st_info) != STT_FUNC || !symtab[i].st_size) continue; unique_ptr func = make_unique(); func->start = symtab[i].st_value - image_base; func->end = func->start + symtab[i].st_size - 1; if(!str_scn_start || !symtab[i].st_value) { funcs.push_back(move(func)); continue; } char buf[1024]; fseek(this->fp, symtab[i].st_name-image_base+str_scn_start, SEEK_SET); fread(buf, 1024, 1, this->fp); func->name = string(buf); funcs.push_back(move(func)); } } return funcs; } vector> LibelfExtractor::extract_functions(BIN_ARCH arch, size_t image_base) { if(type==BIN_TYPE::PE) image_base = 0; vector> funcs; switch(arch) { case BIN_ARCH::X86_64: funcs = extract_functions_64(image_base); break; case BIN_ARCH::X86: funcs = extract_functions_32(image_base); break; } return funcs; } vector> LibelfExtractor::extract_sections() { } ================================================ FILE: src/binaries/extractors/libelf_extractor.h ================================================ #ifndef CERBERUS_LIBELF_EXTRACTOR_H #define CERBERUS_LIBELF_EXTRACTOR_H #include #include class LibelfExtractor : public BinaryExtractor { private: Elf* bin; int32_t fd; FILE* fp; public: LibelfExtractor(std::string bin_path, BIN_TYPE type); ~LibelfExtractor(); BIN_ARCH extract_arch() override; std::vector> extract_functions_32(size_t image_base); std::vector> extract_functions_64(size_t image_base); std::vector> extract_functions(BIN_ARCH arch, size_t image_base) override; std::vector> extract_sections() override; }; #endif //CERBERUS_LIBELF_EXTRACTOR_H ================================================ FILE: src/binaries/extractors/lief_extractor.cpp ================================================ #include #include #include #include #include using namespace std; LiefExtractor::LiefExtractor(std::string bin_path, BIN_TYPE type) : BinaryExtractor(bin_path, type) { this->bin = LIEF::Parser::parse(bin_path); switch(type) { case BIN_TYPE::ELF: this->elf_bin = LIEF::ELF::Parser::parse(bin_path); break; case BIN_TYPE::PE: this->pe_bin = LIEF::PE::Parser::parse(bin_path); break; } } BIN_ARCH LiefExtractor::extract_arch() { switch(this->bin->header().architecture()) { case LIEF::ARCH_X86: if(this->bin->header().is_32()) return BIN_ARCH::X86; return BIN_ARCH::X86_64; } return BIN_ARCH::UNKNOWN_ARCH; } size_t LiefExtractor::extract_image_base() { return this->bin->imagebase(); } size_t LiefExtractor::resolve_pe_rva(size_t rva) { return this->pe_bin->rva_to_offset(rva); } LIEF::PE::Section* LiefExtractor::extract_text_section() { return this->pe_bin->get_section(".text"); } vector> LiefExtractor::extract_functions(BIN_ARCH arch, size_t image_base) { if(type == BIN_TYPE::PE) image_base = 0; vector> funcs; vector lief_funcs; switch(type) { case BIN_TYPE::ELF: lief_funcs = this->elf_bin->functions(); break; case BIN_TYPE::PE: lief_funcs = this->pe_bin->functions(); break; } size_t ordinal = 0; for(LIEF::Function lief_func : lief_funcs) { if(!lief_func.size()) continue; unique_ptr func = make_unique(); switch(type) { case BIN_TYPE::ELF: func->start = lief_func.address() - image_base; break; case BIN_TYPE::PE: func->start = resolve_pe_rva(lief_func.address()); break; } func->end = func->start + lief_func.size() - 1; func->name = lief_func.name(); func->ordinal = ordinal; funcs.push_back(move(func)); ordinal++; } return funcs; } vector> LiefExtractor::extract_sections() { vector> sections; for(LIEF::Section lief_section : this->bin->sections()) { unique_ptr
section = make_unique
(); section->start = lief_section.offset(); section->end = lief_section.offset() + lief_section.size(); section->name = lief_section.name(); sections.push_back(move(section)); } return sections; } bool LiefExtractor::write_elf_output(string output_path, size_t image_base, vector>& funcs, bool stripped) { if(!this->elf_bin->has_section(".symtab")) { LIEF::ELF::Section symtab_sec = LIEF::ELF::Section(); symtab_sec.name(".symtab"); symtab_sec.type(LIEF::ELF::ELF_SECTION_TYPES::SHT_SYMTAB); symtab_sec.entry_size(0x18); symtab_sec.alignment(8); symtab_sec.link(this->elf_bin->header().numberof_sections()+1); vector content(100, 0); symtab_sec.content(content); *this->elf_bin->add(symtab_sec, false); } if(!this->elf_bin->has_section(".strtab")) { LIEF::ELF::Section strtab_sec = LIEF::ELF::Section(); strtab_sec.name(".strtab"); strtab_sec.type(LIEF::ELF::ELF_SECTION_TYPES::SHT_STRTAB); strtab_sec.entry_size(1); strtab_sec.alignment(1); vector content(100, 0); strtab_sec.content(content); *this->elf_bin->add(strtab_sec, false); } vector sym_addresses; if(stripped) { LIEF::ELF::Symbol symbol = LIEF::ELF::Symbol(); symbol.name(""); symbol.type(LIEF::ELF::ELF_SYMBOL_TYPES::STT_NOTYPE); symbol.value(0); symbol.binding(LIEF::ELF::SYMBOL_BINDINGS::STB_LOCAL); symbol.size(0); symbol.shndx(0); this->elf_bin->add_static_symbol(symbol); } else { for(auto& symbol : this->elf_bin->symbols()) { uint64_t sym_addr = symbol.value(); if(find(sym_addresses.begin(), sym_addresses.end(), sym_addr) == sym_addresses.end()) sym_addresses.push_back(sym_addr); } } for(unique_ptr& func : funcs) { if(stripped || std::find(sym_addresses.begin(), sym_addresses.end(), func->start) == sym_addresses.end()) { LIEF::ELF::Symbol symbol = LIEF::ELF::Symbol(); symbol.name(func->name); symbol.type(LIEF::ELF::ELF_SYMBOL_TYPES::STT_FUNC); symbol.value(func->start+image_base); symbol.binding(LIEF::ELF::SYMBOL_BINDINGS::STB_LOCAL); symbol.shndx(14); this->elf_bin->add_static_symbol(symbol); } } this->elf_bin->write(output_path); return true; } bool LiefExtractor::write_pe_output(string output_path, size_t image_base, size_t matches_sz, vector>& funcs) { vector edata_content; EXPORT_DIRECTORY_TABLE edt; for(size_t i = 0; i < sizeof(edt) + matches_sz * 8; i++) edata_content.push_back(0); for(uint16_t i = 0; i < matches_sz; i++) { edata_content.push_back(i & 0xff); edata_content.push_back(i >> 8); } for(unique_ptr& func : funcs) { if(!func->name.size()) continue; for(char c : func->name) edata_content.push_back(c); edata_content.push_back(0); } LIEF::PE::Section edata_section; edata_section.name(".edata"); edata_section.content(edata_content); edata_section.characteristics((uint32_t)( LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_INITIALIZED_DATA | LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_MEM_READ ) | 0x300000); edata_section = *this->pe_bin->add_section(edata_section); this->pe_bin->header().numberof_sections(this->pe_bin->header().numberof_sections() + 1); this->pe_bin->write(output_path); ifstream bin_file(output_path, ios::binary); bin_file.seekg(0, ios::end); size_t bin_file_sz = bin_file.tellg(); ofstream new_bin_file(output_path+".tmp", ios::binary); size_t edata_start = edata_section.virtual_address(); edt.name_rva = edata_start + matches_sz * 10; uint32_t edata_content_sz = edata_content.size(); edt.address_table_entries = matches_sz; edt.number_of_name_pointers = matches_sz; edt.export_address_table_rva = edata_start + sizeof(edt); edt.name_pointer_rva = edata_start + sizeof(edt) + 4 * matches_sz; edt.ordinal_table_rva = edata_start + sizeof(edt) + 8 * matches_sz; for(uint8_t i = 0; i < sizeof(edt); i++) edata_content[i] = (((char*)&edt)[i]); size_t func_i = 0; size_t name_pos = 0; for(unique_ptr& func : funcs) { if(!func->name.size()) continue; uint32_t func_vaddr = this->pe_bin->offset_to_virtual_address(func->start).value(); memcpy((char*)(edata_content.data()+sizeof(edt)+func_i*4), &func_vaddr, sizeof(uint32_t)); uint32_t name_vaddr = edata_start + sizeof(edt) + matches_sz * 10 + name_pos; memcpy((char*)(edata_content.data()+sizeof(edt)+matches_sz*4+func_i*4), &name_vaddr, sizeof(uint32_t)); func_i++; name_pos += func->name.size()+1; } char buffer[1024]; uint32_t pe_start; bin_file.seekg(0x3c); bin_file.read((char*)&pe_start, sizeof(pe_start)); size_t edt_data_dir = pe_start + 0x88; bin_file.seekg(0, ios::beg); bin_file.read(buffer, edt_data_dir); new_bin_file.write(buffer, edt_data_dir); new_bin_file.write((char*)&edata_start, 4); new_bin_file.write((char*)&edata_content_sz, 4); bin_file.seekg((size_t)bin_file.tellg()+8); size_t off = edt_data_dir+0x8; while(off < edata_section.offset() - sizeof(buffer)) { bin_file.read(buffer, sizeof(buffer)); new_bin_file.write(buffer, sizeof(buffer)); off+=sizeof(buffer); } bin_file.read(buffer, edata_section.offset() - off); new_bin_file.write(buffer, edata_section.offset() - off); new_bin_file.write((char*)edata_content.data(), edata_content.size()); bin_file.seekg((size_t)bin_file.tellg()+edata_content.size()); off = edata_section.offset() + edata_content.size(); while(off < bin_file_sz - sizeof(buffer)) { bin_file.read(buffer, sizeof(buffer)); new_bin_file.write(buffer, sizeof(buffer)); off+=sizeof(buffer); } new_bin_file.write(buffer, bin_file_sz - off); bin_file.close(); new_bin_file.close(); filesystem::rename(output_path+".tmp", output_path); return true; } ================================================ FILE: src/binaries/extractors/lief_extractor.h ================================================ #ifndef CERBERUS_LIEF_EXTRACTOR_H #define CERBERUS_LIEF_EXTRACTOR_H #include #include class LiefExtractor : public BinaryExtractor { private: std::unique_ptr bin; std::unique_ptr elf_bin; std::unique_ptr pe_bin; public: LiefExtractor(std::string bin_path, BIN_TYPE type); BIN_ARCH extract_arch() override; size_t extract_image_base(); size_t resolve_pe_rva(size_t rva); LIEF::PE::Section* extract_text_section(); std::vector> extract_functions(BIN_ARCH arch, size_t image_base) override; std::vector> extract_sections() override; bool write_elf_output(std::string output_path, size_t image_base, std::vector>& funcs, bool stripped); bool write_pe_output(std::string output_path, size_t image_base, size_t matches_sz, std::vector>& funcs); }; #endif //CERBERUS_LIEF_EXTRACTOR_H ================================================ FILE: src/binaries/extractors/radare_extractor.cpp ================================================ #include #include using namespace std; BIN_ARCH RadareExtractor::extract_arch() { } vector> RadareExtractor::extract_functions(BIN_ARCH arch, size_t image_base) { LIEF::PE::Section* text_sec; if(type == BIN_TYPE::PE) text_sec = this->lief_extractor.extract_text_section(); vector> funcs; COMMAND_RESULT res; executor.execute_command(string("radare2 -q -c aaa -c afl \"") + bin_path + string("\""), &res); if (!res.code) { vector lines = split_string(res.response, '\n'); for(string& line : lines) { if(line.length() < 2 || line.substr(0, 2) != "0x") continue; vector vals = split_string(line, ' '); vals = filter_empty_strings(vals); unique_ptr func = make_unique(); switch(type) { case BIN_TYPE::ELF: func->start = stoull(vals[0].substr(2), nullptr, 16) - image_base; break; case BIN_TYPE::PE: func->start = stoull(vals[0].substr(2), nullptr, 16) - image_base - text_sec->virtual_address() + text_sec->offset(); break; } func->end = func->start + stoull(vals[2]) - 1; if(!func->name.find("fcn.")) func->name = vals[3]; funcs.push_back(move(func)); } } return funcs; } vector> RadareExtractor::extract_sections() { } ================================================ FILE: src/binaries/extractors/radare_extractor.h ================================================ #ifndef CERBERUS_RADARE_EXTRACTOR_H #define CERBERUS_RADARE_EXTRACTOR_H #include #include #include class RadareExtractor : public BinaryExtractor { private: LiefExtractor& lief_extractor; CommandExecutor executor; public: RadareExtractor(std::string bin_path, BIN_TYPE type, LiefExtractor& lief_extractor) : BinaryExtractor(bin_path, type), lief_extractor(lief_extractor), executor("./") {} BIN_ARCH extract_arch() override; std::vector> extract_functions(BIN_ARCH arch, size_t image_base) override; std::vector> extract_sections() override; }; #endif //CERBERUS_RADARE_EXTRACTOR_H ================================================ FILE: src/binaries/handlers/elf_handler.cpp ================================================ #include #include #include using namespace std; void ElfHandler::strip_analysis() { this->stripped = true; vector> sections = this->lief_extractor->extract_sections(); const vector debug_sections = {".symtab", ".strtab"}; for(unique_ptr
& section : sections) { if(find(debug_sections.begin(), debug_sections.end(), section->name) != debug_sections.end()) { this->stripped = false; return; } } } size_t ElfHandler::functions_analysis() { vector> funcs = this->lief_extractor->extract_functions(this->arch, this->image_base); if(!funcs.size()) funcs = this->radare_extractor->extract_functions(this->arch, this->image_base); if(!funcs.size()) funcs = this->libelf_extractor->extract_functions(this->arch, this->image_base); algorithm->process_binary(&funcs); this->functions = move(funcs); return this->functions.size(); } void ElfHandler::functions_matching(string lib_path) { LiefExtractor lib_lief_extractor(lib_path, type); LibelfExtractor lib_libelf_extractor(lib_path, type); RadareExtractor lib_radare_extractor(lib_path, type, lib_lief_extractor); size_t lib_image_base = lib_lief_extractor.extract_image_base(); vector> funcs = lib_lief_extractor.extract_functions(this->arch, lib_image_base); if(!funcs.size()) funcs = lib_libelf_extractor.extract_functions(this->arch, lib_image_base); if(!funcs.size()) funcs = lib_radare_extractor.extract_functions(this->arch, lib_image_base); algorithm->process_lib(lib_path, &funcs); } void ElfHandler::post_matching() { algorithm->post_process(&functions); } bool ElfHandler::write_output(string output_path) { return this->lief_extractor->write_elf_output(output_path, this->image_base, this->functions, this->stripped); } ================================================ FILE: src/binaries/handlers/elf_handler.h ================================================ #ifndef CERBERUS_ELF_HANDLER_H #define CERBERUS_ELF_HANDLER_H #include class ElfHandler : public BinaryHandler { private: LibelfExtractor* libelf_extractor; public: ElfHandler(std::string bin_path, std::string work_dir, LANG lang, Algorithm* algorithm) : BinaryHandler(bin_path, work_dir, lang, algorithm, BIN_TYPE::ELF) { this->libelf_extractor = new LibelfExtractor(bin_path, type); } void strip_analysis() override; size_t functions_analysis() override; void functions_matching(std::string lib_path) override; void post_matching() override; bool write_output(std::string output_path) override; }; #endif //CERBERUS_ELF_HANDLER_H ================================================ FILE: src/binaries/handlers/pe_handler.cpp ================================================ #include #include using namespace std; void PeHandler::strip_analysis() { this->stripped = !this->extract_section_header_start(".edata", this->bin_path) && !this->extract_section_header_start(".zdebug_info", this->bin_path); } size_t PeHandler::functions_analysis() { vector> funcs = this->lief_extractor->extract_functions(this->arch, this->image_base); if(!funcs.size()) funcs = this->radare_extractor->extract_functions(this->arch, this->image_base); algorithm->process_binary(&funcs); this->functions = move(funcs); return this->functions.size(); } size_t PeHandler::extract_section_header_start(std::string section_name, std::string path) { ifstream bin_file(path); uint32_t pe_start; bin_file.seekg(0x3c); bin_file.read((char*)&pe_start, sizeof(pe_start)); uint16_t number_of_sections; bin_file.seekg(pe_start+0x6); bin_file.read((char*)&number_of_sections, sizeof(number_of_sections)); uint16_t size_of_opt_header; bin_file.seekg(pe_start+0x14); bin_file.read((char*)&size_of_opt_header, sizeof(size_of_opt_header)); for(uint16_t sec_i = 0; sec_i < number_of_sections; sec_i++) { size_t sec_start = pe_start + 0x18 + size_of_opt_header + sec_i * 0x28; bin_file.seekg(sec_start); char sec_name[8]; bin_file.read(sec_name, sizeof(sec_name)); if(!strcmp(sec_name, section_name.c_str())) { bin_file.close(); return sec_start; } } bin_file.close(); return 0; } void PeHandler::fix_functions_names(vector>& funcs, std::string path, LiefExtractor& extractor) { size_t edata_header_start = this->extract_section_header_start(".edata", path); if(!edata_header_start) return; ifstream bin_file(path); uint32_t edata_start; bin_file.seekg(edata_header_start+0xc); bin_file.read((char*)&edata_start, sizeof(edata_start)); edata_start = extractor.resolve_pe_rva(edata_start); uint32_t pointers_sz; bin_file.seekg(edata_start+0x18); bin_file.read((char*)&pointers_sz, sizeof(pointers_sz)); uint32_t eat_start; bin_file.seekg(edata_start+0x1c); bin_file.read((char*)&eat_start, sizeof(eat_start)); eat_start = extractor.resolve_pe_rva(eat_start); uint32_t name_table_start; bin_file.seekg(edata_start+0x20); bin_file.read((char*)&name_table_start, sizeof(name_table_start)); name_table_start = extractor.resolve_pe_rva(name_table_start); map eat; for(size_t entry_i = 0; entry_i < pointers_sz; entry_i++) { size_t eat_entry = eat_start + entry_i * sizeof(uint32_t); uint32_t func_addr; bin_file.seekg(eat_entry); bin_file.read((char*)&func_addr, sizeof(func_addr)); func_addr = extractor.resolve_pe_rva(func_addr); size_t name_entry = name_table_start + entry_i * sizeof(uint32_t); uint32_t name_addr; bin_file.seekg(name_entry); bin_file.read((char*)&name_addr, sizeof(name_addr)); name_addr = extractor.resolve_pe_rva(name_addr); char name[1024]; bin_file.seekg(name_addr); bin_file.read(name, sizeof(name)); eat[func_addr] = string(name); } for(unique_ptr& func : funcs) { if(func->name.length()) continue; if(eat.find(func->start) != eat.end()) { func->name = eat[func->start]; } } bin_file.close(); } void PeHandler::functions_matching(string lib_path) { LiefExtractor lib_lief_extractor(lib_path, type); GoExtractor lib_go_extractor(lib_path, type, lib_lief_extractor); RadareExtractor lib_radare_extractor(lib_path, type, lib_lief_extractor); size_t lib_image_base = lib_lief_extractor.extract_image_base(); vector> funcs = lib_lief_extractor.extract_functions(this->arch, lib_image_base); if(!funcs.size()) funcs = lib_go_extractor.extract_functions(this->arch, lib_image_base); if(!funcs.size()) funcs = lib_radare_extractor.extract_functions(this->arch, lib_image_base); this->fix_functions_names(funcs, lib_path, lib_lief_extractor); algorithm->process_lib(lib_path, &funcs); } void PeHandler::post_matching() { algorithm->post_process(&functions); } bool PeHandler::write_output(string output_path) { return this->lief_extractor->write_pe_output(output_path, this->image_base, this->matches_sz, this->functions); } ================================================ FILE: src/binaries/handlers/pe_handler.h ================================================ #ifndef CERBERUS_PE_HANDLER_H #define CERBERUS_PE_HANDLER_H #include class PeHandler : public BinaryHandler { private: size_t extract_section_header_start(std::string section_name, std::string path); void fix_functions_names(std::vector>& funcs, std::string path, LiefExtractor& extractor); public: PeHandler(std::string bin_path, std::string work_dir, LANG lang, Algorithm* algorithm) : BinaryHandler(bin_path, work_dir, lang, algorithm, BIN_TYPE::PE) {} void strip_analysis() override; size_t functions_analysis() override; void functions_matching(std::string lib_path) override; void post_matching() override; bool write_output(std::string output_path) override; }; #endif //CERBERUS_PE_HANDLER_H ================================================ FILE: src/binaries/lib/install/go_lib_installer.cpp ================================================ #include #include #include #include #include using namespace std; bool GoLibInstaller::pre_install_hook(vector>& libs) { filesystem::create_directory(this->work_dir+"/standalone"); auto go_lib_it = find_if(libs.begin(), libs.end(), [](const unique_ptr& item) { return item->name == "go"; }); if(go_lib_it == libs.end()) return false; LIBRARY* go_lib = go_lib_it->get(); libs.erase(go_lib_it); if(!this->install_go_version(go_lib->version)) return false; CommandExecutor executor(this->work_dir+"/standalone"); COMMAND_RESULT res; executor.execute_command("../go mod init build_mod", &res); if(res.code || res.response.find("creating new go.mod") == string::npos) return false; ofstream build_mod_file(this->work_dir+"/standalone/build_mod.go"); build_mod_file << "package main" << endl << endl; build_mod_file << "import (" << endl; for(std::unique_ptr& lib : libs) { string lib_name = lib->name; if(!lib_name.find("std::")) lib_name = lib_name.substr(string("std::").length()); build_mod_file << "\t_ \"" << lib_name << "\"" << endl; } build_mod_file << ")" << endl << endl; build_mod_file << "func main() {}"; build_mod_file.close(); return true; } bool GoLibInstaller::install_go_version(std::string version) { fcout << "$(info)Installing $(bright_magenta:b)go$$(red):$$(magenta:b)" << version << "$..." << endl; CommandExecutor executor(work_dir); COMMAND_RESULT res; executor.execute_command("goliath "+version, &res); bool success = !res.code; this->goliath_output_dir = this->work_dir+"/go-"+version; if(success) { filesystem::rename(goliath_output_dir+"/go/bin/go", this->work_dir+"/go"); fcout << "$(success)Success !" << endl; } else fcout << "$(error)Failure..." << endl; return success; } bool GoLibInstaller::install_lib(LIBRARY lib) { CommandExecutor executor(this->work_dir+"/standalone"); COMMAND_RESULT res; string lib_name = lib.name; if(!lib_name.find("std::")) lib_name = lib_name.substr(string("std::").length()); if(lib.version != "unk") lib_name += "@v"+lib.version; executor.execute_command("../go get "+lib_name, &res); return !res.code; } bool GoLibInstaller::post_install_hook() { fcout << "$(info)Building standalone library..." << endl; string goos, goarch; string lib_extension; switch(type) { case BIN_TYPE::ELF: goos = "linux"; lib_extension = ".so"; break; case BIN_TYPE::PE: goos = "windows"; lib_extension = ".dll"; break; } switch(arch) { case BIN_ARCH::X86_64: goarch = "amd64"; break; case BIN_ARCH::X86: goarch = "386"; break; } CommandExecutor executor(this->work_dir+"/standalone"); COMMAND_RESULT res; executor.execute_command("GOOS="+goos+" GOARCH="+goarch+" ../go build -o ./standalone"+lib_extension+" build_mod.go", &res); bool success = !res.code; if(success) { filesystem::rename(this->work_dir+"/standalone/standalone"+lib_extension, this->work_dir+"/standalone"+lib_extension); fcout << "$(success)Success !" << endl; } else fcout << "$(error)Failure..." << endl; filesystem::remove(this->work_dir+"/go"); filesystem::remove_all(this->goliath_output_dir); filesystem::remove_all(this->work_dir+"/standalone"); return success; } ================================================ FILE: src/binaries/lib/install/go_lib_installer.h ================================================ #ifndef CERBERUS_GO_LIB_INSTALLER_H #define CERBERUS_GO_LIB_INSTALLER_H #include #include class GoLibInstaller : public LibInstaller { private: bool install_go_version(std::string version); std::string goliath_output_dir; public: GoLibInstaller(std::string work_dir, BIN_ARCH arch, BIN_TYPE type) : LibInstaller(work_dir, arch, type) {} bool install_lib(LIBRARY lib) override; bool pre_install_hook(std::vector>& libs) override; bool post_install_hook() override; }; #endif //CERBERUS_GO_LIB_INSTALLER_H ================================================ FILE: src/binaries/lib/install/lib_installer.h ================================================ #ifndef CERBERUS_LIB_INSTALLER_H #define CERBERUS_LIB_INSTALLER_H #include #include #include #include class LibInstaller { protected: std::string work_dir; BIN_ARCH arch; BIN_TYPE type; public: LibInstaller(std::string work_dir, BIN_ARCH arch, BIN_TYPE type) : work_dir(work_dir), arch(arch), type(type) {} virtual bool install_lib(LIBRARY lib) = 0; virtual bool pre_install_hook(std::vector>& libs) = 0; virtual bool post_install_hook() = 0;}; #endif //CERBERUS_LIB_INSTALLER_H ================================================ FILE: src/binaries/lib/install/rust_lib_installer.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace std; const string RBF_PREFIX = "$(bright_blue:b)[$(blue:b)RBF$]$ "; void newer_edition_patch(string crate_path) { if(fs::exists(crate_path+"/Cargo.toml")) { ifstream cargo_input_file(crate_path + "/Cargo.toml"); string line; vector lines; while(getline(cargo_input_file, line)) { line = strip(line); if(line.find("[package]") != string::npos) { lines.push_back("[package]"); lines.push_back("edition = \"2021\""); } else if(line.find("edition ") == string::npos && line.find("edition=") == string::npos) { lines.push_back(line); } } cargo_input_file.close(); ofstream cargo_output_file(crate_path+"/Cargo.toml"); for(string l : lines) { cargo_output_file.write((l + string("\n")).c_str(), l.size() + 1); } cargo_output_file.close(); } } PATCH NEWER_EDITION_PATCH{ "EDITION 2021", newer_edition_patch }; void std_redefinition_patch(string crate_path) { if(fs::exists(crate_path+"/src/lib.rs")) { ifstream lib_input_file(crate_path+"/src/lib.rs"); string line; vector lines; while(getline(lib_input_file, line)) { if(line.find("no_std") == string::npos && line.find("as std;") == string::npos) lines.push_back(line); } lib_input_file.close(); ofstream lib_output_file(crate_path+"/src/lib.rs"); for(string l : lines) { lib_output_file.write((l + string("\n")).c_str(), l.size() + 1); } lib_output_file.close(); } } PATCH STD_REDEFINITION_PATCH { "STD REDEFINITION", std_redefinition_patch }; void add_workspace_patch(string crate_path) { if(fs::exists(crate_path+"/Cargo.toml")) { ifstream cargo_input_file(crate_path+"/Cargo.toml"); vector lines; string line; while(getline(cargo_input_file, line)) { lines.push_back(line); } lines.push_back("[workspace]"); cargo_input_file.close(); ofstream cargo_output_file(crate_path+"/Cargo.toml"); for(string l : lines) { cargo_output_file.write((l + string("\n")).c_str(), l.size() + 1); } cargo_output_file.close(); } } PATCH ADD_WORKSPACE_PATCH { "ADD WORKSPACE", add_workspace_patch }; map patches = { {"maybe a missing crate `core`?", NEWER_EDITION_PATCH}, {"the name `std` is defined multiple times", STD_REDEFINITION_PATCH}, {"language item required, but not found: `eh_personality`", STD_REDEFINITION_PATCH}, {"current package believes it's in a workspace when it's not", ADD_WORKSPACE_PATCH} }; bool RustBuildFixer::process_error(string command, string error) { for(pair patch_pair : patches) { if(error.find(patch_pair.first) != string::npos) { PATCH patch = patch_pair.second; if(last_patch && patch.patch_func == last_patch->patch_func) return false; last_patch = &patch; fcout << "$(info)" << RBF_PREFIX << "Applying patch $(red:b)" << patch.name << "$..." << endl; patch.patch_func(crate_path); COMMAND_RESULT res; executor.execute_command(command, &res); if(!res.code) return true; return process_error(command, res.response); } } return false; } void RustLibInstaller::check_and_install_arch(string arch_name) { CommandExecutor executor("./"); COMMAND_RESULT res; executor.execute_command("rustup target list", &res); if(res.code) return; vector lines = split_string(res.response, '\n'); for(string& line : lines) { if(line.find(arch_name) != string::npos) { if(line.find("installed") == string::npos) { fcout << "$(info)You need to install the toolchain for $(info:b)" << arch_name << "$ architecture." << endl; bool agree_install = ask_yes_no("Proceed to installation ?", true); if(agree_install) { COMMAND_RESULT res; string install_cmd = "rustup target install " + arch_name; executor.execute_command(install_cmd, &res); if(res.code) fcout << "$(error)An error occurred during installation... Run $(error:b)" << install_cmd << "$ for more information." << endl; else fcout << "$(success)Done !" << endl; return; } else return; } else { fcout << "$(info)Requested architecture is already installed." << endl; return; } } } } RustLibInstaller::RustLibInstaller(string work_dir, BIN_ARCH arch, BIN_TYPE type) : LibInstaller(work_dir, arch, type), downloader() { if(arch == BIN_ARCH::X86) check_and_install_arch("i686-unknown-linux-gnu"); } bool RustLibInstaller::install_lib(LIBRARY lib) { string output_dir_name = this->work_dir+"/"+lib.name+"-"+lib.version; string zip_file_name = output_dir_name+".crate"; string tar_file_name = output_dir_name+".tar"; if(!this->downloader.download_file("https://crates.io/api/v1/crates/"+lib.name+"/"+lib.version+"/download", zip_file_name)) return false; if(!decompress_gzip_file(zip_file_name, tar_file_name)) return false; fs::remove(zip_file_name); if(!decompress_tar_file(tar_file_name, output_dir_name)) return false; fs::remove(tar_file_name); ifstream cargo_toml_input(output_dir_name+"/Cargo.toml"); if(!cargo_toml_input.is_open()) return false; vector cargo_toml_lines; string line; bool found_lib = false; while(getline(cargo_toml_input, line)) { strip(line); if(line.find("[lib]") != string::npos) { found_lib = true; cargo_toml_lines.push_back("[lib]"); cargo_toml_lines.push_back("crate-type = [\"dylib\"]"); } else if(line.find("crate-type ") && line.find("crate-type=")) cargo_toml_lines.push_back(line); } cargo_toml_input.close(); if(!found_lib) { cargo_toml_lines.push_back("[lib]"); cargo_toml_lines.push_back("crate-type = [\"dylib\"]"); } if(!fs::exists(output_dir_name+"/Cargo.toml")) return false; ofstream cargo_toml_output(output_dir_name+"/Cargo.toml"); for(string line : cargo_toml_lines) cargo_toml_output.write((line+string("\n")).c_str(), line.size()+1); cargo_toml_output.close(); CommandExecutor executor(output_dir_name); COMMAND_RESULT res; string command; switch(type) { case BIN_TYPE::ELF: command = "cargo build --release"; if(arch == BIN_ARCH::X86) command += " --target=i686-unknown-linux-gnu"; break; case BIN_TYPE::PE: if(arch == BIN_ARCH::X86_64) command = "cross build --target x86_64-pc-windows-gnu --release"; else if(arch == BIN_ARCH::X86) command = "cross build --target i686-pc-windows-gnu --release"; break; } executor.execute_command(command, &res); bool success = res.code == 0; if(!success) { fcout << "$(warning)An error occurred during build, delegating to $(warning:b)RBF$ (Rust Build Fixer)..." << endl; success = RustBuildFixer(output_dir_name, type).process_error(command, res.response); } if(!success) return false; string release_dir; switch(type) { case BIN_TYPE::ELF: release_dir = output_dir_name+string("/target/release"); break; case BIN_TYPE::PE: if(arch == BIN_ARCH::X86_64) release_dir = output_dir_name+string("/target/x86_64-pc-windows-gnu/release"); else if(arch == BIN_ARCH::X86) release_dir = output_dir_name+string("/target/i686-pc-windows-gnu/release"); break; } string lib_extension; switch(type) { case BIN_TYPE::ELF: lib_extension = ".so"; break; case BIN_TYPE::PE: lib_extension = ".dll"; break; } if(fs::exists(release_dir)) { for (const auto& entry : fs::directory_iterator(release_dir)) { if (fs::is_regular_file(entry)) { fs::path file_path = entry.path(); string file_name = file_path.filename(); if(ends_with(file_name, lib_extension)) { fs::rename(file_path, this->work_dir+"/"+file_name); } } } } fs::remove_all(output_dir_name); return true; } ================================================ FILE: src/binaries/lib/install/rust_lib_installer.h ================================================ #ifndef CERBERUS_RUST_LIB_INSTALLER_H #define CERBERUS_RUST_LIB_INSTALLER_H #include #include #include struct PATCH { std::string name; void (*patch_func)(std::string crate_path); }; class RustBuildFixer { private: std::string crate_path; BIN_TYPE type; PATCH* last_patch = nullptr; CommandExecutor executor; public: RustBuildFixer(std::string crate_path, BIN_TYPE type) : crate_path(crate_path), type(type), executor(crate_path) {} bool process_error(std::string command, std::string error); }; class RustLibInstaller : public LibInstaller { private: FileDownloader downloader; void check_and_install_arch(std::string arch_name); public: RustLibInstaller(std::string work_dir, BIN_ARCH arch, BIN_TYPE type); bool install_lib(LIBRARY lib) override; bool pre_install_hook(std::vector>& libs) override {return true;} bool post_install_hook() override {return true;} }; #endif //CERBERUS_RUST_LIB_INSTALLER_H ================================================ FILE: src/binaries/lib/lib_manager.cpp ================================================ #include #include #include #include using namespace std; void LibManager::main_menu() { fcout << "$(info)Here is the current list of libraries :\n"; fcout << "$(red:b)-------------------------------$\n"; uint8_t lib_i = 1; for(std::unique_ptr& lib : this->libs) { fcout << to_string(lib_i) << ". $(bright_magenta:b)" << lib->name << "$$(red):$$(magenta:b)" << lib->version << "$\n"; lib_i++; } fcout << "$(red:b)-------------------------------$\n"; fcout << "$(info)$(info:b)1$. Validate $(info:b)2$. Add library $(info:b)3$. Change library version $(info:b)4$. Remove library" << endl; } void LibManager::add_lib_menu() { std::unique_ptr lib = make_unique(); lib->name = ""; lib->version = ""; fcout << "$(info)Name:$ "; while(!lib->name.size()) getline(cin, lib->name); fcout << "$(info)Version:$ "; while(!lib->version.size()) getline(cin, lib->version); this->libs.push_back(move(lib)); } void LibManager::change_version_menu() { if(!this->libs.size()) { fcout << "$(warning)No libraries remaining." << endl; return; } size_t index = ask_n("Index:", 1, this->libs.size()); std::unique_ptr& lib = this->libs.at(index - 1); lib->version = ""; fcout << "$(info)Version:$ "; while(!lib->version.size()) getline(cin, lib->version); } void LibManager::remove_lib_menu() { if(!this->libs.size()) { fcout << "$(warning)No libraries remaining." << endl; return; } size_t index = ask_n("Index:", 1, this->libs.size()); this->libs.erase(this->libs.begin() + index - 1); } void LibManager::manage() { USER_CHOICE choice = USER_CHOICE::NO_CHOICE; do { main_menu(); choice = (USER_CHOICE) ask_n("Your choice ?", 1, 4); switch(choice) { case ADD_LIB: this->add_lib_menu(); break; case CHANGE_VERSION: this->change_version_menu(); break; case REMOVE_LIB: this->remove_lib_menu(); break; } } while(choice != USER_CHOICE::VALIDATE); } ================================================ FILE: src/binaries/lib/lib_manager.h ================================================ #ifndef CERBERUS_LIB_MANAGER_H #define CERBERUS_LIB_MANAGER_H #include #include #include #include enum USER_CHOICE { NO_CHOICE = 0, VALIDATE = 1, ADD_LIB = 2, CHANGE_VERSION = 3, REMOVE_LIB = 4 }; class LibManager { private: std::vector>& libs; void main_menu(); void add_lib_menu(); void change_version_menu(); void remove_lib_menu(); public: LibManager(std::vector>& libs) : libs(libs) {} void manage(); }; #endif //CERBERUS_LIB_MANAGER_H ================================================ FILE: src/binaries/pe_types.h ================================================ #ifndef CERBERUS_PE_TYPES_H #define CERBERUS_PE_TYPES_H #include struct EXPORT_DIRECTORY_TABLE { uint32_t export_flags = 0; uint32_t time_date_stamp = 0; uint16_t major_version = 0; uint16_t minor_version = 0; uint32_t name_rva; uint32_t ordinal_base = 1; uint32_t address_table_entries; uint32_t number_of_name_pointers; uint32_t export_address_table_rva; uint32_t name_pointer_rva; uint32_t ordinal_table_rva; }; #endif //CERBERUS_PE_TYPES_H ================================================ FILE: src/command/command_executor.cpp ================================================ #include #include #include #include using namespace std; bool COMMANDS_DEBUG_MODE = false; bool CommandExecutor::test_password(std::string password) { COMMAND_RESULT res; this->execute_command("echo "+password+" | sudo -Sk echo test", &res); return !res.code; } void CommandExecutor::execute_command(string command, COMMAND_RESULT* result) { char current_dir[1024]; getcwd(current_dir, sizeof(current_dir)); if (chdir(this->env_dir.c_str()) != 0) { result->code = -1; result->response = ""; return; } FILE* pipe = popen((command+string(" 2>&1")).c_str(), "r"); if (!pipe) { result->code = -1; result->response = ""; chdir(current_dir); return; } stringstream ss; char buffer[128]; if(COMMANDS_DEBUG_MODE) fcout << "$(debug)------ COMMAND OUTPUT ------" << endl; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { if(COMMANDS_DEBUG_MODE) fcout << "$(debug)" << buffer << "$"; ss << string(buffer, strlen(buffer)); } if(COMMANDS_DEBUG_MODE) fcout << "$(debug)----------------------------" << endl; result->code = pclose(pipe); result->response = ss.str(); chdir(current_dir); } ================================================ FILE: src/command/command_executor.h ================================================ #ifndef CERBERUS_COMMAND_EXECUTOR_H #define CERBERUS_COMMAND_EXECUTOR_H #include #include #include extern bool COMMANDS_DEBUG_MODE; struct COMMAND_RESULT { int32_t code; std::string response; }; class CommandExecutor { private: std::string env_dir; public: CommandExecutor(std::string env_dir) : env_dir(std::filesystem::absolute(env_dir)) {}; bool test_password(std::string password); void execute_command(std::string command, COMMAND_RESULT* result); }; #endif //CERBERUS_COMMAND_EXECUTOR_H ================================================ FILE: src/global_defs.h ================================================ #ifndef CERBERUS_GLOBAL_DEFS_H #define CERBERUS_GLOBAL_DEFS_H #include const std::string TOOL_ART = " ___ _ \n" " / __|___ _ _| |__ ___ _ _ _ _ ___\n" " | (__/ -_) '_| '_ \\/ -_) '_| || (_-<\n" " \\___\\___|_| |_.__/\\___|_| \\_,_/__/\n"; const std::string TOOL_NAME = "Cerberus"; const std::string TOOL_VERSION = "2.0"; const std::string TOOL_AUTHOR = "h311d1n3r"; const std::string install_dir = "~/.local/bin"; #endif //CERBERUS_GLOBAL_DEFS_H ================================================ FILE: src/langs/lang_manager.cpp ================================================ #include #include #include #include #include using namespace std; map name_from_lang = { {LANG::UNKNOWN_LANG, "Unknown"}, {LANG::RUST, "Rust"}, {LANG::GO, "Go"} }; vector> LANG_PATTERNS = { std::pair("/rustc-", LANG::RUST), std::pair("/.cargo/", LANG::RUST), std::pair("\\rustc-", LANG::RUST), std::pair("\\.cargo\\", LANG::RUST), std::pair("/go-", LANG::GO), std::pair("\\go-", LANG::GO), std::pair("runtime.go", LANG::GO) }; LangIdentifier::LangIdentifier(string input_path) { this->input_path = input_path; } value_ordered_map LangIdentifier::identify() { value_ordered_map matches; for(uint32_t i = 1; i < name_from_lang.size(); i++) matches[(LANG)i] = 0; ifstream input_file(this->input_path); if (!input_file.is_open()) { fcout << "$(critical)File $(critical:u)" << input_path << "$ can't be opened !" << endl; exit(1); } input_file.seekg(0, ios::end); size_t input_sz = input_file.tellg(); input_file.seekg(0); char input_data[2048]; size_t input_off = 0; while(input_off < input_sz) { input_file.seekg(input_off); input_file.read(input_data, sizeof(input_data)); for(pair lang_pattern : LANG_PATTERNS) { char* current_pos = input_data; char* occurrence; while(current_pos-input_data < sizeof(input_data)) { if ((occurrence = strstr(current_pos, lang_pattern.first.c_str())) != nullptr) { current_pos = occurrence + lang_pattern.first.length(); matches[lang_pattern.second]++; } else current_pos++; } } input_off += 1024; } input_file.close(); matches.invert_sort(); return matches; } ================================================ FILE: src/langs/lang_manager.h ================================================ #ifndef CERBERUS_LANG_MANAGER_H #define CERBERUS_LANG_MANAGER_H #include #include #include #include #include extern std::map name_from_lang; extern std::vector> LANG_PATTERNS; class LangIdentifier { private: std::string input_path; public: LangIdentifier(std::string input_path); value_ordered_map identify(); }; #endif //CERBERUS_LANG_MANAGER_H ================================================ FILE: src/langs/lang_types.h ================================================ #ifndef CERBERUS_LANG_TYPES_H #define CERBERUS_LANG_TYPES_H enum LANG { UNKNOWN_LANG, RUST, GO }; #endif //CERBERUS_LANG_TYPES_H ================================================ FILE: src/langs/lib_regex.cpp ================================================ #include #include using namespace std; std::vector rust_lib_regex = { "/.cargo/(.+?)\\.rs", "/cargo/(.+?)\\.rs", "index\\.crates\\.io-(.+?)\\\\(.+?)\\\\", "\\\\.cargo\\\\(.+?)\\.rs", "\\\\cargo\\\\(.+?)\\.rs", "index\\.crates\\.io-(.+?)/(.+?)/", }; std::vector go_lib_regex = { "go(.*?)/pkg/mod/(.+?)\\.(s|go)", "go(.*?)/src/(.+?)\\.(s|go)", "go(.*?)\\\\pkg\\\\mod/(.+?)\\.(s|go)", "go(.*?)\\\\src\\\\(.+?)\\.(s|go)", "go\\d+\\.\\d+\\.\\d+\\x00", "go\\d+\\.\\d+\\x00" }; unique_ptr rust_extract_callback(string match) { size_t null_term_index; if((null_term_index = match.find('\x00')) != string::npos) { match = match.substr(0, null_term_index); } string lib_and_version = ""; if(match.find("index.crates.io-") != string::npos) { size_t delim_index = match.find("/"); if(delim_index == string::npos) delim_index = match.find("\\"); if(delim_index == string::npos) return nullptr; vector match_parts = split_string(match, match.at(delim_index)); if(match_parts.size() < 2) return nullptr; lib_and_version = match_parts.at(1); } else { vector match_parts = split_string(match, match.at(0)); if(match_parts.size() < 6) return nullptr; lib_and_version = match_parts.at(5); } size_t delim_index; if((delim_index = lib_and_version.rfind('-')) == string::npos) return nullptr; unique_ptr lib = make_unique(); lib->name = lib_and_version.substr(0, delim_index); lib->version = lib_and_version.substr(delim_index+1); return lib; } unique_ptr go_extract_callback(string match) { size_t null_term_index; if((null_term_index = match.find('\x00')) != string::npos) { match = match.substr(0, null_term_index); } uint8_t mode = 0; if(match.find("/") != string::npos) mode = 1; else if(match.find("\\") != string::npos) mode = 2; if(mode == 0 && match.length() > 2 && isdigit(match.at(2))) { unique_ptr lib = make_unique(); lib->name = "go"; lib->version = match.substr(2); return lib; } size_t pkg_index, src_index; if((pkg_index = (mode == 1 ? match.find("/pkg/mod/") : match.find("\\pkg\\mod\\"))) != string::npos) { size_t version_index = match.find("@"); if(match.find("golang.org") == string::npos && version_index != string::npos) { unique_ptr lib = make_unique(); size_t name_start_index = pkg_index+string("/pkg/mod/").length(); lib->name = match.substr(name_start_index, version_index-name_start_index); lib->version = match.substr(version_index+2); lib->version = lib->version.substr(0, lib->version.find('/')); return lib; } } else if((src_index = (mode == 1 ? match.find("/src/") : match.find("\\src\\"))) != string::npos) { const string forbidden_list[] = {"internal","runtime","github.com","golang.org"}; for(string forbidden : forbidden_list) if(match.find(forbidden) != string::npos) return nullptr; match = match.substr(src_index+string("/src/").length()); match = match.substr(0, match.find_last_of(mode == 1 ? '/' : '\\')); unique_ptr lib = make_unique(); lib->name = "std::" + match; lib->version = "unk"; return lib; } return nullptr; } map lib_extract_callbacks = { {LANG::RUST, rust_extract_callback}, {LANG::GO, go_extract_callback} }; ================================================ FILE: src/langs/lib_regex.h ================================================ #ifndef CERBERUS_LIB_REGEX_H #define CERBERUS_LIB_REGEX_H #include #include #include #include #include #include #include extern std::vector rust_lib_regex; extern std::vector go_lib_regex; using LibExtractCallback = std::function(std::string)>; std::unique_ptr rust_extract_callback(std::string match); std::unique_ptr go_extract_callback(std::string match); extern std::map lib_extract_callbacks; #endif //CERBERUS_LIB_REGEX_H ================================================ FILE: src/main.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace std; std::string work_dir; CONFIG* config; LANG selected_lang = LANG::UNKNOWN_LANG; BIN_TYPE type; LOCAL_CONFIG* usr_config; vector packages = { new OS_PACKAGE{"git", "git"}, new OS_PACKAGE{"cargo", "cargo"}, new OS_PACKAGE{"golang", "go"}, new OS_PACKAGE{"binutils", "c++filt"}, new OS_PACKAGE{"python3", "python3"}, new OS_PACKAGE{"python3-pip", "pip3"}, new OS_PACKAGE{"patch", "patch"}, new PIP3_PACKAGE{"pyinstaller", "pyinstaller"}, new GIT_PACKAGE{"radare2", "radare2", "https://github.com/radareorg/radare2", 0, "cd .. ; mv radare2 ../ ; ../radare2/sys/install.sh", false}, new GIT_PACKAGE{"Goliath", "goliath", "https://github.com/h311d1n3r/Goliath", 0, "cd .. ; mv Goliath ../ ; cd ../Goliath ; ./build.sh; mv ./dist/goliath "+install_dir, false, false}, new CARGO_PACKAGE{"cross", "cross"}, new CUSTOM_PACKAGE{"rust", "rustup", "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust_install.sh ; sh +x rust_install.sh -y ; rm rust_install.sh"} }; void global_init() { elf_version(EV_CURRENT); curl_global_init(CURL_GLOBAL_ALL); string home_dir = string(getenv("HOME")); replace_all((string&)install_dir, "~", home_dir); if(!fs::exists(install_dir)) fs::create_directories(install_dir); const char* current_path = getenv("PATH"); string new_path = string(current_path)+string(":")+install_dir; const char* go_path = getenv("GOPATH"); if(go_path && strlen(go_path)) new_path+=string(":")+string(go_path)+string("/bin"); else new_path+=":"+home_dir+"/go/bin"; setenv("PATH", new_path.c_str(), 1); } void global_exit() { curl_global_cleanup(); } bool install_dependencies(BinaryHandler* handler) { DependencyManager dep_manager(usr_config, work_dir); std::vector os_packages; std::vector git_packages; std::vector pip3_packages; std::vector cargo_packages; std::vector go_packages; std::vector custom_packages; for(PACKAGE* package : packages) { if(!dep_manager.is_package_installed(package)) { switch(package->package_type) { case 0: os_packages.push_back((OS_PACKAGE*) package); break; case 1: pip3_packages.push_back((PIP3_PACKAGE*) package); break; case 2: git_packages.push_back((GIT_PACKAGE*) package); break; case 3: cargo_packages.push_back((CARGO_PACKAGE*) package); break; case 4: go_packages.push_back((GO_PACKAGE*) package); break; case 5: custom_packages.push_back((CUSTOM_PACKAGE*) package); break; } } } if(!os_packages.size() && !git_packages.size() && !pip3_packages.size() && !cargo_packages.size() && !go_packages.size() && !custom_packages.size()) fcout << "$(info)No additional package is required." << endl; else { fcout << "$(info)The following packages are required :" << endl; if(os_packages.size()) { fcout << "$(bright_magenta)With $(bright_magenta:b)" << name_from_package_manager[usr_config->package_manager] << "$:" << endl; for (OS_PACKAGE *package : os_packages) { fcout << "$(magenta)- $(magenta:b)" + package->name << endl; } } if(pip3_packages.size()) { fcout << "$(bright_blue)With $(bright_blue:b)pip3$:" << endl; for (PIP3_PACKAGE *package : pip3_packages) { fcout << "$(blue)- $(blue:b)" + package->package_name << endl; } } if(git_packages.size()) { fcout << "$(bright_red)With $(bright_red:b)git$:" << endl; for (GIT_PACKAGE *package : git_packages) { fcout << "$(red)- $(red:b)" + package->repo_name << endl; } } if(cargo_packages.size()) { fcout << "$(bright_yellow)With $(bright_yellow:b)cargo$:" << endl; for (CARGO_PACKAGE *package : cargo_packages) { fcout << "$(yellow)- $(yellow:b)" + package->package_name << endl; } } if(go_packages.size()) { fcout << "$(bright_white)With $(bright_white:b)go$:" << endl; for (GO_PACKAGE *package : go_packages) { fcout << "$(white)- $(white:b)" + package->package_name << endl; } } if(custom_packages.size()) { fcout << "$(bright_green)$(bright_green:b)Custom$:" << endl; for (CUSTOM_PACKAGE *package : custom_packages) { fcout << "$(green)- $(green:b)" + package->package_name << endl; } } if(usr_config->is_root || usr_config->has_sudo) { if(usr_config->package_manager != PACKAGE_MANAGER::UNKNOWN || !os_packages.size()) { bool agrees_install = ask_yes_no("Proceed to installation ?", true); if(agrees_install) { if(!usr_config->is_root) { CommandExecutor executor("./"); string password; while(true) { password = ask_password("$(info)Input your password for sudo"); if(executor.test_password(password)) break; fcout << "$(warning)Wrong password ! Try again..." << endl; } dep_manager.set_password(password); } for(OS_PACKAGE* package : os_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } for(PIP3_PACKAGE* package : pip3_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } for(GIT_PACKAGE* package : git_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } for(CARGO_PACKAGE* package : cargo_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } for(GO_PACKAGE* package : go_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } for(CUSTOM_PACKAGE* package : custom_packages) { if(dep_manager.install_package(package)) fcout << "$(success)Done." << endl; else { fcout << "$(error)An error occurred during installation..." << endl; return false; } } } else { fcout << "$(error)Ending execution." << endl; return false; } } else { fcout << "$(error)Without a known package manager, please install these packages and try again." << endl; return false; } } else { fcout << "$(error)Without being root or having sudo, please manually install these packages and try again." << endl; return false; } } return true; } void start_analysis() { BinaryHandler* handler; Algorithm* algorithm = new PartHashAlgorithm(config); switch(type) { case PE: handler = new PeHandler(config->binary_path, work_dir, selected_lang, algorithm); break; case ELF: handler = new ElfHandler(config->binary_path, work_dir, selected_lang, algorithm); break; default: return; } if(handler->extract_architecture() == BIN_ARCH::UNKNOWN_ARCH) { fcout << "$(error)Unsupported architecture !" << endl; return; } handler->extract_image_base(); if(!install_dependencies(handler)) return; handler->strip_analysis(); if(handler->is_stripped()) fcout << "$(info)File was found to be $(info:b)stripped$." << endl; else { fcout << "$(warning)File was not found to be $(warning:b)stripped$..." << endl; if(!ask_yes_no("Resume analysis ?", false)) return; } fcout << "$(info)Extracting libraries..." << endl; size_t libs_amount = handler->libs_extraction(); if(!libs_amount) { fcout << "$(error)No libraries were found." << endl; return; } fcout << "$(success)Identified $(success:b)" << to_string(libs_amount) << "$ libraries." << endl; std::vector>& libs = handler->get_libs(); LibManager lib_manager(libs); lib_manager.manage(); if(!libs.size()) { fcout << "$(error)No libraries remaining." << endl; return; } fcout << "$(info)Installing libraries..." << endl; size_t libs_installed = handler->libs_installation(); if(!libs_installed) { fcout << "$(error)No libraries were successfully installed..." << endl; return; } fcout << "$(success)Installed $(success:b)" << to_string(libs_installed) << "$ libraries." << endl; fcout << "$(info)Analyzing target functions..." << endl; size_t funcs_sz = handler->functions_analysis(); if(!funcs_sz) { fcout << "$(error)No functions were successfully analyzed..." << endl; return; } fcout << "$(success)Analyzed $(success:b)" << to_string(funcs_sz) << "$ functions." << endl; fcout << "$(info)Matching with functions from libraries..." << endl; string lib_extension; switch(type) { case BIN_TYPE::ELF: lib_extension = ".so"; break; case BIN_TYPE::PE: lib_extension = ".dll"; break; } for(const auto& entry : fs::directory_iterator(work_dir)) { if (fs::is_regular_file(entry)) { fs::path file_path = entry.path(); if(ends_with(file_path.filename(), lib_extension)) { handler->functions_matching(file_path.string()); } } } handler->post_matching(); size_t matches_sz = handler->get_matches_sz(); if(!matches_sz) { fcout << "$(error)No functions were successfully matched..." << endl; return; } fcout << "$(success)Matched $(success:b)" << to_string(matches_sz) << "$ functions. Matching rate: $(success:b)" << to_string((uint8_t)(matches_sz/(float)funcs_sz*100)) << "%" << endl; fcout << "$(info)Demangling function names..." << endl; handler->demangle_functions(); fcout << "$(success)Done !" << endl; fcout << "$(info)Writing output file..." << endl; if(handler->write_output(config->output_path)) fcout << "$(success)Done !" << endl; else fcout << "$(error)An error occurred when writing to output file..." << endl; } std::string generate_work_dir() { uuid_t uuid; uuid_generate_random(uuid); char uuid_str[37]; uuid_unparse_lower(uuid, uuid_str); work_dir = ".cerberus-"+string(uuid_str, 37).substr(0, 16); fs::create_directory(work_dir); return work_dir; } int main(int argc, char *argv[]) { global_init(); ArgParser parser; config = parser.compute_args(argc, argv); COMMANDS_DEBUG_MODE = config->debug; NO_PROMPT = config->no_prompt; if(config) { fcout << "$(bright_red:b)---------- $(red:b)" << TOOL_NAME << " (v" << TOOL_VERSION << ")$ ----------" << endl; usr_config = identify_local_config(); if(usr_config->package_manager != PACKAGE_MANAGER::UNKNOWN) fcout << "$(info)Identified package manager: $(info:b)" << name_from_package_manager[usr_config->package_manager] << endl; else fcout << "$(warning)Could not identify package manager..." << endl; if(usr_config->is_root) fcout << "$(info)Running as root." << endl; else if(usr_config->has_sudo) fcout << "$(info)Not running as root but sudo detected." << endl; else fcout << "$(warning)Not running as root and sudo couldn't be detected." << endl; type = identify_binary(config->binary_path); if(type == BIN_TYPE::UNKNOWN_TYPE) { fcout << "$(critical)The input file $(critical:u)" << config->binary_path << "$ couldn't be recognized..." << endl; return 1; } fcout << "$(info)Identified file as $(info:b)" << bin_type_names[type] << "$." << endl; LangIdentifier identifier(config->binary_path); value_ordered_map langs = identifier.identify(); if(langs.at(0).second) { fcout << "$(info)Identified language : $(magenta:b)" << name_from_lang[langs.at(0).first] << endl; bool agree_lang = ask_yes_no("Continue analysis with this language ?", true); if(agree_lang) selected_lang = langs.at(0).first; } else fcout << "$(warning)Couldn't identify language automatically..." << endl; if(selected_lang == LANG::UNKNOWN_LANG) { fcout << "$(info)Currently supported languages :\n"; for(size_t lang_i = 0; lang_i < langs.size(); lang_i++) { fcout << "$(magenta)" << to_string(lang_i+1) << ". $(magenta:b)" << name_from_lang[langs.at(lang_i).first] << endl; } uint8_t selected_lang_i = ask_n("$(info)Your choice ?", 1, langs.size()); selected_lang = langs.at(selected_lang_i-1).first; } work_dir = generate_work_dir(); fcout << "$(info)Using $(magenta:b)" << name_from_lang[selected_lang] << "$ for analysis." << endl; start_analysis(); fs::remove_all(work_dir); } global_exit(); return 0; } ================================================ FILE: src/types/value_ordered_map.h ================================================ #ifndef CERBERUS_VALUE_ORDERED_MAP_H #define CERBERUS_VALUE_ORDERED_MAP_H #include #include #include template class value_ordered_map { private: std::vector> map; public: ValueType& operator[](const KeyType key) { for(std::pair& pair : this->map) { if(pair.first == key) return pair.second; } std::pair new_pair = std::pair(key, 0); this->map.push_back(new_pair); return this->map.at(this->map.size()-1).second; } void sort() { std::sort(map.begin(), map.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); } void invert_sort() { std::sort(map.begin(), map.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); } std::pair at(int64_t i) { return this->map[i]; } size_t size() { return this->map.size(); } }; #endif //CERBERUS_VALUE_ORDERED_MAP_H ================================================ FILE: src/user/dependencies/dependency_manager.cpp ================================================ #include #include using namespace std; map install_commands = { {PACKAGE_MANAGER::APT, "apt -y install %s"}, {PACKAGE_MANAGER::APT_GET, "apt-get -y install %s"}, {PACKAGE_MANAGER::DNF, "dnf -y install %s"}, {PACKAGE_MANAGER::YUM, "yum -y install %s"}, {PACKAGE_MANAGER::ZYPPER, "zypper -y install %s"}, {PACKAGE_MANAGER::PACMAN, "pacman -y -S %s"}, {PACKAGE_MANAGER::PORTAGE, "emerge -y %s"}, {PACKAGE_MANAGER::SLACKPKG, "slackpkg -y install %s"}, {PACKAGE_MANAGER::SWARET, "swaret -y --install %s"}, {PACKAGE_MANAGER::XBPS, "xbps-install -y -S %s"}, {PACKAGE_MANAGER::APK, "apk -y add %s"}, {PACKAGE_MANAGER::NIX, "nix-env -y -iA nixpkgs.%s"}, {PACKAGE_MANAGER::PETGET, "petget -y %s.pet"} }; void DependencyManager::set_password(string password) { this->password = password; } bool DependencyManager::is_package_installed(PACKAGE* package) { return is_binary_on_path(package->binary); } bool DependencyManager::install_package(OS_PACKAGE* package) { fcout << "$(info)Installing $(info:b)" << package->name << "$..." << endl; string command = install_commands[config->package_manager]; char formatted_cmd_buf[command.length() - 2 + package->name.length() + 1]; snprintf(formatted_cmd_buf, sizeof(formatted_cmd_buf), command.c_str(), package->name.c_str()); string formatted_cmd(formatted_cmd_buf); if(config->is_root) { COMMAND_RESULT res; executor.execute_command(formatted_cmd, &res); return res.code == 0; } else if(config->has_sudo) { COMMAND_RESULT res; executor.execute_command(string("echo ")+password+string(" | sudo -S ")+formatted_cmd, &res); return res.code == 0; } return false; } bool DependencyManager::install_package(PIP3_PACKAGE *package) { fcout << "$(info)Installing $(info:b)" << package->package_name << "$..." << endl; COMMAND_RESULT res; executor.execute_command(string("pip3 install ")+package->package_name, &res); return !res.code; } bool DependencyManager::install_package(GIT_PACKAGE* package) { fcout << "$(info)Installing $(info:b)" << package->repo_name << "$..." << endl; COMMAND_RESULT res; executor.execute_command(string("git clone ")+package->url, &res); if(res.code != 0) return false; string build_cmd = "mkdir build; cd build; cmake ..; make; make install"; if(package->custom_command.size()) build_cmd = package->custom_command; if(!config->is_root && package->needs_root) build_cmd = string("echo ")+password+string(" | sudo -S sh -c \"unset SUDO_USER ; ") + build_cmd + string("\""); executor.execute_command(string("cd ")+package->repo_name+string("; ")+build_cmd, &res); if(package->remove_dir) filesystem::remove_all(work_dir+string("/")+package->repo_name); return res.code == package->success_code; } bool DependencyManager::install_package(CARGO_PACKAGE *package) { fcout << "$(info)Installing $(info:b)" << package->package_name << "$..." << endl; COMMAND_RESULT res; executor.execute_command(string("cargo install ")+package->package_name, &res); return !res.code; } bool DependencyManager::install_package(GO_PACKAGE *package) { fcout << "$(info)Installing $(info:b)" << package->package_name << "$..." << endl; COMMAND_RESULT res; executor.execute_command(string("go install ")+package->url+string("@latest"), &res); return !res.code; } bool DependencyManager::install_package(CUSTOM_PACKAGE *package) { fcout << "$(info)Installing $(info:b)" << package->package_name << "$..." << endl; COMMAND_RESULT res; executor.execute_command(string("sh -c \"") + package->command + string("\""), &res); return res.code == package->success_code; } ================================================ FILE: src/user/dependencies/dependency_manager.h ================================================ #ifndef CERBERUS_DEPENDENCY_MANAGER_H #define CERBERUS_DEPENDENCY_MANAGER_H #include #include #include #include struct PACKAGE { uint8_t package_type; std::string binary; }; struct OS_PACKAGE : PACKAGE { std::string name; OS_PACKAGE(std::string name, std::string binary) { PACKAGE::package_type = 0; PACKAGE::binary = binary; OS_PACKAGE::name = name; } }; struct PIP3_PACKAGE : PACKAGE { std::string package_name; PIP3_PACKAGE(std::string binary, std::string package_name) { PACKAGE::package_type = 1; PACKAGE::binary = binary; PIP3_PACKAGE::package_name = package_name; } }; struct GIT_PACKAGE : PACKAGE { std::string repo_name; std::string url; std::string custom_command = ""; std::int32_t success_code = 0; bool remove_dir = true; bool needs_root = true; GIT_PACKAGE(std::string repo_name, std::string binary, std::string url) { package_type = 2; PACKAGE::binary = binary; GIT_PACKAGE::repo_name = repo_name; GIT_PACKAGE::url = url; } GIT_PACKAGE(std::string repo_name, std::string binary, std::string url, int32_t success_code) : GIT_PACKAGE(repo_name, binary, url) { GIT_PACKAGE::success_code = success_code; } GIT_PACKAGE(std::string repo_name, std::string binary, std::string url, int32_t success_code, std::string custom_command) : GIT_PACKAGE(repo_name, binary, url, success_code) { GIT_PACKAGE::custom_command = custom_command; } GIT_PACKAGE(std::string repo_name, std::string binary, std::string url, int32_t success_code, std::string custom_command, bool remove_dir) : GIT_PACKAGE(repo_name, binary, url, success_code, custom_command) { GIT_PACKAGE::remove_dir = remove_dir; } GIT_PACKAGE(std::string repo_name, std::string binary, std::string url, int32_t success_code, std::string custom_command, bool remove_dir, bool needs_root) : GIT_PACKAGE(repo_name, binary, url, success_code, custom_command, remove_dir) { GIT_PACKAGE::needs_root = needs_root; } }; struct CARGO_PACKAGE : PACKAGE { std::string package_name; CARGO_PACKAGE(std::string binary, std::string package_name) { PACKAGE::package_type = 3; PACKAGE::binary = binary; CARGO_PACKAGE::package_name = package_name; } }; struct GO_PACKAGE : PACKAGE { std::string package_name; std::string url; GO_PACKAGE(std::string binary, std::string package_name, std::string url) { PACKAGE::package_type = 4; PACKAGE::binary = binary; GO_PACKAGE::package_name = package_name; GO_PACKAGE::url = url; } }; struct CUSTOM_PACKAGE : PACKAGE { std::string package_name; std::string command; int32_t success_code = 0; CUSTOM_PACKAGE(std::string package_name, std::string binary, std::string command) { PACKAGE::package_type = 5; PACKAGE::binary = binary; CUSTOM_PACKAGE::package_name = package_name; CUSTOM_PACKAGE::command = command; } CUSTOM_PACKAGE(std::string package_name, std::string binary, std::string command, int32_t success_code) : CUSTOM_PACKAGE(package_name, binary, command) { CUSTOM_PACKAGE::success_code = success_code; } }; extern std::map install_commands; class DependencyManager { private: LOCAL_CONFIG* config; std::string work_dir; CommandExecutor executor; std::string password; public: DependencyManager(LOCAL_CONFIG* config, std::string work_dir) : config(config), work_dir(work_dir), executor(work_dir) {}; void set_password(std::string password); bool is_package_installed(PACKAGE* package); bool install_package(OS_PACKAGE* package); bool install_package(GIT_PACKAGE* package); bool install_package(PIP3_PACKAGE* package); bool install_package(CARGO_PACKAGE* package); bool install_package(GO_PACKAGE* package); bool install_package(CUSTOM_PACKAGE* package); }; #endif //CERBERUS_DEPENDENCY_MANAGER_H ================================================ FILE: src/user/local_config.cpp ================================================ #include #include #include using namespace std; map name_from_package_manager = { {UNKNOWN, "your package manager"}, {APT, "apt"}, {APT_GET, "apt-get"}, {DNF, "dnf"}, {YUM, "yum"}, {ZYPPER, "zypper"}, {PACMAN, "pacman"}, {PORTAGE, "emerge"}, {SLACKPKG, "slackpkg"}, {SWARET, "swaret"}, {XBPS, "xbps-install"}, {APK, "apk"}, {NIX, "nix-env"}, {PETGET, "petget"} }; PACKAGE_MANAGER find_package_manager() { for(pair manager : name_from_package_manager) { if(is_binary_on_path(manager.second)) return manager.first; } return PACKAGE_MANAGER::UNKNOWN; } bool is_root() { return geteuid() == 0; } bool has_sudo() { return is_binary_on_path("sudo"); } LOCAL_CONFIG* identify_local_config() { LOCAL_CONFIG* config = new LOCAL_CONFIG(); config->package_manager = find_package_manager(); config->is_root = is_root(); config->has_sudo = has_sudo(); return config; } bool is_binary_on_path(string binary) { const char* path_env = std::getenv("PATH"); if (path_env == nullptr) return false; string path_env_str(path_env); vector paths = split_string(path_env_str,':'); for(string path : paths) { string full_path = path + '/' + binary; if (access(full_path.c_str(), X_OK) == 0) return true; } return false; } ================================================ FILE: src/user/local_config.h ================================================ #ifndef CERBERUS_LOCAL_CONFIG_H #define CERBERUS_LOCAL_CONFIG_H #include #include enum PACKAGE_MANAGER { UNKNOWN, APT, APT_GET, DNF, YUM, ZYPPER, PACMAN, PORTAGE, SLACKPKG, SWARET, XBPS, APK, NIX, PETGET }; struct LOCAL_CONFIG { PACKAGE_MANAGER package_manager; bool is_root; bool has_sudo; }; extern std::map name_from_package_manager; LOCAL_CONFIG* identify_local_config(); bool is_binary_on_path(std::string binary); #endif //CERBERUS_LOCAL_CONFIG_H ================================================ FILE: src/user/user_prompt.cpp ================================================ #include #include #include #include #include #include using namespace std; bool NO_PROMPT = false; bool ask_yes_no(string question, bool should_yes) { if(NO_PROMPT) return should_yes; fcout << "$(info)" << question << " (" << (should_yes?"Y":"y") << "/" << (should_yes?"n":"N") << ") $"; string response; getline(cin, response); if(!response.size()) return should_yes; if(should_yes) { if(tolower(response.at(0), locale()) == 'n') return false; return true; } else { if(tolower(response.at(0), locale()) == 'y') return true; return false; } } uint8_t ask_n(string question, uint8_t min, uint8_t max) { if(NO_PROMPT) return min; fcout << "$(info)" << question << " (" << to_string(min) << "-" << to_string(max) << ") $"; string response; uint8_t response_i = max + 1; while(response_i < min || response_i > max) { getline(cin, response); while (!string_to_int(response, response_i)) { fcout << "$(error)Wrong value, please try again (" << to_string(min) << "-" << to_string(max) << ") $"; getline(cin, response); } if(response_i < min || response_i > max) fcout << "$(error)Value is not in range, please try again (" << to_string(min) << "-" << to_string(max) << ") $"; } return response_i; } string ask_password(string question) { termios oldt; tcgetattr(STDIN_FILENO, &oldt); termios newt = oldt; newt.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &newt); fcout << "$(info)" << question << ": $"; string password; getline(cin, password); fcout << endl; tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return password; } ================================================ FILE: src/user/user_prompt.h ================================================ #ifndef CERBERUS_USER_PROMPT_H #define CERBERUS_USER_PROMPT_H #include #include extern bool NO_PROMPT; bool ask_yes_no(std::string question, bool should_yes); uint8_t ask_n(std::string question, uint8_t min, uint8_t max); std::string ask_password(std::string question); #endif //CERBERUS_USER_PROMPT_H ================================================ FILE: src/utils/arg_parser.cpp ================================================ #include #include #include #include namespace fs = std::filesystem; using namespace std; string ArgParser::format_help() { string res = "$(cyan)$(cyan:b)" + TOOL_ART + "$\n"; res += " Version: $(white:b)" + TOOL_VERSION + "$\n"; res += " Author: $(white:b)" + TOOL_AUTHOR + "$\n"; res += "______________________________________\n\n"; res += "$(cyan:b)Syntax: $(red:b)cerberus binary [-param value] [--flag]$$\n\n"; res += "$(cyan:b)Parameters:$\n"; res += " $(red:b)output (o)$ -> Specifies the path for the resulting executable file. $(cyan:b)Default value: [input_binary]-patched$\n"; res += " $(red:b)part_hash_len (phl)$ -> Specifies the length of a part hash. The part hash of a function is just a reduction of the function with a linear pace. This technique is used to prevent fixed addresses from corrupting a standard hash. $(cyan:b)Default value: 20$\n"; res += " $(red:b)part_hash_trust (pht)$ -> Specifies minimum ratio of similarity between the two hashed functions to compare. The kept function will be the one with the most matches anyway. Increasing this value will reduce the number of matched functions but speed up execution time. $(cyan:b)Default value: 0.6$\n"; res += " $(red:b)min_func_size (mfs)$ -> Specifies the minimum length a function must be to get analyzed. Decreasing this value will increase matches but also false positives. $(cyan:b)Default value : 10$\n\n"; res += "$(cyan:b)Flags:$\n"; res += " $(red:b)help (h)$ -> Displays this message.\n"; res += " $(red:b)debug (h)$ -> Displays outputs of commands.\n"; res += " $(red:b)no-prompt (np)$ -> Automatically skips user prompts."; return res; } void ArgParser::prepare_args() { this->parser.add_argument("binary").default_value(""); this->parser.add_argument("-output", "-o"); this->parser.add_argument("-part_hash_len", "-phl"); this->parser.add_argument("-part_hash_trust", "-pht"); this->parser.add_argument("-min_func_size", "-mfs"); this->parser.add_argument("--help", "--h").implicit_value(true); this->parser.add_argument("--debug", "--dbg").implicit_value(true); this->parser.add_argument("--no-prompt", "--np").implicit_value(true); } CONFIG* ArgParser::compute_args(int argc, char **argv) { CONFIG* config = new CONFIG; if(argc >= 2) { this->parser.parse_args(argc, argv); config->binary_path = this->parser.get("binary"); if(this->parser.is_used("-output")) config->output_path = this->parser.get("output"); else { size_t extension_idx; string extension = ""; if((extension_idx = config->binary_path.find_last_of('.')) != string::npos) { if(config->binary_path.find_last_of('/') < extension_idx && config->binary_path.find_last_of('\\') < extension_idx) { extension = config->binary_path.substr(extension_idx); } } config->output_path = config->binary_path + extension + "-patched"; } if (this->parser.is_used("-part_hash_len")) config->part_hash_len = this->parser.get("part_hash_len"); if (this->parser.is_used("-part_hash_trust")) config->part_hash_trust = this->parser.get("part_hash_trust"); if (this->parser.is_used("-min_func_size")) config->min_func_size = this->parser.get("min_func_size"); if (this->parser.is_used("--debug")) config->debug = true; if (this->parser.is_used("--no-prompt")) config->no_prompt = true; } if(argc < 2 || this->parser.is_used("--help") || !config->binary_path.length()) { string help = this->format_help(); fcout << help << endl; exit(0); } if(!fs::exists(config->binary_path)) { fcout << "$(critical)File $(critical:u)" << config->binary_path << "$ does not exist !" << endl; exit(1); } return config; } ArgParser::ArgParser() { this->parser = argparse::ArgumentParser("cerberus"); this->prepare_args(); } ================================================ FILE: src/utils/arg_parser.h ================================================ #ifndef CERBERUS_ARG_PARSER_H #define CERBERUS_ARG_PARSER_H #include #include class ArgParser { private: argparse::ArgumentParser parser; std::string format_help(); void prepare_args(); public: ArgParser(); CONFIG* compute_args(int argc, char *argv[]); }; #endif //CERBERUS_ARG_PARSER_H ================================================ FILE: src/utils/config.h ================================================ #ifndef CERBERUS_CONFIG_H #define CERBERUS_CONFIG_H #include #include struct CONFIG { std::string binary_path; std::string output_path; uint16_t part_hash_len = 20; float part_hash_trust = 0.6; uint16_t min_func_size = 10; bool debug = false; bool no_prompt = false; }; #endif //CERBERUS_CONFIG_H ================================================ FILE: src/utils/convert.cpp ================================================ #include #include #include using namespace std; vector split_string(const string& input, char delimiter) { vector tokens; istringstream token_stream(input); string token; while (getline(token_stream, token, delimiter)) { tokens.push_back(token); } return tokens; } vector filter_empty_strings(const vector& tab) { vector result; for (const std::string& s : tab) { if (!s.empty()) { result.push_back(s); } } return result; } string strip(const string& str) { size_t first = str.find_first_not_of(" \t\n"); if (string::npos == first) { return ""; } size_t last = str.find_last_not_of(" \t\n"); return str.substr(first, (last - first + 1)); } bool ends_with(string const &value, string const &ending) { if (ending.size() > value.size()) return false; return equal(ending.rbegin(), ending.rend(), value.rbegin()); } void replace_all(string& str, const string& from, const string& to) { if(from.empty()) return; size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); } } string demangle_function_name(string& mangled_name) { replace_all(mangled_name, string("$"), string("\\$")); CommandExecutor executor("./"); COMMAND_RESULT res; executor.execute_command(string("c++filt \"")+mangled_name+string("\""), &res); if(!res.code) return res.response.substr(0, res.response.length()-1); return mangled_name; } ================================================ FILE: src/utils/convert.h ================================================ #ifndef CERBERUS_CONVERT_H #define CERBERUS_CONVERT_H #include #include template bool string_to_int(std::string s, IntType& val) { try { if (s.size() >= 2 && s.substr(0, 2) == "0x") val = static_cast(stoll(s.substr(2), nullptr, 16)); else val = static_cast(stoll(s, nullptr, 10)); return true; } catch (const std::exception& e) { return false; } } std::vector split_string(const std::string& input, char delimiter); std::vector filter_empty_strings(const std::vector& tab); std::string strip(const std::string& str); bool ends_with(std::string const& value, std::string const& ending); void replace_all(std::string& str, const std::string& from, const std::string& to); std::string demangle_function_name(std::string& mangled_name); #endif //CERBERUS_CONVERT_H ================================================ FILE: src/utils/file_downloader.cpp ================================================ #include using namespace std; FileDownloader::FileDownloader() { curl = curl_easy_init(); } FileDownloader::~FileDownloader() { curl_easy_cleanup(curl); } size_t write_callback(void* contents, size_t size, size_t nmemb, FILE* file) { return fwrite(contents, size, nmemb, file); } bool FileDownloader::download_file(string url, string path) { FILE* file = fopen(path.c_str(), "wb"); if (file) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); CURLcode res = curl_easy_perform(curl); fclose(file); if (res == CURLE_OK) return true; } return false; } ================================================ FILE: src/utils/file_downloader.h ================================================ #ifndef CERBERUS_FILE_DOWNLOADER_H #define CERBERUS_FILE_DOWNLOADER_H #include #include class FileDownloader { private: CURL* curl; public: FileDownloader(); ~FileDownloader(); bool download_file(std::string url, std::string path); }; #endif //CERBERUS_FILE_DOWNLOADER_H ================================================ FILE: src/utils/file_operations.cpp ================================================ #include #include #include #include #include #include #include #include #include using namespace std; bool decompress_gzip_file(string input, string output) { ifstream input_file(input, ios::binary); input_file.seekg(0, ios::end); size_t input_file_sz = input_file.tellg(); input_file.seekg(0); char* data = (char*) malloc(input_file_sz); if(!data) return false; input_file.read(data, input_file_sz); input_file.close(); if(!gzip::is_compressed(data, input_file_sz)) return false; string decompressed = gzip::decompress(data, input_file_sz); ofstream output_file(output, ios::binary); output_file.write(decompressed.c_str(), decompressed.size()); output_file.close(); free(data); return true; } bool decompress_tar_file(string input, string output) { struct archive* a = archive_read_new(); archive_read_support_format_all(a); if (archive_read_open_filename(a, input.c_str(), 10240) != ARCHIVE_OK) { return false; } if (!filesystem::create_directory(output)) { return false; } struct archive_entry* entry; while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { string entry_name = string(archive_entry_pathname(entry)); size_t first_separator_index = entry_name.find('/'); if(first_separator_index == string::npos) continue; entry_name = entry_name.substr(first_separator_index+1); string output_file_path = string(output) + "/" + entry_name; size_t last_separator_index = output_file_path.rfind('/'); if(last_separator_index != string::npos) { string directory_part = output_file_path.substr(0, last_separator_index); filesystem::create_directories(directory_part); } int outputFd = open(output_file_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (outputFd == -1) { continue; } const void* buffer; size_t size; int64_t offset; while (archive_read_data_block(a, &buffer, &size, &offset) == ARCHIVE_OK) { write(outputFd, buffer, size); } close(outputFd); } archive_read_close(a); archive_read_free(a); return true; } ================================================ FILE: src/utils/file_operations.h ================================================ #ifndef CERBERUS_FILE_OPERATIONS_H #define CERBERUS_FILE_OPERATIONS_H #include bool decompress_gzip_file(std::string input, std::string output); bool decompress_tar_file(std::string input, std::string output); #endif //CERBERUS_FILE_OPERATIONS_H ================================================ FILE: src/utils/logger.cpp ================================================ #include #include using namespace std; FCout fcout; map LOG_COLORS = { {"black", 30}, {"red", 31}, {"green", 32}, {"yellow", 33}, {"blue", 34}, {"magenta", 35}, {"cyan", 36}, {"white", 37}, {"bright_black", 90}, {"bright_red", 91}, {"bright_green", 92}, {"bright_yellow", 93}, {"bright_blue", 94}, {"bright_magenta", 95}, {"bright_cyan", 96}, {"bright_white", 97} }; map> LOG_LEVELS = { {"success", pair(32, '+')}, {"info", pair(36, '*')}, {"debug", pair(34, '~')}, {"warning", pair(33, '#')}, {"error", pair(35, '?')}, {"critical", pair(31, '!')} }; map LOG_STYLES = { {'n', 0}, {'b', 1}, {'i', 3}, {'u', 4}, {'s', 9}, }; FCout FCout::operator<<(string s) { format(s); std::cout << s; fcout = *this; return *this; } FCout FCout::operator<<(std::ostream&(*pManip)(std::ostream&)) { cout << *pManip << "\033[0;"+to_string(LOG_COLORS["gray"])+"m"; args_stack.clear(); fcout = *this; return *this; } void FCout::format(std::string& s) { size_t index; string res = ""; while((index = s.find('$')) != string::npos) { res += s.substr(0, index); if(index < s.length()-1 && s.at(index+1) == '(') { string arg_name = s.substr(index+2); arg_name = arg_name.substr(0, arg_name.find(')')); int index2; uint8_t font_mode = 0; bool has_font_mode = false; if((index2 = arg_name.find(":")) != string::npos) { has_font_mode = true; uint8_t font_mode_chr = arg_name.at(index2+1); if(LOG_STYLES.find(font_mode_chr) != LOG_STYLES.end()) { font_mode = LOG_STYLES[font_mode_chr]; } arg_name = arg_name.substr(0, arg_name.length()-2); } if(LOG_COLORS.find(arg_name) != LOG_COLORS.end()) { args_stack.push_back(pair(LOG_COLORS[arg_name], font_mode)); res += "\033["+to_string(font_mode)+";"+to_string(LOG_COLORS[arg_name])+"m"; } else if(LOG_LEVELS.find(arg_name) != LOG_LEVELS.end()) { pair level = LOG_LEVELS[arg_name]; res += "\033["+to_string(font_mode)+";"+to_string(level.first)+"m"; if(!this->args_stack.size() || this->args_stack.at(this->args_stack.size()-1).first != level.first) res += string("[")+(char)level.second+"] "; args_stack.push_back(pair(level.first, font_mode)); } s = s.substr(index+3+arg_name.length()+(has_font_mode?2:0)); } else if(args_stack.size() > 0) { args_stack.pop_back(); pair arg; if(args_stack.size() > 0) arg = args_stack.at(args_stack.size()-1); else arg = pair(LOG_COLORS["gray"], 0); res += "\033["+to_string(arg.second)+";"+to_string(arg.first)+"m"; s = s.substr(index+1); } } res += s; s = res; } ================================================ FILE: src/utils/logger.h ================================================ #ifndef CERBERUS_LOGGER_H #define CERBERUS_LOGGER_H #include #include #include #include extern std::map LOG_COLORS; extern std::map> LOG_LEVELS; extern std::map LOG_STYLES; class FCout { private: std::vector> args_stack; public: FCout operator<<(std::string s); FCout operator<<(std::ostream&(*pManip)(std::ostream&)); void format(std::string& s); }; extern FCout fcout; #endif //CERBERUS_LOGGER_H ================================================ FILE: src/utils/search.cpp ================================================ #include using namespace std; vector search_regex(char* data, size_t data_sz, string pattern, size_t match_max_sz) { vector matches; regex reg(pattern); size_t data_i = 0; while(data_i < data_sz) { string data_str(data+data_i, 1024+match_max_sz); sregex_iterator it(data_str.begin(), data_str.end(), reg); sregex_iterator end; while (it != end) { smatch match = *it; matches.push_back(match.str()); ++it; } data_i += 1024; } return matches; } ================================================ FILE: src/utils/search.h ================================================ #ifndef CERBERUS_SEARCH_H #define CERBERUS_SEARCH_H #include #include std::vector search_regex(char* data, size_t data_sz, std::string pattern, size_t match_max_sz); #endif //CERBERUS_SEARCH_H ================================================ FILE: test/Go/test_1/result.txt ================================================ github.com/fatih/color.colorPrint crypto/aes.NewCipher ================================================ FILE: test/Go/test_1/src/go.mod ================================================ module test_1 go 1.18 require ( github.com/fatih/color v1.15.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect golang.org/x/sys v0.6.0 // indirect ) ================================================ FILE: test/Go/test_1/src/go.sum ================================================ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ================================================ FILE: test/Go/test_1/src/test_1.go ================================================ package main import ( "github.com/fatih/color" "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "fmt" "io" ) func main() { fmt.Println("Hey ! Starting the program...") key := []byte("The secret key !") plaintext := []byte("Message to encrypt") iv := make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } block, err := aes.NewCipher(key) if err != nil { panic(err) } mode := cipher.NewCBCEncrypter(block, iv) plaintext = padPKCS7(plaintext, aes.BlockSize) ciphertext := make([]byte, len(plaintext)) mode.CryptBlocks(ciphertext, plaintext) ciphertext = append(iv, ciphertext...) encodedText := base64.StdEncoding.EncodeToString(ciphertext) fmt.Println("Encrypted text (base64):", encodedText) decodedText, err := base64.StdEncoding.DecodeString(encodedText) if err != nil { panic(err) } iv = decodedText[:aes.BlockSize] ciphertext = decodedText[aes.BlockSize:] mode = cipher.NewCBCDecrypter(block, iv) decryptedText := make([]byte, len(ciphertext)) mode.CryptBlocks(decryptedText, ciphertext) decryptedText = unpadPKCS7(decryptedText) fmt.Println("Decrypted text:", string(decryptedText)) color.Cyan("Prints text in cyan.") } func padPKCS7(data []byte, blockSize int) []byte { padding := blockSize - (len(data) % blockSize) padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(data, padText...) } func unpadPKCS7(data []byte) []byte { length := len(data) unpadding := int(data[length-1]) return data[:length-unpadding] } ================================================ FILE: test/Rust/test_1/Cargo.toml ================================================ [package] name = "test_1" version = "0.1.0" edition = "2021" [dependencies] aes = "0.8.2" [profile.release] strip = true ================================================ FILE: test/Rust/test_1/result.txt ================================================ aes::soft::fixslice::aes128_encrypt ::new ================================================ FILE: test/Rust/test_1/src/main.rs ================================================ use aes::Aes128; use aes::cipher::{ BlockEncrypt, BlockDecrypt, KeyInit, generic_array::GenericArray, }; fn main() { let key = GenericArray::from([0u8; 16]); let mut block = GenericArray::from([42u8; 16]); let cipher = Aes128::new(&key); let block_copy = block.clone(); cipher.encrypt_block(&mut block); cipher.decrypt_block(&mut block); assert_eq!(block, block_copy); let mut blocks = [block; 100]; cipher.encrypt_blocks(&mut blocks); for block in blocks.iter_mut() { cipher.decrypt_block(block); assert_eq!(block, &block_copy); } cipher.decrypt_blocks(&mut blocks); for block in blocks.iter_mut() { cipher.encrypt_block(block); assert_eq!(block, &block_copy); } }