Skip to content

Latest commit

 

History

History
425 lines (313 loc) · 15.3 KB

README.en.md

File metadata and controls

425 lines (313 loc) · 15.3 KB

oxorany

A heavily obfuscated c++14 compile time any constant encryption.

LICENSE

Description

We integrated some implementation ideas from open source projects ollvm and xorstr, as well as the new constexpr keyword in the c++14 standard and some template knowledge, to complete the compile-time obfuscation encryption of arbitrary constants.

Before C++14, if we want to protect the constants in the program, we first encrypt the constants, here we take the string "some_data_or_string" byte by byte -1 as an example, and then write the encrypted data "rnld^c`s`^nq^rsqhmf" to the code, while doing byte by byte +1 decryption.

Code show as below

char encrypted[] = {"rnld^c`s`^nq^rsqhmf"};
char key = 0x1;
for (size_t i = 0; i < strlen(encrypted); i++) {
	encrypted[i] += key;
}
//output: some_data_or_string
printf("%s\n", encrypted);

The above method can only be used when the amount of data to be protected is relatively small, and when the amount of data increases, the time taken by the tedious encryption process will also rise, and it makes the readability and maintainability of the code greatly reduced. And it is not possible to design a separate decryption algorithm and key for each data, which makes a general decryption tool easier to write.

With the advent of oxorany, the above process will be changed

🎨 Features

  • Support any platform(C++14)
  • Higher operability, using __asm _emit can further increase the difficulty of reverse
  • Obfuscated encryption of any constants at compile time
  • All the decryption process is done inside the stack, and the decrypted data cannot be obtained through runtime dump,unlike Armaririsflounder
  • Decryption algorithm with Bogus Control Flow like ollvm
  • Generate a unique control flow for each encryption algorithm through compile optimization
  • Generate a unique key for each encryption algorithm with the __COUNTER__ macro
  • Dynamically generate key via the __TIME__ macro
  • The code has been crafted so that can Destroying the stack to anti IDA F5
  • Stack variable based Opaque Predicate
  • Fuzzy data length
  • Since most of the code for the decryption algorithm is not executed, the impact on efficiency is not particularly significant
  • The complexity of the decryption algorithm can be improve
  • Because of the implicit conversion feature of constants in C++, some constants may require forced type conversion
  • Easy to use, tested in msvc, clang, gcc
  • There is no guarantee that the data will be embedded directly into codeWant embedded

Data types supported

  • String(char* wchar_t*)
  • Macro
  • Enume
  • Integer(int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t)
  • Floating point(float double)

Compilers Supported

  • msvc
  • clang(llvm)
  • gcc
  • android ndk
  • leetcode gcc
  • ...

🚀 Usage

#include <iostream>
//#define OXORANY_DISABLE_OBFUSCATION
#include "oxorany.h"

enum class MyEnum : int {
	first = 1,
	second = 2,
};

#define NUM_1 1

int main() {
        // output:
        // 1 1 2 12 1234 12345678 1234567887654321 1.000000 2.000000
        // string wstring raw string raw wstring
	printf(oxorany("%d %d %d %hhx %hx %x %llx %f %lf\n%s %S %s %S\n")  //string
            , oxorany(NUM_1)                                               //macro
            , oxorany(MyEnum::first), oxorany(MyEnum::second)              //enum
            , oxorany((uint8_t)0x12)                                       //uint8_t
            , oxorany((uint16_t)0x1234)                                    //uint16_t
            , oxorany((uint32_t)0x12345678)                                //uint32_t
            , oxorany((uint64_t)0x1234567887654321)                        //uint64_t
            , oxorany(1.0f)                                                //float
            , oxorany(2.0)                                                 //double
            , oxorany("string")                                            //string
            , oxorany(L"wstring")                                          //wstring
            , oxorany(R"(raw string)")                                     //raw string
            , oxorany(LR"(raw wstring)")                                   //raw wstring
	);
	return oxorany(0);
}

⚙️ Need Cast

0 error 0 warning

MessageBoxA(0, 0, 0, 0);

error(active) E0167 Real parameters of type "int" are incompatible with formal parameters of type "HWND"

MessageBoxA(oxorany(0), 0, 0, 0);

The reason for the above problem is due to the peculiarity of 0 in C/C++, because it can be implicitly converted to a pointer of any type, and is also related to the definition of NULL

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

So we add a forced type conversion to HWND to solve the problem

MessageBoxA(oxorany((HWND)0), 0, 0, 0);

Control Flow Graph in IDA

image


Compilation Optimization test

#include "oxorany.h"
int main() {
	return oxorany(0);
}

Control Flow Graph in IDA after multiple compilation using msvc

image


Control Flow Graph in IDA after multiple compilation using clang

image


Control Flow Graph in IDA after multiple compilation using gcc

image


Control Flow Graph in IDA after compilation using android ndk

image


✅ Testing with leetcode gcc (Sword Pointing Offer 05. replace space)

S5(LFNXH~_KM6UH@L}U(CY6


Control Flow Graph in IDA after multiple compilation using wdk

image


Control Flow Graph in IDA after compilation using ollvm

image


Opaque predicate

The opaque predicate can be understood as "the judgment of the result cannot be determined",The words themselves do not contain the meaning that the result must be true or must be false, but only the condition that the result must be true is used here for obfuscation

The rand() % 2 == 0 in the code is actually an opaque predicate, because we can't determine its result, so we can't be sure whether the program is outputting hello or world

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
	srand((unsigned int)time(NULL));
	if (rand() % 2 == 0) {
		printf("hello\n");
	}
	else {
		printf("world\n");
	}
	return 0;
}

But in another case, here we create a global variable zeor and assign an initial value of 0, without modifying the value of zeor or making reasonable modifications to ensure that the result of the predicate is constant, then the predicate zeor < 1 is constant, and at the same time the compiler will not optimize due to the natural opacity of global variables, so we add a forged to the control flow. We can add any code inside an unreachable basic block, and here we add a 99 multiplication table as an example.。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int zeor = 0;
int main() {
	if (zeor < 1) {
		printf("hello\n");
	}
	else {
		//unreachable
		for (int i = 1; i <= 9; i++) {
			for (int j = 1; j <= 9; j++) {
				printf("%d*%d=%2d\t", i, j, i * j);
			}
		    	printf("\n");
		}
	}
	return 0;
}

Here copy the code from ollvmASCII Picasso

// Before :
// 	         	     entry
//      		       |
//  	    	  	 ______v______
//   	    		|   Original  |
//   	    		|_____________|
//             		       |
// 		       	       v
//		             return
//
// After :
//           		     entry
//             		       |
//            		   ____v_____
//      		  |condition*| (false)
//           		  |__________|----+
//           		 (true)|          |
//             		       |          |
//           		 ______v______    |
// 	            +-->|   Original* |   |
// 	            |   |_____________| (true)
// 	            |   (false)|    !-----------> return
// 	            |    ______v______    |
// 	            |   |   Altered   |<--!
// 	            |   |_____________|
// 	            |__________|
//
//  * The results of these terminator's branch's conditions are always true, but these predicates are
//    opacificated. For this, we declare two global values: x and y, and replace the FCMP_TRUE
//    predicate with (y < 10 || x * (x + 1) % 2 == 0) (this could be improved, as the global
//    values give a hint on where are the opaque predicates)

Definition of global x, y in ollvm`

      GlobalVariable 	* x = new GlobalVariable(M, Type::getInt32Ty(M.getContext()), false,
          GlobalValue::CommonLinkage, (Constant * )x1,
          *varX);
      GlobalVariable 	* y = new GlobalVariable(M, Type::getInt32Ty(M.getContext()), false,
          GlobalValue::CommonLinkage, (Constant * )y1,
          *varY);

The implementation of the opaque predicate y < 10 || x * (x + 1) % 2 == 0 in ollvm is shown by Instruction::Sub, which, although annotated with x + 1, actually uses x - 1

        //if y < 10 || x*(x+1) % 2 == 0
        opX = new LoadInst ((Value *)x, "", (*i));
        opY = new LoadInst ((Value *)y, "", (*i));

        op = BinaryOperator::Create(Instruction::Sub, (Value *)opX,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 1,
              false), "", (*i));
        op1 = BinaryOperator::Create(Instruction::Mul, (Value *)opX, op, "", (*i));
        op = BinaryOperator::Create(Instruction::URem, op1,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 2,
              false), "", (*i));
        condition = new ICmpInst((*i), ICmpInst::ICMP_EQ, op,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 0,
              false));
        condition2 = new ICmpInst((*i), ICmpInst::ICMP_SLT, opY,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 10,
              false));
        op1 = BinaryOperator::Create(Instruction::Or, (Value *)condition,
            (Value *)condition2, "", (*i));

Adjusting our code above slightly to show the implementation of ollvm, here x * (x + 1) % 2 == 0, thinking that x and x + 1, must be an odd number and an even number, according to the operation of parity we can learn that the result of x * (x + 1) must be even, so the judgment of % 2 == 0 will necessarily hold

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int x = 0;
int y = 0;
int main() {
	if (y < 10 || x * (x + 1) % 2 == 0) {
		printf("hello\n");
	}
	else {
		//unreachable
        	for (int i = 1; i <= 9; i++) {
			for (int j = 1; j <= 9; j++) {
                		printf("%d*%d=%2d\t", i, j, i * j);
			}
            		printf("\n");
        	}
	}
	return 0;
}

Implemention

Inspired by the Bogus Control Flow function in ollvm, we created two global variables x and y and assigned initial values of 0 as the basis for implementing opaque predicates

image


Due to the complexity of the stack environment, we assign the global variables x and y to two local variables stack_x and stack_y respectively to make the inverse more difficult

image


We created label in many positions of the function, used stack_x, stack_y to judge the constant to be true for confusion, and added goto label in the basic block that could not be reached to remove the basic block as much as possible point. We use the wrong key to decrypt the decrypted data decrypted in many places, so that the real key is difficult to identify among the many wrong keys.

image


Generate random numbers with range limition, since the same values can occur here, while duplicate conditions are removed by compilation optimization because of the existence of compilation optimization, which makes us have a different control flow diagram for each compilation

image


We add illegal stack operations within the unreachable basic fast to make IDA's stack frame analysis fail against F5

image


We are aligning the data by 16 bytes and adding a certain random value to obscure the data length, which may waste a little space

image


We are replacing xor with a more complex implementation to make the inversion more difficult

image


Use the __TIME__ macro to implement a different key for each compilation

image


Random number generator with range restrictions, making opaque predicates similar to normal predicates

image


To sum up, with the help of oxorany, the security of the software will be further improved

Reference

Github

https://github.com/llxiaoyuan/oxorany