虚假控制流 BCF (Bogus Control Flow)
原理:
- 虚假控制流混淆通过加入包含不透明谓词的条件跳转(永真or永假)和不可达的基本块,来干扰 IDA 的控制流分析和 F5 反汇编。
反混淆:
1、将全局变量赋值并将 segment 设为只读。
- 对于常规的 ollvm 的 bcf 混淆来说,bcf 的不透明谓词都是处于 .bss段 中。
- Edit->Segments->Edit segment 将 Write 复选框取消勾选, .bss段 就设为只读
2、d810
3、idapython patch 不透明谓词
源码位置:
obfuscator\lib\Transforms\Obfuscation\BogusControlFlow.cpp
源码解析
Pass结构与参数
1 2 3 4 5
| struct BogusControlFlow : public FunctionPass { static char ID; bool flag; BogusControlFlow() : FunctionPass(ID) {} BogusControlFlow(bool flag) : FunctionPass(ID) {this->flag = flag; BogusControlFlow();}
|
继承自 LLVM 的 FunctionPass。
在 LLVM Pass 设计中,ID 用于标识插件自身,flag 可用来控制是否对目标函数实际作用(如按用户参数选择)。这里有两个构造函数,用于接收可选参数。
pass流程主入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| virtual bool runOnFunction(Function &F){
if (ObfTimes <= 0) { errs()<<"BogusControlFlow application number -bcf_loop=x must be x > 0"; return false; }
if ( !((ObfProbRate > 0) && (ObfProbRate <= 100)) ) { errs()<<"BogusControlFlow application basic blocks percentage -bcf_prob=x must be 0 < x <= 100"; return false; }
if(toObfuscate(flag,&F,"bcf")) { bogus(F); doF(*F.getParent()); return true; }
return false; }
|
随机选择、遍历每个基本块
bogus(F)的主要逻辑是:循环要混淆多少轮,每轮遍历函数当前全部基本块。对每个BasicBlock按概率 ObfProbRate 随机决定是否插入虚假控制流——选中后调用addBogusFlow(basicBlock, F)。其中插入虚假流程会导致新生成3个基本块,因此统计量需相应自增。每一轮循环处理后,firstTime位设为false,避免重复初始化统计变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| void bogus(Function &F) { ++NumFunction; int NumBasicBlocks = 0; bool firstTime = true; bool hasBeenModified = false; int NumObfTimes = ObfTimes;
do { std::list<BasicBlock *> basicBlocks; for(Function::iterator i = F.begin(); i != F.end(); ++i){ basicBlocks.push_back(&*i); }
while(!basicBlocks.empty()){ NumBasicBlocks ++; BasicBlock *basicBlock = basicBlocks.front();
if((int)cryptoutils->get_range(100) < ObfProbRate){ hasBeenModified = true; ++NumModifiedBasicBlocks; NumAddedBasicBlocks += 3; FinalNumBasicBlocks += 3;
addBogusFlow(basicBlock, F); }
basicBlocks.pop_front();
if(firstTime){ ++InitNumBasicBlocks; ++FinalNumBasicBlocks; } } firstTime = false; } while(--NumObfTimes > 0); }
|
拆分基本块,制造分支点
1 2 3 4 5
| BasicBlock::iterator i1 = basicBlock->begin(); if(basicBlock->getFirstNonPHIOrDbgOrLifetime()) i1 = (BasicBlock::iterator)basicBlock->getFirstNonPHIOrDbgOrLifetime(); Twine *var = new Twine("originalBB"); BasicBlock *originalBB = basicBlock->splitBasicBlock(i1, *var);
|
将当前basicBlock按照首个非phi与非调试/声明作用域指令拆分:第一块只保留phi与必要的头部信息,剩余的内容新生成 originalBB。这样后续插入分支时可保证ir合法性,不会破坏phi变量。新originalBB成了插入跳转的目标。
创建原始块的变形副本
1 2
| Twine * var3 = new Twine("alteredBB"); BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);
|
生成了一份originalBB的深拷贝,并按后续代码在其中间随机插入了垃圾指令
控制流扇出与插入恒真判定
1 2 3 4 5 6
| Value * LHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0); Value * RHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0); Twine * var4 = new Twine("condition"); FCmpInst * condition = new FCmpInst(*basicBlock, FCmpInst::FCMP_TRUE , LHS, RHS, *var4); BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);
|
实际上就是1.0和1.0做浮点比较,谓词FCMP_TRUE代表“永远为真”。
再用它构造条件分支,如果 condition 为真跳 originalBB,否则跳 alteredBB。
但实际上一直走 originalBB。最后会由doF将该恒真判断替换成更复杂的表达式。
让alteredBB循环回originalBB
1
| BranchInst::Create(originalBB, alteredBB);
|
alteredBB 的终止指令直接跳回 originalBB
再次拆分 originalBB 的结尾,伪造 return / alteredBF 跳转
1 2 3 4 5 6 7 8
| BasicBlock::iterator i = originalBB->end(); Twine * var5 = new Twine("originalBBpart2"); BasicBlock * originalBBpart2 = originalBB->splitBasicBlock(--i , *var5);
Twine * var6 = new Twine("condition2"); FCmpInst * condition2 = new FCmpInst(*originalBB, CmpInst::FCMP_TRUE , LHS, RHS, *var6); BranchInst::Create(originalBBpart2, alteredBB, (Value *)condition2, originalBB);
|
克隆原始基本块并插入垃圾指令
1 2 3 4
| BasicBlock * alteredBB = llvm::CloneBasicBlock (basicBlock, VMap, Name, F);
|
用 LLVM 提供的 CloneBasicBlock API 深拷贝一个IR基本块,然后对其中每条指令做参数和 phi 节点等的 remapping,保证各指向正确,然后按照类型继续在合适位置插入垃圾指令。
全局替换恒真判定条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
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)); BranchInst::Create(((BranchInst*)*i)->getSuccessor(0), ((BranchInst*)*i)->getSuccessor(1),(Value *) op1, ((BranchInst*)*i)->getParent()); (*i)->eraseFromParent();
|
总而言之是将所有的假跳转复杂化
pass 混淆思路核心
1、随机选取基本块
2、将其一拆三,并构造冗余always-true条件和虚假分支
3、插入带有垃圾指令的altered副本块
4、算法完成后,再把这些恒真的 predicate 全变成复杂的“不透明谓词”
5、达到控制流与符号流分析难度大幅提升的效果