ollvm源码分析 - Pass之Substitution
概述
- ollvm这个项目的Substitution这个Pass源码比较简单,功能也很明确,那就是进行操作符替换,那要替换哪些操作符呢?我直接抛出来吧,ollvm中替换了 加 减 与 或 异或这几种
- 那到底怎么替换的呢?直接抛出总结的结果吧
-
加法拆分
- a = b + c => a = b - (-c)
- a = b + c => a = -(-b + (-c))
- a = b + c => {
r = rand();
a = b + r;
a = a + c;
a = a - r;
} - a = b + c => {
r = rand();
a = b - r;
a = a + c;
a = a + r;
}
-
减法拆分
- a = b - c => a = b + (-c)
- a = b - c => {
r = rand();
a = b + r;
a = a - c;
a = a - r;
} - a = b - c => {
r = rand();
a = b - r;
a = a - c;
a = a + r;
}
-
与 拆分
- a = b & c => a = (b^~c) & b
- a = a && b => !(!a | !b) && (r | !r)
-
或 拆分
- a = b | c => a = (b & c) | (b ^ c)
- a = a || b => [(!a && r) || (a && !r) ^ (!b && r) ||(b && !r) ] || [!(!a || !b) && (r ||!r)]
-
异或拆分
- a = a ^ b => (a ^ r) ^ (b ^ r) <=> (!a && r || a && !r) ^ (!b && r || b && !r)
- a = a ~ b => a = (!a && b) || (a && !b)
-
- 上面这几种就是ollvm项目中目前进行指令替换的地方以及如何进行替换的了
源码分析
- 还是一个标准的llvm Pass
-
46 namespace { 47 48 struct Substitution : public FunctionPass { 49 static char ID; // Pass identification, replacement for typeid //这里声明了5个指针数组,分别是Add, Sub, And, Or, Xor操作变换的函数指针 50 void (Substitution::*funcAdd[NUMBER_ADD_SUBST])(BinaryOperator *bo); 51 void (Substitution::*funcSub[NUMBER_SUB_SUBST])(BinaryOperator *bo); 52 void (Substitution::*funcAnd[NUMBER_AND_SUBST])(BinaryOperator *bo); 53 void (Substitution::*funcOr[NUMBER_OR_SUBST])(BinaryOperator *bo); 54 void (Substitution::*funcXor[NUMBER_XOR_SUBST])(BinaryOperator *bo); 55 bool flag; 56 57 Substitution() : FunctionPass(ID) {} 58 59 Substitution(bool flag) : FunctionPass(ID) { 60 this->flag = flag; 61 funcAdd[0] = &Substitution::addNeg; 62 funcAdd[1] = &Substitution::addDoubleNeg; 63 funcAdd[2] = &Substitution::addRand; 64 funcAdd[3] = &Substitution::addRand2; 65 66 funcSub[0] = &Substitution::subNeg; 67 funcSub[1] = &Substitution::subRand; 68 funcSub[2] = &Substitution::subRand2; 69 70 funcAnd[0] = &Substitution::andSubstitution; 71 funcAnd[1] = &Substitution::andSubstitutionRand; 72 73 funcOr[0] = &Substitution::orSubstitution; 74 funcOr[1] = &Substitution::orSubstitutionRand; 75 76 funcXor[0] = &Substitution::xorSubstitution; 77 funcXor[1] = &Substitution::xorSubstitutionRand; 78 } 79 80 bool runOnFunction(Function &F); 81 bool substitute(Function *f); 82 83 void addNeg(BinaryOperator *bo); 84 void addDoubleNeg(BinaryOperator *bo); 85 void addRand(BinaryOperator *bo); 86 void addRand2(BinaryOperator *bo); 87 88 void subNeg(BinaryOperator *bo); 89 void subRand(BinaryOperator *bo); 90 void subRand2(BinaryOperator *bo); 91 92 void andSubstitution(BinaryOperator *bo); 93 void andSubstitutionRand(BinaryOperator *bo); 94 95 void orSubstitution(BinaryOperator *bo); 96 void orSubstitutionRand(BinaryOperator *bo); 97 98 void xorSubstitution(BinaryOperator *bo); 99 void xorSubstitutionRand(BinaryOperator *bo); 100 }; 101 }
- 依然是一个标准的Pass, 类的开头定义了5个指针数组,数组在Substitution(bool flag)这个构造函数中进行的初始化,构造函数中可以看到Add操作对应了4个处理函数,Sub操作对应了3个处理函数,And操作对应了2个处理函数,Or操作对应了2个处理函数,Xor操作对应了2个处理函数
- 先来看主干函数runOnFunction吧,上代码:
107 bool Substitution::runOnFunction(Function &F) { 108 // Check if the percentage is correct 109 if (ObfTimes <= 0) { 110 errs()<<"Substitution application number -sub_loop=x must be x > 0"; 111 return false; 112 } 113 114 Function *tmp = &F; 115 // Do we obfuscate 116 if (toObfuscate(flag, tmp, "sub")) { 117 substitute(tmp); 118 return true; 119 } 120 121 return false; 122 }
- 代码还是逻辑依然简洁,先是验证了 -mllvm -sub_loop=x这个编译参数的正确性,必须>0, 这个参数的作用后面会看到
- 接着是toObfuscate判定当前函数是否需要进行Substitution,一个是判定flag是否为true, 也就是编译命令中是否有 -mllvm -sub命令,一个是判定当前函数是否有 attribute((annotate(("sub"))))的标记
- 然后117行就开始进入substitute进行指令拆分了
- 接着进入Substitution::substitute进行具体的指令拆分,看下经过精简的代码:
-
124 bool Substitution::substitute(Function *f) { 125 Function *tmp = f; 126 127 // Loop for the number of time we run the pass on the function 128 int times = ObfTimes; 129 do { 130 for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) { 131 for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) { 132 if (inst->isBinaryOp()) { 133 switch (inst->getOpcode()) { 134 case BinaryOperator::Add: 137 (this->*funcAdd[llvm::cryptoutils->get_range(NUMBER_ADD_SUBST)])( 138 cast<BinaryOperator>(inst)); 139 ++Add; 140 break; 141 case BinaryOperator::Sub: 144 (this->*funcSub[llvm::cryptoutils->get_range(NUMBER_SUB_SUBST)])( 145 cast<BinaryOperator>(inst)); 146 ++Sub; 147 break; ... 中间省略了无关代码 171 case Instruction::And: 172 (this->* 173 funcAnd[llvm::cryptoutils->get_range(2)])(cast<BinaryOperator>(inst)); 174 ++And; 175 break; 176 case Instruction::Or: 177 (this->* 178 funcOr[llvm::cryptoutils->get_range(2)])(cast<BinaryOperator>(inst)); 179 ++Or; 180 break; 181 case Instruction::Xor: 182 (this->* 183 funcXor[llvm::cryptoutils->get_range(2)])(cast<BinaryOperator>(inst)); 184 ++Xor; 185 break; 186 default: 187 break; 188 } // End switch 189 } // End isBinaryOp 190 } // End for basickblock 191 } // End for Function 192 } while (--times > 0); // for times 193 return false; 194 }
- 外层ObfTimes次循环代表对变换操作重复执行ObfTimes轮,每轮的变换操作逻辑相同,就是叠加变换
- 直接是2个for循环,外层循环遍历当前函数的所有代码块,内层循环遍历每个代码块的每条指令,然后133行根据指令的Opcode来判定是否需要执行替换操作
- 从 134行,141行,171行,176行,181行看,分别对Add, Sub, And, Or, Xor 5种操作进行替换操作,每次操作都是从之前定义的处理函数数组中随机选择一个处理函数,进行操作,其中llvm::cryptoutils->get_range就是一个随机函数,范围随机
- 因为所有的变换操作代码都非常相似,所以就直接选择一个Add操作处理函数来看下,直接看addNeg(BinaryOperator *bo)这个函数:
-
196 // Implementation of a = b - (-c) 197 void Substitution::addNeg(BinaryOperator *bo) { 198 BinaryOperator *op = NULL; 199 200 // Create sub 201 if (bo->getOpcode() == Instruction::Add) { 202 op = BinaryOperator::CreateNeg(bo->getOperand(1), "", bo); 203 op = 204 BinaryOperator::Create(Instruction::Sub, bo->getOperand(0), op, "", bo); 210 bo->replaceAllUsesWith(op); 211 } 216 }
- 这个函数是实现了a=b+c => a=b-(-c)的转换
- 先是把第二个操作数取负数,也就是 c => -c
- 然后创建一个减法操作,和原始的第一个参数拼接起来,b - (-c)操作形成
- 然后210行把新操作替换进老的 BinaryOperator
总结
- 遍历代码块中所有指令,按照预定的变换规则,替换指定类型的操作符
- 通过指令替换,把原本简单的数学或者逻辑操作复杂化,达到降低代码可读性,防破解的目的