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 相关问题,保证语义等价。
以下是源码:
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;
}
看一下实际效果:

是一个很标致的结构
Bogus Control Flow
虚假控制流,
第一步:主入口函数 run
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
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
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
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
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:

还是能看到控制流平坦化的特点,不过明显多了很多块。
Substitution
指令替换,这个简单一点,就是将源码中涉及到的二元运算指令替换为更复杂的表达:
加法运算 (a = b + c)
a = b - (-c)a = -(-b + (-c))r = rand(); a = b + r; a = a + c; a = a - rr = rand(); a = b - r; a = a + c; a = a + r
减法运算 (a = b - c)
a = b + (-c)r = rand(); a = b + r; a = a - c; a = a - rr = rand(); a = b - r; a = a - c; a = a + r
按位与运算 (a = b & c)
a = (b ^ ~c) & ba = ~(~b | ~c) & (r | ~r)
按位或运算 (a = b | c)
a = (b & c) | (b ^ c)a = (((~b & r) | (b & ~r)) ^ ((~c & r) | (c & ~r))) | (~(~b | ~c) & (r | ~r))
按位异或运算 (a = b ^ c)
a = (~b & c) | (b & ~c)a = (b ^ r) ^ (c ^ r)
String Encryption
字符串加密,这个在实践中也是很常见。
原理:
在编译时对程序中的字符串字面量进行加密处理
将加密后的字符串存储在全局数据区
在运行时首次使用字符串时进行解密
后续使用解密后的字符串
Split Basic Block
基本块分割
工作原理
遍历函数中的所有基本块
筛选出适合分割的基本块(不含 PHI 节点且指令数大于 1)
在基本块内部随机选择分割点
将基本块在分割点处分割成多个连续的基本块
保持原有的执行顺序和逻辑关系
主要函数流程
PreservedAnalyses SplitBasicBlockPass::run(Function& F, FunctionAnalysisManager& AM) {
Function *tmp = &F;
// 检查该函数是否需要进行基本块分割混淆
if (toObfuscate(flag, tmp, "split")) {
// 执行分割操作
split(tmp);
// 更新统计信息
++Split;
return PreservedAnalyses::none();
}
return PreservedAnalyses::all();
}
核心分割逻辑
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;
}
}
辅助函数分析
// 检查基本块是否包含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
间接调用
工作原理
识别函数中的所有直接函数调用
为每个被调用的函数分配唯一的编号
创建一个全局数组,存储所有被调用函数的地址
将直接调用替换为通过数组索引访问函数地址的间接调用
使用加密技术进一步隐藏函数地址信息
核心混淆逻辑
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;
}
辅助函数
// 创建存储函数地址的全局变量数组
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);
}
}
}
}
}
这种混淆和平坦化一起使用是很恐怖的:

可以看到原本的函数调用部分变成了一个跳转表达,就是说静态分析完全看不出来是在干什么了。

同时跳转的地址被加密保存,恐怖如斯。
这种混淆在实践中常常会被误认为是 vmp,但实际是 ollvm 的一种而已。
Indirect Branch
间接跳转,和前面的间接调用类似,不过间接调用使用函数形式包装真实块,而间接跳转只是一般跳转而已,只不过是二次解析的跳转(间接跳转)
工作原理
识别函数中的所有条件分支指令
为每个分支目标基本块分配唯一的编号
创建一个全局数组,存储所有分支目标的地址
将直接条件分支替换为通过数组索引访问目标地址的间接跳转
使用加密技术进一步隐藏分支目标地址信息
主要函数流程
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();
}
辅助函数实现
// 为函数中的所有分支目标基本块编号
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;
}
看看应用:


原本的函数结构看不全了,这就是间接跳转都结果。
Indirect Global Variable
全局变量间接访问
工作原理
识别函数中使用的所有全局变量
为每个全局变量分配唯一的编号
创建一个全局数组,存储所有被访问全局变量的地址
将直接的全局变量访问替换为通过数组索引访问地址的间接访问
使用加密技术进一步隐藏全局变量地址信息
主要函数流程
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();
}
辅助函数实现
// 为函数中使用的所有全局变量编号
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;
}