├── Sample.cpp ├── README.md └── Obfuscation.h /Sample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define OBFUSCATE_STRINGS 5 | 6 | #include "Obfuscation.h" 7 | 8 | using namespace std; 9 | 10 | int main() 11 | { 12 | IFN(LoadLibraryA)(XOR("msvcrt.dll")); 13 | IFN(LoadLibraryA)(XOR("user32.dll")); 14 | 15 | IFN(printf)(XOR("%s %s\n"), "Not obfuscated", XOR("Obfuscated")); 16 | IFN(MessageBoxW)(NULL, XORW(L"\u304B\u308F\u3044\u3044\u732B\u597D\u304D"), L"hello world", MB_OK); 17 | IFN(system)(XOR("pause>nul")); 18 | 19 | return 0; 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obfuscator 2 | This is an obfuscator demonstrating how to use C++11/14 to generate, at compile time, obfuscated code without modifying the compiler or using any external obfuscation tools. It obfuscates strings and calls to functions. Based on the [ADVobfuscator](https://github.com/andrivet/ADVobfuscator). 3 | 4 | ## Installing 5 | Just include Obfuscation.h and obfuscate all the way! 6 | 7 | ## Examples 8 | Obfuscate strings: 9 | ```cpp 10 | #define OBFUSCATE_STRINGS // to enable string obfuscation 11 | include "Obfuscation.h" 12 | ... 13 | XOR("This is an obfuscated string"); 14 | XORW(L"This is an obfuscated wide string"); 15 | ``` 16 | Obfuscate function calls: 17 | ```cpp 18 | IFN(LoadLibraryA)("user32.dll"); // load the DLL where the MessageBoxA function is located 19 | IFN(MessageBoxA)(NULL, "Hello World!", "Info", MB_OK); // make obfuscated function call 20 | ``` 21 | 22 | ## How it Works 23 | The fundamentals of string obfuscation is discussed [here](https://github.com/andrivet/ADVobfuscator). As for the obfuscated function call, basically, the approach is to use an indirect function call so that the address must be computed first and then called. The address of the function is hashed at compile-time. The hashed address is then compared to the hash map of functions from the imported modules of our program. If the hashes match, we invoke the function in that module. 24 | 25 | How the hashes of functions from other modules are stored? A hash map of functions from the allowed modules are computed. We only limit a certain number of common imported modules to reduce unnecessary hashed functions that aren't commonly used and also to reduce the slowness of computing the hashes and tabulating them in our hash map. 26 | -------------------------------------------------------------------------------- /Obfuscation.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010-2017, Sebastien Andrivet 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | // 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | // 10 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | 14 | #pragma once 15 | #ifndef OBFUSCATION_H 16 | #define OBFUSCATION_H 17 | 18 | #include 19 | #include 20 | 21 | #ifdef _MSC_VER 22 | #define ALWAYS_INLINE __forceinline 23 | #else 24 | #define ALWAYS_INLINE __attribute__((always_inline)) 25 | #endif 26 | 27 | namespace Obfuscation 28 | { 29 | // Implementation of an obfuscated string 30 | // No limitations: 31 | // - No truncation 32 | // - Key generated at compile time 33 | // - Algorithm selected at compile time (there are three examples below) 34 | 35 | namespace 36 | { 37 | // I use current (compile time) as a seed 38 | 39 | constexpr char time[] = __TIME__; // __TIME__ has the following format: hh:mm:ss in 24-hour time 40 | 41 | // Convert time string (hh:mm:ss) into a number 42 | constexpr int DigitToInt(char c) { return c - '0'; } 43 | const int seed = DigitToInt(time[7]) + 44 | DigitToInt(time[6]) * 10 + 45 | DigitToInt(time[4]) * 60 + 46 | DigitToInt(time[3]) * 600 + 47 | DigitToInt(time[1]) * 3600 + 48 | DigitToInt(time[0]) * 36000; 49 | } 50 | 51 | // 1988, Stephen Park and Keith Miller 52 | // "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard" 53 | // Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation: 54 | // with 32-bit math and without division 55 | 56 | template 57 | struct RandomGenerator 58 | { 59 | private: 60 | static constexpr unsigned a = 16807; // 7^5 61 | static constexpr unsigned m = 2147483647; // 2^31 - 1 62 | 63 | static constexpr unsigned s = RandomGenerator::value; 64 | static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807 65 | static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807 66 | static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bits 67 | static constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of hi 68 | static constexpr unsigned lo3 = lo2 + hi; 69 | 70 | public: 71 | static constexpr unsigned max = m; 72 | static constexpr unsigned value = lo3 > m ? lo3 - m : lo3; 73 | }; 74 | 75 | template<> 76 | struct RandomGenerator<0> 77 | { 78 | static constexpr unsigned value = seed; 79 | }; 80 | 81 | // Note: A bias is introduced by the modulo operation. 82 | // However, I do belive it is neglictable in this case (M is far lower than 2^31 - 1) 83 | 84 | template 85 | struct Random 86 | { 87 | static const int value = RandomGenerator::value % M; 88 | }; 89 | 90 | // std::index_sequence will be available with C++14 (C++1y). For the moment, implement a (very) simplified and partial version. You can find more complete versions on the Internet 91 | // MakeIndex::type generates Indexes<0, 1, 2, 3, ..., N> 92 | 93 | template 94 | struct Indexes { using type = Indexes; }; 95 | 96 | template 97 | struct Make_Indexes { using type = typename Make_Indexes::type::type; }; 98 | 99 | template<> 100 | struct Make_Indexes<0> { using type = Indexes<>; }; 101 | 102 | // Represents an obfuscated string, parametrized with an alrorithm number N, a list of indexes Indexes and a key Key 103 | 104 | template 105 | struct XorString; 106 | 107 | template 108 | struct XorStringW; 109 | 110 | // Partial specialization with a list of indexes I, a key K and algorithm N = 0 111 | // Each character is encrypted (XOR) with the same key, stored at the beginning of the buffer 112 | 113 | template 114 | struct XorString<0, K, Indexes> 115 | { 116 | // Constructor. Evaluated at compile time. Key is stored as the first element of the buffer 117 | constexpr ALWAYS_INLINE XorString(const char* str) 118 | : buffer_{ K, encrypt(str[I])... } { } 119 | 120 | // Runtime decryption. Most of the time, inlined 121 | inline const char* decrypt() 122 | { 123 | for (size_t i = 0; i < sizeof...(I); ++i) 124 | buffer_[i + 1] = decrypt(buffer_[i + 1]); 125 | buffer_[sizeof...(I)+1] = 0; 126 | return buffer_ + 1; 127 | } 128 | 129 | private: 130 | // Encrypt / decrypt a character of the original string with the key 131 | constexpr char key() const { return buffer_[0]; } 132 | constexpr char encrypt(char c) const { return c ^ key(); } 133 | constexpr char decrypt(char c) const { return encrypt(c); } 134 | 135 | // Buffer to store the encrypted string + terminating null byte + key 136 | char buffer_[sizeof...(I)+2]; 137 | }; 138 | 139 | template 140 | struct XorStringW<0, K, Indexes> 141 | { 142 | // Constructor. Evaluated at compile time. Key is stored as the first element of the buffer 143 | constexpr ALWAYS_INLINE XorStringW(const wchar_t* str) 144 | : buffer_{ K, encrypt(str[I])... } { } 145 | 146 | // Runtime decryption. Most of the time, inlined 147 | inline const wchar_t* decrypt() 148 | { 149 | for (size_t i = 0; i < sizeof...(I); ++i) 150 | buffer_[i + 1] = decrypt(buffer_[i + 1]); 151 | buffer_[sizeof...(I)+1] = 0; 152 | return buffer_ + 1; 153 | } 154 | 155 | private: 156 | // Encrypt / decrypt a wchar_tacter of the original string with the key 157 | constexpr wchar_t key() const { return buffer_[0]; } 158 | constexpr wchar_t encrypt(wchar_t c) const { return c ^ key(); } 159 | constexpr wchar_t decrypt(wchar_t c) const { return encrypt(c); } 160 | 161 | // Buffer to store the encrypted string + terminating null byte + key 162 | wchar_t buffer_[sizeof...(I)+2]; 163 | }; 164 | 165 | // Partial specialization with a list of indexes I, a key K and algorithm N = 1 166 | // Each character is encrypted (XOR) with an incremented key. The first key is stored at the beginning of the buffer 167 | 168 | template 169 | struct XorString<1, K, Indexes> 170 | { 171 | // Constructor. Evaluated at compile time. Key is stored as the first element of the buffer 172 | constexpr ALWAYS_INLINE XorString(const char* str) 173 | : buffer_{ K, encrypt(str[I], I)... } { } 174 | 175 | // Runtime decryption. Most of the time, inlined 176 | inline const char* decrypt() 177 | { 178 | for (size_t i = 0; i < sizeof...(I); ++i) 179 | buffer_[i + 1] = decrypt(buffer_[i + 1], i); 180 | buffer_[sizeof...(I)+1] = 0; 181 | return buffer_ + 1; 182 | } 183 | 184 | private: 185 | // Encrypt / decrypt a character of the original string with the key 186 | constexpr char key(size_t position) const { return static_cast(buffer_[0] + position); } 187 | constexpr char encrypt(char c, size_t position) const { return c ^ key(position); } 188 | constexpr char decrypt(char c, size_t position) const { return encrypt(c, position); } 189 | 190 | // Buffer to store the encrypted string + terminating null byte + key 191 | char buffer_[sizeof...(I)+2]; 192 | }; 193 | 194 | template 195 | struct XorStringW<1, K, Indexes> 196 | { 197 | // Constructor. Evaluated at compile time. Key is stored as the first element of the buffer 198 | constexpr ALWAYS_INLINE XorStringW(const wchar_t* str) 199 | : buffer_{ K, encrypt(str[I], I)... } { } 200 | 201 | // Runtime decryption. Most of the time, inlined 202 | inline const wchar_t* decrypt() 203 | { 204 | for (size_t i = 0; i < sizeof...(I); ++i) 205 | buffer_[i + 1] = decrypt(buffer_[i + 1], i); 206 | buffer_[sizeof...(I)+1] = 0; 207 | return buffer_ + 1; 208 | } 209 | 210 | private: 211 | // Encrypt / decrypt a wchar_tacter of the original string with the key 212 | constexpr wchar_t key(size_t position) const { return static_cast(buffer_[0] + position); } 213 | constexpr wchar_t encrypt(wchar_t c, size_t position) const { return c ^ key(position); } 214 | constexpr wchar_t decrypt(wchar_t c, size_t position) const { return encrypt(c, position); } 215 | 216 | // Buffer to store the encrypted string + terminating null byte + key 217 | wchar_t buffer_[sizeof...(I)+2]; 218 | }; 219 | 220 | // Partial specialization with a list of indexes I, a key K and algorithm N = 2 221 | // Shift the value of each character and does not store the key. It is only used at compile-time. 222 | 223 | template 224 | struct XorString<2, K, Indexes> 225 | { 226 | // Constructor. Evaluated at compile time. Key is *not* stored 227 | constexpr ALWAYS_INLINE XorString(const char* str) 228 | : buffer_{ encrypt(str[I])..., 0 } { } 229 | 230 | // Runtime decryption. Most of the time, inlined 231 | inline const char* decrypt() 232 | { 233 | for (size_t i = 0; i < sizeof...(I); ++i) 234 | buffer_[i] = decrypt(buffer_[i]); 235 | return buffer_; 236 | } 237 | 238 | private: 239 | // Encrypt / decrypt a character of the original string with the key 240 | constexpr char key(char key) const { return key % 13; } 241 | constexpr char encrypt(char c) const { return c + key(K); } 242 | constexpr char decrypt(char c) const { return c - key(K); } 243 | 244 | // Buffer to store the encrypted string + terminating null byte. Key is not stored 245 | char buffer_[sizeof...(I)+1]; 246 | }; 247 | 248 | template 249 | struct XorStringW<2, K, Indexes> 250 | { 251 | // Constructor. Evaluated at compile time. Key is *not* stored 252 | constexpr ALWAYS_INLINE XorStringW(const wchar_t* str) 253 | : buffer_{ encrypt(str[I])..., 0 } { } 254 | 255 | // Runtime decryption. Most of the time, inlined 256 | inline const wchar_t* decrypt() 257 | { 258 | for (size_t i = 0; i < sizeof...(I); ++i) 259 | buffer_[i] = decrypt(buffer_[i]); 260 | return buffer_; 261 | } 262 | 263 | private: 264 | // Encrypt / decrypt a wchar_tacter of the original string with the key 265 | constexpr wchar_t key(wchar_t key) const { return key % 13; } 266 | constexpr wchar_t encrypt(wchar_t c) const { return c + key(K); } 267 | constexpr wchar_t decrypt(wchar_t c) const { return c - key(K); } 268 | 269 | // Buffer to store the encrypted string + terminating null byte. Key is not stored 270 | wchar_t buffer_[sizeof...(I)+1]; 271 | }; 272 | 273 | // Helper to generate a key 274 | template 275 | struct RandomChar 276 | { 277 | // Use 0x7F as maximum value since most of the time, char is signed (we have however 1 bit less of randomness) 278 | static const char value = static_cast(1 + Random::value); 279 | }; 280 | 281 | template 282 | struct RandomCharW 283 | { 284 | // Use 0xFFFF as maximum value of wchar_t (we have however 1 bit less of randomness) 285 | static const wchar_t value = static_cast(1 + Random::value); 286 | }; 287 | 288 | // Compile-time recursive mod of string hashing algorithm, the actual algorithm was taken from Qt library 289 | constexpr uint32_t HashPart3(char c, uint32_t hash) { return ((hash << 4) + c); } 290 | constexpr uint32_t HashPart2(char c, uint32_t hash) { return (HashPart3(c, hash) ^ ((HashPart3(c, hash) & 0xF0000000) >> 23)); } 291 | constexpr uint32_t HashPart1(char c, uint32_t hash) { return (HashPart2(c, hash) & 0x0FFFFFFF); } 292 | constexpr uint32_t Hash(const char* str) { return (*str) ? (HashPart1(*str, Hash(str + 1))) : (0); } 293 | 294 | constexpr uint32_t HashPart3W(wchar_t c, uint32_t hash) { return ((hash << 4) + c); } 295 | constexpr uint32_t HashPart2W(wchar_t c, uint32_t hash) { return (HashPart3W(c, hash) ^ ((HashPart3W(c, hash) & 0xF0000000) >> 23)); } 296 | constexpr uint32_t HashPart1W(wchar_t c, uint32_t hash) { return (HashPart2W(c, hash) & 0x0FFFFFFF); } 297 | constexpr uint32_t HashW(const wchar_t* str) { return (*str) ? (HashPart1W(*str, HashW(str + 1))) : (0); } 298 | 299 | } // Obfuscation 300 | 301 | // Prefix notation 302 | #define DEF_XOR(str) Obfuscation::XorString::value, Obfuscation::RandomChar<__COUNTER__>::value, Obfuscation::Make_Indexes::type>(str) 303 | #define DEF_XORW(str) Obfuscation::XorStringW::value, Obfuscation::RandomCharW<__COUNTER__>::value, Obfuscation::Make_Indexes::type>(str) 304 | 305 | #ifdef OBFUSCATE_STRINGS 306 | #define XOR(str) (DEF_XOR(str).decrypt()) 307 | #define XORW(str) (DEF_XORW(str).decrypt()) 308 | #else 309 | #define XOR(str) (str) 310 | #define XORW(str) (str) 311 | #endif 312 | 313 | // Compile-time hashing macro 314 | #define HASH(str) (uint32_t)(Obfuscation::Hash(str) ^ Obfuscation::Random<0, 0x7FFFFFFF>::value) 315 | #define HASHW(str) (uint32_t)(Obfuscation::HashW(str) ^ Obfuscation::Random<0, 0x7FFFFFFF>::value) 316 | 317 | typedef struct _UNICODE_STRING 318 | { 319 | USHORT Length; 320 | USHORT MaximumLength; 321 | PWSTR Buffer; 322 | } UNICODE_STRING, *PUNICODE_STRING; 323 | 324 | typedef struct LDR_DATA_ENTRY 325 | { 326 | LIST_ENTRY InMemoryOrderModuleList; 327 | PVOID BaseAddress; 328 | PVOID EntryPoint; 329 | ULONG SizeOfImage; 330 | UNICODE_STRING FullDllName; 331 | UNICODE_STRING BaseDllName; 332 | ULONG Flags; 333 | SHORT LoadCount; 334 | SHORT TlsIndex; 335 | LIST_ENTRY HashTableEntry; 336 | ULONG TimeDateStamp; 337 | } LDR_DATA_ENTRY, *PLDR_DATA_ENTRY; 338 | 339 | __declspec(naked) PLDR_DATA_ENTRY GetLdrDataEntry() { 340 | __asm 341 | { 342 | mov eax, fs:[0x30] 343 | mov eax, [eax + 0x0C] 344 | mov eax, [eax + 0x1C] 345 | retn 346 | } 347 | } 348 | 349 | bool dll_allowed(const wchar_t* dllName) 350 | { 351 | static const wchar_t* AllowedModules[] = 352 | { 353 | L"advapi32.dll", 354 | L"kernelbase.dll", 355 | L"kernel32.dll", 356 | L"mpr.dll", 357 | L"mscoree.dll", 358 | L"msvcrt.dll", 359 | L"ntdll.dll", 360 | L"user32.dll", 361 | L"winmm.dll" 362 | }; 363 | 364 | for (auto str : AllowedModules) 365 | { 366 | if (_wcsicmp(dllName, str) == 0) 367 | return true; 368 | } 369 | 370 | return false; 371 | } 372 | 373 | void* GetFuncByHash(uint32_t hash) 374 | { 375 | static std::map fnMap; 376 | 377 | if (fnMap[hash]) 378 | return fnMap[hash]; 379 | 380 | PLDR_DATA_ENTRY pLDE; 381 | PIMAGE_DOS_HEADER pIDH; 382 | PIMAGE_NT_HEADERS pINH; 383 | PIMAGE_EXPORT_DIRECTORY pIED; 384 | 385 | HMODULE hModule; 386 | PDWORD Address, Name; 387 | PWORD Ordinal; 388 | 389 | for (pLDE = GetLdrDataEntry(); pLDE->BaseAddress; pLDE = (PLDR_DATA_ENTRY)pLDE->InMemoryOrderModuleList.Flink) 390 | { 391 | if (!dll_allowed(pLDE->BaseDllName.Buffer)) { continue; } 392 | hModule = (HMODULE)pLDE->BaseAddress; 393 | if (!hModule) { continue; } 394 | pIDH = (PIMAGE_DOS_HEADER)hModule; 395 | if (pIDH->e_magic != IMAGE_DOS_SIGNATURE) { continue; } 396 | pINH = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pIDH->e_lfanew); 397 | if (pINH->Signature != IMAGE_NT_SIGNATURE) { continue; } 398 | if (pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0) { continue; } 399 | 400 | pIDH = (PIMAGE_DOS_HEADER)hModule; 401 | pINH = (PIMAGE_NT_HEADERS)((LONG)hModule + pIDH->e_lfanew); 402 | pIED = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 403 | Address = (PDWORD)((BYTE*)hModule + pIED->AddressOfFunctions); 404 | Name = (PDWORD)((BYTE*)hModule + pIED->AddressOfNames); 405 | Ordinal = (PWORD)((BYTE*)hModule + pIED->AddressOfNameOrdinals); 406 | 407 | if (!Address || !Name || !Ordinal) 408 | continue; 409 | 410 | for (DWORD i = 0; i < pIED->NumberOfFunctions; i++) 411 | { 412 | uint32_t fnHash = HASH((char*)hModule + Name[i]); 413 | fnMap[fnHash] = (void*)((BYTE*)hModule + Address[Ordinal[i]]); 414 | 415 | if (fnHash == hash) 416 | return fnMap[fnHash]; 417 | } 418 | } 419 | 420 | return NULL; 421 | } 422 | 423 | #define IFN(name) (reinterpret_cast(GetFuncByHash(HASH(#name)))) 424 | 425 | #endif --------------------------------------------------------------------------------