├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── cmake └── Python.cmake ├── doc ├── X-OR_tutorial │ ├── Makefile │ ├── X-OR_tutorial.rst │ ├── simple.dot │ ├── simple.png │ ├── split.dot │ └── split.png └── llvm_obfuscation_tutorial.rst ├── llvm-passes ├── CMakeLists.txt ├── ObfuscateZero │ └── ObfuscateZero.cpp ├── PropagatedTransformation │ └── PropagatedTransformation.hpp ├── SplitBitwiseOp │ └── SplitBitwiseOp.cpp └── X-OR │ └── X-OR.cpp └── tests ├── ObfuscateZero ├── basic_test.c ├── check_openssl.sh ├── check_zlib.sh ├── null_pointer_test.c ├── test_gep.c └── test_maxint.cpp ├── SplitBitwiseOp ├── check_openssl.sh ├── check_zlib.sh ├── sbo_and.c ├── sbo_max.c ├── sbo_max32.c ├── sbo_max_chained.c ├── sbo_or.c └── sbo_xor.c ├── X-OR ├── check_openssl.sh ├── check_zlib.sh ├── xor.c ├── xor_bigint.c ├── xor_bool.cpp ├── xor_bool_1bit.ll ├── xor_bool_1bit_openssl_false.ll ├── xor_bool_1bit_openssl_true.ll ├── xor_chained.c ├── xor_complex_tree.c ├── xor_max.c ├── xor_max32.c ├── xor_max_chained.c ├── xor_multiple_roots.c ├── xor_simple_chain.c └── xor_stress_tree_base.c └── lit.cfg.in /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(llvm_passes C CXX) 3 | 4 | if(UNIX) 5 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -std=c++11 -fdiagnostics-color -g -UNDEBUG") 6 | endif() 7 | 8 | # where cmake extension lies 9 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 10 | include(Python) 11 | 12 | # LLVM detection part 13 | set(LLVM_ROOT "" CACHE PATH "Root of LLVM install.") 14 | 15 | # A bit of a sanity check: 16 | if( NOT EXISTS ${LLVM_ROOT}/include/llvm ) 17 | message(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) is not a valid LLVM install") 18 | endif() 19 | 20 | 21 | # Incorporate the CMake features provided by LLVM: 22 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LLVM_ROOT}/share/llvm/cmake") 23 | 24 | # only needed for multi target build? 25 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_CFG_INTDIR}) 26 | set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_CFG_INTDIR}) 27 | 28 | # get regular includes 29 | include(LLVMConfig) 30 | include(HandleLLVMOptions) 31 | include(AddLLVM) 32 | 33 | # Now set the header and library paths: 34 | include_directories(${LLVM_INCLUDE_DIRS}) 35 | link_directories(${LLVM_LIBRARY_DIRS}) 36 | add_definitions(${LLVM_DEFINITIONS}) 37 | 38 | # generate config-dependent files 39 | configure_file("${CMAKE_SOURCE_DIR}/tests/lit.cfg.in" "${CMAKE_BINARY_DIR}/tests/lit.cfg") 40 | 41 | # testing part relies on lit 42 | find_python_module(lit REQUIRED) 43 | add_custom_target(check COMMAND ${PYTHON_EXECUTABLE} -m lit.main ${CMAKE_BINARY_DIR}/tests) 44 | 45 | # Finally load appropriate config from subdirs 46 | add_subdirectory(llvm-passes) 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Quarkslab 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 | 23 | -------------------------------------------------------------------------------- /cmake/Python.cmake: -------------------------------------------------------------------------------- 1 | include(FindPackageHandleStandardArgs) 2 | include(FindPythonInterp) 3 | 4 | function(find_python_module module) 5 | string(TOUPPER ${module} module_upper) 6 | if(NOT PY_${module_upper}) 7 | if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") 8 | set(${module}_FIND_REQUIRED TRUE) 9 | endif() 10 | # A module's location is usually a directory, but for binary modules 11 | # it's a .so file. 12 | execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" 13 | "import re, ${module}; print re.compile('/__init__.py.*').sub('',${module}.__file__)" 14 | RESULT_VARIABLE _${module}_status 15 | OUTPUT_VARIABLE _${module}_location 16 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 17 | if(NOT _${module}_status) 18 | set(PY_${module_upper} ${_${module}_location} CACHE STRING 19 | "Location of Python module ${module}") 20 | endif(NOT _${module}_status) 21 | endif(NOT PY_${module_upper}) 22 | find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) 23 | endfunction(find_python_module) 24 | -------------------------------------------------------------------------------- /doc/X-OR_tutorial/Makefile: -------------------------------------------------------------------------------- 1 | # graphs 2 | DOT=$(wildcard *.dot) 3 | PNG=$(DOT:.dot=.png) 4 | 5 | # rst 6 | RST=$(wildcard *.rst) 7 | HTML=$(RST:.rst=.html) 8 | 9 | all: $(PNG) $(HTML) 10 | 11 | $(PNG) : %.png : %.dot 12 | dot -Tpng $< -o $@ 13 | 14 | $(HTML) : %.html : %.rst 15 | rst2html $< > $@ 16 | 17 | clean: 18 | /bin/rm -f *.html *.png 19 | -------------------------------------------------------------------------------- /doc/X-OR_tutorial/X-OR_tutorial.rst: -------------------------------------------------------------------------------- 1 | ========================================================== 2 | Turning Regular Code Into Atrocities With LLVM: The Return 3 | ========================================================== 4 | 5 | Briefing 6 | ======== 7 | 8 | Required Background 9 | ******************* 10 | 11 | This tutorial is the continuation of `this one `_. 12 | If you are not familiar with LLVM pass development you should read the previous tutorial, as the basics won't be covered in this tutorial. 13 | 14 | To go through the code samples given in this tutorial you will need to be able to read C, C++ code and simple LLVM bytecode. 15 | 16 | Riddle 17 | ****** 18 | 19 | Let's start with a riddle. 20 | The obfuscation we are going to implement will replace an operation `OP` by the sequence of instructions below. 21 | Your job is to try to guess what `OP` is. 22 | 23 | Given: 24 | 25 | * two integers `D` and `X` of same bit width 26 | * :math:`Y = D \text{ OP } X` 27 | * :math:`f:x = \sum x_i \cdot 2^i \mapsto x' = \sum x_i \cdot 3^i` 28 | * :math:`h:x = \sum x_i \cdot 3^i \mapsto x' = \sum (x_i \text{ mod } 2) \cdot 2^i` 29 | 30 | We are going to rewrite the operation `OP` as follows: 31 | 32 | * Transform operands `D` and `X` with `f`: 33 | :math:`D'=f(D) \text{ and } X'=f(X)` 34 | * Apply the ADD operation to the new operands: 35 | :math:`A=D'+X'=\sum (d_i + x_i) \cdot 3^i` 36 | * Transform back the result with `h`: 37 | :math:`A'=h(A)=h(D'+X')` 38 | * And we magically obtain: 39 | :math:`h(D'+X')=D \text{ OP } X` 40 | 41 | Don't cheat and try to find out what `OP` could be. 42 | 43 | Obfuscation 44 | *********** 45 | 46 | Ok for those of you who have guessed and the ones that skipped ahead, the answer is `X-OR `_. 47 | 48 | Oops, not that XOR, this one: 49 | 50 | ===== ===== ======= 51 | A B A XOR B 52 | ===== ===== ======= 53 | 0 0 0 54 | 0 1 1 55 | 1 0 1 56 | 1 1 0 57 | ===== ===== ======= 58 | 59 | The sequence of operations above (operands transformation, addition, result transformation) is a more complex, harder to understand way to code a XOR. 60 | If you want to make sure that the transformation is not trivial, post in the comments whether you found the solution of the riddle; and if you did, the time it took you. 61 | 62 | We chose to obfuscate XORs because this operator is very present in cryptography... and because we all get bored sometimes. 63 | 64 | But you shouldn't trust me. Taking a look at OpenSSL code base:: 65 | 66 | `find $OPENSSL_SRC -name "*.c" -exec grep -cE " \^ " {} \; | awk '{s+=$1} END {print s}'` 67 | 68 | we find around 648 XORs. 69 | 70 | Behind the Scenes 71 | ***************** 72 | 73 | Now, since programmers don't really deal in magic (at least not officially), let's try to understand what happened. 74 | The first thing to understand is that a XOR operation is basically an ADD operation without carry. 75 | This means that if we have a representation of our operands in which the ADD carries won't propagate, then XOR and ADD are equivalent. 76 | 77 | And this is exactly what we have done. 78 | The function `f` takes the base 2 coefficients (bits) of the input and multiplies them by the corresponding power of 3. 79 | You may have noticed that this operation is *almost* a change of basis operation from base 2 to base 3 in which the 'bits' are not transformed. 80 | 81 | Since :math:`d_i \le 1 \text{ and } x_i \le 1` then :math:`a_i \le 2`. 82 | This means that the bits of A are never going to propagate to upper bits, since the sum of :math:`d_i` and :math:`x_i` is smaller than the 'basis' in which they are represented (here 3). 83 | 84 | Because no carry has propagated, applying a modulo 2 on the bits of A and writing the result in base 2 (which is done by the `h` function) will give us the same behaviour as a XOR operation on D and X. 85 | Indeed, an addition modulo 2 is equivalent to a XOR. 86 | 87 | Bottom line: by changing the representation of XOR operands we are able to assure that the ADD operation will not propagate carries. 88 | Which means that in this new representation XORs ans ADDs are *almost* the same. 89 | And a simple modulo takes care of this difference. 90 | 91 | 92 | Requirements 93 | ============ 94 | 95 | Environment 96 | *********** 97 | 98 | To use the passes we are going to develop, you will need LLVM and clang sources and you'll have to build them. 99 | If you need details on how to get these, you can refer to the 'Requirements' section of the `previous LLVM tutorial `_. 100 | 101 | To make sure that we all have the same basic project infrastructure you can checkout the corresponding git repository:: 102 | 103 | >$ git clone https://github.com/quarkslab/llvm-passes 104 | 105 | **In this article we are not going to explain every line of code, just the interesting parts. This is why you will need the git repository.** 106 | At every step of the development you will be given the name of the branch holding the appropriate code state (we are going to develop the obfuscation in 3 steps). 107 | Each commit is a fully functional pass, the complexity of which increases with every commit. 108 | 109 | From now on we will be working exclusively inside the cloned git repo ``llvm-passes`` (we will refer to it as ``$PASSDIR``). 110 | It contains the followings: 111 | 112 | * *cmake*: cmake definitions to check the Python environment. Required to generate our passes test suites. 113 | * *doc*: contains the sources of this tutorial, in case you find a shaming typo. 114 | * *llvm-passes*: contains one subdirectory per pass, and a ``CMakeList.txt`` used to generate the passes. 115 | * *tests*: tests and validation for our passes, contains one directory per pass. The tests are using llvm-lit, the LLVM integrated validation tool. 116 | * *CMakeList.txt*: the file used to generate the required Makefiles 117 | 118 | LLVM: the Programmer's Stone! 119 | ============================= 120 | 121 | To implement the obfuscation detailed above we are going a create an LLVM ``BasicBlockPass``. 122 | A ``FunctionPass`` might also be a reasonable choice because we will work on XOR chains (XORs using the result of other XORs as operands) later, and this choice will have a direct impact on our algorithms (spoiler!). 123 | 124 | Here is our plan of attack: 125 | 126 | 1. Write a basic pass transforming single XORs:: 127 | 128 | basic_xor 129 | 130 | 2. Write a more complex pass transforming chained XORs:: 131 | 132 | chained_xor 133 | 134 | 3. Write another pass splitting chained bitwise operations in order to combine it with the XOR obfuscation:: 135 | 136 | propagated_transfo 137 | 138 | We'll start with the ``basic_xor`` branch, you might want to checkout this branch: 139 | 140 | .. code:: bash 141 | 142 | git checkout basic_xor 143 | 144 | Turning XORs into ADDs 145 | ********************** 146 | 147 | Enough chit-chat! To implement the first version of the obfuscation we need to: 148 | 149 | 1. Find all the XORs in the current `BasicBlock`. 150 | 2. Choose the base used to transform the XOR operands. 151 | In the introduction we use base 3, but this can be generalized to an arbitrary base (almost arbitrary...). 152 | 3. Transform the XORs' operands. 153 | 4. Create an ADD between the transformed operands. 154 | 5. Transform back the result of the ADD to a standard representation. 155 | 6. Replace all uses of the result of the original instruction by the result of 5. 156 | 157 | I will look for you 158 | +++++++++++++++++++ 159 | 160 | Let's start with the easy part. 161 | To find the XORs we are going to iterate through every instruction in each basic block and check if it is a XOR. 162 | The checking function looks like this: 163 | 164 | .. code:: C++ 165 | 166 | BinaryOperator *isEligibleInstruction(Instruction *Inst) { 167 | BinaryOperator *Op = dyn_cast(Inst); 168 | if (not Op) 169 | return nullptr; 170 | if (Op->getOpcode() == Instruction::BinaryOps::Xor) 171 | return Op; 172 | return nullptr; 173 | } 174 | 175 | Nothing mind-blowing here, but if you are not familiar with LLVM API this might interest you. 176 | 177 | I will find you 178 | +++++++++++++++ 179 | 180 | Once we have found a XOR we will need to pick a base for the transformation. 181 | It is a perfect opportunity to introduce diversity in our obfuscations. 182 | If we were to use the same base for every XORs the obfuscation pattern would be trivially identifiable. 183 | 184 | *'But you said earlier we could choose an arbitrary base, so let's pick a random number and stop wasting my time.'* 185 | 186 | Humm... we may have oversimplified things a little. 187 | In theory the base can be arbitrary (greater than 2!). 188 | But if we obfuscate operands which type is :math:`N_b` bits long, we will need to store :math:`S = \sum (d_i + x_i) \cdot base^i \text{ , } i < N_b`. 189 | 190 | Are you beginning to see the problem? This value can become **HUGE**, well above what a 'standard' type might hold. 191 | But we are programmers so 'huge' is not accurate enough... 192 | The maximum value of `S` is :math:`base^{N_b} - 1`. 193 | 194 | This means that we need :math:`floor(log_2(base^{N_b} - 1)) + 1` bits to store `S`. 195 | The good thing is that LLVM allows you create integer variables with an arbitrary bit size. 196 | Thanks to the LLVM API we can hold and apply *almost* any operation to integers of any size. 197 | 198 | This is awesome! LLVM is doing all the work for us! 199 | And to take advantage of this we only need two functions. 200 | 201 | A function that, given the number of bits of the operands and a base, returns the required number of bits to represent the obfuscated operands: 202 | 203 | .. code:: C++ 204 | 205 | unsigned requiredBits(unsigned OriginalSize, unsigned TargetBase) { 206 | assert(OriginalSize); 207 | if (TargetBase <= 2 or OriginalSize >= MaxSupportedSize) 208 | return 0; 209 | // 'Exact' formula : std::ceil(std::log2(std::pow(TargetBase, OriginalSize) - 1)); 210 | unsigned ret = (unsigned)std::ceil(OriginalSize * std::log2(TargetBase)); 211 | // Need to make sure that the base can be represented too... 212 | // (For instance if the OriginalSize == 1 and TargetBase == 4) 213 | ret = std::max(ret, (unsigned)std::floor(std::log2(TargetBase)) + 1); 214 | return ret <= MaxSupportedSize ? ret : 0; 215 | } 216 | 217 | Except for the approximated formula to compute the required number of bits there is another difference with the theory. 218 | 219 | This part is tricky so hang on tight. 220 | The returned number of bits actually has to hold two different types of value: 221 | 222 | * The number `S`. 223 | (This is what we wrote the function for). 224 | * The value of the base itself: ``TargetBase``. 225 | This is because we need to compute the values of :math:`TargetBase^i`. 226 | 227 | For instance if ``OriginalSize == 1`` and ``TargetBase == 4`` we only need 2 bits to store `S` **but** 2 bits is not enough to hold the value `4`. 228 | Still there? 229 | 230 | Remember when I said we could apply any operation to any bit size? 231 | Well there is an exception, because of `this bug `_. LLVM does not support division of integers of more than `128` bits. 232 | This is why there are ``MaxSupportedSize`` checks in the previous function. 233 | 234 | Because of this limit we need another function that, given the original size of the XOR operands, will return the maximum base we can use for the operands transformation. 235 | 236 | .. code:: C++ 237 | 238 | // Returns the max supported base for the given OriginalNbBit 239 | // 31 is the max base to avoid overflow 2**sizeof(unsigned) in requiredBits 240 | unsigned maxBase(unsigned OriginalNbBit) { 241 | assert(OriginalNbBit); 242 | const unsigned MaxSupportedBase = sizeof(unsigned) * 8 - 1; 243 | if (OriginalNbBit >= MaxSupportedSize) 244 | return 0; 245 | if (MaxSupportedSize / OriginalNbBit > MaxSupportedBase) 246 | return MaxSupportedBase; 247 | return unsigned(2) << ((MaxSupportedSize / OriginalNbBit) - 1); 248 | } 249 | 250 | With :math:`M_s` the maximum supported size and :math:`N_b` the original number of bits of the operands, the maximum supported base is :math:`M_b = 2^{(M_s/N_b)}`. 251 | But we have to make sure that this value is not going to overflow an unsigned. 252 | For instance if `L` is `1` (for a boolean) the maximum base would be :math:`M_b = 2^128 - 1`. 253 | And on a 64 bits OS, the maximum value for an `unsigned` is usually :math:`2^32 - 1`: this is why the :math:`(M/N_b > M_b)` test is required. 254 | 255 | We know the constraints on the base choice, so we can randomly pick one in :math:`[3, maxBase(N_b)]`. 256 | 257 | And I Will... Transform You? 258 | +++++++++++++++++++++++++++++ 259 | 260 | Ok, now we have XORs, we have transformation bases, so we're ready to implement the transformations. 261 | 262 | We will need two functions: 263 | * One generating the instructions corresponding to the function `f`: `rewriteAsBaseN` 264 | * The other generating the instructions corresponding to the function `h`: `transformToBaseTwoRepr` 265 | 266 | There is nothing worth talking about in `rewriteAsBaseN`. 267 | Just take a look at the way we handle types if you are not familiar with LLVM types. 268 | 269 | .. code:: C++ 270 | 271 | Value *rewriteAsBaseN(Value *Operand, unsigned Base, IRBuilder<> &Builder) { 272 | const unsigned OriginalNbBit = Operand->getType()->getIntegerBitWidth(), 273 | NewNbBit = requiredBits(OriginalNbBit, Base); 274 | if(!NewNbBit) 275 | return nullptr; 276 | 277 | Type *NewBaseType = IntegerType::get(Operand->getContext(), NewNbBit); 278 | 279 | Constant *IRBase = ConstantInt::get(NewBaseType, Base); 280 | // Initializing variables 281 | Value *Accu = ConstantInt::getNullValue(NewBaseType), 282 | *Mask = ConstantInt::get(NewBaseType, 1), 283 | *Pow = ConstantInt::get(NewBaseType, 1); 284 | 285 | // Extending the original value to NewNbBit for bitwise and 286 | Value *ExtendedOperand = Builder.CreateZExt(Operand, NewBaseType); 287 | 288 | for(unsigned Bit = 0; Bit < OriginalNbBit; ++Bit) { 289 | // Updating NewValue 290 | Value *MaskedNewValue = Builder.CreateAnd(ExtendedOperand, Mask); 291 | Value *BitValue = Builder.CreateLShr(MaskedNewValue, Bit); 292 | Value *NewBit = Builder.CreateMul(BitValue, Pow); 293 | Accu = Builder.CreateAdd(Accu, NewBit); 294 | // Updating Exponent 295 | Pow = Builder.CreateMul(Pow, IRBase); 296 | // Updating Mask 297 | Mask = Builder.CreateShl(Mask, 1); 298 | } 299 | return Accu; 300 | } 301 | 302 | 303 | The most interesting part in ``transformToBaseTwoRepr`` is the use of ``APInt`` to hold the :math:`base^{N_b - 1}` value. 304 | Since regular types might not be large enough to hold this value, we use an `APInt` to compute it at runtime (when the pass is applied). 305 | This is done by the function ``APIntPow``. (If you need more info you can check the `doc `_.) 306 | 307 | 308 | .. code:: C++ 309 | 310 | Value *transformToBaseTwoRepr(Value *Operand, unsigned Base, Type *OriginalType, IRBuilder<> &Builder) { 311 | Type *ObfuscatedType = Operand->getType(); 312 | 313 | const unsigned OriginalNbBit = OriginalType->getIntegerBitWidth(); 314 | 315 | APInt APBase(ObfuscatedType->getIntegerBitWidth(), Base); 316 | 317 | // Initializing variables 318 | Value *R = Operand, 319 | *IRBase = ConstantInt::get(ObfuscatedType, Base), 320 | *IR2 = ConstantInt::get(ObfuscatedType, 2), 321 | *Accu = ConstantInt::getNullValue(ObfuscatedType); 322 | 323 | // Computing APInt max operand in case we need more than 64 bits 324 | Value *Pow = ConstantInt::get(ObfuscatedType, APIntPow(APBase, OriginalNbBit - 1)); 325 | 326 | // Euclide Algorithm 327 | for(unsigned Bit = OriginalNbBit; Bit > 0; --Bit) { 328 | // Updating NewValue 329 | Value *Q = Builder.CreateUDiv(R, Pow); 330 | Q = Builder.CreateURem(Q, IR2); 331 | Value *ShiftedBit = Builder.CreateShl(Q, Bit - 1); 332 | Accu = Builder.CreateOr(Accu, ShiftedBit); 333 | R = Builder.CreateURem(R, Pow); 334 | // Updating Exponent 335 | Pow = Builder.CreateUDiv(Pow, IRBase); 336 | } 337 | // Cast back to original type 338 | return Builder.CreateZExtOrTrunc(Accu, OriginalType); 339 | } 340 | 341 | .. code:: C++ 342 | 343 | // Builds the APInt exponent value at runtime 344 | // Required if the exponent value overflows uint64_t 345 | static APInt APIntPow(APInt const& Base, unsigned Exponent) { 346 | APInt Accu(Base.getBitWidth(), 1u); 347 | for(; Exponent != 0; --Exponent) 348 | Accu *= Base; 349 | return Accu; 350 | } 351 | 352 | 353 | Show Time 354 | ********* 355 | 356 | Using the Pass 357 | ++++++++++++++ 358 | 359 | The git branch ``basic_xor`` will allow you to run the pass without having to re-develop it yourself. 360 | The build process is the following: 361 | 362 | .. code:: bash 363 | 364 | >$ cd $PASSDIR 365 | >$ mkdir build 366 | >$ cd build 367 | >$ cmake -DLLVM_ROOT=path/to/your/llvm/build .. 368 | >$ make 369 | 370 | Once the pass is built you will need a test code. 371 | For instance write the following code in a file ``basic_test.c``: 372 | 373 | .. code:: C 374 | 375 | #include 376 | #include 377 | 378 | int main() { 379 | volatile uint8_t a = 0, b = 1, c = 0; 380 | b=a^4; 381 | c=b+1; 382 | printf("%d\n", b); 383 | return 0; 384 | } 385 | 386 | We are using `volatile` variables to prevent LLVM from computing the XOR value at compile time and removing the XOR altogether. 387 | You can now run the pass on the generated bytecode: 388 | 389 | .. code:: bash 390 | 391 | >$ clang -S -emit-llvm path/to/test/basic_test.c -o basic_test.ll 392 | >$ opt -S -load $PASSDIR/build/llvm-passes/LLVMX-OR.so -X_OR path/to/test/basic_test.ll -o obfuscated.ll 393 | 394 | And to make sure the obfuscation is not trivial, you can optimize the obfuscated code: 395 | 396 | .. code:: bash 397 | 398 | >$ opt -S path/to/test/obfuscated.ll -O2 -o obfuscated_optimized.ll 399 | 400 | and make sure the XOR is not back. 401 | 402 | 403 | Generated Code 404 | ++++++++++++++ 405 | 406 | The original LLVM bytecode now looks like this: 407 | 408 | .. code:: llvm 409 | 410 | define i32 @main() #0 { 411 | %1 = alloca i32, align 4 412 | %a = alloca i8, align 1 413 | %b = alloca i8, align 1 414 | %c = alloca i8, align 1 415 | store i32 0, i32* %1 416 | store volatile i8 0, i8* %a, align 1 417 | store volatile i8 1, i8* %b, align 1 418 | store volatile i8 0, i8* %c, align 1 419 | %2 = load volatile i8* %a, align 1 420 | %3 = zext i8 %2 to i32 421 | 422 | %4 = xor i32 %3, 4 423 | 424 | %5 = trunc i32 %4 to i8 425 | store volatile i8 %5, i8* %b, align 1 426 | %6 = load volatile i8* %b, align 1 427 | %7 = zext i8 %6 to i32 428 | %8 = add nsw i32 %7, 1 429 | %9 = trunc i32 %8 to i8 430 | store volatile i8 %9, i8* %c, align 1 431 | %10 = load volatile i8* %b, align 1 432 | %11 = zext i8 %10 to i32 433 | %12 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %11) 434 | ret i32 0 435 | } 436 | 437 | You can see that, even though we used 8 bits variables, LLVM extended them to 32 bits to apply the XOR. 438 | This means that the obfuscation will work with 32 bits integers as `OriginalType`. 439 | 440 | Here is a portion of the obfuscated code after applying the pass. 441 | 442 | .. code:: llvm 443 | 444 | define i32 @main() #0 { 445 | %1 = alloca i32, align 4 446 | %a = alloca i8, align 1 447 | %b = alloca i8, align 1 448 | %c = alloca i8, align 1 449 | store i32 0, i32* %1 450 | store volatile i8 0, i8* %a, align 1 451 | store volatile i8 1, i8* %b, align 1 452 | store volatile i8 0, i8* %c, align 1 453 | %2 = load volatile i8* %a, align 1 454 | %3 = zext i8 %2 to i32 455 | 456 | ; Beginning of the obfuscation 457 | ; produced by rewriteAsBaseN 458 | %4 = zext i32 %3 to i51 459 | %5 = and i51 %4, 1 460 | %6 = lshr i51 %5, 0 461 | %7 = mul i51 %6, 1 462 | %8 = add i51 0, %7 463 | . 464 | . 465 | . 466 | %129 = and i51 %4, 2147483648 467 | %130 = lshr i51 %129, 31 468 | %131 = mul i51 %130, 617673396283947 469 | %132 = add i51 %128, %131 470 | 471 | ; New add corresponding to the XOR! 472 | %133 = add i51 %132, 9 473 | 474 | ; Transforming back the result 475 | ; produced by transformToBaseTwoRepr 476 | %134 = udiv i51 %133, 617673396283947 477 | %135 = urem i51 %134, 2 478 | %136 = shl i51 %135, 31 479 | %137 = or i51 0, %136 480 | %138 = urem i51 %133, 617673396283947 481 | . 482 | . 483 | . 484 | %289 = udiv i51 %288, 1 485 | %290 = urem i51 %289, 2 486 | %291 = shl i51 %290, 0 487 | %292 = or i51 %287, %291 488 | %293 = urem i51 %288, 1 489 | %294 = trunc i51 %292 to i32 490 | 491 | ; Original XOR, to be optimized out later 492 | %295 = xor i32 %3, 4 493 | 494 | %296 = trunc i32 %294 to i8 495 | store volatile i8 %296, i8* %b, align 1 496 | %297 = load volatile i8* %b, align 1 497 | %298 = zext i8 %297 to i32 498 | 499 | ; Operation using the result of the obfuscation instead 500 | ; of the XOR (%295) 501 | %299 = add nsw i32 %298, 1 502 | %300 = trunc i32 %299 to i8 503 | store volatile i8 %300, i8* %c, align 1 504 | %301 = load volatile i8* %b, align 1 505 | %302 = zext i8 %301 to i32 506 | %303 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %302) 507 | ret i32 0 508 | } 509 | 510 | There are 2 important things to notice in this code: 511 | 512 | * You may have noticed that the instructions generated only convert the first XOR operand (`a`). 513 | The other operand was the literal `4` in the original code. 514 | Since this value is known at compile time, the `IRBuilder` will compute the transformation—at compile time—and generate the corresponding transformed literal. 515 | This is why the second operand of `%133` is a literal `9`. 516 | 517 | If you are not convinced here is the transformation: :math:`4 = 1*2^2 + 0*2^1 + 0*2^0 \mapsto 1*3^2 + 0*3^1 + 0*3^0 = 9`. 518 | 519 | The `IRBuilder` has successfully converted the original `4` literal into `9` at compile time, without generating any instructions! 520 | 521 | * The XOR is still in the obfuscated code. 522 | This is because we haven't asked LLVM to delete it. 523 | However we have rendered it useless when we replaced all of its uses by the result of the obfuscation. 524 | This means that the XOR will be deleted by the optimization pass we are going to apply. 525 | 526 | Last thing we need to do is to optimize the code to remove the unused XORs and try to compensate the performance loss (we will check this later). 527 | We will not look at this code but you can check that the XORs are gone: 528 | 529 | .. code:: bash 530 | 531 | >$ grep -Ec ' xor ' path/to/test/obfuscated_optimized.ll 532 | 0 533 | 534 | 535 | Production Ready? 536 | ***************** 537 | 538 | Validation 539 | ++++++++++ 540 | 541 | To make sure the obfuscation produces the same results as the original code you can use the test suite. 542 | 543 | .. code:: bash 544 | 545 | >$ cd $PASSDIR/build 546 | >$ make && make check 547 | 548 | One of the tests downloads, compiles and runs the test suite of OpenSSL. 549 | This may take some time but since OpenSSL heavily uses XORs, it helped us a lot to find very tricky bugs (remember the `requiredBits` function :p). 550 | 551 | 552 | Performances 553 | ++++++++++++ 554 | 555 | ============ =============== ================ 556 | Operation w/o obfuscation with obfuscation 557 | ============ =============== ================ 558 | Compilation 85s 587s 559 | Testing 27s 1217s 560 | ============ =============== ================ 561 | 562 | The enormous increase in compilation time is due to the fact that obfuscation of a single XOR generates about 300 new instructions (for 32 bits operands), and that many optimizations don't scale linearly with the number of instructions. 563 | 564 | Regarding execution time, it is easy to understand that replacing one simple XOR operation by 300 expensive instructions (`mul`, `div`, `mod`) is going to slow things down a bit... 565 | 566 | But before you decide that this obfuscation is too expensive for production, remember that the obfuscation should only be applied to the relevant parts of code (crypto functions, DRM enforcement...). 567 | And, even there, it should only be applied to a subset of the eligible XORs to avoid making the pattern too obvious! 568 | However, when validating your obfuscation you want to apply on *every* candidate to make sure to hit as many tricky cases as possible. 569 | 570 | A Few Improvements 571 | ================== 572 | 573 | Even if we apply the obfuscation to a small number of XORs, we might still want to speed things up. 574 | And we also might want to make the pattern less obvious. 575 | 576 | To do so we are going to add the following to our pass: 577 | 578 | * Handling chained XORs. Right now the `a = b xor c xor d` sequence would be turned into: 579 | 580 | 1. Transform `b` and `c` into `b'` and `c'` 581 | 2. Create `add1' = b' + c'` 582 | 3. Apply modulo 2 on `add1'` bits and transform into base 2 gives us `add1` 583 | 4. Transform `add1` and `d` into `add1''` and `d'` 584 | 5. Create `add2' = add1'' + d'` 585 | 6. Apply modulo 2 on `add2'` bits and transform into base 2 gives us `add2` 586 | 7. Store `add2` in `a` 587 | 588 | Instead of doing this we could transform each operand only once and chain the adds on the transformed representations. 589 | This would give us the following sequence: 590 | 591 | 1. Transform `b`, `c` and `d` into `b'`, `c'` and `d'` 592 | 2. Create `add1'` such as `add1' = b' + c'` 593 | 3. Create `add2'` such as `add2' = add1' + d'` 594 | 4. Apply modulo 2 on `add2'` bits and transform into base 2 gives us `add2` 595 | 5. Store `add2` in `a` 596 | 597 | This will reduce the number of transformations, which will reduce the number of instructions generated, making the code faster and the obfuscation a little less obvious. 598 | This is not that trivial, but we will get the details sorted out later. 599 | 600 | * If you have taken a look at the non-optimized obfuscated code, you've probably noticed that the pattern is very easy to spot. Each computation of a power of the base appears very clearly… *'Awesome an exponentiation \\o/'* 601 | 602 | To make the transformation less regular and make pattern matching harder, we could randomize the order of the transformations operations. 603 | As we will see, this will require a change of transformation algorithms, but if there is chance that it might annoy reverse engineers then it's worth our time :). 604 | 605 | From now on, we will work on the code in the `chained_xor` branch: 606 | 607 | .. code:: bash 608 | 609 | git checkout basic_xor 610 | 611 | 612 | Handling Chained XORs 613 | ********************* 614 | 615 | What we want to do now is to avoid redundant transformations of XOR operands. 616 | And to do so we need the following: 617 | 618 | * Detect and store the XOR chains for analysis. 619 | * Make sure that the base we choose is large enough to handle successive adds. 620 | 621 | Tree Saplings 622 | +++++++++++++ 623 | 624 | What we call a XOR chain is a set of XORs which have a least one operand in the set. 625 | Or simply put a set of XORs using other XORs as operand. 626 | The following code contains such a chain: 627 | 628 | .. code:: C 629 | 630 | int main() { 631 | volatile uint32_t a = 0xffffffff, c = 0xffffffef, d = 0xfeffffef; 632 | uint32_t b=a^0xffffffff^c^d; 633 | printf("%u\n", b); 634 | return 0; 635 | } 636 | 637 | The most natural way to store dependency information is to use a directed graph (acyclic in our case). Here is the DAG (Directed Acyclic Graph) representing the chain in the previous code. 638 | 639 | .. image:: simple.png 640 | 641 | This example may seam oversimplified but since XOR is a commutative and associative operation, LLVM optimizations will always be able to reduce any XOR sequence into a graph of this type (and they usually do...). 642 | But our obfuscation will have to be able to handle non-optimized code hence our algorithms will have to be generic. 643 | 644 | Growing the Trees 645 | +++++++++++++++++ 646 | 647 | Building the DAG is pretty easy thanks to LLVM's SSA representation. Each instruction has some ``Use``, generally other instruction that use it as an operand. So building the DAG is just a matter of walking the uses and the operands of each instruction, keeping the ones that involve a XOR and leaving the others aside. The recursive part looks like this: 648 | 649 | .. code:: C++ 650 | 651 | void walkInstructions(Tree_t &T, Instruction *Inst) { 652 | if(not isEligibleInstruction(Inst)) 653 | return; 654 | [...] 655 | for (auto const &NVUse : Inst->uses()) { 656 | if(Instruction *UseInst = dyn_cast(NVUse.getUser())) { 657 | walkInstructions(T, UseInst); 658 | } 659 | } 660 | [...] 661 | for (auto const &Op : Inst->operands()) { 662 | Instruction *OperandInst = dyn_cast(&Op); 663 | if (OperandInst and isEligibleInstruction(OperandInst)) 664 | T.at(Inst).insert(OperandInst); 665 | } 666 | } 667 | 668 | Range-based loops from C++11 are really handy! 669 | 670 | 671 | 672 | Climbing Trees 673 | ++++++++++++++ 674 | 675 | If you read the introduction, you should remember that the base 'change' is intended to prevent the ADD carry from propagating. 676 | If we want to handle chained XORs we have to make sure that no carry is going to propagate when chaining ADDs. 677 | For the previous example, it means that :math:`a_i + c_i + d_i < Base, i \in [0, N_b[` 678 | 679 | To determine the minimum base eligible for the tree transformation we use the following algorithm: 680 | 681 | .. code:: C++ 682 | 683 | unsigned minimalBase(Value *Node, Tree_t const &T, 684 | std::map &NodeBaseMap) { 685 | // Emplace new value and check if already passed this node 686 | if (NodeBaseMap[Node] != 0) 687 | return NodeBaseMap.at(Node); 688 | Instruction *Inst = dyn_cast(Node); 689 | // We reached a leaf 690 | if (not Inst or T.find(Inst) == T.end()) { 691 | NodeBaseMap.at(Node) = 1; 692 | return 1; 693 | } else { 694 | // Recursively check operands 695 | unsigned sum = 0; 696 | for (auto const &Operand : Inst->operands()) { 697 | if (NodeBaseMap[Operand] == 0) 698 | minimalBase(Operand, T, NodeBaseMap); 699 | sum += NodeBaseMap.at(Operand); 700 | } 701 | // Compute this node's min base 702 | NodeBaseMap[Node] = sum; 703 | return sum; 704 | } 705 | } 706 | 707 | This algorithm will recusively go through the tree, and assign to each node X the maximum value that its :math:`x_i, i \in [0, N_b[` can attain. 708 | 709 | And this maximum is: 710 | 711 | * `1` for a leaf because a leaf is directly converted from binary. 712 | * The sum of its parents' maxima for any other node. 713 | 714 | If this is not clear enough you can take a look at the edge labels in the above graph. 715 | 716 | To choose a base for a tree we, need to apply the previous algorithm to all the roots of tree. 717 | The minimum base for the tree will then be the maximum of the returned values. 718 | Finally we randomly pick a base between the minimum and the maximum (see ``maxBase`` function) if possible. 719 | 720 | .. code:: C++ 721 | 722 | unsigned chooseTreeBase(Tree_t const &T, Tree_t::mapped_type const &Roots) { 723 | assert(T.size()); 724 | unsigned Max = maxBase( 725 | T.begin()->first->getType()->getIntegerBitWidth()), 726 | Min = 0; 727 | 728 | // Computing minimum base 729 | // Each node of the tree has a base equal to the sum of its two 730 | // successors' min base 731 | std::map NodeBaseMap; 732 | for (auto const &Root : Roots) 733 | Min = std::max(minimalBase(Root, T, NodeBaseMap), Min); 734 | 735 | if (++Min < 3 or Min > Max) 736 | return 0; 737 | std::uniform_int_distribution Rand(Min, Max); 738 | return Rand(Generator); 739 | } 740 | 741 | Cut Them Down! 742 | ++++++++++++++ 743 | 744 | The last thing to do with these trees is to transform them. 745 | This will be done as before in the ``runOnBasicBlock`` function. 746 | This function will now apply a recursive transformation on all the roots of each tree. 747 | (We won't paste the code here so you should open the ``$PASSDIR/llvm-passes/X-OR/X-OR.cpp``.) 748 | 749 | The recursive transformation function ``recursiveTransform`` will, given a node `N`: 750 | 751 | 1. Check each of `N`'s operands: 752 | 1. If it has not been transformed, i.e it is not in ``TransfoRegister``: 753 | 1. If it is not a XOR **or** if it's a XOR not in the current ``BasicBlock``, transform it and register the association (original value, new base) :math:`\mapsto` transformed value in ``TransfoRegister``. 754 | 2. Else call recursively ``recursiveTransform`` on the operand. 755 | 2. Else recover the transformed value. 756 | 2. Once the operands have been transformed, apply an ADD on the transformed operands and register the result of the add in ``TransfoRegister`` as (original XOR, new base) :math:`\mapsto` new add. 757 | We register the new value so that when the recursive function hits a XOR operand, we use the result of the ADD as the new operand. 758 | 3. Prepare the transformed back value of the ADD in case the result of the XOR is used outside of the tree (i.e by something else than a XOR, or by a XOR outside the current ``BasicBlock``). 759 | And replace those uses with the new transformed back value. 760 | 761 | 762 | Breaking the Patterns 763 | ********************* 764 | 765 | Okay, after changing everything to handle chained XOR let's do something easier... 766 | 767 | We want to be able to randomly re-order the transformations' instructions. However, the transformation algorithms we are currently using do not allow this. But let's pull our sleeves up and find new ones! 768 | 769 | rewriteAsBaseN 770 | ++++++++++++++ 771 | 772 | Changing the ``rewriteAsBaseN`` is trivial. 773 | The only thing we need to change is the way the successive exponents are computed. 774 | 775 | .. code:: C++ 776 | 777 | for(unsigned Bit = 0; Bit < OriginalNbBit; ++Bit) { 778 | . 779 | // Updating Exponent 780 | Pow = Builder.CreateMul(Pow, IRBase); 781 | . 782 | } 783 | 784 | In the original version of the algorithm we updated the exponent when going through the loop. 785 | But if we want to go through the loop in a random order, we will need to compute the exponents beforehand (don't forget that we need to use ``APInt`` to compute those exponents). 786 | We can store those values in a mapping :math:`i \mapsto Base^i`. 787 | This mapping will be computed on demand, since we can not compute it for every possible base. 788 | If you are interested in the details of the function ``getExponentMap`` please refer to the code. 789 | 790 | Here is the new ``rewriteAsBaseN`` function: 791 | 792 | .. code:: C++ 793 | 794 | Value *rewriteAsBaseN(Value *Operand, unsigned Base, IRBuilder<> &Builder) { 795 | const unsigned OriginalNbBit = Operand->getType()->getIntegerBitWidth(), 796 | NewNbBit = requiredBits(OriginalNbBit, Base); 797 | if (not NewNbBit) { 798 | return nullptr; 799 | } 800 | 801 | Type *NewBaseType = IntegerType::get(Operand->getContext(), NewNbBit); 802 | 803 | auto const &ExpoMap = getExponentMap(Base, OriginalNbBit, NewBaseType); 804 | 805 | // Initializing variables 806 | Value *Accu = Constant::getNullValue(NewBaseType), 807 | *InitMask = ConstantInt::get(NewBaseType, 1u); 808 | 809 | // Extending the original value to NewNbBit for bitwise and 810 | Value *ExtendedOperand = Builder.CreateZExt(Operand, NewBaseType); 811 | 812 | auto Range = getShuffledRange(OriginalNbBit); 813 | 814 | for (auto Bit : Range) { 815 | Value *Mask = Builder.CreateShl(InitMask, Bit); 816 | Value *MaskedNewValue = Builder.CreateAnd(ExtendedOperand, Mask); 817 | Value *BitValue = Builder.CreateLShr(MaskedNewValue, Bit); 818 | Value *Expo = ConstantInt::get(NewBaseType, ExpoMap.at(Bit)); 819 | Value *NewBit = Builder.CreateMul(BitValue, Expo); 820 | Accu = Builder.CreateAdd(Accu, NewBit); 821 | } 822 | 823 | return Accu; 824 | } 825 | 826 | The ``getShuffledRange`` function returns a random shuffle of :math:`[0, N_b[`. 827 | 828 | transformToBaseTwoRepr 829 | ++++++++++++++++++++++ 830 | 831 | This one is a bit trickier. 832 | So far we used Euclide's algorithm, but it is too tightly linked to the computation order. 833 | The new algorithm we are going to use to recover the :math:`x_i` from :math:`\sum x_i \cdot Base^i` is the following: 834 | 835 | :math:`x_j = \frac{\sum x_i \cdot Base^i}{Base^j} \text{ mod } Base` 836 | 837 | And we are going to use the same ``getExponentMap`` as earlier for the different exponents. 838 | 839 | .. code:: C++ 840 | 841 | Value *transformToBaseTwoRepr(Value *Operand, unsigned Base, Type *OriginalType, 842 | IRBuilder<> &Builder) { 843 | Type *ObfuscatedType = Operand->getType(); 844 | 845 | const unsigned OriginalNbBit = OriginalType->getIntegerBitWidth(); 846 | 847 | // Initializing variables 848 | Value *IR2 = ConstantInt::get(ObfuscatedType, 2u), 849 | *IRBase = ConstantInt::get(ObfuscatedType, Base), 850 | *Accu = Constant::getNullValue(ObfuscatedType); 851 | 852 | auto const &ExpoMap = 853 | getExponentMap(Base, OriginalNbBit, ObfuscatedType); 854 | 855 | auto Range = getShuffledRange(OriginalNbBit); 856 | 857 | for (auto Bit : Range) { 858 | Value *Pow = ConstantInt::get(ObfuscatedType, ExpoMap.at(Bit)); 859 | Value *Q = Builder.CreateUDiv(Operand, Pow); 860 | Q = Builder.CreateURem(Q, IRBase); 861 | Q = Builder.CreateURem(Q, IR2); 862 | Value *ShiftedBit = Builder.CreateShl(Q, Bit); 863 | Accu = Builder.CreateOr(Accu, ShiftedBit); 864 | } 865 | // Cast back to original type 866 | return Builder.CreateZExtOrTrunc(Accu, OriginalType); 867 | } 868 | 869 | Code Sample 870 | *********** 871 | 872 | After all this work, let's take a look at the code produced. 873 | 874 | Here is the code to obfuscate: 875 | 876 | .. code:: C 877 | 878 | int main() { 879 | volatile uint32_t a = -1, b = 42, c = 100; 880 | printf("%d\n", a^b^c); 881 | return 0; 882 | } 883 | 884 | This chosen code is *very* simple, to make it easier to explain. 885 | 886 | We are not going to optimize the obfuscated bytecode because optimizations completely break our patterns (which is a good thing). 887 | This makes understanding the bytecode **very** laborious ... 888 | 889 | "I don't want to do it anymore, please let me gooooooooooo!" 890 | 891 | ... and our debugging goblins are becoming crazy. Or is it me? 892 | 893 | .. code:: bash 894 | 895 | clang -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMX-OR.so path/to/chained.c -O0 -S -emit-llvm 896 | 897 | .. code:: llvm 898 | 899 | define i32 @main() #0 { 900 | 901 | ; Some boring stuff 902 | 903 | %2 = load volatile i32* %a, align 4 904 | %3 = load volatile i32* %b, align 4 905 | 906 | ; Transforming 'a' 907 | %4 = zext i32 %2 to i64 908 | %5 = and i64 %4, 64 909 | %6 = lshr i64 %5, 6 910 | %7 = mul i64 %6, 4096 911 | %8 = add i64 0, %7 912 | 913 | ; Transforming 'b' 914 | %133 = zext i32 %3 to i64 915 | %134 = and i64 %133, 2048 916 | %135 = lshr i64 %134, 11 917 | %136 = mul i64 %135, 4194304 918 | %137 = add i64 0, %136 919 | 920 | ; Applying 'a^b' 921 | %262 = add i64 %132, %261 922 | 923 | ; Preparing an exit point. 924 | ; Will be optimized out since it's unused. 925 | 926 | ; Transforming 'c' 927 | %425 = load volatile i32* %c, align 4 928 | %426 = zext i32 %425 to i64 929 | %427 = and i64 %426, 67108864 930 | %428 = lshr i64 %427, 26 931 | %429 = mul i64 %428, 4503599627370496 932 | %430 = add i64 0, %429 933 | 934 | ; Applying '(a^b)^c' 935 | %555 = add i64 %262, %554 936 | 937 | ; Transforming back '(a^b)^c' 938 | %556 = udiv i64 %555, 4611686018427387904 939 | %557 = urem i64 %556, 4 940 | %558 = urem i64 %557, 2 941 | %559 = shl i64 %558, 31 942 | %560 = or i64 0, %559 943 | 944 | ; Final value 945 | %716 = trunc i64 %715 to i32 946 | 947 | ; Some boring stuff 948 | } 949 | 950 | Good news it's working as expected! 951 | You should probably optimize the bytecode and take a look at it, just to see how it looks like. 952 | But the transformations are hard to recognize! 953 | 954 | Performances 955 | ************ 956 | 957 | ============ =============== ================ 958 | Operation w/o obfuscation with obfuscation 959 | ============ =============== ================ 960 | Compilation 85s 490s 961 | Testing 27s 1380s 962 | ============ =============== ================ 963 | 964 | As you can see, when reducing the number of transformations thanks to the chained XORs, we have reduced compile time by ~15%. 965 | 966 | But at the same time we have increased execution time by ~10%. 967 | One of the reasons of this slowdown is that, by chaining XORs, we use larger bases. 968 | And using a larger base means using larger integer types. 969 | 970 | In the previous version, an obfuscated `i32` XOR was most likely to be transformed using a type 'smaller' than `i64`. 971 | Which meant that all transformation instructions could use the CPU hard coded instructions. 972 | However, with chained XORs it is likely that the obfuscated types are greater than `i64` and require the use of software implementation of `mul`, `mod` for non-native integer size... 973 | But even if the complexity of instructions increases, their number is reduced. This double variation probably helps mitigate the slowdown. 974 | 975 | To have a better understanding of what is happening we are going to benchmark the following code: 976 | 977 | .. code:: C 978 | 979 | #define LOOP 100000000 980 | 981 | int main() { 982 | volatile uint32_t a, b = -1, c = 100, d = -10, e = 750, f = 854721, g = 42; 983 | 984 | for(size_t i = 0; i < LOOP; ++i) { 985 | a = b^c^d^e^f^g; 986 | } 987 | 988 | printf("%d\n", a); 989 | 990 | return 0; 991 | } 992 | 993 | We are going to change the number of XOR executed in the loop and study the variations in the number of instruction, compilation time, execution time and obfuscated types. 994 | 995 | ========= ============== ========= ========================= ============= ==== 996 | number of Original code Obfuscated code 997 | --------- -------------- --------------------------------------------------------- 998 | XORs exec time exec time additional number of inst compile time type 999 | ========= ============== ========= ========================= ============= ==== 1000 | 1 0.07s +21757% 480 +80% i51 1001 | 2 0.1s +4600% 390 +60% i64 1002 | 3 0.12s +114900% 800 +300% i75 1003 | 4 0.14s +107042% 930 +400% i83 1004 | 5 0.16s +106150% 1090 +500% i90 1005 | ========= ============== ========= ========================= ============= ==== 1006 | 1007 | Don't put this in your hot paths :-) 1008 | 1009 | 1010 | Divide to Conquer 1011 | ================= 1012 | 1013 | The last thing we will do to improve this pass is to combine with another pass. 1014 | The size (in bits) of the operands we want to obfuscate has a huge impact on: 1015 | 1016 | * Wether or not we can apply the obfuscation on a XOR chain. 1017 | For instance the longest 64 bits XOR chain we can obfuscate is 4 XORs long. 1018 | More than this would require to use integers greater than 128 bits which are not supported. 1019 | * The speed of the instructions used and their number (see the performance section above). 1020 | 1021 | Therefore it would be nice to reduce the size of those operands before applying the X-OR pass. 1022 | One way to do this would be to develop a pass that: 1023 | 1024 | * Split the XOR operands into smaller variables. 1025 | * Apply XORs on the new operands. 1026 | * Merge the results. 1027 | 1028 | Transforming this code snippet... 1029 | 1030 | .. code:: llvm 1031 | 1032 | %res = xor i32 %a, %b 1033 | 1034 | ... Would look like this: 1035 | 1036 | .. image:: split.png 1037 | 1038 | Actually, this transformation could be applied not only to XORs but to *any* bitwise operator (XOR, AND, OR). 1039 | And you could chain transformations in the exact same way we chained XORs transformations! 1040 | Bottom line: this new pass would be pretty similar to X-OR. 1041 | 1042 | We will now use the last branch ``propagated_transfo``: 1043 | 1044 | .. code:: bash 1045 | 1046 | git checkout propagated_transfo 1047 | 1048 | Core Logic 1049 | ********** 1050 | 1051 | To take advantage of the work we have already done, we have extracted a generic 'propagated transformation' class. 1052 | This class will detect eligible variables (to be defined by the specific transformation), build the dependency trees and apply the transformations (to be defined). 1053 | 1054 | The only main change we have to make to the functions we developed for X-OR is to handle transformation turning one ``Value`` into an array of ``Value``. 1055 | 1056 | If you are interested in developing a new transformation with the same properties as X-OR you should be able to use it pretty easily. 1057 | However, we will not get into the details of its implementation here. 1058 | 1059 | Get a Knife 1060 | +++++++++++ 1061 | 1062 | Since this new pass is very similar to X-OR the interesting parts are the new transformation functions. 1063 | 1064 | The 'forward' transformation splits a variable into :math:`\frac{N_b}{SplitSize}` new variables. 1065 | Each new variable will be obtained by masking ans shifting the original variable: 1066 | 1067 | .. code:: C++ 1068 | 1069 | std::vector transformOperand(Value *Operand, 1070 | IRBuilder<> &Builder) override { 1071 | const unsigned OriginalNbBit = Operand->getType()->getIntegerBitWidth(), 1072 | SplitSize = SizeParam, 1073 | NumberNewOperands = OriginalNbBit / SplitSize; 1074 | 1075 | Type *NewType = IntegerType::get(Operand->getContext(), SplitSize); 1076 | 1077 | std::vector NewOperands(NumberNewOperands); 1078 | 1079 | Value *InitMask = ConstantInt::get(Operand->getType(), -1); 1080 | InitMask = Builder.CreateLShr(InitMask, OriginalNbBit - SplitSize); 1081 | 1082 | auto Range = getShuffledRange(NumberNewOperands); 1083 | 1084 | for (auto I : Range) { 1085 | Value *Mask = Builder.CreateShl(InitMask, SplitSize * I); 1086 | Value *MaskedNewValue = Builder.CreateAnd(Operand, Mask); 1087 | Value *NewOperandValue = 1088 | Builder.CreateLShr(MaskedNewValue, I * SplitSize); 1089 | // Using NewOperands to keep the order of split operands 1090 | NewOperands[I] = Builder.CreateTrunc(NewOperandValue, NewType); 1091 | } 1092 | return NewOperands; 1093 | } 1094 | 1095 | And to transform back a vector of ``Value``, we do the exact opposite: 1096 | 1097 | .. code:: C++ 1098 | 1099 | Value *transformBackOperand(std::vector const &Operands, 1100 | IRBuilder<> &Builder) override { 1101 | assert(Operands.size()); 1102 | const unsigned NumberOperands = Operands.size(), SplitSize = SizeParam; 1103 | 1104 | Value *Accu = Constant::getNullValue(OriginalType); 1105 | 1106 | auto Range = getShuffledRange(NumberOperands); 1107 | 1108 | for (auto I : Range) { 1109 | Value *ExtendedOperand = 1110 | Builder.CreateZExt(Operands[I], OriginalType); 1111 | Value *ShiftedValue = 1112 | Builder.CreateShl(ExtendedOperand, I * SplitSize); 1113 | Accu = Builder.CreateOr(Accu, ShiftedValue); 1114 | } 1115 | return Accu; 1116 | } 1117 | 1118 | Pretty straight forward. 1119 | But since we only handle splits of identical size (for simplicity), we need to choose a `SplitSize` that is a divisor of :math:`N_b`. This is done by computing all the divisors of :math:`N_b` (in :math:`O(sqrt(N_b))`) and randomly picking one of them. 1120 | 1121 | A Blunt Knife 1122 | +++++++++++++ 1123 | 1124 | After applying the split obfuscation to this code: 1125 | 1126 | .. code:: C 1127 | 1128 | int main() { 1129 | volatile uint32_t a = -1, b = 100, c = 42; 1130 | printf("%d\n", a | (b & c)); 1131 | return 0; 1132 | } 1133 | 1134 | With: 1135 | 1136 | .. code:: bash 1137 | 1138 | clang -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMSplitBitwiseOp.so split.c -O0 -S -emit-llvm 1139 | 1140 | We get: 1141 | 1142 | .. code:: llvm 1143 | 1144 | define i32 @main() #0 { 1145 | ; LLVM stuff 1146 | 1147 | %2 = load i32* %a, align 4 1148 | %3 = load i32* %b, align 4 1149 | %4 = load i32* %c, align 4 1150 | 1151 | ; Transforming 'b' 1152 | %5 = and i32 %3, 3 1153 | %6 = lshr i32 %5, 0 1154 | %7 = trunc i32 %6 to i2 1155 | 1156 | ; Transforming 'c' 1157 | %53 = and i32 %4, 192 1158 | %54 = lshr i32 %53, 6 1159 | %55 = trunc i32 %54 to i2 1160 | 1161 | ; Applying 'b & c' 1162 | %101 = and i2 %46, %94 1163 | %102 = and i2 %22, %88 1164 | %103 = and i2 %10, %64 1165 | 1166 | ; Unused back transformation of 'b & c' 1167 | %117 = zext i2 %107 to i32 1168 | %118 = shl i32 %117, 10 1169 | %119 = or i32 0, %118 1170 | 1171 | ; Original 'b & c' now unused 1172 | %165 = and i32 %3, %4 1173 | 1174 | ; Transforming 'a' 1175 | %166 = and i32 %2, 3 1176 | %167 = lshr i32 %166, 0 1177 | %168 = trunc i32 %167 to i2 1178 | 1179 | ; Applying 'a | (b & c)' 1180 | %214 = or i2 %210, %107 1181 | %215 = or i2 %207, %103 1182 | %216 = or i2 %186, %111 1183 | 1184 | ; Back transformation of 'a | (b & c)' 1185 | %230 = zext i2 %226 to i32 1186 | %231 = shl i32 %230, 6 1187 | %232 = or i32 0, %231 1188 | 1189 | %279 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %277) 1190 | } 1191 | 1192 | So everything looks good, right? 1193 | 1194 | Well, now try to optimize the obfuscated code... 1195 | 1196 | .. code:: bash 1197 | 1198 | clang -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMSplitBitwiseOp.so split.c -O2 -S -emit-llvm 1199 | 1200 | Everything is gone :/. 1201 | LLVM managed to understand our transformation and optimize it out. 1202 | So let's file a bug report to the LLVM devs telling them that their optimizations are annoying and that they should nerf them. 1203 | 1204 | Or we could try to combine this transformation with the X-OR obfuscation! 1205 | 1206 | Working Together 1207 | **************** 1208 | 1209 | To combine the two passes you can either apply them one by one with `opt` or apply them both at one: 1210 | 1211 | .. code:: bash 1212 | 1213 | LD_LIBRARY_PATH=$PASSDIR/build/llvm-passes clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so -Xclang -load -Xclang LLVMX-OR.so split.c -S -emit-llvm 1214 | 1215 | After applying the two optimizations, the code becomes too big to paste here. 1216 | But this happens: 1217 | 1218 | * The XORs are split into several smaller ones, hence generating a forest of independent small XORs trees (actually DAGs). 1219 | * Each XOR tree is *independently* obfuscated by X-OR. This means that the obfuscated types of each subtree can be different (and they really are in practice)! 1220 | * And the optimizer will not optimize out the splits! 1221 | 1222 | I'll let you take a look at the result. With the given example LLVM produces ~1300 obfuscated LLVM instructions from the original ~10. 1223 | 1224 | When optimizing with -O2 the ~1300 instructions are reduced to ~600. 1225 | It looks like LLVM managed to merge some parts of the transformations. 1226 | However, since I don't want to loose what sanity I have left, I haven't looked too closely to what's happening... 1227 | If you have enough courage, let us know in the comments! 1228 | 1229 | Performances 1230 | ************ 1231 | 1232 | Here are the statistics when building OpenSSL: 1233 | 1234 | ============ =============== ================ 1235 | Operation w/o obfuscation with obfuscation 1236 | ============ =============== ================ 1237 | Compilation 85s 690s 1238 | Testing 27s 1275s 1239 | ============ =============== ================ 1240 | 1241 | We have increased compilation time by 40% compared to non-chained X-OR, but since we added a new pass this seams reasonable. 1242 | Regarding runtime we have gained 10%! 1243 | This is probably due to the reduction of the size of integer types used during the X-OR obfuscation, but I have not checked it in depth. 1244 | 1245 | Now you should remember that obfuscation are **not** meant to be applied on the whole program to be obfuscated. 1246 | Those performances measurements are worst case scenarios for a program using a lot of XORs! 1247 | So don't throw out this obfuscation because of those numbers. 1248 | 1249 | THE END! 1250 | ======== 1251 | 1252 | In this post, we tried to present the different steps of obfuscation pass development, from the conception to the improvements. 1253 | There are a few things that could be improved, most notably handling operations other than XORs. 1254 | But we'll leave that to you! 1255 | -------------------------------------------------------------------------------- /doc/X-OR_tutorial/simple.dot: -------------------------------------------------------------------------------- 1 | digraph simple_graph { 2 | n1 [label="%1 = load volatile i32* %a", shape=box]; 3 | n2 [label="%2 = xor i32 %1, -1"]; 4 | n3 [label="%3 = load volatile i32* %c", shape=box]; 5 | n4 [label="%4 = xor i32 %3, %2"]; 6 | n5 [label="%5 = load volatile i32* %d", shape=box]; 7 | n6 [label="%6 = xor i32 %4, %5"]; 8 | n9 [label="i32 -1", shape=box]; 9 | 10 | n6 -> n4 [label="3"]; 11 | n6 -> n5 [label="1",style=dotted]; 12 | n4 -> n3 [label="1",style=dotted]; 13 | n4 -> n2 [label="2"]; 14 | n2 -> n1 [label="1",style=dotted]; 15 | n2 -> n9 [label="1",style=dotted]; 16 | } 17 | -------------------------------------------------------------------------------- /doc/X-OR_tutorial/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkslab/llvm-passes/1daffaa095502ce9108ad862d2227df89786e9e4/doc/X-OR_tutorial/simple.png -------------------------------------------------------------------------------- /doc/X-OR_tutorial/split.dot: -------------------------------------------------------------------------------- 1 | digraph split_graph { 2 | na [label="i32 %a", shape=box]; 3 | nb [label="i32 %b", shape=box]; 4 | na1 [label="i8 %a1"]; 5 | na2 [label="i8 %a2"]; 6 | na3 [label="i8 %a3"]; 7 | na4 [label="i8 %a4"]; 8 | nb1 [label="i8 %b1"]; 9 | nb2 [label="i8 %b2"]; 10 | nb3 [label="i8 %b3"]; 11 | nb4 [label="i8 %b4"]; 12 | xor1 [label="xor i8 %a1, %b1"]; 13 | xor2 [label="xor i8 %a2, %b2"]; 14 | xor3 [label="xor i8 %a3, %b3"]; 15 | xor4 [label="xor i8 %a4, %b4"]; 16 | res [label="i32 %res", shape=box]; 17 | 18 | na -> na1; nb -> nb1; 19 | na -> na2; nb -> nb2; 20 | na -> na3; nb -> nb3; 21 | na -> na4; nb -> nb4; 22 | na1 -> xor1; nb1 -> xor1; 23 | na2 -> xor2; nb2 -> xor2; 24 | na3 -> xor3; nb3 -> xor3; 25 | na4 -> xor4; nb4 -> xor4; 26 | xor1 -> res; 27 | xor2 -> res; 28 | xor3 -> res; 29 | xor4 -> res; 30 | } 31 | -------------------------------------------------------------------------------- /doc/X-OR_tutorial/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkslab/llvm-passes/1daffaa095502ce9108ad862d2227df89786e9e4/doc/X-OR_tutorial/split.png -------------------------------------------------------------------------------- /doc/llvm_obfuscation_tutorial.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | Turning Regular Code Into Atrocities With LLVM 3 | ============================================== 4 | 5 | Objective 6 | ========= 7 | 8 | The objective of this post is to explain the basics of LLVM bytecode obfuscation through an out-of-source build single simple pass. 9 | *'But why obfuscate the LLVM bytecode? Why not the source code? Or the binary?'*, you may ask. 10 | 11 | Well it's because LLVM is *super swag* right now and using it is cool. 12 | But, regarding the engineering aspects, it is because there are lots of front-ends converting different languages into the same LLVM bytecode (Clang/Clang++ for C/C++, Mono LLVM for C#, Pyston for... Python and so on). 13 | Hence by working at the bytecode level we can obfuscate programs written in many languages without even knowing them. 14 | Another good thing is that the obfuscation can be easily integrated with the existing compilation chains: just add a few obfuscation flags. 15 | 16 | Now let's talk about what we're going to do. 17 | Our mission (and we have no choice but to accept it) is to obfuscate all null literals in the code. 18 | It means that we are going to replace (almost) all the zeroes in the code by a non-trivial boolean expression, proved to be always false. 19 | 20 | prime1 * ((x | any1)**2) != prime2 * ((y | any2)**2) 21 | 22 | Given that: 23 | 24 | - ``prime1`` and ``prime2`` are *distinct* prime numbers 25 | - ``any1`` and ``any2`` are *distinct* strictly positive random numbers 26 | - ``x`` and ``y`` are two variables picked from the program (they have to be reachable from the obfuscation instructions) 27 | 28 | This expression will always return a boolean zero (false). The idea is to insert this test into our code, just before the 0 we want to obfuscate and to replace this 0 by the result of our comparison. 29 | As you have probably noticed we will have to pay attention to the type of the original 0 and make sure we cast the result of our expression to its type. 30 | 31 | This obfuscation may not be the most sophisticated ever written but it's enough to learn the basics of LLVM bytecode obfuscation and maybe to annoy our friends in reverse engineering for a few minutes... until they use a nicely crafted `miasm `_ script! 32 | 33 | Requirements 34 | ============ 35 | 36 | Programming background 37 | ********************** 38 | To go through this tutorial you only need to be able to read C/C++ code. We will learn the basics of LLVM API and LLVM bytecode together. 39 | 40 | Environment 41 | *********** 42 | If you want to experiment along with this tutorial (which is strongly recommended) you will need to set up an LLVM development environment. 43 | 44 | * First let's download the LLVM 3.5 sources:: 45 | 46 | >$ git clone --depth=1 --branch=release_35 https://github.com/llvm-mirror/llvm path/to/llvm/sources 47 | 48 | * Now download the clang sources inside the llvm_src/tools directory:: 49 | 50 | >$ cd path/to/llvm/sources/tools && git clone --depth=1 --branch=release_35 https://github.com/llvm-mirror/clang 51 | 52 | * Create a build directory somewhere *out of the llvm source tree*:: 53 | 54 | >$ mkdir path/to/llvm/build 55 | 56 | * Let's build LLVM:: 57 | 58 | >$ cd path/to/llvm/build && cmake path/to/llvm/sources 59 | >$ make -j 60 | 61 | * And... wait...:: 62 | 63 | >IRL sleep 1000 64 | 65 | * (Optional) You should set your path to include the freshly baked LLVM tools and clang:: 66 | 67 | >$ export PATH=path/to/llvm/build/bin:$PATH 68 | 69 | * (Optional) You can run the test suite to make sure that LLVM was built correctly:: 70 | 71 | >$ make check 72 | 73 | * (Optional) If you want to run the existing tests you first need to install `pip `_ (python-pip). Then use it to install lit:: 74 | 75 | >$ pip install lit 76 | 77 | 78 | You have just built LLVM and clang but we are going to build the passes out of the LLVM source tree. To do so we have prepared a git repository with the basic infrastructure:: 79 | 80 | >$ git clone https://github.com/quarkslab/llvm-passes 81 | 82 | From now on we will be working exclusively inside the ``llvm-passes`` folder (we will refer to it as ``$PASSDIR``). So let's visit our new office: 83 | 84 | * *cmake*: cmake definitions to check the Python environment. Required to generate our passes test suites. 85 | * *doc*: contains the sources of this tutorial, in case you find a shaming typo. 86 | * *llvm-passes*: contains one subdirectory per pass, and a ``CMakeList.txt`` used to generate the passes. 87 | * *tests*: tests and validation for our passes, contains one directory per pass. The tests are using llvm-lit, the LLVM integrated validation tool. 88 | * *CMakeList.txt*: the file used to generate the required Makefiles 89 | 90 | 91 | Let's obfuscate! 92 | ================ 93 | 94 | Now that the environment is ready we will start writing the obfuscating pass. You may have noticed that there already is an ``ObfuscateZero`` dir in ``$PASSDIR/llvm-passes``. 95 | This is the pass we are going to reproduce step by step. So unless you want to get spoiled don't look at it yet. 96 | 97 | Now we have to deal with the hardest part of LLVM pass development (and software development in general), namely finding a name for our project. 98 | Since I am not really inspired and ``ObfuscateZero`` is already taken, let's call our new pass *MyPass*. 99 | 100 | We need a new directory for our pass:: 101 | 102 | >$ mkdir $PASSDIR/llvm-passes/MyPass 103 | 104 | And we will write the pass in ``$PASSDIR/llvm-passes/MyPass/MyPass.cpp``. 105 | 106 | 107 | One with nothing 108 | **************** 109 | 110 | The minimal compiling code for an LLVM pass is the following. It is explained `there `_ so I won't explain it again and focus on the obfuscation part. 111 | 112 | .. code:: C++ 113 | 114 | #include "llvm/Pass.h" 115 | #include "llvm/IR/Function.h" 116 | #include "llvm/Support/raw_ostream.h" 117 | 118 | #include "llvm/IR/LegacyPassManager.h" 119 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 120 | 121 | using namespace llvm; 122 | 123 | namespace { 124 | class MyPass : public BasicBlockPass { 125 | public: 126 | static char ID; 127 | 128 | MyPass() : BasicBlockPass(ID) {} 129 | 130 | bool runOnBasicBlock(BasicBlock &BB) override { 131 | errs() << "I m running on a block...\n"; 132 | return false; 133 | } 134 | 135 | }; 136 | } 137 | 138 | char MyPass::ID = 0; 139 | static RegisterPass X("MyPass", "Obfuscates zeroes", 140 | false, false); 141 | 142 | // register pass for clang use 143 | static void registerMyPassPass(const PassManagerBuilder &, 144 | PassManagerBase &PM) { 145 | PM.add(new MyPass()); 146 | } 147 | static RegisterStandardPasses 148 | RegisterMBAPass(PassManagerBuilder::EP_EarlyAsPossible, 149 | registerMyPassPass); 150 | 151 | If you have been paying attention so far you should remember that we are going to obfuscate null literals. 152 | And to do so we will randomly pick two variables reachable from where the replacement occurs. 153 | So, in order to keep the pass as simple as possible we are going to work at the basic bloc level, this way there will be no reachability problems with the variables we encounter. 154 | This is why our class derives from the ``BasicBlockPass`` class. 155 | 156 | This could be greatly enhanced using `dominators `_ and a scan for Module scope variables, but that's... another story! 157 | 158 | .. code:: C++ 159 | 160 | class MyPass : public BasicBlockPass 161 | 162 | 163 | Do or do not there is no... test 164 | ******************************** 165 | 166 | I am sure that your are eager to compile and run this empty pass. Thanks to the files provided in the `git repo you've just cloned `_ it's actually quite easy. 167 | First you need to tell cmake that your pass should be compiled by adding it in the file ``$PASSDIR/llvm-passes/CMakeList.txt``. 168 | It should now look like this: 169 | 170 | .. code:: cmake 171 | 172 | set(EPONA_LLVM_MODULES 173 | ObfuscateZero 174 | MyPass 175 | ) 176 | 177 | Now we are going to build the pass: 178 | 179 | .. code:: bash 180 | 181 | >$ cd $PASSDIR 182 | >$ mkdir build 183 | >$ cd build 184 | >$ cmake -DLLVM_ROOT=path/to/your/llvm/build .. 185 | >$ make 186 | 187 | And now let's run our pass with clang. We need a test file, write the following code somewhere: 188 | 189 | .. code:: c 190 | 191 | #include 192 | 193 | int foo(){return 1;} 194 | 195 | int main() { 196 | puts("Hello world"); 197 | 198 | return 0; 199 | } 200 | 201 | You can turn it into LLVM bytecode using: 202 | 203 | .. code:: bash 204 | 205 | >$ clang -S -emit-llvm path/to/test/file.c -o file.ll 206 | 207 | Or compile it with our awesome pass using: 208 | 209 | .. code:: bash 210 | 211 | >$ clang -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMMyPass.so path/to/test/file.c -o awesome.out 212 | 213 | Or if you just want to process the LLVM bytecode file: 214 | 215 | .. code:: bash 216 | 217 | >$ opt -S -load $PASSDIR/build/llvm-passes/LLVMMyPass.so -MyPass path/to/test/file.ll -S -o out.ll 218 | 219 | You can also generate the modified LLVM bytecode in a single call: 220 | 221 | .. code:: bash 222 | 223 | >$ clang -S -emit-llvm -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMMyPass.so path/to/test/file 224 | 225 | Since there are two basic blocks in our code (one in each function, ``foo`` and ``main``), we see the message "I m running on a block..." twice! 226 | 227 | Congratulations you have compiled your first program with an LLVM pass! (You can test the executable, it should work... shouldn't it?) 228 | 229 | Playtime is over 230 | **************** 231 | 232 | The method we have to implement is ``runOnBasicBlock`` which takes as parameter a reference to the current block. Let's proceed step by step. 233 | 234 | Finding null literals 235 | +++++++++++++++++++++ 236 | 237 | To find the null literals we need to iterate over every instruction of the block and check if one of the operands is null. 238 | 239 | .. code:: C++ 240 | 241 | //Add the following to your headers 242 | #include "llvm/IR/Constants.h" 243 | #include "llvm/IR/Instructions.h" 244 | 245 | //Add the following to MyPass 246 | bool runOnBasicBlock(BasicBlock &BB) override { 247 | // Not iterating from the beginning to avoid obfuscation of Phi instructions 248 | // parameters 249 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 250 | end = BB.end(); 251 | I != end; ++I) { 252 | Instruction &Inst = *I; 253 | // We are not using an iterator because we will need i later. 254 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 255 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 256 | errs() << "I've found one sir!\n"; 257 | } 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | Constant *isValidCandidateOperand(Value *V) { 264 | Constant *C; 265 | if (!(C = dyn_cast(V))) return nullptr; 266 | if (!C->isNullValue()) return nullptr; 267 | // We found a NULL constant, lets validate it 268 | if(!C->getType()->isIntegerTy()) { 269 | // dbgs() << "Ignoring non integer value\n"; 270 | return nullptr; 271 | } 272 | return C; 273 | } 274 | 275 | The ``runOnBasicBlock`` method is going to iterate through all the instructions of the block (``for`` loop) and check if any operand of those instructions is an eligible null literal. 276 | If any of the operand is a null literal we print a message on the debug stream and we continue. 277 | You may have noticed the for loop is initialized with ``BB.getFirstInsertionPt()``. 278 | We could have iterated through the block with a foreach like: 279 | 280 | .. code:: C++ 281 | 282 | for(auto &I : BB) { 283 | } 284 | 285 | But we do not want to modify some of the special instructions located at the beginning of the block (the `phi instructions `_), so we skip them altogether and set the iterator to the first 'normal' instruction. 286 | 287 | The ``isValidCandidateOperand`` method checks if its parameter is a literal (constant means literal in LLVM, not variable declared ``const``). 288 | It also checks the type of the literal, it must not be a pointer or a floating point value (you will see later why). 289 | The type checks are done with the ``dyn_cast<>`` function which checks if its parameter can be cast to the type given by the template parameter. 290 | (``dyn_cast<>`` is used in LLVM instead of RTTI(run time type information) because it was deemed too `expensive `_.) 291 | If all those conditions are satisfied and the literal is null we return a pointer to the operand (cast as a ``Constant``) else ``nullptr``. 292 | 293 | If you compile and run the pass on our test code it finds **two** null literals when we just expected it to find the one from ``return 0``. 294 | 295 | Let's take a look at the LLVM bytecode generated by clang: 296 | 297 | .. code:: bash 298 | 299 | # The pass is not necessary now since it doesn't change anything, but it will be later. 300 | >$ clang++ -S -emit-llvm -Xclang -load -Xclang $PASSDIR/build/llvm-passes/LLVMMyPass.so path/to/test/file -o /tmp/awesome.ll 301 | 302 | We get the following: 303 | 304 | .. code:: llvm 305 | 306 | ; Function Attrs: nounwind uwtable 307 | define i32 @foo() #0 { 308 | ret i32 1 309 | } 310 | 311 | ; Function Attrs: nounwind uwtable 312 | define i32 @main() #0 { 313 | %1 = alloca i32, align 4 ; This instruction... 314 | store i32 0, i32* %1 ; ... and this one are useless, they would be deleted if we used an optimization flag. 315 | %2 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)) 316 | ret i32 0 317 | } 318 | 319 | The two 0 that triggered the debug message from our pass are in the ``store`` and ``ret`` instructions. 320 | As you can see the lowering from C to LLVM bytecode produces a slightly more verbose code. 321 | While debugging your future passes you will probably have to read a lot of bytecode so you should familiarize yourself with it. 322 | Lucky for you it's pretty easy to read (at least compared to asm) and strongly typed (this helps a lot). 323 | 324 | 325 | We've found your replacement 326 | ++++++++++++++++++++++++++++ 327 | 328 | Now that we can find null literals, we need to be able to replace them. 329 | We need: 330 | 331 | 1. To know the variables reachable from the instruction containing the eligible literal 332 | 2. To generate the instructions of the arithmetic expression seen earlier 333 | 3. To insert those expressions back into the code 334 | 4. (Optional) Generate random prime numbers 335 | 336 | Reachable variables 337 | ~~~~~~~~~~~~~~~~~~~ 338 | 339 | To be sure to have a pool of **reachable** variable during our obfuscation, we are going to register all the variables with integral type we come across while iterating through the block instructions. 340 | 341 | We will slightly modify the code to: 342 | * add a class member vector storing pointers to the Integer/values of interest. We will empty it at the end of every block. 343 | * add a method to check the type of the instruction and store it in the vector if it is eligible. 344 | * call the above mentioned method from the main loop. 345 | 346 | Our class becomes: 347 | 348 | .. code:: C++ 349 | 350 | //Add this to your includes 351 | #include 352 | 353 | 354 | class MyPass : public BasicBlockPass { 355 | std::vector IntegerVect; 356 | 357 | public: 358 | 359 | static char ID; 360 | 361 | MyPass() : BasicBlockPass(ID) {} 362 | 363 | bool runOnBasicBlock(BasicBlock &BB) override { 364 | IntegerVect.clear(); 365 | 366 | // Not iterating from the beginning to avoid obfuscation of Phi instructions 367 | // parameters 368 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 369 | end = BB.end(); 370 | I != end; ++I) { 371 | Instruction &Inst = *I; 372 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 373 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 374 | errs() << "I've found one sir!\n"; 375 | } 376 | } 377 | registerInteger(Inst); 378 | } 379 | return false; 380 | } 381 | 382 | private: 383 | void registerInteger(Value &V) { 384 | if (V.getType()->isIntegerTy()) { 385 | IntegerVect.push_back(&V); 386 | errs() << "Registering an integer!" << V << "\n"; 387 | } 388 | } 389 | 390 | Constant *isValidCandidateOperand(Value *V) { 391 | Constant *C; 392 | if (!(C = dyn_cast(V))) return nullptr; 393 | if (!C->isNullValue()) return nullptr; 394 | // We found a NULL constant, lets validate it 395 | if(!C->getType()->isIntegerTy()) { 396 | // dbgs() << "Ignoring non integer value\n"; 397 | return nullptr; 398 | } 399 | return C; 400 | } 401 | }; 402 | 403 | 404 | and replace your test code by this updated version: 405 | 406 | .. code:: c 407 | 408 | #include 409 | 410 | int foo(){return 1;} 411 | 412 | int main() { 413 | int a = 2; 414 | puts("Hello world"); 415 | a *= 3; 416 | 417 | return 0; 418 | } 419 | 420 | 421 | If you run the pass on our new test file you'll notice that the pass finds **3** integers to register corresponding to %2, %3 and %4 in the following bytecode: 422 | 423 | .. code:: llvm 424 | 425 | ; Function Attrs: nounwind uwtable 426 | define i32 @main() #0 { 427 | %1 = alloca i32, align 4 428 | %a = alloca i32, align 4 429 | store i32 0, i32* %1 430 | store i32 2, i32* %a, align 4 431 | %2 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)) 432 | %3 = load i32* %a, align 4 433 | %4 = mul nsw i32 %3, 3 434 | store i32 %4, i32* %a, align 4 435 | ret i32 0 436 | } 437 | 438 | 439 | There are a few things that you should remember from this little modification: 440 | * The LLVM bytecode is in `SSA form `_, so you will see variables that you didn't explicitly declared appear in the bytecode. Typically temporary result or ``loads``. 441 | * A variable declaration in your code returns a **pointer** in the bytecode not an instance of the type of the variable. This is because Clang translates variable declarations into variables allocated on the stack (through the ``alloca`` instruction). A later pass (Mem2reg) takes care of putting them in registers when possible. 442 | * You *need* to look at the bytecode to understand what you're *actually* telling LLVM to do (at least at first :p). 443 | * The return value of ``errs()`` is overloaded for most LLVM types, so use it! This is **very** useful for debug. (You can even use it on blocks, functions, ...) 444 | 445 | I will make this entire pig disappear! 446 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 447 | 448 | Ok we're almost done, the only thing left is to generate the new instructions and insert them into the code. 449 | For those of you who forgot (or skipped the intro) we are going to replace the null integer literals by the result of the expression: 450 | 451 | prime1 * ((x | any1)**2) != prime2 * ((y | any2)**2) 452 | 453 | Given that: 454 | 455 | - prime1 and prime2 are *distinct* prime numbers 456 | - any1 and any2 are *distinct* strictly positive random numbers 457 | - x and y are two variables picked from the program (they have to be reachable from the obfuscation instructions) 458 | 459 | We will write a new method ``replaceZero`` that will do all the funny stuff. However given the size of the function we will detail it step by step: 460 | 461 | First please add the following to your source file. 462 | 463 | .. code:: C++ 464 | 465 | // Insert with the other #include 466 | #include "llvm/IR/IRBuilder.h" 467 | #include 468 | 469 | // Insert just before the MyClass declaration 470 | using prime_type = uint32_t; 471 | 472 | Our ``replaceZero`` method will replace the null operand(s) of an instruction and return a pointer to the new operand(s) (or ``nullptr`` if a problem occurs). This gives us the following signature: 473 | 474 | .. code:: C++ 475 | 476 | Value* replaceZero(Instruction &Inst, Value* VReplace) { 477 | // Replacing 0 by: 478 | // prime1 * ((x | any1)^2) != prime2 * ((y | any2)^2) 479 | // with prime1 != prime2 and any1 != 0 and any2 != 0 480 | 481 | To generate our new formula we need 2 distinct prime numbers: 482 | 483 | .. code:: C++ 484 | 485 | prime_type p1 = getPrime(), 486 | p2 = getPrime(p1); 487 | 488 | if(p2 == 0 || p1 == 0) 489 | return nullptr; 490 | 491 | The LLVM bytecode is strongly typed so we will need to play a little with the types. 492 | The important types are the type of the operand we are going to replace and the type in which we will do the operations of the obfuscation expression. 493 | For the intermediary operations we will use the ``prime_type`` we've just declared (in this case ``uint32_t``). 494 | However we need to be careful about type conversions and the type overflows (we will see later why and how). 495 | 496 | .. code:: C++ 497 | 498 | Type *ReplacedType = VReplace->getType(), 499 | *IntermediaryType = IntegerType::get(Inst.getParent()->getContext(), 500 | sizeof(prime_type) * 8); 501 | 502 | Next we need to choose randomly two reachable variables (possibly twice the same) and two random strictly positive integers. 503 | For the variables we are going to randomly pick values in ``IntegerVect``. 504 | 505 | .. code:: C++ 506 | 507 | // Abort the obfuscation if we have encontered no integers so far 508 | if (IntegerVect.empty()) { 509 | return nullptr; 510 | } 511 | 512 | // Random distribution to pick variables from IntegerVect 513 | std::uniform_int_distribution Rand(0, IntegerVect.size() - 1); 514 | // Random distribution to pick Any1 and Any2 from [1, 10] 515 | std::uniform_int_distribution RandAny(1, 10); 516 | 517 | // Indexes chosen for x and y 518 | size_t Index1 = Rand(Generator), Index2 = Rand(Generator); 519 | 520 | If we overflow our intermediary type in one of the new instructions we could lose the property that the obfuscating comparison is always false. 521 | We could replace a zero by... something else. 522 | So we could change the result(s) produced by the code, and we want to avoid that all costs. 523 | To prevent overflowing we have set the maximum for Any1 and Any2 to 10, but this is not enough. 524 | We need to make sure that x and y are not too big. The trick is that we have no information on their value at compile time. 525 | The solution we chose is to apply a bitmask to x and y in order to obtain a variable of which we know the max value. 526 | 527 | The careful reader may have noticed that uniformly picking from ``IntegerVect`` is not truly uniform as we did not check for uniqueness of its elements ;-) 528 | 529 | .. code:: C++ 530 | 531 | // Creating the LLVM objects representing literals 532 | Constant *any1 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 533 | *any2 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 534 | *prime1 = ConstantInt::get(IntermediaryType, p1), 535 | *prime2 = ConstantInt::get(IntermediaryType, p2), 536 | // Bitmask to prevent overflow 537 | *OverflowMask = ConstantInt::get(IntermediaryType, 0x00000007); 538 | 539 | Now that we have everything we need we will create our new instructions. 540 | To insert new instructions **before** a specific instruction we use an ``IRBuilder``. 541 | This object will create instructions and insert them before the instruction given to its constructor. 542 | And we need to insert our new instructions before the instruction we are working on. That's why ``replaceZero`` takes an Instruction as parameter. We will forward it to the builder. 543 | 544 | .. code:: C++ 545 | 546 | IRBuilder<> Builder(&Inst); 547 | 548 | // lhs 549 | // Casting x to our intermediary type 550 | Value *LhsCast = 551 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index1), IntermediaryType); 552 | // Registering the new integers for a future obfuscation 553 | registerInteger(*LhsCast); 554 | // To avoid overflow and truncate x 555 | Value *LhsAnd = Builder.CreateAnd(LhsCast, OverflowMask); 556 | registerInteger(*LhsAnd); 557 | // Creating LhsOr = (x | any1) 558 | Value *LhsOr = Builder.CreateOr(LhsAnd, any1); 559 | registerInteger(*LhsOr); 560 | // LhsOr * LhsOr 561 | Value *LhsSquare = Builder.CreateMul(LhsOr, LhsOr); 562 | registerInteger(*LhsSquare); 563 | // prime1 * LhsOr^2 564 | Value *LhsTot = Builder.CreateMul(LhsSquare, prime1); 565 | registerInteger(*LhsTot); 566 | 567 | // rhs 568 | // The same as lhs with prime2, any2 and y 569 | Value *RhsCast = 570 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index2), IntermediaryType); 571 | registerInteger(*RhsCast); 572 | Value *RhsAnd = Builder.CreateAnd(RhsCast, OverflowMask); 573 | registerInteger(*RhsAnd); 574 | Value *RhsOr = Builder.CreateOr(RhsAnd, any2); 575 | registerInteger(*RhsOr); 576 | Value *RhsSquare = Builder.CreateMul(RhsOr, RhsOr); 577 | registerInteger(*RhsSquare); 578 | Value *RhsTot = Builder.CreateMul(RhsSquare, prime2); 579 | registerInteger(*RhsTot); 580 | 581 | // The final comparison always returning false 582 | Value *comp = 583 | Builder.CreateICmp(CmpInst::Predicate::ICMP_EQ, LhsTot, RhsTot); 584 | registerInteger(*comp); 585 | // Casting the boolean '0' back to the type of the replaced operand 586 | Value *castComp = Builder.CreateZExt(comp, ReplacedType); 587 | registerInteger(*castComp); 588 | 589 | return castComp; 590 | } 591 | 592 | OK! 593 | Almost there... we need to call our new function in the main loop and explicitly replace the operand: 594 | 595 | .. code:: C++ 596 | 597 | bool runOnBasicBlock(BasicBlock &BB) override { 598 | IntegerVect.clear(); 599 | bool modified = false; 600 | 601 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 602 | end = BB.end(); 603 | I != end; ++I) { 604 | Instruction &Inst = *I; 605 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 606 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 607 | if (Value *New_val = replaceZero(Inst, C)) { 608 | Inst.setOperand(i, New_val); 609 | modified = true; 610 | } else { 611 | // If sthg wrong happens during the replacement, 612 | // almost certainly because IntegerVect is empty 613 | errs() << "MyPass: could not rand pick a variable for replacement\n"; 614 | } 615 | } 616 | } 617 | registerInteger(Inst); 618 | } 619 | return modified; 620 | } 621 | 622 | and here is the code full code (with the tabulated prime numbers): 623 | 624 | .. code:: C++ 625 | 626 | namespace { 627 | using prime_type = uint32_t; 628 | 629 | static const prime_type Prime_array[] = { 630 | 2 , 3 , 5 , 7, 11, 13, 17, 19, 23, 29, 631 | 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 632 | 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 633 | 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 634 | 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 635 | 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 636 | 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 637 | 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 638 | 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 639 | 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 640 | 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 641 | 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 642 | 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 643 | 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 644 | 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 645 | 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 646 | 947, 953, 967, 971, 977, 983, 991, 997}; 647 | 648 | class MyPass : public BasicBlockPass { 649 | std::vector IntegerVect; 650 | std::default_random_engine Generator; 651 | 652 | public: 653 | 654 | static char ID; 655 | 656 | MyPass() : BasicBlockPass(ID) {} 657 | 658 | bool runOnBasicBlock(BasicBlock &BB) override { 659 | IntegerVect.clear(); 660 | bool modified = false; 661 | 662 | // Not iterating from the beginning to avoid obfuscation of Phi instructions 663 | // parameters 664 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 665 | end = BB.end(); 666 | I != end; ++I) { 667 | Instruction &Inst = *I; 668 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 669 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 670 | if (Value *New_val = replaceZero(Inst, C)) { 671 | Inst.setOperand(i, New_val); 672 | modified = true; 673 | } else { 674 | errs() << "ObfuscateZero: could not rand pick a variable for replacement\n"; 675 | } 676 | } 677 | } 678 | registerInteger(Inst); 679 | } 680 | 681 | return modified; 682 | } 683 | 684 | private: 685 | 686 | Constant *isValidCandidateOperand(Value *V) { 687 | Constant *C; 688 | if (!(C = dyn_cast(V))) return nullptr; 689 | if (!C->isNullValue()) return nullptr; 690 | // We found a NULL constant, lets validate it 691 | if(!C->getType()->isIntegerTy()) { 692 | // dbgs() << "Ignoring non integer value\n"; 693 | return nullptr; 694 | } 695 | return C; 696 | } 697 | 698 | void registerInteger(Value &V) { 699 | if (V.getType()->isIntegerTy()) 700 | IntegerVect.push_back(&V); 701 | } 702 | 703 | // Return a random prime number not equal to DifferentFrom 704 | // If an error occurs returns 0 705 | prime_type getPrime(prime_type DifferentFrom = 0) { 706 | static std::uniform_int_distribution Rand(0, std::extend(decltype(Prime_array) - 1); 707 | size_t MaxLoop = 10; 708 | prime_type Prime; 709 | 710 | do { 711 | Prime = Prime_array[Rand(Generator)]; 712 | } while(Prime == DifferentFrom && --MaxLoop); 713 | 714 | if(!MaxLoop) { 715 | return 0; 716 | } 717 | 718 | return Prime; 719 | } 720 | 721 | Value *replaceZero(Instruction &Inst, Value *VReplace) { 722 | // Replacing 0 by: 723 | // prime1 * ((x | any1)**2) != prime2 * ((y | any2)**2) 724 | // with prime1 != prime2 and any1 != 0 and any2 != 0 725 | prime_type p1 = getPrime(), 726 | p2 = getPrime(p1); 727 | 728 | if(p2 == 0 || p1 == 0) 729 | return nullptr; 730 | 731 | Type *ReplacedType = VReplace->getType(), 732 | *IntermediaryType = IntegerType::get(Inst.getParent()->getContext(), 733 | sizeof(prime_type) * 8); 734 | 735 | if (IntegerVect.empty()) { 736 | return nullptr; 737 | } 738 | 739 | std::uniform_int_distribution Rand(0, IntegerVect.size() - 1); 740 | std::uniform_int_distribution RandAny(1, 10); 741 | 742 | size_t Index1 = Rand(Generator), Index2 = Rand(Generator); 743 | 744 | // Masking Any1 and Any2 to avoid overflow in the obsfuscation 745 | Constant *any1 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 746 | *any2 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 747 | *prime1 = ConstantInt::get(IntermediaryType, p1), 748 | *prime2 = ConstantInt::get(IntermediaryType, p2), 749 | // Bitmask to prevent overflow 750 | *OverflowMask = ConstantInt::get(IntermediaryType, 0x00000007); 751 | 752 | IRBuilder<> Builder(&Inst); 753 | 754 | // lhs 755 | // To avoid overflow 756 | Value *LhsCast = 757 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index1), IntermediaryType); 758 | registerInteger(*LhsCast); 759 | Value *LhsAnd = Builder.CreateAnd(LhsCast, OverflowMask); 760 | registerInteger(*LhsAnd); 761 | Value *LhsOr = Builder.CreateOr(LhsAnd, any1); 762 | registerInteger(*LhsOr); 763 | Value *LhsSquare = Builder.CreateMul(LhsOr, LhsOr); 764 | registerInteger(*LhsSquare); 765 | Value *LhsTot = Builder.CreateMul(LhsSquare, prime1); 766 | registerInteger(*LhsTot); 767 | 768 | // rhs 769 | Value *RhsCast = 770 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index2), IntermediaryType); 771 | registerInteger(*RhsCast); 772 | Value *RhsAnd = Builder.CreateAnd(RhsCast, OverflowMask); 773 | registerInteger(*RhsAnd); 774 | Value *RhsOr = Builder.CreateOr(RhsAnd, any2); 775 | registerInteger(*RhsOr); 776 | Value *RhsSquare = Builder.CreateMul(RhsOr, RhsOr); 777 | registerInteger(*RhsSquare); 778 | Value *RhsTot = Builder.CreateMul(RhsSquare, prime2); 779 | registerInteger(*RhsTot); 780 | 781 | // comp 782 | Value *comp = 783 | Builder.CreateICmp(CmpInst::Predicate::ICMP_EQ, LhsTot, RhsTot); 784 | registerInteger(*comp); 785 | Value *castComp = Builder.CreateZExt(comp, ReplacedType); 786 | registerInteger(*castComp); 787 | 788 | return castComp; 789 | } 790 | }; 791 | } 792 | 793 | DOOOOOOOOOOOOOOOOOOOOONE! 794 | 795 | Let's try this awesome pass! If we use it on the last version of our test code we get: 796 | 797 | .. code:: llvm 798 | 799 | ; Function Attrs: nounwind uwtable 800 | define i32 @main() #0 { 801 | %1 = alloca i32, align 4 802 | %a = alloca i32, align 4 803 | store i32 0, i32* %1 804 | store i32 2, i32* %a, align 4 805 | %2 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)) 806 | %3 = load i32* %a, align 4 807 | %4 = mul nsw i32 %3, 3 808 | store i32 %4, i32* %a, align 4 809 | %5 = and i32 %3, 7 810 | %6 = or i32 %5, 2 811 | %7 = mul i32 %6, %6 812 | %8 = mul i32 %7, 719 813 | %9 = and i32 %2, 7 814 | %10 = or i32 %9, 8 815 | %11 = mul i32 %10, %10 816 | %12 = mul i32 %11, 397 817 | %13 = icmp eq i32 %8, %12 818 | %14 = zext i1 %13 to i32 819 | ret i32 %14 820 | } 821 | 822 | Look at the assignments %5 to %14, looks familiar? We have successfully obfuscated the ``return 0`` instruction with the expression we gave at the beginning. 823 | 824 | But there are a few important things left to read, so stay tunned! 825 | 826 | 827 | You didn't think it would be that easy? 828 | +++++++++++++++++++++++++++++++++++++++ 829 | 830 | The optimizer is your enemy 831 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 832 | 833 | So far we have not tried to optimize our code. 834 | But the compiler could optimize away some of your obfuscations and turn the code back to its original form. 835 | Our obfuscation depends on some rather complex arithmetic properties so we are safe but you should keep in mind that the compiler might be working against you. 836 | 837 | Even though our arithmetic is optimization-proof the rest of the code is not. The optimizer can still modify your code and delete all the candidate variables for x and y. If you want to see this effect, comment out the ``puts`` call in our test code and add the -O3 flag to your compilation command. 838 | 839 | You should get this: 840 | 841 | .. code:: llvm 842 | 843 | ; Function Attrs: nounwind readnone uwtable 844 | define i32 @main() #0 { 845 | ret i32 0 846 | } 847 | 848 | In this case the compiler has optimized out ``a`` which was the only integer available for the obfuscation. 849 | This explains why the obfuscation aborted. 850 | 851 | Even if it is frustrating it is not a real problem, since the compiler won't delete all the potential integer in a real code. 852 | However this is very annoying when writing tests. 853 | The easiest work-around is to declare ``volatile`` the variables you don't want to be optimized out. 854 | 855 | You might think that not using the optimizer is a good solution but: 856 | * If your obfuscation can't resist an optimizer, it won't resist reverse engineers. 857 | * Obfuscation often makes your program run slower, take more memory... So optimizing your obfuscated code might help mitigate these drawbacks. 858 | * Optimization can introduce some randomness in your obfuscations which would make your obfuscation patterns harder to recognize. 859 | 860 | 861 | Final modification 862 | ~~~~~~~~~~~~~~~~~~ 863 | 864 | Now let's go back to our pass code for the last time. 865 | So far we have supposed that we could replace *any* integer operand of *any* instruction. 866 | Well, this is not actually true. Let's study the following code: 867 | 868 | .. code:: C 869 | 870 | struct s { 871 | char a; 872 | int b; 873 | }; 874 | 875 | int main() { 876 | struct s s1; 877 | int a = 3; 878 | 879 | s1.a = a; 880 | 881 | return 0; 882 | } 883 | 884 | In LLVM bytecode access to structure members turns into the ``GetElementPointer`` instruction. It looks like this: 885 | 886 | .. code:: llvm 887 | 888 | %4 = getelementptr inbounds %struct.s* %s1, i32 0, i32 0 889 | 890 | As you can see there are two integer operands at the end. The first one is used when going through an array, so in our case it will always be 0. 891 | The second one is the index of structure member we are accessing. If you access ``s.a`` it will be 0 and it will be 1 for ``s.b``. 892 | For more info on ``getelementptr``, see http://llvm.org/docs/GetElementPtr.html. 893 | 894 | The array index can be a literal, or a variable, this is why we can write ``array[i]``. 895 | So our obfuscation can safely replace this operand by a variable if it was a literal 0. 896 | **But** the tricky thing is that the second index *has* to be a literal, it can not be a variable. 897 | But our obfuscation is going to replace this literal by a new variable if it is equal to zero. 898 | 899 | I'm sure you want to know what happens when our pass breaks LLVM laws (clue: nothing to do with the FBI). 900 | Well compile the above code with your pass and no optimizations and see for yourself. 901 | Don't generate the LLVM bytecode, generate the binary (i.e remove the ``-S -emit-llvm`` options). 902 | You should get a segfault... Not ideal, our pass makes compilation crash... 903 | 904 | To solve this we just have to filter the type of instruction we are obfuscating. 905 | We need to add a new function and add a new condition in our main loop: 906 | 907 | .. code:: C++ 908 | 909 | bool runOnBasicBlock(BasicBlock &BB) override { 910 | IntegerVect.clear(); 911 | bool modified = false; 912 | 913 | // Not iterating from the beginning to avoid obfuscation of Phi instructions 914 | // parameters 915 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 916 | end = BB.end(); 917 | I != end; ++I) { 918 | Instruction &Inst = *I; 919 | if (isValidCandidateInstruction(Inst)) { 920 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 921 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 922 | if (Value *New_val = replaceZero(Inst, C)) { 923 | Inst.setOperand(i, New_val); 924 | modified = true; 925 | } else { 926 | //dbgs() << "ObfuscateZero: could not rand pick a variable for replacement\n"; 927 | } 928 | } 929 | } 930 | } 931 | registerInteger(Inst); 932 | } 933 | 934 | 935 | bool isValidCandidateInstruction(Instruction &Inst) { 936 | if (isa(&Inst)) { 937 | // dbgs() << "Ignoring GEP\n"; 938 | return false; 939 | } else if (isa(&Inst)) { 940 | // dbgs() << "Ignoring Switch\n"; 941 | return false; 942 | } else if (isa(&Inst)) { 943 | // dbgs() << "Ignoring Calls\n"; 944 | return false; 945 | } else { 946 | return true; 947 | } 948 | } 949 | 950 | Pretty easy, no? Well the hard part is that this kind of problems is almost impossible to anticipate unless you know all the LLVM instructions. 951 | The only solution to find this id to run your passes on big projects, see where it crashes and find out why. 952 | 953 | Your code should now be pretty close to the ``ObfuscateZero`` pass. 954 | And since I don't want to dump all the code on this page (again) from now on we are going to use the ObfuscateZero pass for our tests. 955 | 956 | Tests, tests and more tests 957 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 958 | 959 | I hope this last part made you understand that validation is critical before using your obfuscations in prod. 960 | For ObfuscateZero we used the ``lit`` testing tool (LLVM Integrated Tester) we installed earlier. 961 | This tool runs the tests you specify with a particular syntax. Take a look in the files in the ``$PASSDIR/tests/ObfuscateZero`` folder to learn how to use it. 962 | 963 | For ObufuscateZero we have two types of tests: 964 | * Simple tests checking if the pass actually does what we want and doesn't crash in some tricky cases (GEP :p) 965 | * The validation scripts (*.sh files). Those files download the sources from openssl and zlib, compile them with our pass and run their validation suite. If the project compiles without error *and* passes its validation suite, we can suppose that our pass doesn't introduce bugs. 966 | 967 | If you have installed ``lit`` then go to ``$PASSDIR/build`` and run: 968 | 969 | .. code:: bash 970 | 971 | >$ make check 972 | 973 | This will run the ``ObfuscateZero`` tests, which you can modify to test your pass. But it's going to take some time. 974 | To validate ``ObfuscateZero`` we also compiled a C++ code since some constructs are not present when compiling from C. 975 | However the test file has not been shipped in the git. 976 | 977 | This is just the beginning 978 | ========================== 979 | 980 | This tutorial was just an introduction to writing LLVM passes and using them for obfuscation. 981 | There are many more funny things to do to make your code very annoying for reverse engineers. 982 | I hope this will help you get started. 983 | But remember, if you choose the quick and easy path as Vader did - you will become an agent of evil. 984 | 985 | 986 | Thanks 987 | ====== 988 | 989 | - Kevin Szkudlapski, for the careful proof reading 990 | - Mehdi Amini, for the extreme code review 991 | - Jeanne Marcel, the ghostly presence 992 | - and Serge Guelton, for the supreme coaching! 993 | -------------------------------------------------------------------------------- /llvm-passes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EPONA_LLVM_MODULES 2 | ObfuscateZero 3 | X-OR 4 | SplitBitwiseOp 5 | ) 6 | 7 | # automatically create modules based on EPONA_LLVM_MODULES and directory content 8 | foreach(MODULE ${EPONA_LLVM_MODULES}) 9 | aux_source_directory(${CMAKE_SOURCE_DIR}/llvm-passes/${MODULE} ${MODULE}_SRC) 10 | add_llvm_loadable_module(LLVM${MODULE} ${${MODULE}_SRC}) 11 | endforeach() 12 | 13 | -------------------------------------------------------------------------------- /llvm-passes/ObfuscateZero/ObfuscateZero.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/Pass.h" 2 | #include "llvm/IR/Function.h" 3 | #include "llvm/IR/Constants.h" 4 | #include "llvm/Support/raw_ostream.h" 5 | 6 | #include "llvm/IR/LegacyPassManager.h" 7 | #include "llvm/Transforms/Utils/BasicBlockUtils.h" 8 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 9 | 10 | #include "llvm/IR/Instructions.h" 11 | #include "llvm/IR/IRBuilder.h" 12 | 13 | #ifndef NDEBUG 14 | #include "llvm/IR/Verifier.h" 15 | #include "llvm/Support/Debug.h" 16 | #endif 17 | 18 | #include 19 | #include 20 | 21 | using namespace llvm; 22 | 23 | namespace { 24 | using prime_type = uint32_t; 25 | 26 | static const prime_type Prime_array[] = { 27 | 2 , 3 , 5 , 7, 11, 13, 17, 19, 23, 29, 28 | 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 29 | 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 30 | 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 31 | 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 32 | 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 33 | 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 34 | 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 35 | 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 36 | 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 37 | 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 38 | 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 39 | 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 40 | 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 41 | 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 42 | 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 43 | 947, 953, 967, 971, 977, 983, 991, 997}; 44 | 45 | class ObfuscateZero : public BasicBlockPass { 46 | std::vector IntegerVect; 47 | std::default_random_engine Generator; 48 | 49 | public: 50 | 51 | static char ID; 52 | 53 | ObfuscateZero() : BasicBlockPass(ID) {} 54 | 55 | bool runOnBasicBlock(BasicBlock &BB) override { 56 | IntegerVect.clear(); 57 | bool modified = false; 58 | 59 | // Not iterating from the beginning to avoid obfuscation of Phi instructions 60 | // parameters 61 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 62 | end = BB.end(); 63 | I != end; ++I) { 64 | Instruction &Inst = *I; 65 | if (isValidCandidateInstruction(Inst)) { 66 | for (size_t i = 0; i < Inst.getNumOperands(); ++i) { 67 | if (Constant *C = isValidCandidateOperand(Inst.getOperand(i))) { 68 | if (Value *New_val = replaceZero(Inst, C)) { 69 | Inst.setOperand(i, New_val); 70 | modified = true; 71 | } else { 72 | //dbgs() << "ObfuscateZero: could not rand pick a variable for replacement\n"; 73 | } 74 | } 75 | } 76 | } 77 | registerInteger(Inst); 78 | } 79 | 80 | #ifndef NDEBUG 81 | verifyFunction(*BB.getParent()); 82 | #endif 83 | return modified; 84 | } 85 | 86 | private: 87 | bool isValidCandidateInstruction(Instruction &Inst) { 88 | if (isa(&Inst)) { 89 | // dbgs() << "Ignoring GEP\n"; 90 | return false; 91 | } else if (isa(&Inst)) { 92 | // dbgs() << "Ignoring Switch\n"; 93 | return false; 94 | } else if (isa(&Inst)) { 95 | // dbgs() << "Ignoring Calls\n"; 96 | return false; 97 | } else { 98 | return true; 99 | } 100 | } 101 | 102 | Constant *isValidCandidateOperand(Value *V) { 103 | Constant *C; 104 | if (!(C = dyn_cast(V))) return nullptr; 105 | if (!C->isNullValue()) return nullptr; 106 | // We found a NULL constant, lets validate it 107 | if(!C->getType()->isIntegerTy()) { 108 | //dbgs() << "Ignoring non integer value\n"; 109 | return nullptr; 110 | } 111 | return C; 112 | } 113 | 114 | void registerInteger(Value &V) { 115 | if (V.getType()->isIntegerTy()) 116 | IntegerVect.push_back(&V); 117 | } 118 | 119 | // Return a random prime number not equal to DifferentFrom 120 | // If an error occurs returns 0 121 | prime_type getPrime(prime_type DifferentFrom = 0) { 122 | static std::uniform_int_distribution Rand(0, std::extent::value); 123 | size_t MaxLoop = 10; 124 | prime_type Prime; 125 | 126 | do { 127 | Prime = Prime_array[Rand(Generator)]; 128 | } while(Prime == DifferentFrom && --MaxLoop); 129 | 130 | if(!MaxLoop) { 131 | return 0; 132 | } 133 | 134 | return Prime; 135 | } 136 | 137 | Value *replaceZero(Instruction &Inst, Value *VReplace) { 138 | // Replacing 0 by: 139 | // prime1 * ((x | any1)**2) != prime2 * ((y | any2)**2) 140 | // with prime1 != prime2 and any1 != 0 and any2 != 0 141 | prime_type p1 = getPrime(), 142 | p2 = getPrime(p1); 143 | 144 | if(p2 == 0 || p1 == 0) 145 | return nullptr; 146 | 147 | Type *ReplacedType = VReplace->getType(), 148 | *IntermediaryType = IntegerType::get(Inst.getParent()->getContext(), 149 | sizeof(prime_type) * 8); 150 | 151 | if (IntegerVect.empty()) { 152 | return nullptr; 153 | } 154 | 155 | std::uniform_int_distribution Rand(0, IntegerVect.size() - 1); 156 | std::uniform_int_distribution RandAny(1, 10); 157 | 158 | size_t Index1 = Rand(Generator), Index2 = Rand(Generator); 159 | 160 | // Getting the literals as LLVM objects 161 | Constant *any1 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 162 | *any2 = ConstantInt::get(IntermediaryType, 1 + RandAny(Generator)), 163 | *prime1 = ConstantInt::get(IntermediaryType, p1), 164 | *prime2 = ConstantInt::get(IntermediaryType, p2), 165 | // Bitmask to prevent overflow 166 | *OverflowMask = ConstantInt::get(IntermediaryType, 0x00000007); 167 | 168 | IRBuilder<> Builder(&Inst); 169 | 170 | // lhs 171 | // To avoid overflow 172 | Value *LhsCast = 173 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index1), IntermediaryType); 174 | registerInteger(*LhsCast); 175 | Value *LhsAnd = Builder.CreateAnd(LhsCast, OverflowMask); 176 | registerInteger(*LhsAnd); 177 | Value *LhsOr = Builder.CreateOr(LhsAnd, any1); 178 | registerInteger(*LhsOr); 179 | Value *LhsSquare = Builder.CreateMul(LhsOr, LhsOr); 180 | registerInteger(*LhsSquare); 181 | Value *LhsTot = Builder.CreateMul(LhsSquare, prime1); 182 | registerInteger(*LhsTot); 183 | 184 | // rhs 185 | Value *RhsCast = 186 | Builder.CreateZExtOrTrunc(IntegerVect.at(Index2), IntermediaryType); 187 | registerInteger(*RhsCast); 188 | Value *RhsAnd = Builder.CreateAnd(RhsCast, OverflowMask); 189 | registerInteger(*RhsAnd); 190 | Value *RhsOr = Builder.CreateOr(RhsAnd, any2); 191 | registerInteger(*RhsOr); 192 | Value *RhsSquare = Builder.CreateMul(RhsOr, RhsOr); 193 | registerInteger(*RhsSquare); 194 | Value *RhsTot = Builder.CreateMul(RhsSquare, prime2); 195 | registerInteger(*RhsTot); 196 | 197 | // comp 198 | Value *comp = 199 | Builder.CreateICmp(CmpInst::Predicate::ICMP_EQ, LhsTot, RhsTot); 200 | registerInteger(*comp); 201 | Value *castComp = Builder.CreateZExt(comp, ReplacedType); 202 | registerInteger(*castComp); 203 | 204 | return castComp; 205 | } 206 | }; 207 | } 208 | 209 | char ObfuscateZero::ID = 0; 210 | static RegisterPass X("ObfuscateZero", "Obfuscates zeroes", 211 | false, false); 212 | 213 | // register pass for clang use 214 | static void registerObfuscateZeroPass(const PassManagerBuilder &, 215 | PassManagerBase &PM) { 216 | PM.add(new ObfuscateZero()); 217 | } 218 | static RegisterStandardPasses 219 | RegisterMBAPass(PassManagerBuilder::EP_EarlyAsPossible, 220 | registerObfuscateZeroPass); 221 | -------------------------------------------------------------------------------- /llvm-passes/PropagatedTransformation/PropagatedTransformation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __PROPAGATED_TRANSFORMATION_HPP__ 2 | #define __PROPAGATED_TRANSFORMATION_HPP__ 3 | 4 | #include "llvm/IR/Instructions.h" 5 | #include "llvm/Transforms/Utils/BasicBlockUtils.h" 6 | 7 | #include "llvm/IR/Instructions.h" 8 | #include "llvm/IR/IRBuilder.h" 9 | #include "llvm/Support/ErrorOr.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace llvm; 19 | 20 | struct Tree_t : public std::unordered_map> { 21 | 22 | mapped_type roots() const { 23 | mapped_type Roots; 24 | std::transform(begin(), 25 | end(), 26 | std::inserter(Roots, Roots.begin()), 27 | [this](Tree_t::value_type const &It) { return It.first; }); 28 | 29 | for (auto const &Node : (*this)) 30 | std::for_each(Node.second.cbegin(), 31 | Node.second.cend(), 32 | [&Roots](Tree_t::mapped_type::key_type const &Successors){Roots.erase(Successors);}); 33 | return Roots; 34 | } 35 | }; 36 | 37 | namespace PropagatedTransformation { 38 | class PropagatedTransformation { 39 | protected: 40 | std::default_random_engine Generator; 41 | 42 | std::list Forest; 43 | std::unordered_map> TreeMap; 44 | 45 | std::map, std::vector> TransfoRegister; 46 | 47 | unsigned SizeParam; 48 | 49 | // Pure virtual members 50 | virtual BinaryOperator *isEligibleInstruction(Instruction *Inst) const = 0; 51 | // Should return an empty vector if sthg went wrong 52 | virtual std::vector transformOperand(Value *Operand, 53 | IRBuilder<> &Builder) = 0; 54 | virtual Value *transformBackOperand(std::vector const &Operands, 55 | IRBuilder<> &Builder) = 0; 56 | virtual std::vector 57 | applyNewOperation(std::vector const &Operand1, 58 | std::vector const &Operand2, 59 | Instruction *OriginalInstruction, 60 | IRBuilder<> &Builder) = 0; 61 | 62 | // Implemented members 63 | void populateForest(BasicBlock &BB) { 64 | TreeMap.clear(); 65 | Forest.clear(); 66 | TransfoRegister.clear(); 67 | 68 | for (typename BasicBlock::iterator I = BB.getFirstInsertionPt(), 69 | end = BB.end(); 70 | I != end; ++I) { 71 | Instruction *Inst = &*I; 72 | if (isEligibleInstruction(Inst)) { 73 | // Adding an empty tree to the Forest to pass it to walkInstructions 74 | // If a merge occurs an older tree will be removed this 75 | // means that there can't be any empty tree in the Torest 76 | Forest.emplace_back(); 77 | walkInstructions(Forest.back(), Inst); 78 | } 79 | } 80 | } 81 | 82 | 83 | void walkInstructions(Tree_t &T, Instruction *Inst) { 84 | if (not isEligibleInstruction(Inst)) 85 | return; 86 | // Look for the Inst in the TreeMap 87 | auto Pos = TreeMap.find(Inst); 88 | if (Pos == TreeMap.end()) { 89 | T.emplace(Inst, Tree_t::mapped_type()); 90 | for (auto const &Op : Inst->operands()) { 91 | Instruction *OperandInst = dyn_cast(&Op); 92 | if (OperandInst and isEligibleInstruction(OperandInst)) 93 | T.at(Inst).insert(OperandInst); 94 | } 95 | TreeMap.emplace(Inst, T); 96 | for (auto const &NVUse : Inst->uses()) { 97 | Instruction *UseInst = dyn_cast(NVUse.getUser()); 98 | if (not UseInst) 99 | continue; 100 | walkInstructions(T, UseInst); 101 | } 102 | } else { 103 | Tree_t &OlderTree = Pos->second.get(); 104 | if (&OlderTree != &T) { 105 | // Merge trees 106 | for (auto It : OlderTree) { 107 | T[It.first].insert(OlderTree[It.first].begin(), 108 | OlderTree[It.first].end()); 109 | // Redirecting TreeMap refs 110 | TreeMap.at(It.first) = T; 111 | } 112 | Forest.remove_if( 113 | [&OlderTree](Tree_t const &T) { return &T == &OlderTree; }); 114 | } 115 | } 116 | } 117 | 118 | std::vector getShuffledRange(unsigned UpTo) { 119 | std::vector Range(UpTo); 120 | std::iota(Range.begin(), Range.end(), 0u); 121 | std::random_shuffle(Range.begin(), Range.end()); 122 | return Range; 123 | } 124 | 125 | void replaceUses(Value *OriginalValue, Value *NewValue, Tree_t const &T) { 126 | for (auto const &NVUse : OriginalValue->uses()) { 127 | Instruction *UseInst = dyn_cast(NVUse.getUser()); 128 | if (not UseInst or T.find(UseInst) != T.cend()) 129 | continue; 130 | for (unsigned I = 0; I < UseInst->getNumOperands(); ++I) { 131 | if (UseInst->getOperand(I) == OriginalValue) { 132 | UseInst->setOperand(I, NewValue); 133 | } 134 | } 135 | } 136 | } 137 | 138 | // Checking if we've already transformed the operand or transform it 139 | ErrorOr const &> findOrTransformOperand(Value *Operand, 140 | IRBuilder<> &Builder) { 141 | auto Pos = TransfoRegister.find(std::make_pair(Operand, SizeParam)); 142 | if (Pos == TransfoRegister.end()) { 143 | std::vector NewOperands = 144 | transformOperand(Operand, Builder); 145 | if (NewOperands.empty()) { 146 | dbgs() << "Obfuscation failed\n"; 147 | return {std::errc::operation_not_supported}; 148 | } else { 149 | TransfoRegister.emplace(std::make_pair(Operand, SizeParam), 150 | std::move(NewOperands)); 151 | return {TransfoRegister.at(std::make_pair(Operand, SizeParam))}; 152 | } 153 | } else 154 | return Pos->second; 155 | } 156 | 157 | ErrorOr const &> 158 | RecursiveTransform(Instruction *Inst, Tree_t const &T, 159 | BasicBlock const &CurrentBB) { 160 | assert(Inst && "Invalid instruction."); 161 | IRBuilder<> Builder(Inst); 162 | 163 | Value *Operand1 = Inst->getOperand(0), *Operand2 = Inst->getOperand(1); 164 | 165 | ErrorOr&> NewOperands1{std::errc::operation_not_supported}, 166 | NewOperands2{std::errc::operation_not_supported}; 167 | 168 | auto const &Successors = T.at(Inst); 169 | 170 | Instruction *IOperand1 = dyn_cast(Operand1), 171 | *IOperand2 = dyn_cast(Operand2); 172 | 173 | // If Operand1 is not a node (i.e not a xor) 174 | if (not IOperand1 or IOperand1->getParent() != &CurrentBB or 175 | Successors.find(IOperand1) == Successors.cend()) 176 | NewOperands1 = findOrTransformOperand(Operand1, Builder); 177 | else 178 | NewOperands1 = RecursiveTransform(IOperand1, T, CurrentBB); 179 | 180 | // Idem for Operand2 181 | if (not IOperand2 or IOperand2->getParent() != &CurrentBB or 182 | Successors.find(IOperand2) == Successors.cend()) 183 | NewOperands2 = findOrTransformOperand(Operand2, Builder); 184 | else 185 | NewOperands2 = RecursiveTransform(IOperand2, T, CurrentBB); 186 | 187 | if (not NewOperands1 or not NewOperands2) 188 | return {std::errc::operation_not_supported}; 189 | 190 | auto NewValues = applyNewOperation(NewOperands1.get(), NewOperands2.get(), Inst, Builder); 191 | 192 | if (NewValues.empty()) 193 | return {std::errc::operation_not_supported}; 194 | 195 | // Preparing the result in base 2 for later use 196 | // Should be optimized out if we don't use it. 197 | Value *InvertResult = transformBackOperand(NewValues, Builder); 198 | 199 | if (not InvertResult) 200 | return {std::errc::operation_not_supported}; 201 | 202 | TransfoRegister.emplace(std::make_pair(Inst, SizeParam), 203 | std::move(NewValues)); 204 | 205 | replaceUses(Inst, InvertResult, T); 206 | 207 | return TransfoRegister.at(std::make_pair(Inst, SizeParam)); 208 | } 209 | }; 210 | } 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /llvm-passes/SplitBitwiseOp/SplitBitwiseOp.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/Pass.h" 2 | #include "llvm/IR/Function.h" 3 | #include "llvm/IR/Constants.h" 4 | 5 | #include "llvm/IR/LegacyPassManager.h" 6 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 7 | 8 | #ifndef NDEBUG 9 | #include "llvm/IR/Verifier.h" 10 | #endif 11 | 12 | #include "llvm/Support/Debug.h" 13 | 14 | #include "llvm/ADT/APInt.h" 15 | #include "llvm/Support/raw_ostream.h" 16 | #include "llvm/IR/Instructions.h" 17 | #include "llvm/IR/IRBuilder.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "../PropagatedTransformation/PropagatedTransformation.hpp" 26 | 27 | using namespace llvm; 28 | 29 | namespace { 30 | 31 | std::set integerFactors(unsigned BitSize) { 32 | if(BitSize < 2) 33 | return {}; 34 | std::set Factors; 35 | unsigned MaxFactor = (unsigned)std::sqrt(BitSize); 36 | for(unsigned I = 1; I <= MaxFactor; ++I) 37 | if(BitSize % I == 0) { 38 | Factors.insert(I); 39 | Factors.insert(BitSize / I); 40 | } 41 | return Factors; 42 | } 43 | 44 | // PASS 45 | class SplitBitwiseOp 46 | : protected PropagatedTransformation::PropagatedTransformation, 47 | public BasicBlockPass { 48 | 49 | Type *OriginalType; 50 | 51 | public: 52 | static char ID; 53 | 54 | SplitBitwiseOp() : BasicBlockPass(ID) {} 55 | 56 | virtual bool runOnBasicBlock(BasicBlock &BB) { 57 | bool modified = false; 58 | 59 | populateForest(BB); 60 | 61 | for (auto const &T : Forest) { 62 | const auto Roots = T.roots(); 63 | // Choosing SizeParam 64 | SizeParam = chooseSplitSize(T); 65 | // If there was no valid Size available: 66 | if (SizeParam == 0) { 67 | dbgs() << "split_binop: Couldn't pick split size.\n"; 68 | continue; 69 | } 70 | 71 | OriginalType = T.begin()->first->getType(); 72 | 73 | for (Instruction* Root : Roots) { 74 | if (RecursiveTransform(Root, T, BB)) { 75 | modified = true; 76 | } 77 | else { 78 | dbgs() << "SplitBinOp: Obfuscation failed.\n"; 79 | break; 80 | } 81 | } 82 | } 83 | #ifndef NDEBUG 84 | verifyFunction(*BB.getParent()); 85 | #endif 86 | return modified; 87 | } 88 | 89 | private: 90 | std::default_random_engine Generator; 91 | 92 | unsigned chooseSplitSize(Tree_t const &T) { 93 | unsigned OriginalSize = 94 | T.begin()->first->getType()->getIntegerBitWidth(); 95 | 96 | std::set Factors = integerFactors(OriginalSize); 97 | 98 | if (Factors.empty()) 99 | return 0; 100 | 101 | std::uniform_int_distribution Rand(0, Factors.size() - 1); 102 | auto Pos = Factors.cbegin(); 103 | std::advance(Pos, Rand(Generator)); 104 | return *Pos; 105 | } 106 | 107 | BinaryOperator *isEligibleInstruction(Instruction *Inst) const override { 108 | if(BinaryOperator *Op = dyn_cast(Inst)) { 109 | const Instruction::BinaryOps OpCode = Op->getOpcode(); 110 | if (OpCode == Instruction::BinaryOps::Xor or 111 | OpCode == Instruction::BinaryOps::And or 112 | OpCode == Instruction::BinaryOps::Or) { 113 | return Op; 114 | } 115 | } 116 | return nullptr; 117 | } 118 | 119 | std::vector 120 | applyNewOperation(std::vector const &Operands1, 121 | std::vector const &Operands2, 122 | Instruction *OriginalInstruction, 123 | IRBuilder<> &Builder) override { 124 | assert(not Operands1.empty() and not Operands2.empty() && "Empty operand vector."); 125 | assert(Operands1.size() == Operands2.size() && "Operand vectors must have the same size."); 126 | 127 | BinaryOperator *Op = cast(OriginalInstruction); 128 | 129 | const unsigned NumberOperations = Operands1.size(); 130 | 131 | Instruction::BinaryOps OpCode = Op->getOpcode(); 132 | std::vector NewResults(NumberOperations); 133 | 134 | auto Range = getShuffledRange(NumberOperations); 135 | 136 | for (auto I : Range) 137 | NewResults[I] = 138 | Builder.CreateBinOp(OpCode, Operands1[I], Operands2[I]); 139 | 140 | return NewResults; 141 | } 142 | 143 | std::vector transformOperand(Value *Operand, 144 | IRBuilder<> &Builder) override { 145 | const unsigned OriginalNbBit = Operand->getType()->getIntegerBitWidth(), 146 | SplitSize = SizeParam, 147 | NumberNewOperands = OriginalNbBit / SplitSize; 148 | 149 | Type *NewType = IntegerType::get(Operand->getContext(), SplitSize); 150 | 151 | std::vector NewOperands(NumberNewOperands); 152 | 153 | Value *InitMask = ConstantInt::get(Operand->getType(), -1); 154 | InitMask = Builder.CreateLShr(InitMask, OriginalNbBit - SplitSize); 155 | 156 | auto Range = getShuffledRange(NumberNewOperands); 157 | 158 | for (auto I : Range) { 159 | Value *Mask = Builder.CreateShl(InitMask, SplitSize * I); 160 | Value *MaskedNewValue = Builder.CreateAnd(Operand, Mask); 161 | Value *NewOperandValue = 162 | Builder.CreateLShr(MaskedNewValue, I * SplitSize); 163 | // Using NewOperands to keep the order of split operands 164 | NewOperands[I] = Builder.CreateTrunc(NewOperandValue, NewType); 165 | } 166 | return NewOperands; 167 | } 168 | 169 | Value *transformBackOperand(std::vector const &Operands, 170 | IRBuilder<> &Builder) override { 171 | assert(Operands.size() && "Empty operand vector."); 172 | const unsigned NumberOperands = Operands.size(), SplitSize = SizeParam; 173 | 174 | Value *Accu = Constant::getNullValue(OriginalType); 175 | 176 | auto Range = getShuffledRange(NumberOperands); 177 | 178 | for (auto I : Range) { 179 | Value *ExtendedOperand = 180 | Builder.CreateZExt(Operands[I], OriginalType); 181 | Value *ShiftedValue = 182 | Builder.CreateShl(ExtendedOperand, I * SplitSize); 183 | Accu = Builder.CreateOr(Accu, ShiftedValue); 184 | } 185 | return Accu; 186 | } 187 | }; 188 | } 189 | 190 | char SplitBitwiseOp::ID = 0; 191 | static RegisterPass X("SplitBitwiseOp", 192 | "Splits bitwise operators", false, false); 193 | 194 | // register pass for clang use 195 | static void registerSplitBitwiseOpPass(const PassManagerBuilder &, 196 | PassManagerBase &PM) { 197 | PM.add(new SplitBitwiseOp()); 198 | } 199 | static RegisterStandardPasses 200 | RegisterSplitBitwisePass(PassManagerBuilder::EP_EarlyAsPossible, 201 | registerSplitBitwiseOpPass); 202 | -------------------------------------------------------------------------------- /llvm-passes/X-OR/X-OR.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/Pass.h" 2 | #include "llvm/IR/Function.h" 3 | #include "llvm/IR/Constants.h" 4 | #include "llvm/ADT/APInt.h" 5 | #include "llvm/IR/Instructions.h" 6 | #include "llvm/IR/IRBuilder.h" 7 | 8 | #include "llvm/IR/LegacyPassManager.h" 9 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 10 | 11 | #ifndef NDEBUG 12 | #include "llvm/IR/Verifier.h" 13 | #endif 14 | #include "llvm/Support/Debug.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "../PropagatedTransformation/PropagatedTransformation.hpp" 23 | 24 | using namespace llvm; 25 | 26 | namespace { 27 | class X_OR : protected PropagatedTransformation::PropagatedTransformation, 28 | public BasicBlockPass { 29 | 30 | typedef std::map, std::map> 31 | ExponentMaps_t; 32 | ExponentMaps_t ExponentMaps; 33 | 34 | Type *OriginalType; 35 | 36 | public: 37 | static char ID; 38 | 39 | X_OR() : BasicBlockPass(ID) {} 40 | 41 | virtual bool runOnBasicBlock(BasicBlock &BB) { 42 | bool modified = false; 43 | 44 | populateForest(BB); 45 | 46 | for (auto const &T : Forest) { 47 | auto Roots = T.roots(); 48 | // Choosing NewBase 49 | SizeParam = chooseTreeBase(T, Roots); 50 | // If there was no valid base available: 51 | if (SizeParam < 3) { 52 | dbgs() << "X-OR: Couldn't pick base.\n"; 53 | continue; 54 | } 55 | 56 | OriginalType = T.begin()->first->getType(); 57 | 58 | for (auto Root : Roots) { 59 | if (RecursiveTransform(Root, T, BB)) 60 | modified = true; 61 | else { 62 | dbgs() << "X_OR: Obfuscation failed.\n"; 63 | break; 64 | } 65 | } 66 | } 67 | #ifndef NDEBUG 68 | verifyFunction(*BB.getParent()); 69 | #endif 70 | return modified; 71 | } 72 | 73 | private: 74 | // FIXME: capping at 128 bits because of APInt multiplication bug: 75 | // https://llvm.org/bugs/show_bug.cgi?id=19797 76 | const unsigned MaxSupportedSize = 128; 77 | std::default_random_engine Generator; 78 | 79 | std::map, std::map> 80 | ExponentMap; 81 | 82 | BinaryOperator *isEligibleInstruction(Instruction *Inst) const override { 83 | BinaryOperator *Op = dyn_cast(Inst); 84 | if (not Op) 85 | return nullptr; 86 | if (Op->getOpcode() == Instruction::BinaryOps::Xor) 87 | return Op; 88 | return nullptr; 89 | } 90 | 91 | std::vector 92 | applyNewOperation(std::vector const &Operands1, 93 | std::vector const &Operands2, Instruction *, 94 | IRBuilder<> &Builder) override { 95 | assert(not Operands1.empty() and not Operands2.empty()); 96 | 97 | return std::vector{ 98 | Builder.CreateAdd(Operands1[0], Operands2[0])}; 99 | } 100 | 101 | std::vector transformOperand(Value *Operand, 102 | IRBuilder<> &Builder) override { 103 | if (!Operand->getType()->isIntegerTy()) 104 | return std::vector(); 105 | 106 | const unsigned OriginalNbBit = Operand->getType()->getIntegerBitWidth(), 107 | Base = SizeParam, 108 | NewNbBit = requiredBits(OriginalNbBit, Base); 109 | 110 | if (not NewNbBit) { 111 | return std::vector(); 112 | } 113 | 114 | Type *NewBaseType = IntegerType::get(Operand->getContext(), NewNbBit); 115 | 116 | auto const &ExpoMap = getExponentMap(Base, OriginalNbBit, NewBaseType); 117 | 118 | // Initializing variables 119 | Value *Accu = Constant::getNullValue(NewBaseType), 120 | *InitMask = ConstantInt::get(NewBaseType, 1u); 121 | 122 | // Extending the original value to NewNbBit for bitwise and 123 | Value *ExtendedOperand = Builder.CreateZExt(Operand, NewBaseType); 124 | 125 | auto Range = getShuffledRange(OriginalNbBit); 126 | 127 | for (auto Bit : Range) { 128 | Value *Mask = Builder.CreateShl(InitMask, Bit); 129 | Value *MaskedNewValue = Builder.CreateAnd(ExtendedOperand, Mask); 130 | Value *BitValue = Builder.CreateLShr(MaskedNewValue, Bit); 131 | Value *Expo = ConstantInt::get(NewBaseType, ExpoMap.at(Bit)); 132 | Value *NewBit = Builder.CreateMul(BitValue, Expo); 133 | Accu = Builder.CreateAdd(Accu, NewBit); 134 | } 135 | return std::vector{Accu}; 136 | } 137 | 138 | Value *transformBackOperand(std::vector const &Operands, 139 | IRBuilder<> &Builder) override { 140 | assert(Operands.size() && "No instructions provided."); 141 | Value *Operand = Operands[0]; 142 | 143 | Type *ObfuscatedType = Operand->getType(); 144 | 145 | const unsigned OriginalNbBit = OriginalType->getIntegerBitWidth(), 146 | Base = SizeParam; 147 | 148 | // Initializing variables 149 | Value *IR2 = ConstantInt::get(ObfuscatedType, 2u), 150 | *IRBase = ConstantInt::get(ObfuscatedType, Base), 151 | *Accu = Constant::getNullValue(ObfuscatedType); 152 | 153 | auto const &ExpoMap = 154 | getExponentMap(Base, OriginalNbBit, ObfuscatedType); 155 | 156 | auto Range = getShuffledRange(OriginalNbBit); 157 | 158 | for (auto Bit : Range) { 159 | Value *Pow = ConstantInt::get(ObfuscatedType, ExpoMap.at(Bit)); 160 | Value *Q = Builder.CreateUDiv(Operand, Pow); 161 | Q = Builder.CreateURem(Q, IRBase); 162 | Q = Builder.CreateURem(Q, IR2); 163 | Value *ShiftedBit = Builder.CreateShl(Q, Bit); 164 | Accu = Builder.CreateOr(Accu, ShiftedBit); 165 | } 166 | // Cast back to original type 167 | return Builder.CreateTrunc(Accu, OriginalType); 168 | } 169 | 170 | unsigned chooseTreeBase(Tree_t const &T, Tree_t::mapped_type const &Roots) { 171 | assert(T.size() && "Can't process an empty tree."); 172 | unsigned Max = maxBase( 173 | T.begin()->first->getType()->getIntegerBitWidth()), 174 | MinEligibleBase = 0; 175 | 176 | // Computing minimum base 177 | // Each node of the tree has a base equal to the sum of its two 178 | // successors' min base 179 | std::map NodeBaseMap; 180 | for (auto const &Root : Roots) 181 | MinEligibleBase = std::max(minimalBase(Root, T, NodeBaseMap), MinEligibleBase); 182 | 183 | ++MinEligibleBase; 184 | if (MinEligibleBase < 3 or MinEligibleBase > Max) 185 | return 0; 186 | std::uniform_int_distribution Rand(MinEligibleBase, Max); 187 | return Rand(Generator); 188 | } 189 | 190 | unsigned minimalBase(Value *Node, Tree_t const &T, 191 | std::map &NodeBaseMap) { 192 | // Emplace new value and check if already passed this node 193 | if (NodeBaseMap[Node] != 0) 194 | return NodeBaseMap.at(Node); 195 | Instruction *Inst = dyn_cast(Node); 196 | // We reached a leaf 197 | if (not Inst or T.find(Inst) == T.end()) { 198 | NodeBaseMap.at(Node) = 1; 199 | return 1; 200 | } else { 201 | // Recursively check operands 202 | unsigned sum = 0; 203 | for (auto const &Operand : Inst->operands()) { 204 | if (NodeBaseMap[Operand] == 0) 205 | minimalBase(Operand, T, NodeBaseMap); 206 | sum += NodeBaseMap.at(Operand); 207 | } 208 | // Compute this node's min base 209 | NodeBaseMap[Node] = sum; 210 | return sum; 211 | } 212 | } 213 | 214 | // Returns the max supported base for the given OriginalNbBit 215 | // 31 is the max base to avoid overflow 2**sizeof(unsigned) in requiredBits 216 | unsigned maxBase(unsigned OriginalNbBit) { 217 | assert(OriginalNbBit && "Bisize must be > 1"); 218 | const unsigned MaxSupportedBase = sizeof(unsigned) * 8 - 1; 219 | if (OriginalNbBit >= MaxSupportedSize) 220 | return 0; 221 | if (MaxSupportedSize / OriginalNbBit > MaxSupportedBase) 222 | return MaxSupportedBase; 223 | return unsigned(2) << ((MaxSupportedSize / OriginalNbBit) - 1); 224 | } 225 | 226 | // numbers of bits required to store the original type in the new base 227 | // Can hold up to twice the max of the original type to store the max result 228 | // of the add 229 | // returns 0 if more than 128 bits are needed 230 | unsigned requiredBits(unsigned OriginalSize, unsigned TargetBase) const { 231 | assert(OriginalSize); 232 | if (TargetBase <= 2 or OriginalSize >= MaxSupportedSize) 233 | return 0; 234 | // 'Exact' formula : std::ceil(std::log2(std::pow(TargetBase, 235 | // OriginalSize) - 1)); 236 | unsigned ret = 237 | (unsigned)std::ceil(OriginalSize * std::log2(TargetBase)); 238 | // Need to make sure that the base can be represented too... 239 | // (For instance for 2 chained boolean xor) 240 | ret = std::max(ret, (unsigned)std::floor(std::log2(TargetBase)) + 1); 241 | return ret <= MaxSupportedSize ? ret : 0; 242 | } 243 | 244 | ExponentMaps_t::mapped_type 245 | getExponentMap(unsigned Base, unsigned OriginalNbBit, const Type *Ty) { 246 | // Eplacing if the pair doesn't exist, else return an iter to the 247 | // existing 248 | auto Position = ExponentMaps.emplace( 249 | std::make_pair(Base, OriginalNbBit), std::map()); 250 | // If the map has not been computed yet 251 | if (Position.second) { 252 | unsigned NewNbBit = Ty->getIntegerBitWidth(); 253 | APInt Pow(NewNbBit, 1u), APBase(NewNbBit, Base); 254 | for (unsigned Bit = 0; Bit < OriginalNbBit; ++Bit) { 255 | ExponentMaps.at(Position.first->first).emplace(Bit, Pow); 256 | Pow *= APBase; 257 | } 258 | } 259 | return Position.first->second; 260 | } 261 | }; 262 | } 263 | 264 | char X_OR::ID = 0; 265 | static RegisterPass X("X-OR", "Obfuscates XORs", false, false); 266 | 267 | // register pass for clang use 268 | static void registerX_ORPass(const PassManagerBuilder &, PassManagerBase &PM) { 269 | PM.add(new X_OR()); 270 | } 271 | static RegisterStandardPasses 272 | RegisterX_ORPass(PassManagerBuilder::EP_EarlyAsPossible, registerX_ORPass); 273 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/basic_test.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMObfuscateZero.so %s -S -emit-llvm -O2 -o %t1.ll 2 | // RUN: test `grep -c ' ret i32 0' %t1.ll` = 0 3 | 4 | int main(int argc, char *argv[]) { 5 | int a = argc; 6 | 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/check_openssl.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=openssl_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | git clone --single-branch git://git.openssl.org/openssl.git 10 | cd openssl 11 | CC=clang ./Configure --openssldir=$PWD/build linux-x86_64 "-Xclang -load -Xclang LLVMObfuscateZero.so" > /dev/null || exit 2 12 | make -j2 13 | make test 14 | 15 | cd .. 16 | rm -rf $builddir 17 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/check_zlib.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=zlib_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | wget http://zlib.net/zlib-1.2.8.tar.gz 10 | rm -rf zlib-1.2.8 11 | tar xf zlib-1.2.8.tar.gz 12 | cd zlib-1.2.8 13 | prefix=$PWD CC=clang CFLAGS="-Xclang -load -Xclang LLVMObfuscateZero.so" ./configure 14 | make 15 | make test 16 | 17 | cd .. 18 | rm -rf $builddir 19 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/null_pointer_test.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMObfuscateZero.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' null' %t1.ll` = 2 3 | 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | int a = 10; 8 | volatile int *p = NULL; 9 | if(p == NULL) 10 | return 1; 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/test_gep.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMObfuscateZero.so %s -S -emit-llvm -O2 -o %t1.ll 2 | // RUN: test `grep -c ' ret i32 0' %t1.ll` = 0 3 | 4 | #include 5 | 6 | struct f { 7 | int a; 8 | float b; 9 | }; 10 | 11 | void foo(struct f* s) { 12 | exit(s->b); 13 | } 14 | 15 | 16 | int main(int argc, char* argv[]) { 17 | int a = 0; 18 | struct f *test = malloc(10 * sizeof(struct f)); 19 | test[argc].a = 10; 20 | test[argc + 1].b = 100; 21 | foo(test + argc + 1); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /tests/ObfuscateZero/test_maxint.cpp: -------------------------------------------------------------------------------- 1 | // RUN: clang++ -std=c++11 -Xclang -load -Xclang LLVMObfuscateZero.so %s -S -emit-llvm -O2 -o %t1.ll 2 | // RUN: clang++ -std=c++11 -Xclang -load -Xclang LLVMObfuscateZero.so %s -O2 -o %t1.exe 3 | // RUN: clang++ -std=c++11 %s -S -emit-llvm -O2 -o %t2.exe 4 | // RUN: test `grep -c ' ret i32 0' %t1.ll` = 0 5 | // RUN: test `%t1.exe` = `%t2.exe` 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | uint64_t foo(uint64_t x) { 12 | volatile uint64_t a = x; 13 | return 0; 14 | } 15 | 16 | int main() { 17 | volatile uint64_t a = std::numeric_limits::max(); 18 | return foo(a); 19 | } 20 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/check_openssl.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=openssl_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | git clone --single-branch git://git.openssl.org/openssl.git 10 | cd openssl 11 | CC=clang ./Configure --openssldir=$PWD/build linux-x86_64 "-Xclang -load -Xclang LLVMSplitBitwiseOp.so" > /dev/null || exit 2 12 | make -j6 13 | make test 14 | 15 | cd .. 16 | rm -rf $builddir 17 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/check_zlib.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=zlib_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | wget http://zlib.net/zlib-1.2.8.tar.gz 10 | rm -rf zlib-1.2.8 11 | tar xf zlib-1.2.8.tar.gz 12 | cd zlib-1.2.8 13 | prefix=$PWD CC=clang CFLAGS="-Xclang -load -Xclang LLVMSplitBitwiseOp.so" ./configure 14 | make 15 | make test 16 | 17 | cd .. 18 | rm -rf $builddir 19 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_and.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' and ' %t1.ll` -gt 1 3 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 4 | // RUN: clang %s -O0 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile unsigned a = 150, b = -1; 11 | printf("%d\n", a & b); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_max.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` -gt 1 3 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 4 | // RUN: clang %s -O0 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile uint64_t a = 0xffffffffffffffff, b = 0; 11 | b=a^0xffffffffffffffff; 12 | printf("%ld\n", b); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_max32.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' and ' %t1.ll` -gt 1 3 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 4 | // RUN: clang %s -O0 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile uint32_t a = 0xffffffff, b = 0; 11 | b=a&150000; 12 | printf("%u\n", b); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_max_chained.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` -gt 2 3 | // RUN: test `grep -c ' and ' %t1.ll` -gt 2 4 | // RUN: test `grep -c ' or ' %t1.ll` -gt 2 5 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 6 | // RUN: clang %s -O0 -o %t3.out 7 | // RUN: test `%t2.out` = `%t3.out` 8 | #include 9 | #include 10 | 11 | int main() { 12 | volatile uint32_t a = 0xffffffff, b = 0, c = 0xffffffef, d = 0xfeffffef, e = 0xffffffe7, f = 0xffffffef, g = 0xfeffffff; 13 | b=a|0xffffffff&c^d^e&f|g; 14 | printf("%u\n", b); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_or.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' or ' %t1.ll` -gt 1 3 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 4 | // RUN: clang %s -O0 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile unsigned a = 150, b = -1; 11 | printf("%d\n", a | b); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/SplitBitwiseOp/sbo_xor.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -S -emit-llvm -O0 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` -gt 1 3 | // RUN: clang -Xclang -load -Xclang LLVMSplitBitwiseOp.so %s -O0 -o %t2.out 4 | // RUN: clang %s -O0 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile unsigned a = 150, b = -1; 11 | printf("%d\n", a ^ b); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/X-OR/check_openssl.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=openssl_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | git clone --single-branch git://git.openssl.org/openssl.git 10 | cd openssl 11 | CC=clang ./Configure --openssldir=$PWD/build linux-x86_64 "-Xclang -load -Xclang LLVMX-OR.so" > /dev/null || exit 2 12 | make -j6 13 | make test 14 | 15 | cd .. 16 | rm -rf $builddir 17 | -------------------------------------------------------------------------------- /tests/X-OR/check_zlib.sh: -------------------------------------------------------------------------------- 1 | # RUN: sh %s 2 | set -e 3 | builddir=zlib_build 4 | 5 | rm -rf $builddir 6 | mkdir $builddir 7 | cd $builddir 8 | 9 | wget http://zlib.net/zlib-1.2.8.tar.gz 10 | rm -rf zlib-1.2.8 11 | tar xf zlib-1.2.8.tar.gz 12 | cd zlib-1.2.8 13 | prefix=$PWD CC=clang CFLAGS="-Xclang -load -Xclang LLVMX-OR.so" ./configure 14 | make 15 | make test 16 | 17 | cd .. 18 | rm -rf $builddir 19 | -------------------------------------------------------------------------------- /tests/X-OR/xor.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -S -emit-llvm -O2 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` = 0 3 | // RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -O2 -o %t2.out 4 | // RUN: clang %s -O2 -o %t3.out 5 | // RUN: test `%t2.out` = `%t3.out` 6 | #include 7 | #include 8 | 9 | int main() { 10 | volatile uint8_t a = 0, b = 1, c = 0; 11 | b=a^4; 12 | c=b+1; 13 | printf("%d\n", b); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /tests/X-OR/xor_bigint.c: -------------------------------------------------------------------------------- 1 | // RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -S -emit-llvm -O2 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` = 0 3 | // RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -O2 -o %t2.out 4 | // RUN: test `%t2.out` = 150003 5 | #include 6 | #include 7 | 8 | int main() { 9 | volatile uint64_t a = 3, b = 1, c = 0; 10 | b=a^150000; 11 | c=b+1; 12 | printf("%ld\n", b); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/X-OR/xor_bool.cpp: -------------------------------------------------------------------------------- 1 | // RUN: clang++ -Xclang -load -Xclang LLVMX-OR.so %s -S -emit-llvm -O1 -o %t1.ll 2 | // RUN: test `grep -c ' xor ' %t1.ll` = 0 3 | // RUN: clang++ -Xclang -load -Xclang LLVMX-OR.so %s -O1 -o %t2.out 4 | // RUN: test `%t2.out` = 0 5 | #include 6 | 7 | int main() { 8 | volatile bool a = true, b = false; 9 | if(a^b) 10 | std::cout << 0 << std::endl; 11 | else 12 | std::cout << 1 << std::endl; 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/X-OR/xor_bool_1bit.ll: -------------------------------------------------------------------------------- 1 | ;; RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -S -emit-llvm -O2 -o %t1.ll 2 | ;; RUN: test `grep -c ' xor ' %t1.ll` = 0 3 | ;; RUN: clang -Xclang -load -Xclang LLVMX-OR.so %s -O2 -o %t2.out 4 | ;; RUN: test `%t2.out` = 1 5 | ; ModuleID = 'xor_bool2.c' 6 | target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" 7 | target triple = "x86_64-pc-linux-gnu" 8 | 9 | @.str = private unnamed_addr constant [2 x i8] c"0\00", align 1 10 | @.str1 = private unnamed_addr constant [2 x i8] c"1\00", align 1 11 | 12 | ; Function Attrs: nounwind uwtable 13 | define i32 @main() #0 { 14 | %a = alloca i1, align 4 15 | %b = alloca i1, align 4 16 | store volatile i1 1, i1* %a, align 4 17 | store volatile i1 1, i1* %b, align 4 18 | %1 = load volatile i1* %a, align 4 19 | %2 = load volatile i1* %b, align 4 20 | %3 = xor i1 %1, %2 21 | %4 = icmp eq i1 %3, 1 22 | br i1 %4, label %5, label %7 23 | 24 | ;