本文主要是介绍我的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 correctif (ObfTimes <= 0) {errs()<<"BogusControlFlow application number -bcf_loop=x must be x > 0";return false;}// Check if the number of applications is correctif ( !((ObfProbRate > 0) && (ObfProbRate <= 100)) ) {errs()<<"BogusControlFlow application basic blocks percentage -bcf_prob=x must be 0 < x <= 100";return false;}// If fla annotationsif(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 declarationif (f->isDeclaration()) {return false;}// Check external linkageif(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 annotationsif (readAnnotate(f).find(attr) != std::string::npos) {return true;}
首先检查当前函数是不是仅仅是一个函数声明,如果是的话则返回false,即不进行BCF保护;
接着检查这个函数是不是extern函数,如果是的话返回false;
再接着读取这个函数上的标注值,如果找到了'nobcf',则返回false;
读取函数标注值时如果找到了'bcf',则返回true;
4.2,flag分析
// If bcf flag is setif (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 functionbool 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 liststd::list<BasicBlock *> 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' selectionif((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 listbasicBlocks.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 blockTwine * 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 conditionTwine * 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<Instruction*> toEdit, toDelete;BinaryOperator *op,*op1 = NULL;LoadInst * opX , * opY;ICmpInst * condition, * condition2;
2)寻找所有的恒为true的语句
std::vector<Instruction*> toEdit, toDelete;BinaryOperator *op,*op1 = NULL;LoadInst * opX , * opY;ICmpInst * condition, * condition2;// Looking for the conditions and branches to transformfor(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 conditiontoEdit.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 foundfor(std::vector<Instruction*>::iterator i =toEdit.begin();i!=toEdit.end();++i){//if y < 10 || x*(x-1) % 2 == 0opX = 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 foundfor(std::vector<Instruction*>::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篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!