我的LLVM学习笔记——OLLVM混淆研究之BCF篇

因为要做代码保护,所以抽时间研究了下OLLVM中的三种保护方案:BCF(Bogus Control Flow,中文名虚假控制流)、FLA(Control Flow Flattening,中文名控制流平坦化)、SUB(Instructions Substitution,中文名指令替换),本文是BCF介绍篇。

1,查看BCF的头文件,暴露给外界的两个函数如下:

// Namespace
namespace llvm {
	Pass *createBogus ();
	Pass *createBogus (bool flag);
}

两个函数用于创建BCF对应的PASS,这两个函数主要区别表现在toObfuscate函数上,用于判断当前函数是否需要进行BCF保护。

2,在OLLVM中,BCF的PASS通过PassManager进行管理,BCF对应的PASS添加和调用参见://need add

3,BCF调用入口是runOnFunction函数,如下所示:

    virtual bool runOnFunction(Function &F){
      // Check if the percentage is correct
      if (ObfTimes <= 0) {
        errs()<<"BogusControlFlow application number -bcf_loop=x must be x > 0";
		return false;
      }

      // Check if the number of applications is correct
      if ( !((ObfProbRate > 0) && (ObfProbRate <= 100)) ) {
        errs()<<"BogusControlFlow application basic blocks percentage -bcf_prob=x must be 0 < x <= 100";
		return false;
      }
      // If fla annotations
      if(toObfuscate(flag,&F,"bcf")) {
        bogus(F);
        doF(*F.getParent());
        return true;
      }

      return false;
    } // end of runOnFunction()

1)首先检查ObfTimes是否不比0大,如果是的话就不进行混淆了。ObfTimes代表在一个函数上的混淆次数,在编译程序时可以通过下面参数进行设置:

-mllvm -bcf_loop=3 

这代表在每个函数上进行三次BCF混淆。

2)接着检查ObfProbRate的取值范围是不是在0到100之间,如果不是,则不进行混淆。ObfProbRate代表每个基本块被混淆的概率,在编译程序时可以通过下面参数进行设置:

-mllvm -bcf_prob=40

这代码每个基本块有40%的概率进行BCF混淆

3)通过toObfuscate函数来检查创建BCF时传入的flag值以及待保护函数上的标注值,如果检查通过则开始进行BCF混淆。下面我们在第4步对toObfuscate函数进行分析,在第5步对BCF混淆步骤进行分析

4,Utils(/lib/Transforms/Obfuscation/Utils.cpp)-->toObfuscate

其函数原型如下:

bool toObfuscate(bool flag, Function *f, std::string attribute)

它可以接收三个参数:第一个参数flag就是我们在创建FLA的PASS时传入的那个flag值(见第1步),如果在创建PASS时没有传递flag值,则默认为false;第二个参数是我们当前正在分析的函数对应的指针,注意这里的函数指的是处于IR状态下的函数;第三个参数是标注字符串(可以在编写代码时通过attribute关键字添加)。这个函数总体来说分为两个步骤,标注分析与flag分析:

4.1,标注分析

  std::string attr = attribute;
  std::string attrNo = "no" + attr;
 
  // Check if declaration
  if (f->isDeclaration()) {
    return false;
  }
 
  // Check external linkage
  if(f->hasAvailableExternallyLinkage() != 0) {
    return false;
  }
 
  // We have to check the nobcf flag first
  // Because .find("bcf") is true for a string like "bcf" or
  // "nofla"
  if (readAnnotate(f).find(attrNo) != std::string::npos) {
    return false;
  }
 
  // If bcf annotations
  if (readAnnotate(f).find(attr) != std::string::npos) {
    return true;
  }


首先检查当前函数是不是仅仅是一个函数声明,如果是的话则返回false,即不进行BCF保护;

接着检查这个函数是不是extern函数,如果是的话返回false;

再接着读取这个函数上的标注值,如果找到了'nobcf',则返回false;

读取函数标注值时如果找到了'bcf',则返回true;

4.2,flag分析

  // If bcf flag is set
  if (flag == true) {
    return true;
  }

在上面的检测都完成后如果还没有返回,则再检查一下flag(能到这一步说明函数上不属于外部函数,也不是纯声明函数,而且没有对应的标注),如果是true,则返回true,否则返回false。

5,BogusControlFlow(/lib/Transforms/Obfuscation/BogusControlFlow.cpp)-->bogus(Function &F)

紧接着上面第3步,在检查通过确认需要进行BCF混淆后,先调用bogus函数,再调用doF函数。其中bogus函数会进行实际的BCF混淆,而doF主要是替换模块中永远为true的语句。bogus代码分段解析如下:

5.1,统计和调试信息

void bogus(Function &F) {
      // For statistics and debug
      ++NumFunction;
      int NumBasicBlocks = 0;
      bool firstTime = true; // First time we do the loop in this function
      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");
      if(ObfProbRate < 0 || ObfProbRate > 100){
        DEBUG_WITH_TYPE("opt", errs() << "bcf: Incorrect value,"
            << " probability rate set to default value: "
            << defaultObfRate <<" \n");
        ObfProbRate = defaultObfRate;
      }
      DEBUG_WITH_TYPE("opt", errs() << "bcf: How many times: "<< ObfTimes<< "\n");
      if(ObfTimes <= 0){
        DEBUG_WITH_TYPE("opt", errs() << "bcf: Incorrect value,"
            << " must be greater than 1. Set to default: "
            << defaultObfTime <<" \n");
        ObfTimes = defaultObfTime;
      }
      NumTimesOnFunctions = ObfTimes;
      int NumObfTimes = ObfTimes;

        

上面这段代码主要进行统计和调试用,另外记录下传入的混淆次数,作为后面循环体的判定变量(NumObfTimes)

5.2,记录基本块

do{
          DEBUG_WITH_TYPE("cfg", errs() << "bcf: Function " << F.getName()
              <<", before the pass:\n");
          DEBUG_WITH_TYPE("cfg", F.viewCFG());
          // Put all the function's block in a list
          std::list basicBlocks;
          for (Function::iterator i=F.begin();i!=F.end();++i) {
            basicBlocks.push_back(&*i);
          }
          DEBUG_WITH_TYPE("gen", errs() << "bcf: Iterating on the Function's Basic Blocks\n");

上面这段代码是遍历函数的所有基本块,然后保存到basicBlocks中

5.3,对单个基本块添加虚假控制流

          while(!basicBlocks.empty()){
            NumBasicBlocks ++;
            // Basic Blocks' selection
            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;
              // Add bogus flow to the given Basic Block (see description)
              BasicBlock *basicBlock = basicBlocks.front();
              addBogusFlow(basicBlock, F);
            }
            else{
              DEBUG_WITH_TYPE("opt", errs() << "bcf: Block "
                  << NumBasicBlocks <<" not selected.\n");
            }
            // remove the block from the list
            basicBlocks.pop_front();

            if(firstTime){ // first time we iterate on this function
              ++InitNumBasicBlocks;
              ++FinalNumBasicBlocks;
            }
          } 

对于函数中的基本块,随机决定当前基本块是否要进行混淆,如果被选中,则调用addBogusFlow函数进行虚假控制流添加,addBogusFlow是进行BCF的核心,其具体细节如下:

5.3.1,BogusControlFlow(/lib/Transforms/Obfuscation/BogusControlFlow.cpp)-->addBogusFlow(BasicBlock * basicBlock, Function &F)

addBogusFlow函数分块代码如下:

1)分割基本块

      BasicBlock::iterator i1 = basicBlock->begin();
      if(basicBlock->getFirstNonPHIOrDbgOrLifetime())
        i1 = (BasicBlock::iterator)basicBlock->getFirstNonPHIOrDbgOrLifetime();
      Twine *var;
      var = new Twine("originalBB");
      BasicBlock *originalBB = basicBlock->splitBasicBlock(i1, *var);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: First and original basic blocks: ok\n");

上面这段代码是对当前这个基本块进行分割,分割完成后第一个块中只包含PHI和调试信息,第二块(新名字是originalBB)则保存剩余的指令

2)创建alteredBB块(一个虚假块)

      Twine * var3 = new Twine("alteredBB");
      BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Altered basic block: ok\n");

以originalBB块为模板创建alteredBB块,在创建时会复制originalBB块,然后复制出来的块上添加一些花指令。createAlteredBasicBlock的具体逻辑见后面第7步

3)调整basicBlock块与alteredBB块的尾部节点

      alteredBB->getTerminator()->eraseFromParent();
      basicBlock->getTerminator()->eraseFromParent();
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Terminator removed from the altered"
          <<" and first basic blocks\n");

把alteredBB块(上面创建的虚假块)和basicBlock(只包含PHI和调试信息的块)尾部的terminator指令(通常是一个块的结尾点,如return指令和branch指令)从其对应的块中擦除,这么做主要是取消它们与原有的后继块的关系。

4)创建一个总是true的比较指令

      Value * LHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);
      Value * RHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Value LHS and RHS created\n");

      // The always true condition. End of the first block
      Twine * var4 = new Twine("condition");
      FCmpInst * condition = new FCmpInst(*basicBlock, FCmpInst::FCMP_TRUE , LHS, RHS, *var4);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Always true condition created\n");

上面这个比较是浮点数1.0和1.0的一个比较式,condition为此比较指令

5)创建basicBlock、originalBB、alteredBB三个块的逻辑跳转关系

      // Jump to the original basic block if the condition is true or
      // to the altered block if false.
      BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);
      DEBUG_WITH_TYPE("gen",
          errs() << "bcf: Terminator instruction in first basic block: ok\n");

      // The altered block loop back on the original one.
      BranchInst::Create(originalBB, alteredBB);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Terminator instruction in altered block: ok\n");

利用上面的那个总为true的比较创建一条分支指令(插入到basicBlock尾部),如果条件为真则从basicBlock跳到originalBB块,如果为假则跳到alteredBB块(实际上永远不会跳到alteredBB块)。

然后在alteredBB块尾部插入一条无条件跳转指令,使其可以跳到originalBB块

6)继续分割originalBB块

      BasicBlock::iterator i = originalBB->end();

      // Split at this point (we only want the terminator in the second part)
      Twine * var5 = new Twine("originalBBpart2");
      BasicBlock * originalBBpart2 = originalBB->splitBasicBlock(--i , *var5);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Terminator part of the original basic block"
          << " is isolated\n");
      // the first part go either on the return statement or on the begining
      // of the altered block.. So we erase the terminator created when splitting.
      originalBB->getTerminator()->eraseFromParent();
      // We add at the end a new always true condition
      Twine * var6 = new Twine("condition2");
      FCmpInst * condition2 = new FCmpInst(*originalBB, CmpInst::FCMP_TRUE , LHS, RHS, *var6);
      BranchInst::Create(originalBBpart2, alteredBB, (Value *)condition2, originalBB);

把originalBB块尾部的terminator指令分割到originalBBpart2块中,然后在分割后的originalBB块尾部添加一条分支跳转指令,如果条件为真,则跳到originalBBpart2块,如果为假则跳转到alteredBB块。由于比较指令比较的是浮点数1.0与1.0,因此比较式恒为真,所以实际只会从originalBB块跳到originalBBpart2块。

 

以上就是对单个基本块进行混淆的核心逻辑,下面介绍doF函数逻辑

6,BogusControlFlow(/lib/Transforms/Obfuscation/BogusControlFlow.cpp)-->doF(Module &M)

doF函数会找出模块(一般是当前文件)中所有的永远为true的比较语句(上面第5步在每个基本块中都创建了两个),然后将它们替换为下面语句:

(y < 10 || x * (x + 1) % 2 == 0)

可以看出,实际上面这个语句也永远为真,只不过比单纯的1.0与1.0的比较复杂了一些。doF的具体代码逻辑如下:

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


      std::vector toEdit, toDelete;
      BinaryOperator *op,*op1 = NULL;
      LoadInst * opX , * opY;
      ICmpInst * condition, * condition2;

2)寻找所有的恒为true的语句

      std::vector toEdit, toDelete;
      BinaryOperator *op,*op1 = NULL;
      LoadInst * opX , * opY;
      ICmpInst * condition, * condition2;
      // Looking for the conditions and branches to transform
      for(Module::iterator mi = M.begin(), me = M.end(); mi != me; ++mi){
        for(Function::iterator fi = mi->begin(), fe = mi->end(); fi != fe; ++fi){
          //fi->setName("");
          TerminatorInst * 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){
                  DEBUG_WITH_TYPE("gen",
                      errs()<<"bcf: an always true predicate !\n");
                  toDelete.push_back(cond); // The condition
                  toEdit.push_back(tbb);    // The branch using the condition
                }
              }
            }
          }
          /*
          for (BasicBlock::iterator bi = fi->begin(), be = fi->end() ; bi != be; ++bi){
            bi->setName(""); // setting the basic blocks' names
          }
          */
        }
      }

上面语句比较简单,循环遍历Module中的所有基本块,找出条件为true的比较语句。

3)表达式替换

      // Replacing all the branches we found
      for(std::vector::iterator i =toEdit.begin();i!=toEdit.end();++i){
        //if y < 10 || x*(x-1) % 2 == 0
        opX = new LoadInst ((Value *)x, "", (*i));
        opY = new LoadInst ((Value *)y, "", (*i));

        op = BinaryOperator::Create(Instruction::Sub, (Value *)opX,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 1,
              false), "", (*i));
        op1 = BinaryOperator::Create(Instruction::Mul, (Value *)opX, op, "", (*i));
        op = BinaryOperator::Create(Instruction::URem, op1,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 2,
              false), "", (*i));
        condition = new ICmpInst((*i), ICmpInst::ICMP_EQ, op,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 0,
              false));
        condition2 = new ICmpInst((*i), ICmpInst::ICMP_SLT, opY,
            ConstantInt::get(Type::getInt32Ty(M.getContext()), 10,
              false));
        op1 = BinaryOperator::Create(Instruction::Or, (Value *)condition,
            (Value *)condition2, "", (*i));

        BranchInst::Create(((BranchInst*)*i)->getSuccessor(0),
            ((BranchInst*)*i)->getSuccessor(1),(Value *) op1,
            ((BranchInst*)*i)->getParent());
        DEBUG_WITH_TYPE("gen", errs() << "bcf: Erase branch instruction:"
            << *((BranchInst*)*i) << "\n");
        (*i)->eraseFromParent(); // erase the branch
      }

上面这坨代码,就是在指令i前创建了一个表达式: if y < 10 || x*(x-1) % 2 == 0

4)去除原有的条件式

      // Erase all the associated conditions we found
      for(std::vector::iterator i =toDelete.begin();i!=toDelete.end();++i){
        DEBUG_WITH_TYPE("gen", errs() << "bcf: Erase condition instruction:"
            << *((Instruction*)*i)<< "\n");
        (*i)->eraseFromParent();
      }

以上就是OLLVM中进行BCF变换的基本代码逻辑。附一张官方变换前后图 

我的LLVM学习笔记——OLLVM混淆研究之BCF篇_第1张图片

我的LLVM学习笔记——OLLVM混淆研究之BCF篇_第2张图片 

你可能感兴趣的:(LLVM)