我的LLVM学习笔记——OLLVM混淆研究之FLA篇
因为要做代码保护,所以抽时间研究了下OLLVM中的三种保护方案:BCF(Bogus Control Flow,中文名虚假控制流)、FLA(Control Flow Flattening,中文名控制流平坦化)、SUB(Instructions Substitution,中文名指令替换),本文是FLA介绍篇。
1,查看头文件 llvm/Transforms/Obfuscation/Flattening.h 可知,FLA暴露给外界的函数如下:
Pass *createFlattening();
Pass *createFlattening(bool flag);
这两个函数用于创建FLA对应的PASS,这两个函数主要区别表现在toObfuscate函数上,用于判断当前函数是否需要进行FLA保护,具体分析见第4步。
2,在OLLVM中,FLA的PASS通过PassManager进行管理,FLA对应的PASS添加和调用参见://need add
3,FLA调用入口是runOnFunction函数,如下所示:首先调用toObfuscate函数判断当前函数是否需要采用FLA保护,如果采用,则调用flatten函数执行真正的FLA保护。接下来,我们首先分析toObfuscate函数的判断逻辑,再分析FLA的保护逻辑。
bool Flattening::runOnFunction(Function &F) {Function *tmp = &F;// Do we obfuscateif (toObfuscate(flag, tmp, "fla")) {if (flatten(tmp)) {++Flattened;}}return false;
}
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 nofla flag first// Because .find("fla") is true for a string like "fla" or// "nofla"if (readAnnotate(f).find(attrNo) != std::string::npos) {return false;}// If fla annotationsif (readAnnotate(f).find(attr) != std::string::npos) {return true;}
首先检查当前函数是不是仅仅是一个函数声明,如果是的话则返回false,即不进行fla保护;
接着检查这个函数是不是extern函数,如果是的话返回false;
再接着读取这个函数上的标注值,如果找到了'nofla',则返回false;
读取函数标注值时如果找到了'fla',则返回true;
4.2,flag分析
// If fla flag is setif (flag == true) {return true;}
在上面的检测都完成后如果还没有返回,则再检查一下flag(能到这一步说明函数上不属于外部函数,也不是纯声明函数,而且没有对应的标注),如果是true,则返回true,否则返回false。
5,Flattening(/lib/Transforms/Obfuscation/Flattening.cpp)-->flatten
在toObfuscate函数调用后,如果判定需要混淆,那么开始调用flatten函数进行FLA保护,主要原理就是创建一个大的switch循环,第一个块直接跳入这个循环中,其余块在将该函数。具体细节我们分段来分析(省略部分无关代码):
5.1,生成随机数key
// SCRAMBLERchar scrambling_key[16];llvm::cryptoutils->get_bytes(scrambling_key, 16);// END OF SCRAMBLER
这一段很简单,就是相当于生成了一个16位的key,这个key主要是用来生成随机数的
5.2,转换所有的switch指令
// Lower switchFunctionPass *lower = createLowerSwitchPass();lower->runOnFunction(*f);
这一段是通过一个LowerSwitch PASS来把该函数中的所有switch指令转为if指令
5.3,保存函数中需要平坦化的块
// Save all original BBfor (Function::iterator i = f->begin(); i != f->end(); ++i) {BasicBlock *tmp = &*i;origBB.push_back(tmp);BasicBlock *bb = &*i;if (isa<InvokeInst>(bb->getTerminator())) {return false;}}// Nothing to flattenif (origBB.size() <= 1) {return false;}// Remove first BBorigBB.erase(origBB.begin());
遍历函数中的所有块,并把它们添加到origBB这个集合中。如果在遍历过程中发现某个块末尾是一条Invoke指令(也就是调用其它函数指令,则返回,该函数不进行保护。遍历完成后,如果发现origBB块的数目不大于1,则返回(块太少了)。最后的最后,把origBB中保存的函数的第一个块去掉(第一块是函数的入口块,顺序不能乱啊)
5.4,第一个基本块处理
// Get a pointer on the first BBFunction::iterator tmp = f->begin(); //++tmp;BasicBlock *insert = &*tmp;// If main begin with an ifBranchInst *br = NULL;if (isa<BranchInst>(insert->getTerminator())) {br = cast<BranchInst>(insert->getTerminator());}if ((br != NULL && br->isConditional()) ||insert->getTerminator()->getNumSuccessors() > 1) {BasicBlock::iterator i = insert->end();--i;if (insert->size() > 1) {--i;}BasicBlock *tmpBB = insert->splitBasicBlock(i, "first");origBB.insert(origBB.begin(), tmpBB);}
令insert指针指向函数的第一个基本块,如果第一个基本块末尾是一条BranchInst指令,也就是分支指令(注意:可能是有条件分支,也就是通常的if else形式的,也有可能是无条件分支,相当于goto了),则令br指向这条分支指令;
如果br是一条有条件跳转分支指令(后继块这时有两个),则对这个基本块进行切割,会将最后一条分支跳转指令连同它前面的一条指令一起分割到一个新的基本块中,并为其取名first块,举个例子:
define i32 @fib(i32 %AnArg) {
EntryBlock:%cond = icmp sle i32 %AnArg, 2br i1 %cond, label %return, label %recursereturn: ; preds = %EntryBlockret i32 1recurse: ; preds = %EntryBlock%arg = sub i32 %AnArg, 1%fibx1 = tail call i32 @fib(i32 %arg)%arg1 = sub i32 %AnArg, 2%fibx2 = tail call i32 @fib(i32 %arg1)%addresult = add i32 %fibx1, %fibx2ret i32 %addresult
}
这时一个斐波那契数列的函数对应的IR指令,可以看到EntryBlock块末尾就是一个有条件跳转,有两个后继块,分别是return块和recurse块,这个时候对这个块分割后就变成下面这个样子:
define i32 @fib(i32 %AnArg) {
EntryBlock:first:%cond = icmp sle i32 %AnArg, 2br i1 %cond, label %return, label %recursereturn: ; preds = %EntryBlockret i32 1recurse: ; preds = %EntryBlock%arg = sub i32 %AnArg, 1%fibx1 = tail call i32 @fib(i32 %arg)%arg1 = sub i32 %AnArg, 2%fibx2 = tail call i32 @fib(i32 %arg1)%addresult = add i32 %fibx1, %fibx2ret i32 %addresult
}
由于EntryBlock只有两条指令,所以分割后暂时就是一个空块了
5.5,第一个基本块中创建switch循环的判定变量
// Remove jumpinsert->getTerminator()->eraseFromParent();// Create switch variable and set as itswitchVar =new AllocaInst(Type::getInt32Ty(f->getContext()), 0, "switchVar", insert);new StoreInst(ConstantInt::get(Type::getInt32Ty(f->getContext()),llvm::cryptoutils->scramble32(0, scrambling_key)),switchVar, insert);
上面提到,insert块指向函数中的第一个基本块。
首先移除insert块的terminator指令(其实上面应该就算是移除了);
接着通过AllocaInst指令为变量switchVar申请一块空间,switchVar变量的类型是32位(LLVM中的类型用多少多少位表示);
最后通过StoreInst指令为switchVar变量赋一个伪随机值,注意这个值的生成(通过scramble32函数生成)受两个因素影响,如果这两个值不变,生成的随机值也不变。
在创建AllocaInst指令和StoreInst指令时指定了insert指针作为最后一个变量,所以这两条指令被添加到insert执行的块,也就是第一块中。
5.6,设置循环块
// Create main looploopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);load = new LoadInst(switchVar, "switchVar", loopEntry);// Move first BB on topinsert->moveBefore(loopEntry);BranchInst::Create(loopEntry, insert);// loopEnd jump to loopEntryBranchInst::Create(loopEntry, loopEnd);BasicBlock *swDefault =BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);BranchInst::Create(loopEnd, swDefault);// Create switch instruction itself and set conditionswitchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry);switchI->setCondition(load);// Remove branch jump from 1st BB and make a jump to the whilef->begin()->getTerminator()->eraseFromParent();BranchInst::Create(loopEntry, &*f->begin());
首先创建两个基本块loopEntry、loopEnd;
然后在loopEntry块中创建一条读取switchVar值的指令,然后以这个switchVar为判定变量创建一个switch循环;
调整一下insert块(也就是函数中第一个基本块)与loopEntry的顺序,insert块在前,loopEntry块在后;
再创建一个switchDefault块,switchDefault块添加无条件跳转到loopEnd块指令;
在第一个基本块尾部添加无条件跳转到loopEntry块指令。
还是以上面介绍的斐波那契数列函数为例,这个时候就成这个样子了
define i32 @fib(i32 %AnArg) {
EntryBlock:%switchVar = alloca i32store i32 -1287043439, i32* %switchVarbr label %loopEntryloopEntry: ; preds = %EntryBlock, %loopEnd%switchVar2 = load i32, i32* %switchVarswitch i32 %switchVar2, label %switchDefault []switchDefault: ; preds = %loopEntrybr label %loopEndloopEnd: ; preds = %first, %switchDefaultbr label %loopEntry
}
5.7,为原有基本块设置跳入的label case值
// Put all BB in the switchfor (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end();++b) {BasicBlock *i = *b;ConstantInt *numCase = NULL;// Move the BB inside the switch (only visual, no code logic)i->moveBefore(loopEnd);// Add case to switchnumCase = cast<ConstantInt>(ConstantInt::get(switchI->getCondition()->getType(),llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key)));switchI->addCase(numCase, i);}
逻辑很简单,对于在origBB中的每个块,都为其生成一个case值,并把这个case值添加到swich指令中,然后把当前这个块添加到loopEnd前。
这里有个地方需要注意,对于origBB中的第一个块来说,在计算case值时由于给llvm::cryptoutils->scramble32函数传入的第一个参数为0,所以这个地方计算出的随机数(也就是当前块对应的switch指令case值)与5.5中给switchVar变量赋的初值相同,这就保证了从第一个基本块进行loopEntry做switch跳转时可以顺利跳转后它的后继块(也就是origBB中的第一个块)
5.8,调整原有基本块与后继块的跳转
// Recalculate switchVarfor (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end();++b) {BasicBlock *i = *b;ConstantInt *numCase = NULL;// Ret BBif (i->getTerminator()->getNumSuccessors() == 0) {continue;}// If it's a non-conditional jumpif (i->getTerminator()->getNumSuccessors() == 1) {// Get successor and delete terminatorBasicBlock *succ = i->getTerminator()->getSuccessor(0);i->getTerminator()->eraseFromParent();// Get next casenumCase = switchI->findCaseDest(succ);// If next case == default case (switchDefault)if (numCase == NULL) {numCase = cast<ConstantInt>(ConstantInt::get(switchI->getCondition()->getType(),llvm::cryptoutils->scramble32(switchI->getNumCases() - 1, scrambling_key)));}// Update switchVar and jump to the end of loopnew StoreInst(numCase, load->getPointerOperand(), i);BranchInst::Create(loopEnd, i);continue;}// If it's a conditional jumpif (i->getTerminator()->getNumSuccessors() == 2) {// Get next casesConstantInt *numCaseTrue =switchI->findCaseDest(i->getTerminator()->getSuccessor(0));ConstantInt *numCaseFalse =switchI->findCaseDest(i->getTerminator()->getSuccessor(1));// Check if next case == default case (switchDefault)if (numCaseTrue == NULL) {numCaseTrue = cast<ConstantInt>(ConstantInt::get(switchI->getCondition()->getType(),llvm::cryptoutils->scramble32(switchI->getNumCases() - 1, scrambling_key)));}if (numCaseFalse == NULL) {numCaseFalse = cast<ConstantInt>(ConstantInt::get(switchI->getCondition()->getType(),llvm::cryptoutils->scramble32(switchI->getNumCases() - 1, scrambling_key)));}// Create a SelectInstBranchInst *br = cast<BranchInst>(i->getTerminator());SelectInst *sel =SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "",i->getTerminator());// Erase terminatori->getTerminator()->eraseFromParent();// Update switchVar and jump to the end of loopnew StoreInst(sel, load->getPointerOperand(), i);BranchInst::Create(loopEnd, i);continue;}}
上面这一堆代码主要做了一件事,那就是循环遍历origBB中的所有块,找到当前块的后继块对应的switch循环中的case值,然后把它赋值给switchVar变量,最后再无条件跳转到loopEnd块。由于loopEnd会无条件跳转到loopEntry,所以也就实际上跳入了loopEntry循环。具体来说又分为三种情况:
1)如果当前块没有后继块,什么都不做了,这个块也算是switch循环的跳出点了
2)当前块有一个后继块,那就首先找到这个后继块succ,然后在switch指令中找到succ对应的case值,接着更新switchVar变量的值,保证下一次循环可以跳到succ块,最后令当前块无条件跳入loopEnd块
3)当前块有两个后继块,那就分别找到这两个后继块对应的case值,暂时标记为numCaseTrue和numCaseFalse,然后创建一条SelectInst指令,以便选择出当前块后继块对应的case值,用这个选出来的case值更新switchVar变量,最后令当前块无条件跳入loopEnd块
好了,以上就是OLLVM中FLA算法的主要实现逻辑,最后添加一个做完变换后的IR
define i32 @fib(i32 %AnArg) {
EntryBlock:%switchVar = alloca i32store i32 -1287043439, i32* %switchVarbr label %loopEntryloopEntry: ; preds = %EntryBlock, %loopEnd%switchVar2 = load i32, i32* %switchVarswitch i32 %switchVar2, label %switchDefault [i32 -1287043439, label %firsti32 1604828523, label %returni32 -296943929, label %recurse]switchDefault: ; preds = %loopEntrybr label %loopEndfirst: ; preds = %loopEntry%cond = icmp sle i32 %AnArg, 2%0 = select i1 %cond, i32 1604828523, i32 -296943929store i32 %0, i32* %switchVarbr label %loopEndreturn: ; preds = %loopEntryret i32 1recurse: ; preds = %loopEntry%arg = sub i32 %AnArg, 1%fibx1 = tail call i32 @fib(i32 %arg)%arg1 = sub i32 %AnArg, 2%fibx2 = tail call i32 @fib(i32 %arg1)%addresult = add i32 %fibx1, %fibx2ret i32 %addresultloopEnd: ; preds = %first, %switchDefaultbr label %loopEntry
}
我的LLVM学习笔记——OLLVM混淆研究之FLA篇相关推荐
- oracle rac 仲裁盘_【学习笔记】深入研究Oracle RAC节点驱逐的条件和案例
天萃荷净 Oracle研究中心学习笔记:分享一篇关于Oracle数据库RAC环境中节点间管理的文章,详细介绍了RAC节点驱逐条件和管理方法. 本站文章除注明转载外,均为本站原创: 转载自loveOra ...
- 【学习笔记】 科目一之概念篇
[学习笔记] 科目一之概念篇 概念题方法 1)抓重点:科目一设计知识范围太广,不要妄想所有知识点都复习到,这是不可能的,我们的目标是45分几个而不是考高分,复习时间有限,所以要学会抓重点,比如法律条文 ...
- Spring.NET学习笔记10——方法的注入(基础篇) Level 200
多数用户都会将容器中的大部分对象布署为singleton模式.当一个singleton对象需要和另一个singleton对象协作,或者一个非singleton对象需要和另一个非singleson对象协 ...
- Python学习入门基础教程(learning Python)--4.2.3 Python的for实现递归,(0629学习笔记)我研究出来了!...
呵呵,本节主要简单的讨论一下用for来实现递归操作. 先看一下本节设计的小程序要求,有这样一个list: [1, 2, [3, 4], 5, 6, [7, 8, 9]] 用for循环打印结果是什么样的 ...
- 论文书写学习笔记之论文研究假设
论文研究假设 "研究假设(hypothesis)",特别重要,它连接了理论和数据,是贯穿一篇文章的黄金线.读一篇实证文章的时候,哪怕什么都没看懂,也要把研究假设看懂了,看懂了研究假 ...
- LLVM学习笔记(43-2)
V7.0的变化 V7.0的 SubtargetEmitter::EmitProcessorModels()改写颇多,因为对处理器的描述进行了相当程度的增强. 1344 void SubtargetE ...
- LLVM学习笔记(43)
3.6.2.5. 输出代码与数据结构 3.6.2.5.1. 资源使用与时延 SchedTables保存在WriteProcResources,WriteLatencies,ReadAdvanceEnt ...
- LLVM学习笔记(16)
3.4.2.4. PatFrag的处理 3.4.2.4.1. 模式树的构建 PatFrag是一个可重用的构件,TableGen会在PatFrag出现的地方展开其定义,有点像C/C++中的宏.为此,Co ...
- llvm学习笔记(2)
2. LLVM的后端描述 2.1. 类型描述 为了更好地描述寄存器所能支持的值类型(大小),以及操作数的类型(大小),Tablegen在ValueTypes.td里给出了一系列的类型定义,它们都继承自 ...
最新文章
- java socket modbus_Java modbus tcp 编程有懂得吗?给个示例看看。。。十分感谢。
- bootstrap栅格分5等分
- 如何设置input实现同时选中多个文件并同时上传
- ArrayList实现原理
- Android 性能优化——布局优化
- python会议室系统预定_python项目篇-酒店(会议室,电影)预定
- 面经——嵌入式软件工程师ARM体系与架构相关
- HDU - 4608 I-number
- 如何通过大华sdk采集一帧图像?_EasyData解放数据标注员双手,采集清洗标注一站搞定...
- 大文件打开工具 PilotEdit
- python中center()函数的用法
- js时间和时间戳之间如何转换(汇总)
- boost电路输出电流公式_BOOST电路参数计算公式
- OpenAI发布DALL·E 2
- 干货!底层视觉研究,我们应该往哪里走?
- Spring Boot Actuator自定义健康检查
- 2019 面试实战 - 第二回合
- sqlserver 查看服务器名称以及sql语句跨服务器查询设置
- php延时5秒显示,JS/jQuery实现DIV延时几秒后消失或显示
- 2022.12.28雷神加速器更新问题
热门文章
- 自动化测试岗位求职简历编写规范+注意事项,让你的简历脱颖而出
- 透明GIF图片显示控件
- 金鸡落幕:拓荒电影元宇宙,还需先烧“三把火”
- IGARSS2017_SSRN 读后感DEEP RESIDUAL NETWORKS FOR HYPERSPECTRAL IMAGE CLASSIFICATION
- 【华为OD机试真题 python】竖直四子棋【2022 Q4 | 200分】
- 【组队学习】【31期】李宏毅机器学习(含深度学习)
- 通过div+css做出好看的横排导航栏
- 视博云虚拟现实制造业技术创新战略联盟共创VR产业新未来!
- 基于SSM美发店管理系统
- linux在开始ubifs前错误,UBIFS文件系统说明文档答题.docx