从源码入手理解OLLVM(二)

OLLVM

Flattening

Passes\Obfuscation\Flattening.cpp
控制流平坦化,这是最常见的一种ollvm混淆。其中有这些概念:

  • 序言块,origBB.begin
  • 分发块,SwDefault、dispatchBB
  • 真实块,origBB
  • 返回块,returnBB
    这些都是基础块,只是根据在控制流平坦化的中的作用进行分类。
    主要流程是:基于 LLVM 划分的基本块进行处理,首先区分入口序言块与其余真实基本块;其次对序言块进行规范化处理,若序言块的结束是条件跳转,则将其截断并拆分出新的基本块,使序言块的出口始终为无条件跳转。随后创建分发块(dispatchBB)与返回块(returnBB),并将序言块的控制流重定向至 dispatchBB。
    在序言块中分配并初始化一个状态变量,用于表示当前应执行的基本块;在 dispatchBB 中加载该状态变量,并通过 switch 语句根据其取值分发至对应的真实基本块。接着将所有真实基本块统一移动至 returnBB 之前,并为每个基本块分配一个唯一的 case 值,注册到 switch 表中。
    随后重写各真实基本块的出口逻辑:对于原本为无条件跳转的基本块,删除原有跳转指令,改为写入其后继基本块对应的 case 值并无条件跳转至 returnBB;对于原本为条件跳转的基本块,使用 select 指令根据原条件选择不同的 case 值写入状态变量,再无条件跳转至 returnBB。最终通过 returnBB 回到 dispatchBB,形成以状态变量驱动的统一调度循环,并在末尾修复 PHI 节点与 SSA 相关问题,保证语义等价。
    以下是源码:
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
bool FlatteningPass::flatten(Function &F) {

// 基本块数量不超过1则无需平坦化

if(F.size() <= 1){
//outs() << "\033[0;33mFunction size is lower then one\033[0m\n"; // warning
return false;
}

// 函数名称为$basic_ostream则不进行平坦化

if (F.getName().str().find("$basic_ostream") != std::string::npos) {
outs() << "[obf] force_nofla: " << F.getName().str().c_str() << "\n";
return false;
}

// 将除入口块(第一个基本块)以外的基本块保存到一个 vector 容器中,便于后续处理

// 首先保存所有基本块

vector<BasicBlock*> origB
for(BasicBlock &BB: F){
origBB.push_back(&BB);
}

// 从vector中去除第一个基本块

origBB.erase(origBB.begin());
BasicBlock &entryBB = F.getEntryBlock();

// 如果第一个基本块的末尾是条件跳转,单独分离

bool bEntryBB_isConditional = false;
if(BranchInst *br = dyn_cast<BranchInst>(entryBB.getTerminator())){
if(br->isConditional()){
BasicBlock *newBB = entryBB.splitBasicBlock(br, "newBB");
origBB.insert(origBB.begin(), newBB);
bEntryBB_isConditional = true;
}
}


// 创建分发块和返回块

BasicBlock *dispatchBB = BasicBlock::Create(*CONTEXT, "dispatchBB", &F, &entryBB);
BasicBlock *returnBB = BasicBlock::Create(*CONTEXT, "returnBB", &F, &entryBB);
BranchInst::Create(dispatchBB, returnBB);
entryBB.moveBefore(dispatchBB);

// 去除第一个基本块末尾的跳转

if (bEntryBB_isConditional) {
entryBB.getTerminator()->eraseFromParent();
}

// 使第一个基本块跳转到dispatchBB

BranchInst *brDispatchBB = BranchInst::Create(dispatchBB, &entryBB);


// 在入口块插入alloca和store指令创建并初始化switch变量,初始值为随机值

int randNumCase = rand();
AllocaInst *swVarPtr = new AllocaInst(TYPE_I32, 0, "swVar.ptr", brDispatchBB);
new StoreInst(CONST_I32(randNumCase), swVarPtr, brDispatchBB);

// 在分发块插入load指令读取switch变量

LoadInst *swVar = new LoadInst(TYPE_I32, swVarPtr, "swVar", false, dispatchBB);

// 在分发块插入switch指令实现基本块的调度

BasicBlock *swDefault = BasicBlock::Create(*CONTEXT, "swDefault", &F, returnBB);
BranchInst::Create(returnBB, swDefault);
SwitchInst *swInst = SwitchInst::Create(swVar, swDefault, 0, dispatchBB);

// 将原基本块插入到返回块之前,并分配case值

for(BasicBlock *BB : origBB){
BB->moveBefore(returnBB);
swInst->addCase(CONST_I32(randNumCase), BB);
randNumCase = rand();
}

// 在每个基本块最后添加修改switch变量的指令和跳转到返回块的指令

for(BasicBlock *BB : origBB){

// retn BB

if(BB->getTerminator()->getNumSuccessors() == 0){
continue;
}

// 非条件跳转

else if(BB->getTerminator()->getNumSuccessors() == 1){
BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
BB->getTerminator()->eraseFromParent();
ConstantInt *numCase = swInst->findCaseDest(sucBB);
new StoreInst(numCase, swVarPtr, BB);
BranchInst::Create(returnBB, BB);
}

// 条件跳转

else if(BB->getTerminator()->getNumSuccessors() == 2){
// BranchInst *br = cast<BranchInst>(BB->getTerminator());
BranchInst *br = dyn_cast<BranchInst>(BB->getTerminator());
if (!br) {
//outs() << "[FAILED] dyn_cast<BranchInst>(BB->getTerminator()); " << BB->getName() << "\n";
continue;
}
if (!br->isConditional()) {
//outs() << "[FAILED] br->isConditional(); " << BB->getName() << "\n";
continue;
}
ConstantInt *numCaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccessor(0));
ConstantInt *numCaseFalse = swInst->findCaseDest(BB->getTerminator()->getSuccessor(1));
SelectInst *sel = SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "", BB->getTerminator());
BB->getTerminator()->eraseFromParent();
new StoreInst(sel, swVarPtr, BB);
BranchInst::Create(returnBB, BB);
}
}
fixStack(F); // 修复逃逸变量和PHI指令
return true;
}

看一下实际效果:1

是一个很标致的结构

Bogus Control Flow

虚假控制流,

第一步:主入口函数 run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PreservedAnalyses BogusControlFlowPass::run(Function& F, FunctionAnalysisManager& AM) {
// 检查参数有效性
if (ObfTimes <= 0){
errs() << "BogusControlFlow application number -bcf_loop=x must be x > 0";
return PreservedAnalyses::all();
}
if (!((ObfProbRate > 0) && (ObfProbRate <= 100))) {
errs() << "BogusControlFlow application basic blocks percentage "
"-bcf_prob=x must be 0 < x <= 100";
return PreservedAnalyses::all();
}
// 检查是否对该函数启用混淆
if (toObfuscate(flag, &F, "bcf")){
bogus(F);
doF(*F.getParent(), F);
return PreservedAnalyses::none();
}
return PreservedAnalyses::all();
}

第二步:主要混淆逻辑 bogus

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
44
45
46
47
48
49
50
void BogusControlFlowPass::bogus(Function &F) {
// 初始化统计信息
++NumFunction;
int NumBasicBlocks = 0;
bool firstTime = true;
bool hasBeenModified = false;

// 获取混淆参数
DEBUG_WITH_TYPE("opt",
errs() << "bcf: Started on function " << F.getName() << "\n");
DEBUG_WITH_TYPE("opt",
errs() << "bcf: Probability rate: " << ObfProbRate << "\n");

// 循环执行混淆多次(根据 ObfTimes 参数)
NumTimesOnFunctions = ObfTimes;
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++;
// 根据概率决定是否对当前基本块进行混淆
if ((int)llvm::cryptoutils->get_range(100) <= ObfProbRate) {
DEBUG_WITH_TYPE("opt", errs() << "bcf: Block " << NumBasicBlocks
<< " selected. \n");
hasBeenModified = true;
++NumModifiedBasicBlocks;
NumAddedBasicBlocks += 3;
FinalNumBasicBlocks += 3;
// 对选中的基本块应用虚假控制流
BasicBlock *basicBlock = basicBlocks.front();
addBogusFlow(basicBlock, F);
} else {
DEBUG_WITH_TYPE("opt", errs() << "bcf: Block " << NumBasicBlocks
<< " not selected.\n");
}
basicBlocks.pop_front();

if (firstTime) {
++InitNumBasicBlocks;
++FinalNumBasicBlocks;
}
}
firstTime = false;
} while (--NumObfTimes > 0);
}

第三步:添加虚假控制流 addBogusFlow

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
44
45
46
47
48
49
50
51
void BogusControlFlowPass::addBogusFlow(BasicBlock *basicBlock, Function &F) {
// 1. 分割基本块
BasicBlock::iterator i1 = basicBlock->begin();
if (basicBlock->getFirstNonPHIOrDbgOrLifetime())
i1 = (BasicBlock::iterator)basicBlock->getFirstNonPHIOrDbgOrLifetime();
if (basicBlock->getFirstNonPHI()->isEHPad())
return;

// 将原基本块分割为两部分
Twine *var = new Twine("originalBB");
BasicBlock *originalBB = basicBlock->splitBasicBlock(i1, *var);
DEBUG_WITH_TYPE("gen", errs()
<< "bcf: First and original basic blocks: ok\n");

// 2. 创建修改后的虚假基本块
Twine *var3 = new Twine("alteredBB");
BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);
DEBUG_WITH_TYPE("gen", errs() << "bcf: Altered basic block: ok\n");

// 3. 移除原有终止指令
alteredBB->getTerminator()->eraseFromParent();
basicBlock->getTerminator()->eraseFromParent();

// 4. 创建条件判断
Value *LHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);
Value *RHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);

// 创建始终为真的条件 (FCMP_TRUE)
Twine *var4 = new Twine("condition");
FCmpInst *condition =
new FCmpInst(*basicBlock, FCmpInst::FCMP_TRUE, LHS, RHS, *var4);

// 5. 创建分支指令
// 如果条件为真,跳转到原始基本块;否则跳转到虚假基本块
BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);

// 虚假基本块跳转回原始基本块
BranchInst::Create(originalBB, alteredBB);

// 6. 进一步处理原始基本块的终止部分
BasicBlock::iterator i = originalBB->end();
Twine *var5 = new Twine("originalBBpart2");
BasicBlock *originalBBpart2 = originalBB->splitBasicBlock(--i, *var5);
originalBB->getTerminator()->eraseFromParent();

// 创建另一个始终为真的条件
Twine *var6 = new Twine("condition2");
FCmpInst *condition2 =
new FCmpInst(*originalBB, CmpInst::FCMP_TRUE, LHS, RHS, *var6);
BranchInst::Create(originalBBpart2, alteredBB, (Value *)condition2, originalBB);
}

第四步:创建修改后的基本块 createAlteredBasicBlock

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
BasicBlock *createAlteredBasicBlock(BasicBlock *basicBlock, const Twine &Name, Function *F) {
// 1. 克隆基本块
ValueToValueMapTy VMap;
BasicBlock *alteredBB = llvm::CloneBasicBlock(basicBlock, VMap, Name, F);
DEBUG_WITH_TYPE("gen", errs() << "bcf: Original basic block cloned\n");

// 2. 重新映射操作数和元数据
BasicBlock::iterator ji = basicBlock->begin();
for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end();
i != e; ++i) {
// 映射操作数
for (User::op_iterator opi = i->op_begin(), ope = i->op_end(); opi != ope;
++opi) {
Value *v = MapValue(*opi, VMap, RF_None, 0);
if (v != 0) {
*opi = v;
}
}
// 映射 PHI 节点
if (PHINode *pn = dyn_cast<PHINode>(i)) {
for (unsigned j = 0, e = pn->getNumIncomingValues(); j != e; ++j) {
Value *v = MapValue(pn->getIncomingBlock(j), VMap, RF_None, 0);
if (v != 0) {
pn->setIncomingBlock(j, cast<BasicBlock>(v));
}
}
}
// 映射调试信息
i->setDebugLoc(ji->getDebugLoc());
ji++;
}

// 3. 在克隆的基本块中添加垃圾指令
for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end();
i != e; ++i) {
// 对二元操作进行处理
if (i->isBinaryOp()) {
unsigned opcode = i->getOpcode();
Instruction *op, *op1 = NULL;
Twine *var = new Twine("_");

// 根据操作码类型添加不同的垃圾指令
if (opcode == Instruction::Add || opcode == Instruction::Sub ||
opcode == Instruction::Mul || opcode == Instruction::UDiv ||
// ... 其他整数操作
) {
// 随机添加垃圾指令
for (int random = (int)llvm::cryptoutils->get_range(10); random < 10;
++random) {
switch (llvm::cryptoutils->get_range(4)) {
case 0: // 什么都不做
break;
case 1: // 添加负数操作
op = BinaryOperator::CreateNeg(i->getOperand(0), *var, &*i);
op1 = BinaryOperator::Create(Instruction::Add, op, i->getOperand(1),
"gen", &*i);
break;
case 2: // 添加减法和乘法操作
op1 = BinaryOperator::Create(Instruction::Sub, i->getOperand(0),
i->getOperand(1), *var, &*i);
op = BinaryOperator::Create(Instruction::Mul, op1, i->getOperand(1),
"gen", &*i);
break;
case 3: // 添加移位操作
op = BinaryOperator::Create(Instruction::Shl, i->getOperand(0),
i->getOperand(1), *var, &*i);
break;
}
}
}
// 类似处理浮点操作和其他指令类型
}
}
return alteredBB;
}

第五步:最终处理 doF

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
bool BogusControlFlowPass::doF(Module &M, Function &F) {
// 将明显为真的条件 (FCMP_TRUE) 替换为不那么明显的复杂表达式
// 例如,将 FCMP_TRUE 替换为 "(y < 10 || x * (x + 1) % 2 == 0)"

// 1. 创建两个全局变量 x 和 y
Twine *varX = new Twine("x");
Twine *varY = new Twine("y");
Value *x1 = ConstantInt::get(Type::getInt32Ty(M.getContext()), 0, false);
Value *y1 = ConstantInt::get(Type::getInt32Ty(M.getContext()), 0, false);

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

// 2. 查找所有 FCMP_TRUE 条件
std::vector<Instruction *> toEdit, toDelete;
for (Function::iterator fi = F.begin(), fe = F.end(); fi != fe; ++fi) {
Instruction *tbb = fi->getTerminator();
if (tbb->getOpcode() == Instruction::Br) {
BranchInst *br = (BranchInst *)(tbb);
if (br->isConditional()) {
FCmpInst *cond = (FCmpInst *)br->getCondition();
unsigned opcode = cond->getOpcode();
if (opcode == Instruction::FCmp) {
if (cond->getPredicate() == FCmpInst::FCMP_TRUE) {
toDelete.push_back(cond);
toEdit.push_back(tbb);
}
}
}
}
}

// 3. 替换条件为复杂表达式
for (std::vector<Instruction *>::iterator i = toEdit.begin();
i != toEdit.end(); ++i) {
// 构造复杂条件: y < 10 || x*(x+1) % 2 == 0
opX = new LoadInst(Type::getInt32Ty(M.getContext()), (Value *)x, "", (*i));
opY = new LoadInst(Type::getInt32Ty(M.getContext()), (Value *)y, "", (*i));

// 计算 x*(x+1) % 2
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));

// 比较结果是否等于0
condition = new ICmpInst(
(*i), ICmpInst::ICMP_EQ, op,
ConstantInt::get(Type::getInt32Ty(M.getContext()), 0, false));

// 比较 y < 10
condition2 = new ICmpInst(
(*i), ICmpInst::ICMP_SLT, opY,
ConstantInt::get(Type::getInt32Ty(M.getContext()), 10, false));

// 最终条件: (y < 10) || (x*(x+1) % 2 == 0)
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();
}

// 删除原来的条件指令
for (std::vector<Instruction *>::iterator i = toDelete.begin();
i != toDelete.end(); ++i) {
(*i)->eraseFromParent();
}

return true;
}

在实践中常爱将平坦化和其他几种混淆掺在一起使用。
Flatting+Bogus Control Flow:
2
还是能看到控制流平坦化的特点,不过明显多了很多块。

Substitution

指令替换,这个简单一点,就是将源码中涉及到的二元运算指令替换为更复杂的表达:

加法运算 (a = b + c)

  1. a = b - (-c)
  2. a = -(-b + (-c))
  3. r = rand(); a = b + r; a = a + c; a = a - r
  4. r = rand(); a = b - r; a = a + c; a = a + r

减法运算 (a = b - c)

  1. a = b + (-c)
  2. r = rand(); a = b + r; a = a - c; a = a - r
  3. r = rand(); a = b - r; a = a - c; a = a + r

按位与运算 (a = b & c)

  1. a = (b ^ ~c) & b
  2. a = ~(~b | ~c) & (r | ~r)

按位或运算 (a = b | c)

  1. a = (b & c) | (b ^ c)
  2. a = (((~b & r) | (b & ~r)) ^ ((~c & r) | (c & ~r))) | (~(~b | ~c) & (r | ~r))

按位异或运算 (a = b ^ c)

  1. a = (~b & c) | (b & ~c)
  2. a = (b ^ r) ^ (c ^ r)

String Encryption

字符串加密,这个在实践中也是很常见。
原理:

  1. 在编译时对程序中的字符串字面量进行加密处理
  2. 将加密后的字符串存储在全局数据区
  3. 在运行时首次使用字符串时进行解密
  4. 后续使用解密后的字符串

Split Basic Block

基本块分割

工作原理

  1. 遍历函数中的所有基本块
  2. 筛选出适合分割的基本块(不含PHI节点且指令数大于1)
  3. 在基本块内部随机选择分割点
  4. 将基本块在分割点处分割成多个连续的基本块
  5. 保持原有的执行顺序和逻辑关系

主要函数流程

1
2
3
4
5
6
7
8
9
10
11
12
PreservedAnalyses SplitBasicBlockPass::run(Function& F, FunctionAnalysisManager& AM) {
Function *tmp = &F;
// 检查该函数是否需要进行基本块分割混淆
if (toObfuscate(flag, tmp, "split")) {
// 执行分割操作
split(tmp);
// 更新统计信息
++Split;
return PreservedAnalyses::none();
}
return PreservedAnalyses::all();
}

核心分割逻辑

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void SplitBasicBlockPass::split(Function *f) {
// 1. 保存原始基本块列表,防止在分割过程中迭代器失效
std::vector<BasicBlock *> origBB;
for (Function::iterator I = f->begin(), IE = f->end(); I != IE; ++I) {
origBB.push_back(&*I);
}

// 2. 遍历所有基本块
for (std::vector<BasicBlock *>::iterator I = origBB.begin(), IE = origBB.end();
I != IE; ++I) {
BasicBlock *curr = *I;
int splitN = SplitNum; // 默认分割次数为3

// 3. 筛选可分割的基本块
// 跳过指令数小于2或包含PHI节点的基本块
if (curr->size() < 2 || containsPHI(curr)) {
continue;
}

// 4. 调整分割次数
// 如果指定的分割次数大于等于基本块大小,则调整为基本块大小-1
if ((size_t)splitN >= curr->size()) {
splitN = curr->size() - 1;
}

// 5. 生成分割点
std::vector<int> test;
// 将所有可能的分割点位置加入向量(除了第一个位置)
for (unsigned i = 1; i < curr->size(); ++i) {
test.push_back(i);
}

// 6. 随机打乱分割点顺序
if (test.size() != 1) {
shuffle(test);
// 对前splitN个分割点进行排序,确保按顺序分割
std::sort(test.begin(), test.begin() + splitN);
}

// 7. 执行分割操作
BasicBlock::iterator it = curr->begin();
BasicBlock *toSplit = curr;
int last = 0;
for (int i = 0; i < splitN; ++i) {
// 如果当前要分割的基本块太小则跳过
if (toSplit->size() < 2) {
continue;
}

// 移动迭代器到分割点位置
for (int j = 0; j < test[i] - last; ++j) {
++it;
}
last = test[i];

// 在当前位置分割基本块
toSplit = toSplit->splitBasicBlock(it, toSplit->getName() + ".split");
}
++Split;
}
}

辅助函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 检查基本块是否包含PHI节点
bool SplitBasicBlockPass::containsPHI(BasicBlock *BB) {
for (Instruction &I : *BB) {
if (isa<PHINode>(&I)) {
return true;
}
}
return false;
}

// 随机打乱向量中的元素顺序
void SplitBasicBlockPass::shuffle(std::vector<int> &vec) {
int n = vec.size();
for (int i = n - 1; i > 0; --i) {
std::swap(vec[i], vec[cryptoutils->get_uint32_t() % (i + 1)]);
}
}

Indirect Call

间接调用

工作原理

  1. 识别函数中的所有直接函数调用
  2. 为每个被调用的函数分配唯一的编号
  3. 创建一个全局数组,存储所有被调用函数的地址
  4. 将直接调用替换为通过数组索引访问函数地址的间接调用
  5. 使用加密技术进一步隐藏函数地址信息

核心混淆逻辑

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
bool IndirectCallPass::doIndirctCall(Function &Fn){
// 检查是否跳过该函数
if (Options && Options->skipFunction(Fn.getName())) {
return false;
}

LLVMContext &Ctx = Fn.getContext();

// 清空之前的数据结构
CalleeNumbering.clear();
Callees.clear();
CallSites.clear();

// 为函数中的所有被调用函数编号
NumberCallees(Fn);

// 如果没有调用任何函数,则直接返回
if (Callees.empty()) {
return false;
}

// 生成随机密钥用于地址加密
uint64_t V = RandomEngine.get_uint64_t();
IntegerType *intType = Type::getInt32Ty(Ctx);

// 根据指针大小选择合适的整数类型(32位或64位)
unsigned pointerSize = Fn.getEntryBlock().getModule()->getDataLayout().getTypeAllocSize(PointerType::getUnqual(Fn.getContext()));
if (pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}

// 创建加密密钥和解密密钥
ConstantInt *EncKey = ConstantInt::get(intType, V, false);
ConstantInt *EncKey1 = ConstantInt::get(intType, -V, false);

Value *MySecret = ConstantInt::get(intType, 0, true);
ConstantInt *Zero = ConstantInt::get(intType, 0);

// 创建存储函数地址的全局变量数组
GlobalVariable *Targets = getIndirectCallees(Fn, EncKey1);

// 遍历所有调用点并进行间接调用转换
for (auto CI : CallSites) {
SmallVector<Value *, 8> Args;
SmallVector<AttributeSet, 8> ArgAttrVec;

CallBase *CB = CI;
Function *Callee = CB->getCalledFunction();
FunctionType *FTy = CB->getFunctionType();
IRBuilder<> IRB(CB);

Args.clear();
ArgAttrVec.clear();

// 获取被调用函数的编号
Value *Idx = ConstantInt::get(intType, CalleeNumbering[CB->getCalledFunction()]);

// 从全局数组中获取加密的函数地址
Value *GEP = IRB.CreateGEP(Targets->getValueType(), Targets, {Zero, Idx});
LoadInst *EncDestAddr = IRB.CreateLoad(GEP->getType(), GEP, CI->getName());

// 获取调用的参数信息
const AttributeList &CallPAL = CB->getAttributes();
auto I = CB->arg_begin();
unsigned i = 0;

// 收集函数调用参数
for (unsigned e = FTy->getNumParams(); i != e; ++I, ++i) {
Args.push_back(*I);
AttributeSet Attrs = CallPAL.getParamAttrs(i);
ArgAttrVec.push_back(Attrs);
}

for (auto E = CB->arg_end(); I != E; ++I, ++i) {
Args.push_back(*I);
ArgAttrVec.push_back(CallPAL.getParamAttrs(i));
}

// 解密函数地址
Value *Secret = IRB.CreateAdd(EncKey, MySecret);
Value *DestAddr = IRB.CreateGEP(Type::getInt8Ty(Ctx), EncDestAddr, Secret);

// 将地址转换为函数指针类型
Value *FnPtr = IRB.CreateBitCast(DestAddr, FTy->getPointerTo());
FnPtr->setName("Call_" + Callee->getName());

// 替换原来的直接调用为间接调用
CB->setCalledOperand(FnPtr);
}

return true;
}

辅助函数

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 创建存储函数地址的全局变量数组
GlobalVariable *IndirectCallPass::getIndirectCallees(Function &F, ConstantInt *EncKey){
// 构造全局变量名称
std::string GVName(F.getName().str() + "_IndirectCallees");

// 检查是否已经创建过该全局变量
GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName);
if (GV){
return GV;
}

// 为每个被调用函数创建加密的地址条目
std::vector<Constant *> Elements;
for (auto Callee : Callees){
// 将函数地址强制转换为int8指针
Constant *CE = ConstantExpr::getBitCast(Callee, llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0));
// 使用密钥对地址进行加密(通过GEP实现地址偏移)
CE = ConstantExpr::getGetElementPtr(Type::getInt8Ty(F.getContext()), CE, EncKey);
Elements.push_back(CE);
}

// 创建数组类型和常量数组
ArrayType *ATy = ArrayType::get(llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0), Elements.size());
Constant *CA = ConstantArray::get(ATy, ArrayRef<Constant *>(Elements));

// 创建全局变量存储函数地址数组
GV = new GlobalVariable(*F.getParent(), ATy, false, GlobalValue::LinkageTypes::PrivateLinkage, CA, GVName);
appendToCompilerUsed(*F.getParent(), {GV});
return GV;
}

// 为函数中的所有被调用函数编号
void IndirectCallPass::NumberCallees(Function &F){
// 遍历函数中的所有基本块和指令
for (auto &BB : F){
for (auto &I : BB){
// 检查是否为调用指令
if (dyn_cast<CallInst>(&I)){
CallSite CS(&I);
Function *Callee = CS.getCalledFunction();

// 跳过空函数和内建函数
if (Callee == nullptr){
continue;
}
if (Callee->isIntrinsic()){
continue;
}

// 记录调用点
CallSites.push_back((CallInst *)&I);

// 为被调用函数分配编号
if (CalleeNumbering.count(Callee) == 0){
CalleeNumbering[Callee] = Callees.size();
Callees.push_back(Callee);
}
}
}
}
}

这种混淆和平坦化一起使用是很恐怖的:
3
可以看到原本的函数调用部分变成了一个跳转表达,就是说静态分析完全看不出来是在干什么了。
4
同时跳转的地址被加密保存,恐怖如斯。
这种混淆在实践中常常会被误认为是vmp,但实际是ollvm的一种而已。

Indirect Branch

间接跳转,和前面的间接调用类似,不过间接调用使用函数形式包装真实块,而间接跳转只是一般跳转而已,只不过是二次解析的跳转(间接跳转)

工作原理

  1. 识别函数中的所有条件分支指令
  2. 为每个分支目标基本块分配唯一的编号
  3. 创建一个全局数组,存储所有分支目标的地址
  4. 将直接条件分支替换为通过数组索引访问目标地址的间接跳转
  5. 使用加密技术进一步隐藏分支目标地址信息

主要函数流程

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
PreservedAnalyses IndirectBranchPass::run(Module &M, ModuleAnalysisManager &AM) {
if (this->flag) {
outs() << "[Soule] force.run.IndirectBranchPass\n";
}

// 遍历模块中的所有函数
for (Function &Fn : M) {
// 检查该函数是否需要进行间接跳转混淆
if (toObfuscate(flag, &Fn, "ibr")) {
// 检查是否跳过该函数
if (Options && Options->skipFunction(Fn.getName())) {
continue;
}

// 跳过空函数、弱链接函数和初始化函数
if (Fn.empty() || Fn.hasLinkOnceLinkage() || Fn.getSection() == ".text.startup") {
continue;
}

LLVMContext &Ctx = Fn.getContext();

// 初始化成员字段
BBNumbering.clear();
BBTargets.clear();

// 分割所有关键边(因为LLVM无法从IndirectBrInst分割关键边)
SplitAllCriticalEdges(Fn, CriticalEdgeSplittingOptions(nullptr, nullptr));

// 为基本块编号
NumberBasicBlock(Fn);

// 如果没有基本块需要处理则跳过
if (BBNumbering.empty()) {
continue;
}

// 生成随机密钥用于地址加密
uint64_t V = RandomEngine.get_uint64_t();
IntegerType *intType = Type::getInt32Ty(Ctx);

// 根据指针大小选择合适的整数类型(32位或64位)
unsigned pointerSize = Fn.getEntryBlock().getModule()->getDataLayout().getTypeAllocSize(
PointerType::getUnqual(Fn.getContext()));
if (pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}

// 创建加密密钥和解密密钥
ConstantInt *EncKey = ConstantInt::get(intType, V, false);
ConstantInt *EncKey1 = ConstantInt::get(intType, -V, false);

Value *MySecret = ConstantInt::get(intType, 0, true);
ConstantInt *Zero = ConstantInt::get(intType, 0);

// 创建存储分支目标地址的全局变量数组
GlobalVariable *DestBBs = getIndirectTargets(Fn, EncKey1);

// 遍历函数中的所有基本块,处理条件分支指令
for (auto &BB : Fn) {
auto *BI = dyn_cast<BranchInst>(BB.getTerminator());
// 检查是否为条件分支指令
if (BI && BI->isConditional()) {
IRBuilder<> IRB(BI);

// 获取条件值
Value *Cond = BI->getCondition();
Value *Idx;
Value *TIdx, *FIdx;

// 获取真分支和假分支的目标基本块编号
TIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(0)]);
FIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(1)]);

// 根据条件值选择目标索引
Idx = IRB.CreateSelect(Cond, TIdx, FIdx);

// 从全局数组中获取加密的目标地址
Value *GEP = IRB.CreateGEP(DestBBs->getValueType(), DestBBs, {Zero, Idx});
Value *EncDestAddr = IRB.CreateLoad(GEP->getType(), GEP, "EncDestAddr");

// 解密目标地址
Value *DecKey = IRB.CreateAdd(EncKey, MySecret);
Value *DestAddr = IRB.CreateGEP(Type::getInt8Ty(Ctx), EncDestAddr, DecKey);

// 创建间接跳转指令
IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
IBI->addDestination(BI->getSuccessor(0));
IBI->addDestination(BI->getSuccessor(1));

// 用间接跳转指令替换原来的条件分支指令
ReplaceInstWithInst(BI, IBI);
}
}
}
}
return PreservedAnalyses::none();
}

辅助函数实现

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 为函数中的所有分支目标基本块编号
void IndirectBranchPass::NumberBasicBlock(Function &F) {
// 遍历函数中的所有基本块,收集条件分支的目标基本块
for (auto &BB : F) {
if (auto *BI = dyn_cast<BranchInst>(BB.getTerminator())) {
if (BI->isConditional()) {
unsigned N = BI->getNumSuccessors();
// 遍历所有后继基本块
for (unsigned I = 0; I < N; I++) {
BasicBlock *Succ = BI->getSuccessor(I);
// 如果该基本块尚未编号,则添加到目标列表中
if (BBNumbering.count(Succ) == 0) {
BBTargets.push_back(Succ);
BBNumbering[Succ] = 0;
}
}
}
}
}

// 使用随机种子打乱基本块顺序
long seed = RandomEngine.get_uint32_t();
std::default_random_engine e(seed);
std::shuffle(BBTargets.begin(), BBTargets.end(), e);

// 为基本块分配编号
unsigned N = 0;
for (auto BB : BBTargets) {
BBNumbering[BB] = N++;
}
}

// 创建存储分支目标地址的全局变量数组
GlobalVariable *IndirectBranchPass::getIndirectTargets(Function &F, ConstantInt *EncKey) {
// 构造全局变量名称
std::string GVName(F.getName().str() + "_IndirectBrTargets");
GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName);

// 如果已经存在则直接返回
if (GV)
return GV;

// 为每个分支目标创建加密的地址条目
std::vector<Constant *> Elements;
for (const auto BB : BBTargets) {
// 获取基本块地址并强制转换为int8指针
Constant *CE = ConstantExpr::getBitCast(BlockAddress::get(BB),
llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0));
// 使用密钥对地址进行加密(通过GEP实现地址偏移)
CE = ConstantExpr::getGetElementPtr(Type::getInt8Ty(F.getContext()), CE, EncKey);
Elements.push_back(CE);
}

// 创建数组类型和常量数组
ArrayType *ATy = ArrayType::get(llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0), Elements.size());
Constant *CA = ConstantArray::get(ATy, ArrayRef<Constant *>(Elements));

// 创建全局变量存储分支目标地址数组
GV = new GlobalVariable(*F.getParent(), ATy, false,
GlobalValue::LinkageTypes::PrivateLinkage, CA, GVName);
appendToCompilerUsed(*F.getParent(), {GV});
return GV;
}

看看应用:
5
6
原本的函数结构看不全了,这就是间接跳转都结果。

Indirect Global Variable

全局变量间接访问

工作原理

  1. 识别函数中使用的所有全局变量
  2. 为每个全局变量分配唯一的编号
  3. 创建一个全局数组,存储所有被访问全局变量的地址
  4. 将直接的全局变量访问替换为通过数组索引访问地址的间接访问
  5. 使用加密技术进一步隐藏全局变量地址信息

主要函数流程

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
PreservedAnalyses IndirectGlobalVariablePass::run(Module &M, ModuleAnalysisManager &AM) {
if (this->flag) {
outs() << "[Soule] force.run.IndirectGlobalVariablePass\n";
}

// 遍历模块中的所有函数
for (Function &Fn : M) {
// 检查该函数是否需要进行全局变量间接访问混淆
if (!toObfuscate(flag, &Fn, "igv")) {
continue;
}

// 检查是否跳过该函数
if (Options && Options->skipFunction(Fn.getName())) {
continue;
}

LLVMContext &Ctx = Fn.getContext();

// 初始化成员字段
GVNumbering.clear();
GlobalVariables.clear();

// 将常量表达式降级为指令
LowerConstantExpr(Fn);

// 为全局变量编号
NumberGlobalVariable(Fn);

// 如果没有全局变量需要处理则跳过
if (GlobalVariables.empty()) {
continue;
}

// 生成随机密钥用于地址加密
uint64_t V = RandomEngine.get_uint64_t();
IntegerType *intType = Type::getInt32Ty(Ctx);

// 根据指针大小选择合适的整数类型(32位或64位)
unsigned pointerSize = Fn.getEntryBlock().getModule()->getDataLayout().getTypeAllocSize(
PointerType::getUnqual(Fn.getContext()));
if (pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}

// 创建加密密钥和解密密钥
ConstantInt *EncKey = ConstantInt::get(intType, V, false);
ConstantInt *EncKey1 = ConstantInt::get(intType, -V, false);

Value *MySecret = ConstantInt::get(intType, 0, true);
ConstantInt *Zero = ConstantInt::get(intType, 0);

// 创建存储全局变量地址的全局变量数组
GlobalVariable *GVars = getIndirectGlobalVariables(Fn, EncKey1);

// 遍历函数中的所有指令,处理全局变量引用
for (inst_iterator I = inst_begin(Fn), E = inst_end(Fn); I != E; ++I) {
Instruction *Inst = &*I;

// 跳过一些特殊指令
if (isa<LandingPadInst>(Inst) || isa<CleanupPadInst>(Inst) ||
isa<CatchPadInst>(Inst) || isa<CatchReturnInst>(Inst) ||
isa<CatchSwitchInst>(Inst) || isa<ResumeInst>(Inst) ||
isa<CallInst>(Inst)) {
continue;
}

// 处理PHI节点
if (PHINode *PHI = dyn_cast<PHINode>(Inst)) {
for (unsigned int i = 0; i < PHI->getNumIncomingValues(); ++i) {
Value *val = PHI->getIncomingValue(i);
// 检查PHI节点的输入值是否为全局变量
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(val)) {
// 检查该全局变量是否已编号
if (GVNumbering.count(GV) == 0) {
continue;
}

// 获取PHI节点所在基本块的终止指令作为插入点
Instruction *IP = PHI->getIncomingBlock(i)->getTerminator();
IRBuilder<> IRB(IP);

// 获取全局变量编号
Value *Idx = ConstantInt::get(intType, GVNumbering[GV]);

// 从全局数组中获取加密的全局变量地址
Value *GEP = IRB.CreateGEP(GVars->getValueType(), GVars, {Zero, Idx});
LoadInst *EncGVAddr = IRB.CreateLoad(GEP->getType(), GEP, GV->getName());

// 解密全局变量地址
Value *Secret = IRB.CreateAdd(EncKey, MySecret);
Value *GVAddr = IRB.CreateGEP(Type::getInt8Ty(Ctx), EncGVAddr, Secret);

// 将地址转换为正确的类型
GVAddr = IRB.CreateBitCast(GVAddr, GV->getType());
GVAddr->setName("IndGV0_");

// 替换PHI节点中的全局变量引用
PHI->setIncomingValue(i, GVAddr);
}
}
} else {
// 处理普通指令的操作数
for (User::op_iterator op = Inst->op_begin(); op != Inst->op_end(); ++op) {
// 检查操作数是否为全局变量
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(*op)) {
// 检查该全局变量是否已编号
if (GVNumbering.count(GV) == 0) {
continue;
}

IRBuilder<> IRB(Inst);

// 获取全局变量编号
Value *Idx = ConstantInt::get(intType, GVNumbering[GV]);

// 从全局数组中获取加密的全局变量地址
Value *GEP = IRB.CreateGEP(GVars->getValueType(), GVars, {Zero, Idx});
LoadInst *EncGVAddr = IRB.CreateLoad(GEP->getType(), GEP, GV->getName());

// 解密全局变量地址
Value *Secret = IRB.CreateAdd(EncKey, MySecret);
Value *GVAddr = IRB.CreateGEP(Type::getInt8Ty(Ctx), EncGVAddr, Secret);

// 将地址转换为正确的类型
GVAddr = IRB.CreateBitCast(GVAddr, GV->getType());
GVAddr->setName("IndGV1_");

// 替换指令中的全局变量引用
Inst->replaceUsesOfWith(GV, GVAddr);
}
}
}
}
}
return PreservedAnalyses::none();
}

辅助函数实现

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
44
45
46
47
48
49
50
51
52
53
// 为函数中使用的所有全局变量编号
void IndirectGlobalVariablePass::NumberGlobalVariable(Function &F) {
// 遍历函数中的所有指令
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
// 遍历指令的所有操作数
for (User::op_iterator op = (*I).op_begin(); op != (*I).op_end(); ++op) {
Value *val = *op;
// 检查操作数是否为全局变量
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(val)) {
// 检查全局变量是否符合要求(非线程局部、未编号、非DLL导入)
if (!GV->isThreadLocal() && GVNumbering.count(GV) == 0 &&
!GV->isDLLImportDependent()) {
// 为全局变量分配编号并添加到列表中
GVNumbering[GV] = GlobalVariables.size();
GlobalVariables.push_back((GlobalVariable *)val);
}
}
}
}
}

// 创建存储全局变量地址的全局变量数组
GlobalVariable* IndirectGlobalVariablePass::getIndirectGlobalVariables(Function &F, ConstantInt *EncKey) {
// 构造全局变量名称
std::string GVName(F.getName().str() + "_IndirectGVars");
GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName);

// 如果已经存在则直接返回
if (GV)
return GV;

// 为每个全局变量创建加密的地址条目
std::vector<Constant *> Elements;
for (auto GVar : GlobalVariables) {
// 将全局变量地址强制转换为int8指针
Constant *CE = ConstantExpr::getBitCast(GVar,
llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0));
// 使用密钥对地址进行加密(通过GEP实现地址偏移)
CE = ConstantExpr::getGetElementPtr(Type::getInt8Ty(F.getContext()), CE, EncKey);
Elements.push_back(CE);
}

// 创建数组类型和常量数组
ArrayType *ATy = ArrayType::get(llvm::PointerType::get(Type::getInt8Ty(F.getContext()),0),
Elements.size());
Constant *CA = ConstantArray::get(ATy, ArrayRef<Constant *>(Elements));

// 创建全局变量存储全局变量地址数组
GV = new GlobalVariable(*F.getParent(), ATy, false,
GlobalValue::LinkageTypes::PrivateLinkage, CA, GVName);
appendToCompilerUsed(*F.getParent(), {GV});
return GV;
}