本文为看雪论坛精华文章
看雪论坛作者ID:34r7hm4n
0x00. 什么是LLVM和LLVM Pass
更详细的内容可以看:深入浅出让你理解什么是LLVM
0x01. 首先写一个能跑起来的LLVM Pass
test.sh
./build/bin/clang -c -emit-llvm test.cpp -o test.bc
./build/bin/opt -load ./build/lib/LLVMHello.so -hello test.bc -o /dev/null
void func1(){}
void func2(){}
int main(){
puts("Hello!");
}
然而示例代码还是有点复杂,可以把它的代码简化一下,方便我们理解:
Obfu.cpp
using namespace llvm;
namespace{
struct Obfu : public FunctionPass{
static char ID;
Obfu() : FunctionPass(ID){}
bool runOnFunction(Function &F) override{
outs() << "Function: " << F.getName() << "n";
return false;
}
};
}
char Obfu::ID = 0;
static RegisterPass<Obfu> X("obfu", "My obfuscating pass");
0x02. 控制流平坦化的基本思想和实现思路

经过控制流平坦化后的执行流程就如下图:
可以看到除了基本块1外,其他的基本块都集中到了同一个层次上。不同基本块的调度顺序由主分发器决定(在程序里可以看做一个switch,不同的基本块就对应不同的case)。这样可以模糊基本块之间的前后关系,增加程序分析的难度。
基本块1
基本块2
if(condition){
基本块3
}else{
基本块4
}
基本块5
基本块6
基本块1
switchVar = 2;
while(true){
switch(switchVar){
case 2:
基本块2
switchVar = condition ? 3 : 4;
case 3:
基本块3
switchVar = 5
case 4:
基本块4
switchVar = 5
case 5:
基本块5
switchVar = 6
case 6:
基本块6
goto end;
}
}
end:
0x03. 基于LLVM Pass实现控制流平坦化
LLVM Pass的一些重要API也很有必要看一看:LLVM Programmer’s Manual(https://llvm.org/docs/ProgrammersManual.html)
using namespace llvm;
namespace{
struct Obfu : public FunctionPass{
static char ID;
Obfu() : FunctionPass(ID){}
bool flatten(Function *f);
bool runOnFunction(Function &F);
};
}
bool Obfu::runOnFunction(Function &F){
return flatten(&F);
}
bool Obfu::flatten(Function *f){
}
char Obfu::ID = 0;
static RegisterPass<Obfu> X("obfu", "My obfuscating pass");
// 遍历函数所有基本块,将其存到vector中
vector<BasicBlock*> origBB;
for(BasicBlock &BB: *f){
origBB.push_back(&BB);
}
// 基本块数量不超过1则无需平坦化
if(origBB.size() <= 1){
return false;
}
// 从vector中去除第一个基本块
origBB.erase(origBB.begin());
BasicBlock *firstBB = &f->front();
// 如果第一个基本块的末尾是条件跳转
if(isa<BranchInst>(firstBB->getTerminator())){
BranchInst *br = cast<BranchInst>(firstBB->getTerminator());
if(br->isConditional()){
CmpInst *cmpInst = cast<CmpInst>(firstBB->getTerminator()->getPrevNode());
BasicBlock *newBB = firstBB->splitBasicBlock(cmpInst,"newBB");
origBB.insert(origBB.begin(), newBB);
}
}
// 创建循环
BasicBlock *loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, firstBB);
BasicBlock *loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, firstBB);
firstBB->moveBefore(loopEntry);
// 去除第一个基本块末尾的跳转
firstBB->getTerminator()->eraseFromParent();
// 用随机数初始化switch on变量
srand(time(0));
int randNumCase = rand();
AllocaInst *swVarPtr = new AllocaInst(int32Type, 0, "swVar.ptr", firstBB);
new StoreInst(ConstantInt::get(int32Type, randNumCase), swVarPtr, firstBB);
// 使第一个基本块跳转到loopEntry
BranchInst::Create(loopEntry, firstBB);
// 在进入loopEntry读取switch on变量
LoadInst *swVar = new LoadInst(int32Type, swVarPtr, "swVar", false, loopEntry);
BranchInst::Create(loopEntry, loopEnd);
// 初始化switch的default case
// default case实际上不会被执行
BasicBlock *swDefault = BasicBlock::Create(f->getContext(), "swDefault", f, loopEnd);
BranchInst::Create(loopEnd, swDefault);
SwitchInst *swInst = SwitchInst::Create(swVar, swDefault, 0, loopEntry);
for(BasicBlock *BB : origBB){
ConstantInt *numCase = cast<ConstantInt>(ConstantInt::get(int32Type, randNumCase));
BB->moveBefore(loopEnd);
swInst->addCase(numCase,BB);
randNumCase = rand();
}
所有基本块按后继基本块的数量分成了三类:
-
第一类是没有后继基本块,这类基本块一般是以retn或者call exit结尾的基本块,统一叫做retn BB这类基本块不用做处理。
-
第二类是仅有一个后继基本块,即以非条件跳转结尾的基本块,在这类基本块的末尾我们需要更新switch on的变量,使下一轮循环中能够按原有的逻辑到达下一个基本块。
-
第三类是有两个后继基本块,即以条件跳转结尾的基本块,在这类基本块的末尾我们要插入select指令,类似于C语言的三元运算符。
// 添加case
for(BasicBlock *BB : origBB){
// retn BB
if(BB->getTerminator()->getNumSuccessors() == 0){
continue;
}
if(BB->getTerminator()->getNumSuccessors() == 1){
BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
BB->getTerminator()->eraseFromParent();
ConstantInt *numCase = swInst->findCaseDest(sucBB);
new StoreInst(numCase, swVarPtr, BB);
BranchInst::Create(loopEnd, BB);
continue;
}
if(BB->getTerminator()->getNumSuccessors() == 2){
ConstantInt *numCaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccessor(0));
ConstantInt *numCaseFalse = swInst->findCaseDest(BB->getTerminator()->getSuccessor(1));
BranchInst *br = cast<BranchInst>(BB->getTerminator());
SelectInst *sel = SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "", BB->getTerminator());
BB->getTerminator()->eraseFromParent();
new StoreInst(sel, swVarPtr, BB);
BranchInst::Create(loopEnd, BB);
}
}
0x04. 混淆效果测试
看雪ID:34r7hm4n
https://bbs.pediy.com/user-home-910514.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
本文始发于微信公众号(看雪学院):基于LLVM Pass实现控制流平坦化
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论