├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── examples ├── compile-examples-test.bat ├── compile-examples.py ├── simple.cpp └── types.cpp └── include └── polymorph-lib.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all executables 2 | *.exe 3 | *.log 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 JarateKing 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 | # Polymorph-lib 2 | 3 | This is a header-only library that provides various functionality for randomization on compile-time, in a convenient to use manner that is easy to integrate without any external dependencies or runtime cost. 4 | 5 | This is *not* a polymorphic code engine, since it doesn't change the code signature every time it runs, but it takes inspiration from the concept of polymorphic code. You can, however, simulate a polymorphic code engine by recompiling your program each time you would like to run it. 6 | 7 | ## Use-cases 8 | 9 | * **binary fingerprinting** -- you could distribute a different but functionally equivalent binary to multiple people so that they all have a unique executable, and keep track of who received what binary. If your program gets leaked, you can trace the leaker back using the original binary. 10 | * **signature evasion** -- signatures can be generated by hashing files or memory. Code that involves compile-time randomization can evade signature detection between different compiles. 11 | * **non-deterministic algorithms** -- some efficient algorithms are non-deterministic and involve random numbers. Using these at compile-time is made possible through the usage of compile-time random number generators. 12 | * **experimentation** -- it can be useful to ensure that different permutations of code are equivalent through random sampling, as can be done by compiling the program many times with randomization and testing whether they work. 13 | 14 | ## Usage 15 | 16 | 1. Put `include/polymorph-lib.h` somewhere in your project. 17 | 2. Include it to your project through `#include "polymorph-lib.h"` or some related include statement 18 | 3. Use randomized functions. Some major ones are: 19 | - `poly_random(n)` - get a random number between 0 (inclusive) and n (exclusive) 20 | - `poly_junk()` - make junk code that doesn't do anything 21 | - `poly_random_order(f1,f2)` - run the functions `f1` and `f2` in some random order 22 | - `poly_random_chance(c,f)` - random chance to call function `f` -- approximately every `c` distinct calls will call `f` once 23 | - `poly_int()` / `uint()` / `ll()` / `ull()` / `float()` / `double()` - random value of that data type (floating point types range from 0.0 to 1.0). 24 | - `poly_normal(sigma,mu)` - generate a random floating point value following a normal distribution, using sigma and mu values. 25 | 4. Compile with some level of optimization (so that redundant branching is removed) 26 | 27 | If you want to either manually set a specific seed, or generate a seed using an external program, you can use set the macro `__POLY_RANDOM_SEED__` as in `-D __POLY_RANDOM_SEED__=1234567890ull`. This is optional, and if this is unspecified a seed will be automatically generated based off the current time. 28 | 29 | ## Details 30 | 31 | Before I describe the details of the random number generator, I should delve into the source of entropy used. The GCC supports `__DATE__` and `__TIME__` macros which change on each compile (assuming the compiles aren't made within the same second). We combine those into an integer, that acts as our initial seed. 32 | 33 | We then make use of a counter-based-psuedo-random-number-generator (CBPRNG). Normal PRNGs will use usually use their own output as the state to use for their next calculations. Because we are dealing with compile-time calculations this is easier said than done, and it's easier if our state is an available macro like `__COUNTER__` which will automatically increment each time it is referenced. A good fit for this is the [Widynski's Squares](https://arxiv.org/abs/2004.06278) CBPRNG. Because this is all done as a `constexpr` it can be calculated at compile time. 34 | 35 | Now we want to turn these compile-time random variables into different code. We make different branches of code (via `if` or `case` statements) with their conditional based on these random variables, and then rely on compiler optimizations to eliminate unused branches. 36 | 37 | It's important to note that using functions this way would only evaluate the branch elimination once, and go with that. This means that any call to `poly_junk()` would produce the same code no matter when or where it was used in the program (though it would change on runtime) even when inlined. We instead use `#define` macros for this purpose, so that the preprocessor replaces `poly_junk()` with the corresponding code, which allows the compiler to evaluate each branch separately and results in different code output each time. 38 | -------------------------------------------------------------------------------- /examples/compile-examples-test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rmdir /S /Q output 4 | mkdir output 5 | 6 | echo compiling 7 | compile-examples.py 8 | echo compiled 9 | 10 | echo. 11 | echo automatically seeded 12 | output\random1.exe 13 | output\random2.exe 14 | output\random3.exe 15 | 16 | echo. 17 | echo fixed seed 18 | output\fixed1.exe 19 | output\fixed2.exe 20 | output\fixed3.exe 21 | 22 | echo. 23 | echo externally seeded 24 | output\seeded1.exe 25 | output\seeded2.exe 26 | output\seeded3.exe 27 | 28 | echo. 29 | echo types 30 | output\types.exe 31 | 32 | pause -------------------------------------------------------------------------------- /examples/compile-examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import time 4 | 5 | # default seed, wait in between for different seed 6 | os.system('g++ -g -O2 -std=gnu++17 -static simple.cpp -o output/random1.exe') 7 | time.sleep(1) 8 | os.system('g++ -g -O2 -std=gnu++17 -static simple.cpp -o output/random2.exe') 9 | time.sleep(1) 10 | os.system('g++ -g -O2 -std=gnu++17 -static simple.cpp -o output/random3.exe') 11 | 12 | # fixed seed 13 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=1234567890ull -static simple.cpp -o output/fixed1.exe') 14 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=1234567890ull -static simple.cpp -o output/fixed2.exe') 15 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=1234567890ull -static simple.cpp -o output/fixed3.exe') 16 | 17 | # external seed 18 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=' + str(random.randrange(18446744073709551615)) + 'ull -static simple.cpp -o output/seeded1.exe') 19 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=' + str(random.randrange(18446744073709551615)) + 'ull -static simple.cpp -o output/seeded2.exe') 20 | os.system('g++ -g -O2 -std=gnu++17 -D __POLY_RANDOM_SEED__=' + str(random.randrange(18446744073709551615)) + 'ull -static simple.cpp -o output/seeded3.exe') 21 | 22 | # different types 23 | os.system('g++ -g -O2 -std=gnu++17 -static types.cpp -o output/types.exe') -------------------------------------------------------------------------------- /examples/simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/polymorph-lib.h" 3 | 4 | int main() { 5 | // print out random number "0 <= x < 10000" 6 | // this number will only change when you recompile 7 | std::cout << poly_random(10000) << '\n'; 8 | } -------------------------------------------------------------------------------- /examples/types.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/polymorph-lib.h" 3 | 4 | int main() { 5 | std::cout << "int " << poly_int() << std::endl; 6 | std::cout << "uint " << poly_uint() << std::endl; 7 | std::cout << "ll " << poly_ll() << std::endl; 8 | std::cout << "ull " << poly_ull() << std::endl; 9 | std::cout << "float " << poly_float() << std::endl; 10 | std::cout << "double " << poly_double() << std::endl; 11 | } -------------------------------------------------------------------------------- /include/polymorph-lib.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // ================ 5 | // COMPILE-TIME RNG 6 | // ================ 7 | 8 | struct poly { 9 | private: 10 | // common types 11 | typedef unsigned long long ull; 12 | typedef unsigned int ui; 13 | 14 | // arithmetic simplification functions 15 | static constexpr ull sq(ull x) { return x * x; } 16 | static constexpr ull sm(ull x) { return sq(x) + x; } 17 | static constexpr ull sh(ull x) { return (x>>32) | (x<<32); } 18 | 19 | public: 20 | // normal prng's are hard to use here, since we can't easily modify our state 21 | // we need to use a counter-based rng, to use __COUNTER__ as our state instead 22 | // https://en.wikipedia.org/wiki/Counter-based_random_number_generator_(CBRNG) 23 | // we use Widynski's Squares method to achieve this: https://arxiv.org/abs/2004.06278 24 | static constexpr ui Widynski_Squares(ull count, ull seed) { 25 | unsigned long long cs = (count + 1) * seed; 26 | return (sq(sh(sq(sh(sm(cs))) + cs + seed)) + cs) >> 32; 27 | } 28 | 29 | // we use Box-Muller as our method to obtain a normal distribution 30 | // we add the lowest positive double value to prevent log(0) from being run 31 | static constexpr double BoxMuller(double a, double b, double sigma, double mu) { 32 | const double e = std::numeric_limits::min(); 33 | return sqrt(-2.0 * log(a+e)) * cos(2.0 * M_PI * b) * sigma + mu; 34 | } 35 | 36 | // we define our seed based off of the __DATE__ and __TIME__ macros 37 | // this allows us to have different compile-time seed values 38 | static constexpr ull Day = 39 | (__DATE__[5] - '0') + 40 | (__DATE__[4]==' ' ? 0 : __DATE__[4]-'0')*10; 41 | 42 | static constexpr ull Month = 43 | (__DATE__[1]=='a'&&__DATE__[2]=='n') * 1 + 44 | (__DATE__[2]=='b') * 2 + 45 | (__DATE__[1]=='a'&&__DATE__[2]=='r') * 3 + 46 | (__DATE__[1]=='p'&&__DATE__[2]=='r') * 4 + 47 | (__DATE__[2]=='y') * 5 + 48 | (__DATE__[1]=='u'&&__DATE__[2]=='n') * 6 + 49 | (__DATE__[2]=='l') * 7 + 50 | (__DATE__[2]=='g') * 8 + 51 | (__DATE__[2]=='p') * 9 + 52 | (__DATE__[2]=='t') * 10 + 53 | (__DATE__[2]=='v') * 11 + 54 | (__DATE__[2]=='c') * 12; 55 | 56 | static constexpr ull Year = 57 | (__DATE__[9] - '0') + 58 | (__DATE__[10] - '0') * 10; 59 | 60 | static constexpr ull Time = 61 | (__TIME__[0] - '0') * 1 + 62 | (__TIME__[1] - '0') * 10 + 63 | (__TIME__[3] - '0') * 100 + 64 | (__TIME__[4] - '0') * 1000 + 65 | (__TIME__[6] - '0') * 10000 + 66 | (__TIME__[7] - '0') * 100000; 67 | 68 | #ifndef __POLY_RANDOM_SEED__ 69 | static constexpr ull Seed = 70 | Time + 71 | 100000ll * Day + 72 | 10000000ll * Month + 73 | 1000000000ll * Year; 74 | #else 75 | static constexpr ull Seed = __POLY_RANDOM_SEED__; 76 | #endif 77 | }; 78 | 79 | // ===================== 80 | // POLYMORPHIC FUNCTIONS 81 | // ===================== 82 | 83 | // various random types 84 | #define poly_uint() (poly::Widynski_Squares(__COUNTER__, poly::Seed)) 85 | #define poly_int() ((int)poly_uint()) 86 | #define poly_ull() (((unsigned long long)poly_int() << 32) ^ poly_int()) 87 | #define poly_ll() ((long long)poly_ull()) 88 | #define poly_float() (static_cast(poly_uint()) / static_cast(UINT_MAX)) 89 | #define poly_double() (static_cast(poly_ull()) / static_cast(ULLONG_MAX)) 90 | 91 | // random number modulo max 92 | #define poly_random(max) (poly_uint() % max) 93 | 94 | // random no-ops, inserts junk code 95 | #define poly_junk() { \ 96 | int chance = poly_random(21); \ 97 | if (chance == 0) { volatile int value = poly_random(10000); } \ 98 | if (chance == 1) { volatile float value = poly_random(1000); } \ 99 | if (chance == 2) { volatile double value = poly_random(1000); } \ 100 | if (chance == 3) { volatile char value = poly_random(100000); } \ 101 | if (chance == 4) { volatile int v[4] = {poly_random(1000), poly_random(1000), poly_random(1000), poly_random(1000)}; } \ 102 | if (chance == 5) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] + v[2]; } \ 103 | if (chance == 6) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] * v[2]; } \ 104 | if (chance == 7) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] | v[2]; } \ 105 | if (chance == 8) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] ^ v[2]; } \ 106 | if (chance == 9) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] & v[2]; } \ 107 | if (chance == 10) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] - v[2]; } \ 108 | if (chance == 11) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[1] / (v[2] + 1); } \ 109 | if (chance == 12) { volatile int v[2] = {poly_random(10000), poly_random(10000)}; volatile int vo = v[2] % (v[1] + 1); } \ 110 | if (chance == 13) { volatile int v1 = poly_random(10000), v2 = v1 + poly_random(10000); } \ 111 | if (chance == 14) { volatile int v1 = poly_random(10000), v2 = v1 * poly_random(10000); } \ 112 | if (chance == 15) { volatile int v1 = poly_random(10000), v2 = v1 | poly_random(10000); } \ 113 | if (chance == 16) { volatile int v1 = poly_random(10000), v2 = v1 ^ poly_random(10000); } \ 114 | if (chance == 17) { volatile int v1 = poly_random(10000), v2 = v1 & poly_random(10000); } \ 115 | if (chance == 18) { volatile int v1 = poly_random(10000), v2 = v1 - poly_random(10000); } \ 116 | if (chance == 19) { volatile int v1 = poly_random(10000), v2 = v1 / (poly_random(10000) + 1); } \ 117 | if (chance == 20) { volatile int v1 = poly_random(10000), v2 = v1 % (poly_random(10000) + 1); } \ 118 | } 119 | 120 | // random order of operations for two functions 121 | #define poly_random_order(f1,f2) { \ 122 | int chance = poly_random(2); \ 123 | if (chance == 0) { f1; f2; } \ 124 | else { f2; f1; } \ 125 | } 126 | 127 | // every `c` calls, on average the function `f` will only get executed once 128 | #define poly_random_chance(c,f) { \ 129 | int chance = poly_random(c); \ 130 | if (chance == 0) { f; } \ 131 | } 132 | 133 | // random normal distribution 134 | #define poly_normal(sigma,mu) (poly::BoxMuller(poly_double(),poly_double(),sigma,mu)) --------------------------------------------------------------------------------