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

2024-02-17 00:32

本文主要是介绍我的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篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/716157

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件