因为要做代码保护,所以抽时间研究了下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<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' 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<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 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<Instruction*>::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<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变换的基本代码逻辑。附一张官方变换前后图