1. 控制流平坦化

1. 原理介绍

控制流平坦化,将正常控制流中基本块之间的跳转关系删除,用一个集中分发块来调度基本块的执行顺序。控制流平坦化在IDA中一般伪代码是while+switch结构。

控制流平坦化中,将基本块分为以下几类:

  • 入口块:进入函数执行的第一个基本块

  • 分发块:负责跳转到下一个要执行的原基本块

  • 原基本块:混淆之前的基本块

  • 返回块:返回到主分发块

实现步骤如下:

  • 保存原基本块:存储原基本块,并对入口块进行处理,

  • 创建分发块与返回块:

  • 实现分发块调度

  • 实现调度变量自动调整

  • 修复PHI指令和逃逸变量

1. 保存原基本块

  • 将除入口块以外的基本块保存到vector容器中,方便后续处理。

  • 对于入口块,如果入口块终结指令(最后一条指令)是条件分支指令,则将该指令单独分离出来作为一个基本块,加入到vector容器的最前面。

关键代码如下:

// 将除入口块(第一个基本块)以外的基本保存到一个vector容器中,便于后续处理
// 首先保存所有基本块
vector<BasickBlock*> origBB;
for(BasicBlock &BB: F){
  origBB.push_back(&BB);
}
// 从vector中去除第一个基本块,即入口块
origBB.erase(origBB.begin());
BasicBlock &entryBB = F.getEntryBlock();
// 如果第一个基本块末尾是条件跳转,将该指令分离单独一个块
if(BranchInst *br = dyn_case<BranchInst>(entryBB.getTerminator())){
  if(br->isConditional()){
    BasicBlock *newBB = entryBB.splitBasicBlock(br, "newBB");
    origBB.insert(origBB.begin(), newBB);
  }
}

2. 创建分发块与返回块

  • 创建一个分发块来调度基本块的执行顺序,并建立入口块到分发块的绝对跳转。

  • 创建一个返回块,原基本快执行后,都要跳转到这个返回块

关键代码如下:

// 创建分发块与返回块
BasicBlock *dispatchBB = BasicBlock::create(F.getContet(), "dispatchBB", &F, &entryBB);
BasicBlock *retrunBB= BasicBlock::create(F.getContet(), "retrunBB", &F, &entryBB);
BranchInst::Create(dispatchBB, returnBB);
entryBB.moveBefore(dispatchBB);
// 去除第一个基本块末尾的跳转
entryBB.getTerminator()->eraseFromParent();
// 试第一个基本块跳转到dispatchBB
BranchInst *brDispatchBB = BranchInst::Create(dispatchBB, &entryBB);

3. 实现分发块调度

  • 在入口块中创建并初始化switch变量,在调度块中插入switch指令实现分发功能。

  • 将原本基本快移动到返回块之前,并分配随机的case值,并将其添加到switch指令的分支中。

关键代码如下:

// 在入口块插入alloca和stone指令,创建并初始化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指令,实现基本块调度
BasickBlock *swDefault = BasicBlock::Create(F.getContext(), "swDefault", &F, returnBB);
BranchInst::Create(returnBB, swDefault);
SwtichInst *swInst = SwtichInst::Create(swVar, swDefalut, 0, dispatchBB);
// 将原基本块插入到返回块之前,并分配case值
for(BasickBlock *BB : origBB){
  BB->moewBefor(returnBB);
  swInst->addCase(CONST_I32(randNumCase), BB);
  randNumCase = rand();
}

4. 实现调度器变量自动调整

  • 在每个原基本块最后添加修改switch变量值的指令,以便返回分发块之后,能够正确执行到下一个基本块。

  • 删除原基本快末尾的跳转,使其结束后跳转到返回块

关键代码如下:

// 在每个基本块最后添加修改switch变量的指令和跳转到返回块的指令
for(BasicBlock *BB :origBB){
  // retn BB
  if(BB->getTerminator()->getNumSuccessors() == 0){
    continue;
  }
  // 非条件跳转
  else if(BB->getTerminator()->getNumSuccessors() == 1){
    BasickBlock *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){
    ConstantInt *numcaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccesor(0));
    ConstantInt *numcaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccesor(1));
    BranchInst *br = case<BranchInst>(BB->getTerminator());
    SelectInst *sel = SelcInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "", BB->getTerminator());
    BB->getTerminator()->eraseFromParent();
    new StoreInst(numCase, swVarPtr, BB);
    BranchInst::Create(returnBB, BB);  
  }

5. 修复PHI指令与逃逸变量

  • PHI指令的值由前驱块决定,平坦化所有原基本快的前驱块都变成分发块了,因此PHI指令发生了损坏

  • 逃逸变量指的是在一个基本块中定义,在另一个基本块中被引用的变量。在源程序中某些基本块可能引用之前你某个基本块中的变量,平坦化后原基本块之间不存在确定的前后关系了,改为由分发块决定,因此某些变量的引用可能回损坏。

  • 修复方法:将PHI指令和逃逸变量都转化为内存存取指令。

void Flattening::fixStack(Function &F){
  vector<PHINode*> origPHI;
  vector<Instruction*> origReg;
  BasicBlock &entryBB = F.getEntryBlock();
  for(BasicBlock &BB : F){
    for(Instruction &I :BB){
      if(PHINode *PN = dyn_case<PHINode>(&I)){
        origPHI.push_back(PN);
      }else if(!(isa<AllocaInst>(&I) && I.getParent() == &entryBB)
          && I.isUsedOutsideOfBlock(&BB)){
           origReg.push_back(&I);
      }
    }
  }
  for(PHINode *PN : origPHI){
    DemotePHIToStack(PN, entryBB.getTerminator());
  }
  for(Instruction *I : origReg){
    DemoteRegToStack(*I, entryBB.getTerminator());
  }
}

2. 虚假控制流

1. 原理介绍

虚假控制流是指:通过向控制流中插入若干不可达基本块和由不透明谓词造成的虚假跳转,以产生大量垃圾代码干扰攻击者分析的混淆。

  • 不可达基本块:就是永远不会执行的基本块,举个例子if (false) { 基本块1 } else { 基本块2 } ,基本块1中的代码是永远不会被执行的。

  • 不透明谓词(Opaque Predicates):是一种逻辑表达式,其执行结果在静态分析中难以预测,但在运行时具有确定性。例如 x=0; y=0; if(10 * x <=0 || y >= 0){}else{} ,if中条件永远为真,所以就不会执行else中指令。

经过虚假控制流混淆的代码的控制流图呈现长条状:

虚假控制流以基本块为按位进行混淆,每个基本块经过分裂、克隆、构建虚假控制跳转等操作。

主要操作步骤如下:

  • 基本块拆分

  • 基本块克隆

  • 构建虚假跳转

2. 基本块拆分

  • 通过getFirstNonPHI函数,获取第一个不是PHINode的指令,以该指令为界限进行分割,得到entryBB和bodyBB,

  • 以bodyBB的终结指令进行分割,最终得到头部、中部、和尾部三个基本块,也就是entryBB、bodyBB、endBB

核心代码如下:

void BogusControlFlow::bogus(BasicBlock *entryBB){
  // 第一步 拆分得到entryBB、bodyBB、endBB
  // 其中所有的PHI指令都在 entryBB(如果有的话)
  // endBB只保留一条终结指令
  BasicBlock *bodyBB = entryBB->splitBasicBlock(entryBB->getFirstNonPHI(), "bodyBB");
  BasicBlock *endBB = bodyBB->splitBasicBlock(bodyBB->getTerminator(), "endBB");

}

3. 基本块克隆

  • 克隆中间的bodyBB,得到克隆快clone_bodyBB

  • LLVM 中提供了cloneBasicBlock函数,但是该函数为不完全克隆。克隆后,克隆块中仍然引用了原基本块中的变量符号,这种引用是非法的,不满足SSA,需要将符号名称给修改掉,例如在符号末尾加上".clone"

BasicBlock* llvm::createCloneBasicBlock(BasicBlock *BB){
  ValueToValueMapTy VMap;
  BasicBlock *cloneBB = CloneBasicBlock(BB, VMap, BB->getName() + "cloneBB", BB->getParent());
  // 对克隆基本块的引用进行修复
  for(Instruction &I : *cloneBB){
    for(int i = 0; i < I.getNumOperands(); i++){
      Value *V = MapValue(I,getOperand(i), VMap);
      if(V){
          I.setOperand(i,V);
      }
    }
  }
  return cloneBB;
}

4. 构建虚假跳转

  • 将 entryBB 到 bodyBB 的绝对跳转改为条件跳转

  • 将 bodyBB 到 endBB 的绝对跳转改为条件跳转

  • 添加 cloneBB 到 bodyBB的绝对跳转

// 第三步:构造虚假跳转
// 1. 将entryBB, bodyBB, cloneBB,末尾的绝对跳转移除
entryBB-?getTerminator->eraseFromParent();
bodyBB-?getTerminator->eraseFromParent();
cloneBB-?getTerminator->eraseFromParent();
// 2. 在 entryBB 和 bodyBB 的末尾插入条件恒为真的虚假比较指令
Value *cond1 = createBogusCmp(entryBB);
Value *cond2 = createBogusCmp(bodyBB);
// 3. 将entryBB 到 bodyBB 的绝对跳转改为条件跳转
BranchInst::Create(bodyBB, cloneBB, cond1, entryBB);
// 4. 将 bodybb 到 endBB 的绝对跳转改为条件跳转
BranchInst::Create(bodyBB, cloneBB, cond2, bodybb);
// 5. 将bodyBB 到 cloneBB 的绝对跳转改为条件跳转
BranchInst::Create(bodyBB, cloneBB);
  • 构建不透明谓词,可以将下面方法组合,生成更复杂的不透明谓词。

    • 基础数理论恒真式

      • 相邻整数特性

        • 表达式:x(x+k) ≡ 0 mod n

        • 定理依据:任何相邻整数必含偶数因子,当取k=1, n=2时恒成立

        • 变体扩展:(x * (x + 3)) % 4 == 0 恒真

      • 平方数特性

        • 表达式:x² ≡ x mod 2

        • 数学征明:

          • 当x为偶数:x=2k ⇒ x²=4k² ≡ 0 ≡ 2k mod 2

          • 当x为奇数:x=2k+1 ⇒ x²=4k²+4k+1 ≡ 1 ≡ (2k+1) mod 2

      • 数论函数恒等式

      • ...

Value* BogusControlFlow::createBogusCmp(BasicBlock *insertAfter){
  // if((y<10 || x * (X+1) %2 == 0)) 也就是if true
  Module *M = insertAfter->getModule();
  GlobalVariable *xptr = new GlobalVariable(*M, TYPE_I32, false, GlobalValue::CommonLinkage, CONST_I32(0), "x");
  GlobalVariable *yptr = new GlobalVariable(*M, TYPE_I32, false, GlobalValue::CommonLinkage, CONST_I32(0), "y");
  LoadInst *X = new LoadInst(TYPE_I32, xptr, "", insertAfter);
  LoadInst *y = new LoadInst(TYPE_I32, yptr, "", insertAfter);
  ICmpInst *cond1 = new ICmpInst(*insertAfter, CmpInst::ICMP_SLT, y, CONST_I32(0));
  BinaryOperator *op1 = BinaryOperator::CreateAdd(x, CONST_I32(1), "", insertAfter);
  BinaryOperator *op2 = BinaryOperator::CreateMul(op1, x, "", insertAfter);
  BinaryOperator *op3 = BinaryOperator::CreateURem(op2, CONST_I32(2), "", insertAfter);
  ICmpInst *cond2 = new ICmpInst(*insertAfter, CmpInst::ICMP_EQ, op3, CONST_I32(0));
  return BinaryOperator::CreateOr(cond1, cond2, "", insertAfter);
}

3. 指令替代

1. 原理介绍

指令替代:就是将正常的二元运算指令(如加、减、异或等),替换为更为复杂的指令序列,以达到混淆计算过程的目的。例如将 a + b 替换为 a - (-b),将 a ^ b 替换为 (~a & b) | (a & ~b)等。

实现思路就是扫描所有指令,对目标指令进行替换

bool Substitution::runOnFuntion(Function &F){
  for(int i=0; i < ObfuTime; i++){
    vector<Instruction*> origInst;
    for(Instruction &I : BB){
      origInst.push_back(&I);
    }
    for(Instruction *I : origInst){
      if(isa<BinaryOperator>(I)){
        BinaryOperator *BI = cast<BinaryOperator>(I);
        substitute(BI);
      }
    }
  }
}

2. 加法替换

  • a = b + c

    • addNeg:a = b -(-c)

    • addDoubleNeg:a = -(-b + (-c))

    • addRand:r = rand(); a = b + r; a = a + c; a = a - r;

    • addRand2:r = rand(); a = b-r; a = a + b; a = a + r;

3. 减法替换

  • a = b - c:

    • subNeg:a = b + (-c)

    • subRand:r = rand(); a = b + r; a = a - c; a = a - r;

    • subRand2:r = rand(); a = b - r; a = a - c; a = a+r;

4. 与指令

  • a = b & c

    • andSubsitute:a = (b * ~c) & b

    • andSubstituteRand:a = ~(~b | ~c) & (r | ~r)

5. 或指令

  • a = b | c

    • orSubstitute:a = ( b & c) | ( b ^ c )

    • orSubstituteRand:a = ~(~b & ~c) & (r | ~r)

6. 异或指令

  • a = b ^ c

    • xorSubstitute:a = (~a & b) | (a & ~b)

    • xorsubstituteRand:a = (b ^ r) ^ (c ^ r) <=> a = (~b & r | b & ~r) ^ (~c & r | c & ~r)