├── .gitignore ├── .gitmodules ├── 3RD-PARTY.md ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── obfuscator.hpp ├── obfuscator ├── obfs │ ├── fsm.hpp │ ├── random.hpp │ ├── sequence.hpp │ └── string.hpp └── obfuscator.hpp ├── sample ├── CMakeLists.txt ├── random.cpp ├── state_machine.cpp └── string_obfs.cpp ├── script ├── Dockerfile.bionic ├── __init__.py ├── azure-pipelines-template-mac.yml ├── azure-pipelines-template-unix.yml ├── azure-pipelines-template-win.yml ├── merge.py └── string_obfs_tester.py └── test ├── CMakeLists.txt ├── impl ├── fsm.cpp ├── random.cpp ├── sequence.cpp └── string.cpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # editor 2 | .vscode 3 | 4 | # build 5 | build 6 | 7 | # cl with debug option 8 | *.ilk 9 | *.obj 10 | *.pdb 11 | 12 | # sample program 13 | *.exe 14 | 15 | # python cache 16 | __pycache__ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Catch2"] 2 | path = external/Catch2 3 | url = https://github.com/catchorg/Catch2 4 | -------------------------------------------------------------------------------- /3RD-PARTY.md: -------------------------------------------------------------------------------- 1 | cpp-obfuscator uses Catch2 for testing obfuscator modules 2 | 3 | Boost Software License - Version 1.0 - August 17th, 2003 4 | 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | 27 | --- -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 YoungJoong Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-obfuscator 2 | [![License](https://img.shields.io/badge/Licence-MIT-blue.svg)](https://github.com/revsic/cpp-obfuscator/blob/master/LICENSE) 3 | [![Build Status](https://dev.azure.com/revsic99/cpp-obfuscator/_apis/build/status/revsic.cpp-obfuscator?branchName=master)](https://dev.azure.com/revsic99/cpp-obfuscator/_build/latest?definitionId=3&branchName=master) 4 | 5 | C++ implementation of compile time obfuscator 6 | 7 | ## Compiler Support 8 | | Compiler | Version | 9 | | -- | -- | 10 | | Visual Studio | 2019 | 11 | | Clang++ | 8.0 | 12 | | g++ | 7.3 | 13 | | Apple Clang | Mojave | 14 | 15 | ## Usage 16 | 17 | Use existing single header [obfuscator.hpp](./obfuscator.hpp) or run [script](./script/merge.py) to merge multiple headers in directory [obfuscator](./obfuscator). 18 | 19 | ```bash 20 | python -m script.merge 21 | ``` 22 | 23 | Include [obfuscator.hpp](./obfuscator.hpp) to use it. 24 | ```C++ 25 | #include "../obfuscator.hpp" 26 | 27 | template 28 | constexpr char xor_(char c) { 29 | return c ^ key; 30 | } 31 | 32 | std::cout << obfs::make_string, xor_<0x50>>("Hello World !\n").decode(); 33 | ``` 34 | 35 | ## String 36 | 37 | Compile time string obfuscator. No more raw string in binary, sample [string_obfuscator](./sample/string_obfs.cpp). 38 | 39 | 0. Sample encoder, decoder 40 | ```C++ 41 | template 42 | constexpr char xor_(char c) { 43 | return c ^ key; 44 | } 45 | 46 | template 47 | constexpr char add(char c) { 48 | return c + Key; 49 | } 50 | 51 | template 52 | constexpr char comp(char c) { 53 | return f(g(c)); 54 | } 55 | ``` 56 | 57 | 1. Single encoder, decoder. 58 | ```C++ 59 | std::cout << obfs::make_string, xor_<0x50>>("Hello World !\n").decode(); 60 | ``` 61 | 62 | 2. Multiple encoder, decoder and random selection. 63 | ```C++ 64 | using table = obfs::make_table< 65 | obfs::encoder_seq, add<10>, comp, add<10>>>, 66 | obfs::decoder_seq, add<-10>, comp, xor_<0x50>>>>; 67 | 68 | std::cout << obfs::make_string("Hello World !\n").decode(); 69 | ``` 70 | 71 | 3. Multiple encoder, decoder as pair. 72 | ```C++ 73 | using table = obfs::make_pair_table< 74 | obfs::encoder_pair, xor_<0x50>>, 75 | obfs::encoder_pair, add<-10>>, 76 | obfs::encoder_pair, add<10>>, comp, xor_<0x50>>> 77 | >; 78 | 79 | std::cout << obfs::make_string
("Hello World !\n").decode(); 80 | ``` 81 | 82 | 4. String obfuscator macro. 83 | ```C++ 84 | MAKE_STRING(str, "Hello World !\n", table); 85 | std::cout << str.decode(); 86 | ``` 87 | 88 | ## Finite state machine 89 | 90 | Compile time finite state machine based routine obfuscator, sample [state_machine](./sample/state_machine.cpp). 91 | 92 | 0. Make state table. 93 | ```C++ 94 | using namespace obfs; 95 | using machine = StateMachine< 96 | Stage, 97 | Next>, 98 | Stage>, 99 | Stage>, 100 | Stage, 101 | Next>, 102 | Stage>>; 103 | ``` 104 | 105 | 1. Run state machine, each execution returns next state. 106 | ```C++ 107 | auto next1 = machine::run(state1{}, event5{}); // dummy1 executed 108 | auto next2 = machine::run(next1, event2{}); 109 | auto next3 = machine::run(next2, event3{}); // dummy2 executed 110 | auto next4 = machine::run(next3, Trigger{}); // action executed 111 | ``` -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - template: script/azure-pipelines-template-mac.yml 3 | parameters: 4 | name: macOS 5 | vmImage: macOS-10.14 6 | 7 | - template: script/azure-pipelines-template-unix.yml 8 | parameters: 9 | name: Linux 10 | vmImage: ubuntu-16.04 11 | 12 | - template: script/azure-pipelines-template-win.yml 13 | parameters: 14 | name: Windows 15 | vmImage: windows-2019 16 | -------------------------------------------------------------------------------- /obfuscator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBFUSCATOR_HPP 2 | #define OBFUSCATOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | #define COMPILE_TIME_SEQUENCE 8 | #define OBFS_FINITE_STATE_MACHINE 9 | #define COMPILE_TIME_RANDOM 10 | #define MAKE_RAND_VAL(MOD) obfs::RAND_VAL<__LINE__, MOD> 11 | #define OBFS_STRING 12 | #define MAKE_STRING(Var, String, ...) constexpr auto Var = obfs::make_string<__VA_ARGS__>(String); 13 | 14 | 15 | namespace obfs { 16 | template 17 | struct TypeVal { 18 | using value_type = T; 19 | constexpr static T value = Val; 20 | }; 21 | 22 | struct Nothing {}; 23 | 24 | template 25 | struct Sequence { 26 | using value = TypeVal; 27 | using next = Sequence; 28 | 29 | constexpr static std::size_t size = 1 + sizeof...(Others); 30 | 31 | template 32 | using index = std::conditional_t>; 33 | }; 34 | 35 | template 36 | struct Sequence { 37 | using value = TypeVal; 38 | 39 | constexpr static std::size_t size = 1; 40 | 41 | template 42 | using index = std::conditional_t; 43 | }; 44 | 45 | template 46 | struct TypeSeq { 47 | using type = T; 48 | using next = TypeSeq; 49 | 50 | constexpr static std::size_t size = 1 + sizeof...(Ts); 51 | 52 | template 53 | using index = std::conditional_t>; 54 | }; 55 | 56 | template 57 | struct TypeSeq { 58 | using type = T; 59 | 60 | constexpr static std::size_t size = 1; 61 | 62 | template 63 | using index = std::conditional_t; 64 | }; 65 | 66 | template 67 | struct MinVal { 68 | constexpr static std::size_t value = 69 | Val < MinVal::value ? Val : MinVal::value; 70 | }; 71 | 72 | template 73 | struct MinVal { 74 | constexpr static std::size_t value = Val; 75 | }; 76 | 77 | template 78 | struct SeqPack { 79 | constexpr static std::size_t size = MinVal::value; 80 | 81 | template 82 | using getter = typename U::template index; 83 | 84 | template 85 | using index = TypeSeq...>; 86 | }; 87 | 88 | struct Pass {}; 89 | 90 | template 91 | struct First { 92 | using type = std::conditional_t< 93 | std::is_same_v, 94 | typename First::type, 95 | T>; 96 | }; 97 | 98 | template 99 | struct First { 100 | using type = std::conditional_t< 101 | std::is_same_v, 102 | IfAllPass, 103 | T>; 104 | }; 105 | } 106 | 107 | 108 | namespace obfs { 109 | void FreeAction() {} 110 | 111 | template 112 | struct Next { 113 | using event = Event; 114 | using state = State; 115 | constexpr static void(*action)() = Action; 116 | }; 117 | 118 | struct None {}; 119 | 120 | template 121 | struct Stage { 122 | using state = State; 123 | 124 | template 125 | using act = std::conditional_t< 126 | std::is_same_v, NextInfo, Pass>; 127 | 128 | template 129 | using next = typename First...>::type; 130 | }; 131 | 132 | template 133 | struct next_stage { 134 | using type = typename Stage_::template next; 135 | }; 136 | 137 | template 138 | struct next_stage { 139 | using type = None; 140 | }; 141 | 142 | template 143 | struct action_invoker { 144 | static auto action() { 145 | State::action(); 146 | return typename State::state{}; 147 | } 148 | }; 149 | 150 | template <> 151 | struct action_invoker { 152 | static auto action() { 153 | return None{}; 154 | } 155 | }; 156 | 157 | template 158 | struct StateMachine { 159 | template 160 | using filter = std::conditional_t< 161 | std::is_same_v, StageT, Pass>; 162 | 163 | template 164 | using find = typename First...>::type; 165 | 166 | template 167 | using next_t = typename next_stage, Event>::type; 168 | 169 | template 170 | static auto run(State state, Event event) { 171 | using next_state = next_t, std::decay_t>; 172 | return action_invoker::action(); 173 | } 174 | }; 175 | } 176 | 177 | 178 | namespace obfs { 179 | using size_t = decltype(sizeof(void*)); 180 | 181 | constexpr char TIME[] = __TIME__; 182 | constexpr int digit(char c) { 183 | return c - '0'; 184 | } 185 | constexpr size_t SEED = digit(TIME[7]) + 186 | digit(TIME[6]) * 10 + 187 | digit(TIME[4]) * 60 + 188 | digit(TIME[3]) * 600 + 189 | digit(TIME[1]) * 3600 + 190 | digit(TIME[0]) * 36000; 191 | 192 | template 193 | struct Xorshiftplus { 194 | using prev = Xorshiftplus; 195 | 196 | constexpr static size_t update() { 197 | constexpr size_t x = prev::state0 ^ (prev::state0 << 23); 198 | constexpr size_t y = prev::state1; 199 | return x ^ y ^ (x >> 17) ^ (y >> 26); 200 | } 201 | 202 | constexpr static size_t state0 = prev::state1; 203 | constexpr static size_t state1 = update(); 204 | 205 | constexpr static size_t value = state0 + state1; 206 | }; 207 | 208 | template 209 | struct Xorshiftplus { 210 | constexpr static size_t state0 = Seed; 211 | constexpr static size_t state1 = Seed << 1; 212 | constexpr static size_t value = state0 + state1; 213 | }; 214 | 215 | template 216 | constexpr size_t RAND_VAL = Xorshiftplus::value % Mod; 217 | } 218 | 219 | 220 | namespace obfs { 221 | using Encoder = char(*)(char); 222 | using Decoder = char(*)(char); 223 | 224 | template 225 | class String { 226 | public: 227 | template 228 | constexpr String(char const* str, 229 | std::index_sequence): 230 | str{ encoder(str[Idx])... } { 231 | // Do Nothing 232 | } 233 | 234 | inline char const* decode() const { 235 | for (char& chr : str) { 236 | chr = decoder(chr); 237 | } 238 | return str; 239 | } 240 | 241 | private: 242 | mutable char str[size]; 243 | }; 244 | 245 | template 246 | constexpr auto make_string(char const (&str)[size]) { 247 | return String(str, std::make_index_sequence()); 248 | } 249 | 250 | template 251 | using encoder_seq = Sequence; 252 | 253 | template 254 | using decoder_seq = Sequence; 255 | 256 | template 257 | using make_table = SeqPack; 258 | 259 | template 260 | using encoder_pair = Sequence; 261 | 262 | template 263 | using make_pair_table = TypeSeq; 264 | 265 | template 266 | constexpr auto make_string(char const(&str)[size]) { 267 | using pair = typename Table::template index; 268 | constexpr Encoder encoder = pair::template index<0>::value; 269 | constexpr Decoder decoder = pair::template index<1>::value; 270 | 271 | return make_string(str); 272 | } 273 | } 274 | 275 | 276 | #endif 277 | -------------------------------------------------------------------------------- /obfuscator/obfs/fsm.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBFS_FINITE_STATE_MACHINE 2 | #define OBFS_FINITE_STATE_MACHINE 3 | 4 | #include "sequence.hpp" 5 | 6 | #include 7 | 8 | namespace obfs { 9 | void FreeAction() {} 10 | 11 | template 12 | struct Next { 13 | using event = Event; 14 | using state = State; 15 | constexpr static void(*action)() = Action; 16 | }; 17 | 18 | struct None {}; 19 | 20 | template 21 | struct Stage { 22 | using state = State; 23 | 24 | template 25 | using act = std::conditional_t< 26 | std::is_same_v, NextInfo, Pass>; 27 | 28 | template 29 | using next = typename First...>::type; 30 | }; 31 | 32 | template 33 | struct next_stage { 34 | using type = typename Stage_::template next; 35 | }; 36 | 37 | template 38 | struct next_stage { 39 | using type = None; 40 | }; 41 | 42 | template 43 | struct action_invoker { 44 | static auto action() { 45 | State::action(); 46 | return typename State::state{}; 47 | } 48 | }; 49 | 50 | template <> 51 | struct action_invoker { 52 | static auto action() { 53 | return None{}; 54 | } 55 | }; 56 | 57 | template 58 | struct StateMachine { 59 | template 60 | using filter = std::conditional_t< 61 | std::is_same_v, StageT, Pass>; 62 | 63 | template 64 | using find = typename First...>::type; 65 | 66 | template 67 | using next_t = typename next_stage, Event>::type; 68 | 69 | template 70 | static auto run(State state, Event event) { 71 | using next_state = next_t, std::decay_t>; 72 | return action_invoker::action(); 73 | } 74 | }; 75 | } 76 | 77 | #endif -------------------------------------------------------------------------------- /obfuscator/obfs/random.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMPILE_TIME_RANDOM 2 | #define COMPILE_TIME_RANDOM 3 | 4 | namespace obfs { 5 | using size_t = decltype(sizeof(void*)); 6 | 7 | constexpr char TIME[] = __TIME__; 8 | constexpr int digit(char c) { 9 | return c - '0'; 10 | } 11 | constexpr size_t SEED = digit(TIME[7]) + 12 | digit(TIME[6]) * 10 + 13 | digit(TIME[4]) * 60 + 14 | digit(TIME[3]) * 600 + 15 | digit(TIME[1]) * 3600 + 16 | digit(TIME[0]) * 36000; 17 | 18 | template 19 | struct Xorshiftplus { 20 | using prev = Xorshiftplus; 21 | 22 | constexpr static size_t update() { 23 | constexpr size_t x = prev::state0 ^ (prev::state0 << 23); 24 | constexpr size_t y = prev::state1; 25 | return x ^ y ^ (x >> 17) ^ (y >> 26); 26 | } 27 | 28 | constexpr static size_t state0 = prev::state1; 29 | constexpr static size_t state1 = update(); 30 | 31 | constexpr static size_t value = state0 + state1; 32 | }; 33 | 34 | template 35 | struct Xorshiftplus { 36 | constexpr static size_t state0 = Seed; 37 | constexpr static size_t state1 = Seed << 1; 38 | constexpr static size_t value = state0 + state1; 39 | }; 40 | 41 | template 42 | constexpr size_t RAND_VAL = Xorshiftplus::value % Mod; 43 | } 44 | 45 | #define MAKE_RAND_VAL(MOD) obfs::RAND_VAL<__LINE__, MOD> 46 | 47 | #endif -------------------------------------------------------------------------------- /obfuscator/obfs/sequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMPILE_TIME_SEQUENCE 2 | #define COMPILE_TIME_SEQUENCE 3 | 4 | #include 5 | 6 | namespace obfs { 7 | template 8 | struct TypeVal { 9 | using value_type = T; 10 | constexpr static T value = Val; 11 | }; 12 | 13 | struct Nothing {}; 14 | 15 | template 16 | struct Sequence { 17 | using value = TypeVal; 18 | using next = Sequence; 19 | 20 | constexpr static std::size_t size = 1 + sizeof...(Others); 21 | 22 | template 23 | using index = std::conditional_t>; 24 | }; 25 | 26 | template 27 | struct Sequence { 28 | using value = TypeVal; 29 | 30 | constexpr static std::size_t size = 1; 31 | 32 | template 33 | using index = std::conditional_t; 34 | }; 35 | 36 | template 37 | struct TypeSeq { 38 | using type = T; 39 | using next = TypeSeq; 40 | 41 | constexpr static std::size_t size = 1 + sizeof...(Ts); 42 | 43 | template 44 | using index = std::conditional_t>; 45 | }; 46 | 47 | template 48 | struct TypeSeq { 49 | using type = T; 50 | 51 | constexpr static std::size_t size = 1; 52 | 53 | template 54 | using index = std::conditional_t; 55 | }; 56 | 57 | template 58 | struct MinVal { 59 | constexpr static std::size_t value = 60 | Val < MinVal::value ? Val : MinVal::value; 61 | }; 62 | 63 | template 64 | struct MinVal { 65 | constexpr static std::size_t value = Val; 66 | }; 67 | 68 | template 69 | struct SeqPack { 70 | constexpr static std::size_t size = MinVal::value; 71 | 72 | template 73 | using getter = typename U::template index; 74 | 75 | template 76 | using index = TypeSeq...>; 77 | }; 78 | 79 | struct Pass {}; 80 | 81 | template 82 | struct First { 83 | using type = std::conditional_t< 84 | std::is_same_v, 85 | typename First::type, 86 | T>; 87 | }; 88 | 89 | template 90 | struct First { 91 | using type = std::conditional_t< 92 | std::is_same_v, 93 | IfAllPass, 94 | T>; 95 | }; 96 | } 97 | 98 | #endif -------------------------------------------------------------------------------- /obfuscator/obfs/string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBFS_STRING 2 | #define OBFS_STRING 3 | 4 | #include "random.hpp" 5 | #include "sequence.hpp" 6 | 7 | #include 8 | 9 | namespace obfs { 10 | using Encoder = char(*)(char); 11 | using Decoder = char(*)(char); 12 | 13 | template 14 | class String { 15 | public: 16 | template 17 | constexpr String(char const* str, 18 | std::index_sequence): 19 | str{ encoder(str[Idx])... } { 20 | // Do Nothing 21 | } 22 | 23 | inline char const* decode() const { 24 | for (char& chr : str) { 25 | chr = decoder(chr); 26 | } 27 | return str; 28 | } 29 | 30 | private: 31 | mutable char str[size]; 32 | }; 33 | 34 | template 35 | constexpr auto make_string(char const (&str)[size]) { 36 | return String(str, std::make_index_sequence()); 37 | } 38 | 39 | template 40 | using encoder_seq = Sequence; 41 | 42 | template 43 | using decoder_seq = Sequence; 44 | 45 | template 46 | using make_table = SeqPack; 47 | 48 | template 49 | using encoder_pair = Sequence; 50 | 51 | template 52 | using make_pair_table = TypeSeq; 53 | 54 | template 55 | constexpr auto make_string(char const(&str)[size]) { 56 | using pair = typename Table::template index; 57 | constexpr Encoder encoder = pair::template index<0>::value; 58 | constexpr Decoder decoder = pair::template index<1>::value; 59 | 60 | return make_string(str); 61 | } 62 | } 63 | 64 | #define MAKE_STRING(Var, String, ...) constexpr auto Var = obfs::make_string<__VA_ARGS__>(String); 65 | 66 | #endif -------------------------------------------------------------------------------- /obfuscator/obfuscator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBFUSCATOR 2 | #define OBFUSCATOR 3 | 4 | #include "obfs/fsm.hpp" 5 | #include "obfs/random.hpp" 6 | #include "obfs/sequence.hpp" 7 | #include "obfs/string.hpp" 8 | 9 | #endif -------------------------------------------------------------------------------- /sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(sample) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | add_executable(string_obfs string_obfs.cpp) 6 | add_executable(state_machine state_machine.cpp) 7 | add_executable(random random.cpp) 8 | -------------------------------------------------------------------------------- /sample/random.cpp: -------------------------------------------------------------------------------- 1 | #include "../obfuscator.hpp" 2 | 3 | #include 4 | 5 | int main() { 6 | std::cout << MAKE_RAND_VAL(100) << std::endl; 7 | return 0; 8 | } -------------------------------------------------------------------------------- /sample/state_machine.cpp: -------------------------------------------------------------------------------- 1 | #include "../obfuscator.hpp" 2 | 3 | #include 4 | 5 | #define STATE(Name) struct Name {}; 6 | #define EVENT(Name) struct Name {}; 7 | 8 | STATE(Final); 9 | EVENT(Trigger); 10 | 11 | STATE(state1); STATE(state2); STATE(state3); STATE(state4); STATE(state5); 12 | EVENT(event1); EVENT(event2); EVENT(event3); EVENT(event4); EVENT(event5); 13 | 14 | struct Action { 15 | static bool trigged; 16 | static void action() { 17 | trigged = true; 18 | std::cout << "Trigged" << std::endl; 19 | } 20 | }; 21 | bool Action::trigged = true; 22 | 23 | struct Dummy { 24 | static int dummy; 25 | static void dummy1() { 26 | dummy += 10; 27 | std::cout << "Dummy1" << std::endl; 28 | } 29 | static void dummy2() { 30 | dummy *= 20; 31 | std::cout << "Dummy2" << std::endl; 32 | } 33 | static void dummy3() { 34 | std::exit(1); 35 | } 36 | }; 37 | int Dummy::dummy = 0; 38 | 39 | int main() { 40 | using namespace obfs; 41 | using machine = StateMachine< 42 | Stage, 43 | Next>, 44 | Stage>, 45 | Stage>, 46 | Stage, 47 | Next>, 48 | Stage>>; 49 | 50 | auto next1 = machine::run(state1{}, event5{}); 51 | auto next2 = machine::run(next1, event2{}); 52 | auto next3 = machine::run(next2, event3{}); 53 | auto next4 = machine::run(next3, Trigger{}); 54 | 55 | return 0; 56 | } -------------------------------------------------------------------------------- /sample/string_obfs.cpp: -------------------------------------------------------------------------------- 1 | #include "../obfuscator.hpp" 2 | 3 | #include 4 | 5 | template 6 | constexpr char xor_(char c) { 7 | return c ^ key; 8 | } 9 | 10 | template 11 | constexpr char add(char c) { 12 | return c + Key; 13 | } 14 | 15 | template 16 | constexpr char comp(char c) { 17 | return f(g(c)); 18 | } 19 | 20 | int main() { 21 | using table = obfs::make_table< 22 | obfs::encoder_seq, add<10>, comp, add<10>>>, 23 | obfs::decoder_seq, add<-10>, comp, xor_<0x50>>>>; 24 | 25 | MAKE_STRING(str, "Hello World !", table); 26 | std::cout << str.decode() << std::endl; 27 | } -------------------------------------------------------------------------------- /script/Dockerfile.bionic: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update -yq && apt-get install -yq build-essential cmake python3.6 python3.6-dev python3-pip python3-setuptools python3-wheel 4 | RUN echo `g++ --version` 5 | 6 | ADD . /app 7 | 8 | # Run UnitTest 9 | WORKDIR /app/test/build 10 | RUN cmake .. && \ 11 | make -j `nproc` && \ 12 | ./unittest 13 | 14 | # Run Additional Test 15 | WORKDIR /app/sample/build 16 | RUN cmake .. && \ 17 | make -j `nproc` 18 | 19 | WORKDIR /app 20 | RUN python3 -m script.merge && \ 21 | python3 -m script.string_obfs_tester ./sample/build/string_obfs "Hello World !" 22 | -------------------------------------------------------------------------------- /script/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revsic/cpp-obfuscator/3dd832715270dc58ec75f5dc1100e1d8002242c9/script/__init__.py -------------------------------------------------------------------------------- /script/azure-pipelines-template-mac.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ${{ parameters.name }} 3 | pool: 4 | vmImage: ${{ parameters.vmImage }} 5 | steps: 6 | # Initialize 7 | - script: git submodule update --init 8 | displayName: Initialize submodule 9 | # UnitTest 10 | - task: CMake@1 11 | inputs: 12 | workingDirectory: ./test/build 13 | cmakeArgs: ../ 14 | displayName: CMake unittest 15 | 16 | - script: cd ./test/build && make; 17 | displayName: GNU Make unittest 18 | 19 | - script: ./test/build/unittest 20 | displayName: Run unittest 21 | # Additional tests 22 | - task: UsePythonVersion@0 23 | inputs: 24 | versionSpec: 3.6 25 | architecture: 'x64' 26 | 27 | - script: python -m script.merge 28 | displayName: Remerge obfuscator.hpp 29 | 30 | - task: CMake@1 31 | inputs: 32 | workingDirectory: ./sample/build 33 | cmakeArgs: ../ 34 | displayName: CMake sample 35 | 36 | - script: cd ./sample/build && make; 37 | displayName: GNU Make sample 38 | 39 | - script: python -m script.string_obfs_tester ./sample/build/string_obfs "Hello World !" 40 | displayName: String OBFS test 41 | -------------------------------------------------------------------------------- /script/azure-pipelines-template-unix.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ${{ parameters.name }} 3 | pool: 4 | vmImage: ${{ parameters.vmImage }} 5 | steps: 6 | - script: git submodule update --init 7 | displayName: initialize submodule 8 | 9 | - script: docker build -f script/Dockerfile.bionic -t cpp_obfuscator . 10 | displayName: run docker-bionic 11 | -------------------------------------------------------------------------------- /script/azure-pipelines-template-win.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ${{ parameters.name }} 3 | pool: 4 | vmImage: ${{ parameters.vmImage }} 5 | steps: 6 | # Initialize 7 | - script: git submodule update --init 8 | displayName: Initialize submodule 9 | # UnitTest 10 | - task: CMake@1 11 | inputs: 12 | workingDirectory: .\test\build 13 | cmakeArgs: ..\ 14 | displayName: CMake unittest 15 | 16 | - task: MSBuild@1 17 | inputs: 18 | solution: .\test\build\unittest.sln 19 | displayName: MSBuild unittest 20 | 21 | - script: .\test\build\Debug\unittest.exe 22 | displayName: Start unittest 23 | # Additional tests 24 | - task: UsePythonVersion@0 25 | inputs: 26 | versionSpec: 3.6 27 | architecture: 'x64' 28 | 29 | - script: python -m script.merge 30 | displayName: Remerge obfuscator.hpp 31 | 32 | - task: CMake@1 33 | inputs: 34 | workingDirectory: .\sample\build 35 | cmakeArgs: ..\ 36 | displayName: CMake sample 37 | 38 | - task: MSBuild@1 39 | inputs: 40 | solution: .\sample\build\sample.sln 41 | displayName: MSBuild sample 42 | 43 | - script: python -m script.string_obfs_tester .\sample\build\Debug\string_obfs.exe "Hello World !" 44 | displayName: String OBFS test 45 | -------------------------------------------------------------------------------- /script/merge.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | 5 | 6 | class Format(object): 7 | hpp = '''\ 8 | #ifndef {0} 9 | #define {0} 10 | 11 | {1} 12 | {2} 13 | {3} 14 | #endif''' 15 | 16 | @classmethod 17 | def hpp_beutifier(cls, source): 18 | out = '' 19 | n_blank = 0 20 | for line in source.split('\n'): 21 | if line == '' or line.isspace(): 22 | n_blank += 1 23 | else: 24 | if n_blank > 2: 25 | n_blank = 2 26 | 27 | out += '\n' * n_blank 28 | n_blank = 0 29 | 30 | out += line + '\n' 31 | return out 32 | 33 | 34 | class ReSupport(object): 35 | r_guard = re.compile(r'(#ifndef|#endif)') 36 | r_include_dep = re.compile(r'#include "(.+?)"') 37 | r_include = re.compile(r'#include <(.+?)>') 38 | r_define = re.compile(r'#define (.+)') 39 | 40 | @classmethod 41 | def guard(cls, inp): 42 | return cls.r_guard.findall(inp) 43 | 44 | @classmethod 45 | def include_dep(cls, inp): 46 | return cls.r_include_dep.findall(inp) 47 | 48 | @classmethod 49 | def include(cls, inp): 50 | return cls.r_include.findall(inp) 51 | 52 | @classmethod 53 | def define(cls, inp): 54 | return cls.r_define.findall(inp) 55 | 56 | 57 | class SourceInfo(object): 58 | def __init__(self): 59 | self.out = '' 60 | self.deps = [] 61 | self.includes = [] 62 | self.defines = [] 63 | 64 | @classmethod 65 | def set_with(cls, out, deps, includes, defines): 66 | obj = cls() 67 | obj.out = out 68 | obj.deps = deps 69 | obj.includes = includes 70 | obj.defines = defines 71 | return obj 72 | 73 | @classmethod 74 | def read_file(cls, path): 75 | obj = cls() 76 | with open(path) as f: 77 | for line in f.readlines(): 78 | if len(ReSupport.guard(line)) > 0: 79 | continue 80 | 81 | include_name = ReSupport.include_dep(line) 82 | if len(include_name) > 0: 83 | obj.deps.append(include_name[0]) 84 | continue 85 | 86 | include_name = ReSupport.include(line) 87 | if len(include_name) > 0: 88 | obj.includes.append(include_name[0]) 89 | continue 90 | 91 | define_name = ReSupport.define(line) 92 | if len(define_name) > 0: 93 | obj.defines.append(define_name[0]) 94 | continue 95 | 96 | obj.out += line 97 | return obj 98 | 99 | def __add__(self, other): 100 | out = self.out + other.out 101 | deps = self.deps + other.deps 102 | includes = self.includes + other.includes 103 | defines = self.defines + other.defines 104 | return SourceInfo.set_with(out, deps, includes, defines) 105 | 106 | 107 | def file_list(dirname, ban_dir=[]): 108 | files = [] 109 | if isinstance(ban_dir, str): 110 | ban_dir = [ban_dir] 111 | 112 | for name in os.listdir(dirname): 113 | full_path = os.path.join(dirname, name) 114 | if os.path.isfile(full_path): 115 | files.append(full_path) 116 | elif os.path.isdir(full_path) and name not in ban_dir: 117 | files += file_list(full_path) 118 | return files 119 | 120 | 121 | def in_endswith(name, files): 122 | for f in files: 123 | if f.endswith(name): 124 | return f 125 | return None 126 | 127 | 128 | def order_dep(deps, files, done): 129 | info = SourceInfo() 130 | for dep in deps: 131 | if in_endswith(dep, done) is None: 132 | path = in_endswith(dep.split('/')[-1], files) 133 | 134 | if path is not None: 135 | new = SourceInfo.read_file(path) 136 | dep_new = order_dep(new.deps, files, done) 137 | 138 | info += dep_new + new 139 | done.append(path) 140 | 141 | return info 142 | 143 | 144 | def none_preproc(dirname): 145 | if not os.path.exists(dirname): 146 | return [], '' 147 | 148 | dep = [] 149 | out = '' 150 | includes = '' 151 | for files in os.listdir(dirname): 152 | with open(os.path.join(dirname, files)) as f: 153 | lines = f.readlines() 154 | 155 | if lines[0].startswith('#ifndef') \ 156 | and lines[1].startswith('#define') \ 157 | and lines[-1].startswith('#endif'): 158 | lines = lines[2:-1] 159 | 160 | data = ''.join(lines) 161 | 162 | include_idx = data.find('// merge:np_include') 163 | if include_idx != -1: 164 | start_idx = include_idx + len('// merge:np_include') 165 | end_idx = data.find('// merge:end', include_idx) 166 | 167 | includes += data[start_idx:end_idx] 168 | data = data[end_idx + len('// merge:end'):] 169 | 170 | include_idx = data.find('// merge:include') 171 | if include_idx != -1: 172 | start_idx = include_idx + len('// merge:include') 173 | end_idx = data.find('// merge:end', include_idx) 174 | 175 | for line in data[start_idx:end_idx].split('\n'): 176 | res = ReSupport.include(line) 177 | if len(res) > 0: 178 | dep += res 179 | includes += line + '\n' 180 | 181 | data = data[end_idx + len('// merge:end'):] 182 | 183 | dep.append(files) 184 | out += data 185 | 186 | return dep, includes + out 187 | 188 | 189 | def merge(dirname): 190 | done = [] 191 | info = SourceInfo() 192 | files = file_list(dirname, 'platform') 193 | 194 | preproc_dep, info.out = \ 195 | none_preproc(os.path.join(dirname, 'platform')) 196 | 197 | for full_path in files: 198 | if full_path not in done: 199 | source = SourceInfo.read_file(full_path) 200 | dep = order_dep(source.deps, files, done) 201 | 202 | info += dep + source 203 | done.append(full_path) 204 | 205 | return info, preproc_dep 206 | 207 | 208 | def write_hpp(outfile, merged): 209 | info, preproc_dep = merged 210 | dep_check = lambda file: not any(file.endswith(dep) for dep in preproc_dep) 211 | 212 | idx = outfile.rfind('/') 213 | if idx > -1: 214 | outfile = outfile[idx+1:] 215 | guard_name = outfile.upper().replace('.', '_') 216 | 217 | unique_include = [] 218 | 219 | for include in info.includes: 220 | if include not in unique_include and dep_check(include): 221 | unique_include.append(include) 222 | 223 | includes = '\n'.join( 224 | '#include <{}>'.format(x) for x in sorted(unique_include)) + '\n' 225 | defines = '\n'.join( 226 | '#define {}'.format(x) for x in info.defines) + '\n' 227 | 228 | out = Format.hpp.format(guard_name, includes, defines, info.out) 229 | out = Format.hpp_beutifier(out) 230 | 231 | with open(outfile, 'w') as f: 232 | f.write(out) 233 | 234 | if __name__ == "__main__": 235 | if len(sys.argv) > 1: 236 | outfile = sys.argv[1] 237 | else: 238 | outfile = './obfuscator.hpp' 239 | 240 | if len(sys.argv) > 2: 241 | dirname = sys.argv[2] 242 | else: 243 | dirname = './obfuscator/obfs' 244 | 245 | merged = merge(dirname) 246 | write_hpp(outfile, merged) 247 | -------------------------------------------------------------------------------- /script/string_obfs_tester.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def main(argc, argv): 4 | if argc < 3: 5 | print('USAGE: python string_obfs_tester.py [STRING_OBFS.EXE] [TARGET_STRING]') 6 | return 1 7 | 8 | path = argv[1] 9 | with open(path, 'rb') as f: 10 | data = f.read() 11 | 12 | if data.find(str.encode(argv[2])) == -1: 13 | print('Cannot find string "{}" from "{}"'.format(argv[2], argv[1])) 14 | return 0 15 | else: 16 | print('Find string "{}" from "{}"'.format(argv[2], argv[1])) 17 | return 1 18 | 19 | if __name__ == '__main__': 20 | retn = main(len(sys.argv), sys.argv) 21 | sys.exit(retn) 22 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(unittest) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | file(GLOB test_files 7 | "impl/*.cpp" 8 | ) 9 | 10 | add_executable(unittest main.cpp ${test_files}) 11 | target_include_directories(unittest PRIVATE ../obfuscator) 12 | 13 | add_subdirectory(../external/Catch2 external) 14 | target_link_libraries(unittest Catch2::Catch2) 15 | -------------------------------------------------------------------------------- /test/impl/fsm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define STATE(Name) struct Name {}; 5 | #define EVENT(Name) struct Name {}; 6 | 7 | STATE(Final); 8 | EVENT(Trigger); 9 | 10 | STATE(state1); STATE(state2); STATE(state3); STATE(state4); STATE(state5); 11 | EVENT(event1); EVENT(event2); EVENT(event3); EVENT(event4); EVENT(event5); 12 | 13 | struct Action { 14 | static bool trigged; 15 | static void action() { 16 | trigged = true; 17 | } 18 | }; 19 | bool Action::trigged = true; 20 | 21 | struct Dummy { 22 | static int dummy; 23 | static void dummy1() { 24 | dummy += 10; 25 | } 26 | static void dummy2() { 27 | dummy *= 20; 28 | } 29 | }; 30 | int Dummy::dummy = 0; 31 | 32 | TEST_CASE("Next", "[obfs::StateMachine]") { 33 | using next = obfs::Next; 34 | static_assert(std::is_same_v); 35 | static_assert(std::is_same_v); 36 | static_assert(next::action == obfs::FreeAction); 37 | 38 | using next2 = obfs::Next; 39 | static_assert(next2::action == Action::action); 40 | } 41 | 42 | TEST_CASE("Stage", "[obfs::StateMachine]") { 43 | using stage = obfs::Stage< 44 | state1, 45 | obfs::Next, 46 | obfs::Next>; 47 | 48 | static_assert(std::is_same_v< 49 | obfs::Next, 50 | typename stage::template next>); 51 | 52 | static_assert(std::is_same_v< 53 | obfs::Next, 54 | typename obfs::next_stage::type>); 55 | 56 | static_assert(std::is_same_v< 57 | obfs::Next, 58 | typename stage::template next>); 59 | 60 | static_assert(std::is_same_v< 61 | obfs::Next, 62 | typename obfs::next_stage::type>); 63 | 64 | static_assert(std::is_same_v>); 65 | 66 | static_assert(std::is_same_v::type>); 67 | 68 | static_assert(std::is_same_v::type>); 69 | } 70 | 71 | TEST_CASE("StateMachine", "[obfs::StateMachine]") { 72 | using namespace obfs; 73 | using machine = StateMachine< 74 | Stage, 75 | Next>, 76 | Stage>, 77 | Stage>, 78 | Stage, 79 | Next>, 80 | Stage>>; 81 | 82 | auto failure = machine::run(obfs::None{}, event5{}); 83 | static_assert(std::is_same_v); 84 | 85 | auto next1 = machine::run(state1{}, event5{}); 86 | static_assert(std::is_same_v); 87 | REQUIRE(Dummy::dummy == 10); 88 | 89 | auto next2 = machine::run(next1, event2{}); 90 | static_assert(std::is_same_v); 91 | 92 | auto failure2 = machine::run(next2, event1{}); 93 | static_assert(std::is_same_v); 94 | 95 | auto next3 = machine::run(next2, event3{}); 96 | static_assert(std::is_same_v); 97 | REQUIRE(Dummy::dummy == 200); 98 | 99 | auto next4 = machine::run(next3, Trigger{}); 100 | static_assert(std::is_same_v); 101 | REQUIRE(Action::trigged); 102 | } 103 | -------------------------------------------------------------------------------- /test/impl/random.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using ull = unsigned long long; 5 | 6 | // https://en.wikipedia.org/wiki/Xorshift 7 | ull xorshift128plus(ull state[2]) { 8 | ull t = state[0]; 9 | ull const s = state[1]; 10 | state[0] = s; 11 | t ^= t << 23; // a 12 | t ^= t >> 17; // b 13 | t ^= s ^ (s >> 26); // c 14 | state[1] = t; 15 | return t + s; 16 | } 17 | 18 | TEST_CASE("Random case", "[obfs::Xorshift+]") { 19 | constexpr size_t val = MAKE_RAND_VAL(100); 20 | ull state[2] = { val, val << 1 }; 21 | 22 | REQUIRE(obfs::Xorshiftplus::value == xorshift128plus(state)); 23 | REQUIRE(obfs::Xorshiftplus::value == xorshift128plus(state)); 24 | REQUIRE(obfs::Xorshiftplus::value == xorshift128plus(state)); 25 | } 26 | -------------------------------------------------------------------------------- /test/impl/sequence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST_CASE("TypeVal", "[obfs::Sequence]") { 5 | using val = obfs::TypeVal; 6 | 7 | static_assert(std::is_same_v); 8 | static_assert(val::value == 10); 9 | } 10 | 11 | TEST_CASE("Sequence", "[obfs::Sequence]") { 12 | using seq = obfs::Sequence; 13 | 14 | static_assert(std::is_same_v); 15 | static_assert(seq::size == 10); 16 | static_assert(seq::index<0>::value == 0); 17 | static_assert(seq::index<5>::value == 5); 18 | static_assert(seq::index<9>::value == 9); 19 | static_assert(std::is_same_v, obfs::Nothing>); 20 | } 21 | 22 | TEST_CASE("TypeSeq", "[obfs::Sequence]") { 23 | using namespace obfs; 24 | using seq = TypeSeq, TypeVal, TypeVal, TypeVal>; 25 | 26 | static_assert(seq::size == 4); 27 | static_assert(seq::index<0>::value == 0); 28 | static_assert(seq::index<2>::value == 2); 29 | static_assert(seq::index<3>::value == 3); 30 | static_assert(std::is_same_v, Nothing>); 31 | } 32 | 33 | TEST_CASE("MinVal", "[obfs::Sequence]") { 34 | using min = obfs::MinVal<1, 3, 2, 4, 9, 5, 6, 0, 7, 8>; 35 | static_assert(min::value == 0); 36 | } 37 | 38 | TEST_CASE("SeqPack", "[obfs::Sequence]") { 39 | using pack = obfs::SeqPack< 40 | obfs::Sequence, 41 | obfs::Sequence, 42 | obfs::Sequence 43 | >; 44 | 45 | static_assert(pack::size == 4); 46 | 47 | using item = pack::index<2>; 48 | static_assert(item::size == 3); 49 | static_assert(item::index<0>::value == 2); 50 | static_assert(item::index<1>::value == 3); 51 | static_assert(item::index<2>::value == 4); 52 | } 53 | 54 | template 55 | using condition = std::conditional_t<(Val::value > 3), Val, obfs::Pass>; 56 | 57 | template 58 | using first = typename obfs::First...>::type; 59 | 60 | TEST_CASE("First", "[obfs::Sequence]") { 61 | using result = first< 62 | obfs::TypeVal, 63 | obfs::TypeVal, 64 | obfs::TypeVal, 65 | obfs::TypeVal, 66 | obfs::TypeVal, 67 | obfs::TypeVal>; 68 | 69 | static_assert(result::value == 5); 70 | } -------------------------------------------------------------------------------- /test/impl/string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | constexpr char enc_xor(char value) { 6 | return value ^ Key; 7 | } 8 | 9 | template 10 | constexpr char add(char c) { 11 | return c + Key; 12 | } 13 | 14 | template 15 | constexpr char comp(char c) { 16 | return f(g(c)); 17 | } 18 | 19 | TEST_CASE("Single encoder, decoder", "[obfs::String]") { 20 | REQUIRE( 21 | obfs::make_string, enc_xor<0x50>>("Hello World !").decode() 22 | == std::string_view("Hello World !")); 23 | } 24 | 25 | TEST_CASE("Multiple encoder, decoder", "[obfs::String]") { 26 | using table = obfs::make_table< 27 | obfs::encoder_seq, add<10>, comp, add<10>>>, 28 | obfs::decoder_seq, add<-10>, comp, enc_xor<0x50>>>>; 29 | 30 | MAKE_STRING(str, "Hello World !", table); 31 | REQUIRE(str.decode() == std::string_view("Hello World !")); 32 | } 33 | 34 | TEST_CASE("Multiple encoder, decoder pair", "[obfs::String]") { 35 | using table = obfs::make_pair_table< 36 | obfs::encoder_pair, enc_xor<0x50>>, 37 | obfs::encoder_pair, add<-10>>, 38 | obfs::encoder_pair, add<10>>, comp, enc_xor<0x50>>> 39 | >; 40 | 41 | MAKE_STRING(str, "Hello World !", table); 42 | REQUIRE(str.decode() == std::string_view("Hello World !")); 43 | } 44 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include --------------------------------------------------------------------------------