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)

  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. 保持原有的执行顺序和逻辑关系

主要函数流程

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

间接调用

工作原理

  1. 识别函数中的所有直接函数调用

  2. 为每个被调用的函数分配唯一的编号

  3. 创建一个全局数组,存储所有被调用函数的地址

  4. 将直接调用替换为通过数组索引访问函数地址的间接调用

  5. 使用加密技术进一步隐藏函数地址信息

核心混淆逻辑

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

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

工作原理

  1. 识别函数中的所有条件分支指令

  2. 为每个分支目标基本块分配唯一的编号

  3. 创建一个全局数组,存储所有分支目标的地址

  4. 将直接条件分支替换为通过数组索引访问目标地址的间接跳转

  5. 使用加密技术进一步隐藏分支目标地址信息

主要函数流程

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

看看应用:

5.png
6.png


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

Indirect Global Variable

全局变量间接访问

工作原理

  1. 识别函数中使用的所有全局变量

  2. 为每个全局变量分配唯一的编号

  3. 创建一个全局数组,存储所有被访问全局变量的地址

  4. 将直接的全局变量访问替换为通过数组索引访问地址的间接访问

  5. 使用加密技术进一步隐藏全局变量地址信息

主要函数流程

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