添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Repository files navigation

oxorany

带有混淆的编译时任意常量加密

我们综合了开源项目 ollvm xorstr 一些实现思路,以及 c++14 标准中新加入的 constexpr 关键字和一些模板的知识,完成了编译时的任意常量的混淆 (可选) 和加密功能。

在C++14之前,我们如果要对程序中的常量进行保护,我们首先对常量进行加密操作,这里以字符串 "some_data_or_string" 逐字节 -1 为例,然后将加密后的数据"rnld^c`s`^nq^rsqhmf",写到代码里,同时进行逐字节 +1 解密。

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);

上述的方法只能在需要被保护的数据的数量比较少时使用,当数据量增大,繁琐的加密过程所占用的时间也会水涨船高,而且使得代码的可读性、可维护性大大降低。而且不可能为每一个数据都单独设计一个解密算法和key,使得通用的解密工具更易于编写。

随着 oxorany 的出现,上述过程将被改变

🎨 特性

  • 支持任意平台( C++14 ),已在所有诸多编译器中进行了测试
  • 较高的可操作性,使用 __asm _emit 可进一步提高逆向难度
  • 所有的加密过程均在编译时完成
  • 所有的解密过程均在栈内完成,无法通过运行时 dump 获得解密后的数据,不同于 Armariris flounder
  • 带有 伪造控制流 功能的解密算法
  • 通过 编译优化 为每一个加密算法生成唯一的控制流
  • 通过 __COUNTER__ 宏为每一个加密算法生成唯一的 key
  • 通过 __TIME__ 宏动态产生 key
  • 代码经过 精心编写 ,足以破坏堆栈以对抗 IDA F5
  • 基于堆栈变量的 不透明谓词
  • 模糊数据长度
  • 由于解密算法的大部分代码不会被执行,所以对于效率的影响并不会特别大
  • 解密算法的复杂度仍有提升空间
  • 因为 C++ 中常量的 隐式转换 特性,某些常量可能需要强制类型转换
  • 相当简单的使用方法
  • 不能保证数据会被内联到代码段 想要内联

    支持的数据类型

  • 字符串( char* wchar_t* )
  • 字符( char wchar_t )
  • 指针( NULL nullptr )
  • 整数( int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t )
  • 浮点( float double )( 会保留原数据 )
  • 支持的编译器

  • clang ( llvm )( 支持叠加ollvm )
  • android ndk ( 支持安卓 )
  • leetcode gcc ( 支持类似的云编译器 )
  • wdk ( 支持Windows驱动程序 )
  • 🚀 使用

    #include <iostream>
    #define OXORANY_DISABLE_OBFUSCATION
    //use OXORANY_USE_BIT_CAST for remove float double src data
    #define OXORANY_USE_BIT_CAST
    #include "oxorany.h"
    enum class MyEnum : int {
        first = 1,
        second = 2,
    #define NUM_1 1
    int main() {
        // output:
        // 1 1 2 c w 00000000 00000000 12 1234 12345678 1234567887654321 1.000000 2.000000
        // string u8 string wstring raw string raw wstring abcd
        printf(oxorany("%d %d %d %c %C %p %p %hhx %hx %x %llx %f %lf\n%s %s %S %s %S %s\n")  //string
               , oxorany(NUM_1)                                                           //macro
               , oxorany(MyEnum::first), oxorany(MyEnum::second)                          //enum
               , oxorany('c')                                                             //char
               , oxorany(L'w')                                                            //wchar_t
               , oxorany(NULL), oxorany(nullptr)                                          //pointer
               , oxorany(0x12)                                                            //int8_t
               , oxorany(0x1234)                                                          //int16_t
               , oxorany(0x12345678)                                                      //int32_t
               , oxorany(0x1234567887654321)                                              //int64_t
               , oxorany_flt(1.0f)                                                            //float
               , oxorany_flt(2.0)                                                             //double
               , oxorany("string")                                                        //string
               , oxorany(u8"u8 string")                                                   //u8 string
               , oxorany(L"wstring")                                                      //wstring
               , oxorany(R"(raw string)")                                                 //raw string
               , oxorany(LR"(raw wstring)")                                               //raw wstring
               , oxorany("\x61\x62\x63\x64")                                              //binary data
        return 0;
    

    ⚙️ 需要强制类型转换的示例

    0 error 0 warning

    MessageBoxA(0, 0, 0, 0);

    错误(活动) E0167 "int" 类型的实参与 "HWND" 类型的形参不兼容

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

    出现上述问题的原因是因为C/C++0的特殊性,因为它可以隐式转换到任意类型的指针,也和NULL的定义有关

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

    所以我们添加一个到HWND的强制类型转换就可以解决该问题

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

    ⚙️ 在wdk中使用时需启用__TIME__

    IDA中的控制流程图

    编译优化测试

    这里是测试编译优化对控制流程图的影响,期望的结果是每一次编译都拥有不同的控制流程图

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

    ✅ 使用msvc多次编译后IDA中的控制流程图

    ✅ 使用clang多次编译后IDA中的控制流程图

    ✅ 使用gcc多次编译后IDA中的控制流程图

    ✅ 使用android ndk编译后IDA中的控制流程图

    ✅ 使用leetcode gcc进行测试 (剑指 Offer 05. 替换空格)

    ✅ 使用wdk多次编译后IDA中的控制流程图

    ✅ 使用ollvm编译后IDA中的控制流程图

    不透明谓词

    不透明:opaque
    来自拉丁语opacus,有阴影的,黑暗的,模糊的。

    谓词:predicate
    来自拉丁语praedicare,预测,断言,声称,来自prae,在前,早于,dicare,说,声称,词源同diction.并引申诸相关词义。

    不透明谓词可以理解为“无法确定结果的判断”,词语本身并没有包含结果必为真或者必为假的含义,只是在这里使用了结果必为真的条件进行混淆。

    代码中的rand() % 2 == 0实际上也是一个不透明谓词,因为我们无法确定它的结果,所以就无法确实程序是输出hello还是输出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;
    

    但是换一种情况,这里我们创建了一个全局变量zeor,并赋初值为0,不去修改zeor的值或者在保证谓词结果恒定的情况下进行合理的修改,那么谓词zeor < 1就是恒成立的,同时又由于全局变量的天然的不透明性,编译器不会进行优化,所以我们就增加一个伪造的控制流,无中生有。我们可以在不可达的基本块内加入任意代码,这里我们添加了一个典中典99乘法表作为示例,暗度陈仓

    #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;
    

    这里copy一下ollvm中的代码,ASCII 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)

    ollvm中全局xy的定义

          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);

    ollvm中不透明谓词y < 10 || x * (x + 1) % 2 == 0的实现,由Instruction::Sub可知,虽然注释是x + 1,但实际使用的确实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));
    

    将我们上面的代码稍作调整,以展示ollvm的实现,这里的x * (x + 1) % 2 == 0,以为xx + 1,必然是一个奇数一个偶数,根据奇偶性的运算法则可以得知x * (x + 1)的结果必然是偶数,所以% 2 == 0的判断将必然成立

    #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;
    

    受到ollvm伪造控制流功能的启发,我们创建了两个全局变量xy,并赋初值为0,作为实现不透明谓词的基础