概述
- 承接上一篇Pass之SplitBasicBlock源码分析,这一篇文章继续分析ollvm的核心Pass Flattening, 也就是代码流程平坦化
- 为了描述的完整性,引用一段SplitBasicBlock分析文章中的一段话来说一下什么流程平坦化
- 平坦化一句话概括就是重组原始代码执行流程,把原本易于阅读的代码流程重组成一个switch case形式的代码执行流程,大大降低代码的直观可读性
- 平坦化这么做的目的就是要大幅度降低代码的可读性,从而达到防破解的目的
- 下面我画一个粗略的图来形象的展示一下:
源码分析
-
好了,说了Flattening流程平坦化的大致概念,下面开始进入源码分析,看看代码细节上是怎么实现的吧
-
Flattening操作的代码粒度是函数级别的,因此Flattening继承自FunctionPass
-
25 namespace { 26 struct Flattening : public FunctionPass { 27 static char ID; // Pass identification, replacement for typeid 28 bool flag; 29 30 Flattening() : FunctionPass(ID) {} 31 Flattening(bool flag) : FunctionPass(ID) { this->flag = flag; } 32 33 bool runOnFunction(Function &F); 34 bool flatten(Function *f); 35 }; 36 }
- 标准的FunctionPass, 接着进入runOnFunction继续看
-
42 bool Flattening::runOnFunction(Function &F) { 43 Function *tmp = &F; 44 // Do we obfuscate 45 if (toObfuscate(flag, tmp, "fla")) { 46 47 if (flatten(tmp)) { 48 ++Flattened; 49 } 50 } 51 52 return false; 53 }
- toObfuscate老规矩,判断当前函数是否需要进行流程平坦化,主要检查两个标记
1.1 函数是否存在__atrribute__((__annotate(("fla"))))这种标记
1.2 编译命令行是否传入了 -mllvm -fla参数 - 如果通过参数检查,则进入真正的平坦化流程处理函数flatten,接下来重点来看这个函数的实现吧
- toObfuscate老规矩,判断当前函数是否需要进行流程平坦化,主要检查两个标记
-
代码很长,都贴出来看着头昏,我会精简掉其中的不太重要的细节代码,由于精简之后的代码依然有点长,所以为了看起来清晰,我按照逻辑分段的方式来分开来说
-
55 bool Flattening::flatten(Function *f) { 56 vector<BasicBlock *> origBB; 57 BasicBlock *loopEntry; 58 BasicBlock *loopEnd; 59 LoadInst *load; 60 SwitchInst *switchI; 61 AllocaInst *switchVar; 62 63 // SCRAMBLER 64 char scrambling_key[16]; 65 llvm::cryptoutils->get_bytes(scrambling_key, 16); 66 // END OF SCRAMBLER 67 68 // Lower switch 69 FunctionPass *lower = createLowerSwitchPass(); 70 lower->runOnFunction(*f); 71 72 // Save all original BB 73 for (Function::iterator i = f->begin(); i != f->end(); ++i) { 74 BasicBlock *tmp = &*i; 75 origBB.push_back(tmp); 76 ...这里省略了一些代码 81 } 82
- 65行调用llvm::cryptoutils->get_bytes产生一个随机种子,这个种子在后面生成随机数的时候使用,后面用到的时候再说
- 68~70行这几行代码调用了一个外部Pass, LowerSwitch这个Pass, 主动调用了这个Pass的runOnFunction函数,内部逻辑是消除了当前函数中的switch方式组织的代码,抓换成if else这种分支调用,方便后面进行代码块分割,从而进行平坦化操作
- 接着就是一个for循环,遍历当前函数中的所有BasicBlock,并保存到origBB这个vector数组中给后面的逻辑使用
-
继续往下看代码
-
88 // Remove first BB 89 origBB.erase(origBB.begin()); 90 91 // Get a pointer on the first BB 92 Function::iterator tmp = f->begin(); //++tmp; 93 BasicBlock *insert = &*tmp; 94 95 // If main begin with an if 96 BranchInst *br = NULL; 97 if (isa<BranchInst>(insert->getTerminator())) { 98 br = cast<BranchInst>(insert->getTerminator()); 99 } 100 101 if ((br != NULL && br->isConditional()) || 102 insert->getTerminator()->getNumSuccessors() > 1) { 103 BasicBlock::iterator i = insert->end(); 104 --i; 105 106 if (insert->size() > 1) { 107 --i; 108 } 109 110 BasicBlock *tmpBB = insert->splitBasicBlock(i, "first"); 111 origBB.insert(origBB.begin(), tmpBB); 112 }
- 88行上来就把第一块BasicBlock从origBB这个vector中移除了,因为按照流程平坦化的设计,第一块是单独处理的,作为整个混淆流程的开始逻辑,所以紧接着的代码单独对第一个BasicBlock进行了单独处理
- 95~111行,检查第一块中是否包含条件跳转分支,如果包含条件跳转分支,则按照条件分支的位置进行代码块分割,分割逻辑跟SplitBasicBlock的逻辑一致,整个分割的目的也是为了后面进行流程平坦化准备的
-
接着的代码开始进行流程平坦化骨架代码的准备,上代码:
-
117 // Create switch variable and set as it 118 switchVar = 119 new AllocaInst(Type::getInt32Ty(f->getContext()), 0, "switchVar", insert); 120 new StoreInst( 121 ConstantInt::get(Type::getInt32Ty(f->getContext()), 122 llvm::cryptoutils->scramble32(0, scrambling_key)), 123 switchVar, insert); 124 125 // Create main loop 126 loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert); 127 loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert); 128 129 load = new LoadInst(switchVar, "switchVar", loopEntry); 130 131 // Move first BB on top 132 insert->moveBefore(loopEntry); 133 BranchInst::Create(loopEntry, insert); 134 135 // loopEnd jump to loopEntry 136 BranchInst::Create(loopEntry, loopEnd); 137 138 BasicBlock *swDefault = 139 BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd); 140 BranchInst::Create(loopEnd, swDefault); 141 142 // Create switch instruction itself and set condition 143 switchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry); 144 switchI->setCondition(load); 145 146 // Remove branch jump from 1st BB and make a jump to the while 147 f->begin()->getTerminator()->eraseFromParent(); 148 149 BranchInst::Create(loopEntry, &*f->begin());
- 118~123行创建了一个switch用的变量,相当于switch(caseVar) 这个语句中的caseVar, 并通过120行的StoreInst赋予初始值,初始值通过llvm::cryptoutils->scramble32(0, scrambling_key))生成,其中scrambling_key就是函数开头生成的随机种子,在这里用上了
- 接着126行创建了一个代码块loopEntry, 空代码块,里面还没代码
- 127行创建了一个代码块loopEnd, 也是空代码块,里面还没代码
- 129行创建了一条load指令,load是上面创建的caseVar, 并把这句代码方式loopEntry中
- 132~133行代码把函数第一个BasicBlock和loopEntry用一个无条件跳转指令连起来
- 136行代码把loopEntry, loopEnd用无条件跳转连接起来(loopEnd->loopEntry)
- 138行创建了一个BasicBlock, 并用无条件跳转指令连接起来(swDefault->loopEnd)
- 最后的143行创建了一个switch结构,并放入到loopEntry代码块中,到现在为止,loopEntry中包含了一个 load caseVar, 一个switch结构框架
- 经过了一团操作之后,看着实在懵逼,为了让大家有个直观的印象,我用拙劣的手写体来画一下大致流程
-
骨架代码准备好了之后,下面就开始往这个骨架代码中填充剩余的BasicBlock了,都是直接填充到switch case这个框架里面了
-
151 // Put all BB in the switch 152 for (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end(); 153 ++b) { 154 BasicBlock *i = *b; 155 ConstantInt *numCase = NULL; 156 157 // Move the BB inside the switch (only visual, no code logic) 158 i->moveBefore(loopEnd); 159 160 // Add case to switch 161 numCase = cast<ConstantInt>(ConstantInt::get( 162 switchI->getCondition()->getType(), 163 llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key))); 164 switchI->addCase(numCase, i); 165 }
-
代码都组织好了,留下一个问题,那就是现在简单的把各个代码块放入到switch中,那整个代码逻辑是和未平坦化之前一样的吗?答案是不一样的。先看下到目前为止整个代码流程变成什么样了,直接上图:
- 按照这个逻辑来执行,显然是个死循环啊,而原来的逻辑顺序是ABC这个执行顺序,所以为了保持原有逻辑,需要继续按照流程平坦化的设计思路,在每个case执行完之后,在case的逻辑最后更新state的值,也就是源码中的caseVar, 更新之后的逻辑整体看起来应该是这样的
-
剩下代码就是来更新每个case中代码块对caseVar的操作了,怎么更新呢,很简单,就是更新成当前代码块原后续逻辑块在当前这个代码框架下的case值
-
接下来的操作分三种情况来进行
-
case中代码块没有后续块,那就是一个返回块,不需要更新caseVar
-
case中代码块只有一个后续块,也就是一个无条件跳转分支,直接更新成后续块对应的case即可
178 // If it's a non-conditional jump 179 if (i->getTerminator()->getNumSuccessors() == 1) { 180 // Get successor and delete terminator 181 BasicBlock *succ = i->getTerminator()->getSuccessor(0); 182 i->getTerminator()->eraseFromParent(); 183 184 // Get next case 185 numCase = switchI->findCaseDest(succ); 186 187 // If next case == default case (switchDefault) 188 if (numCase == NULL) { 189 numCase = cast<ConstantInt>( 190 ConstantInt::get(switchI->getCondition()->getType(), 191 llvm::cryptoutils->scramble32( 192 switchI->getNumCases() - 1, scrambling_key))); 193 } 194 195 // Update switchVar and jump to the end of loop 196 new StoreInst(numCase, load->getPointerOperand(), i); 197 BranchInst::Create(loopEnd, i); 198 continue; 199 }
-
case中代码块有2个后续块,也就是一个条件跳转分支,看代码:
201 // If it's a conditional jump 202 if (i->getTerminator()->getNumSuccessors() == 2) { 203 // Get next cases 204 ConstantInt *numCaseTrue = 205 switchI->findCaseDest(i->getTerminator()->getSuccessor(0)); 206 ConstantInt *numCaseFalse = 207 switchI->findCaseDest(i->getTerminator()->getSuccessor(1)); 208 209 // Check if next case == default case (switchDefault) 210 if (numCaseTrue == NULL) { 211 numCaseTrue = cast<ConstantInt>( 212 ConstantInt::get(switchI->getCondition()->getType(), 213 llvm::cryptoutils->scramble32( 214 switchI->getNumCases() - 1, scrambling_key))); 215 } 216 217 if (numCaseFalse == NULL) { 218 numCaseFalse = cast<ConstantInt>( 219 ConstantInt::get(switchI->getCondition()->getType(), 220 llvm::cryptoutils->scramble32( 221 switchI->getNumCases() - 1, scrambling_key))); 222 } 223 224 // Create a SelectInst 225 BranchInst *br = cast<BranchInst>(i->getTerminator()); 226 SelectInst *sel = 227 SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "", 228 i->getTerminator()); 229 230 // Erase terminator 231 i->getTerminator()->eraseFromParent(); 232 233 // Update switchVar and jump to the end of loop 234 new StoreInst(sel, load->getPointerOperand(), i); 235 BranchInst::Create(loopEnd, i); 236 continue; 237 } 238 }
- 通过successor对象(就是后续块的意思)直接在switch结构中查找出对应的case值
- 226行,使用llvm的SelectInst来实现分支选择,像227行写的,根据br->getCondition()是否为True来决定是跳转到numCaseTrue还是numCaseFalse
- 经过前面的一通操作, 按照平坦化的代码流程设计,已经完全组装好了新的代码流程
- 来看下现在的结果吧
-
总结
- 通过最后的效果图可以看到,整个代码流程被完全重组了,按照外层一个while循环,内层一个switch结构组合起来
- 原始代码也贴出来给大家参考下
-
1 2 #include<stdio.h> 3 4 int add(int a, int b) __attribute__((__annotate__(("fla")))){ 5 if(a > b){ 6 printf("a>b,print\n"); 7 return a + b; 8 }else{ 9 printf("a<=b, print2\n"); 10 return b; 11 } 12 }
- 另外吐槽下,印象笔记的markdown真的很难用,文章稍微长一点之后,基本打字都困难,心里一直担心会随时崩溃~~