虚假控制流 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; // Pass identification
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){            //Pass的核心入口

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;
}
// 检查混淆轮数(ObfTimes)和概率参数(ObfProbRate)是否合法。

if(toObfuscate(flag,&F,"bcf")) { //判断此函数是否要混淆
bogus(F);
doF(*F.getParent()); //在全模块层面做一次doF(全局修饰,例如混淆所有恒真的条件)
return true;
}

return false;
} // end of runOnFunction()

随机选择、遍历每个基本块

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;
// --- 1. 把当前F的所有基本块收集到list里 ---
for(Function::iterator i = F.begin(); i != F.end(); ++i){
basicBlocks.push_back(&*i);
}

// --- 2. 遍历所有基本块,按概率挑选 ---
while(!basicBlocks.empty()){
NumBasicBlocks ++;
BasicBlock *basicBlock = basicBlocks.front();

// -- 3. 按概率决定是否对该块混淆 --
if((int)cryptoutils->get_range(100) < ObfProbRate){
hasBeenModified = true;
++NumModifiedBasicBlocks; // 混淆的块计数
NumAddedBasicBlocks += 3; // 每次新增3个BB
FinalNumBasicBlocks += 3;

// --- 核心:对该块做bogus混淆 ----
addBogusFlow(basicBlock, F);
}

// -- 4. pop掉已经处理过的块 --
basicBlocks.pop_front();

// -- 5. 第一次遍历时,用于记录统计 --
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
// 准备条件恒为真的IR
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);
// ...
// 在 originalBB 末尾再插入一条恒为真的判断
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);
// 为每条指令做operand/Phi remap等
// ...遍历 alteredBB->begin(), e= alteredBB->end()
// 根据opcode随机插入诸如取反&加法/移位/乘法等冗余垃圾算术操作

用 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
//(1)遍历找所有 FCMP_TRUE 的分支
//(2)统一替换成复杂不透明谓词:(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));
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、达到控制流与符号流分析难度大幅提升的效果