本文为看雪论坛精华文章
看雪论坛作者ID:R1mao
一
传统平坦化
1.收集原函数中所有的基本块,并初始化随机数种子。
2.对入口基本块进行处理,切分基本块保证入口基本块只有一个后继。
3.给每一个基本块分配一个随机数字,并新建一个变量var,在入口基本块中赋值为入口基本块后继基本块对应的数字。
4.构造出基本的switch结构和循环框架,使得switch链接所有原有基本块。
5.修正每个原有基本块的后继,使其跳转至switch结构,并在跳转之前根据后继和跳转条件构造对var的赋值语句。
二
支持异常处理

但是除此之外,有一个指令为InvokeInst指令,这个指令有两个后继,但是一个是正常的基本块处,另一个是跳转到unwind相关的逻辑中,这个unwind基本块的开头必须为LandingPadInst,且只能通过InvokeInst指令来到达。所以当LandingPadInst被插入到SwitchInst中去的时候,由于不是通过InvokeInst到达的,所以会报错。

-
代码修改如下(新增了判断Terminator是否为BranchInst): -
同时去除了LandingPad所在的基本块。
三
增强反反混淆效果
在现存的对抗思路中,无外乎就是模拟执行和静态分析两种思路。模拟执行首先需要定位到混淆后的控制流图中的真实块,即原控制流图中的基本块,然后从每个基本块开始模拟执行,给所有真实块(原函数中的基本块)下断点,当从基本块A出发,位于B中的断点触发,便能很容易的确定基本块后继(B是A的后继),如果遇到存在两个后继的情况则将对应的条件判断取反,从而使其获得两条路径,这样的思路往往采用angr或者unicorn实现,已经存在很多脚本了。而静态分析往往更加的困难,需要定位到对应的控制变量,并根据程序逻辑,找出每个基本块对应的值,并通过判断计算出每个基本块对应的后继。
对于静态分析,更加偏向于经验化,通过少量的修改即可使得其适用性降低,因此静态方法去除控制流平坦化的思路往往被使用的较少,且普适性较差。
而对于模拟执行,为了实现恢复的完整性,往往都是单独的从一个基本块出发,设下断点,确定该基本块的后继是什么。(当然也存在动态trace整个程序的执行流程,只要确定真实块即可简单的trace恢复执行流程,但是往往程序逻辑的触发条件是复杂的,有可能并不能覆盖所有的分支,因此这里不做讨论)
定义如果从起点E出发,要到达节点B必须经过节点A,则可称A支配节点B。
每个节点都存在一个标志位,用于标记每个节点(基本块)是否被访问。
Function *buildUpdateKeyFunc(Module *m)
{
std::vector<Type*> params;
params.push_back(Type::getInt8Ty(m->getContext()));
params.push_back(Type::getInt32Ty(m->getContext()));
params.push_back(Type::getInt32Ty(m->getContext())->getPointerTo());
params.push_back(Type::getInt32Ty(m->getContext())->getPointerTo());
params.push_back(Type::getInt32Ty(m->getContext()));
FunctionType *funcType=FunctionType::get(Type::getVoidTy(m->getContext()),params,false);
Function *func=Function::Create(funcType,GlobalValue::PrivateLinkage,Twine("ollvm"),m);
BasicBlock *entry=BasicBlock::Create(m->getContext(),"entry",func);
BasicBlock *cond=BasicBlock::Create(m->getContext(),"cond",func);
BasicBlock *update=BasicBlock::Create(m->getContext(),"update",func);
BasicBlock *end=BasicBlock::Create(m->getContext(),"end",func);
Function::arg_iterator iter=func->arg_begin();
Value *flag=iter;
Value *len=++iter;
Value *posArray=++iter;
Value *keyArray=++iter;
Value *num=++iter;
IRBuilder<> irb(entry);
Value *i=irb.CreateAlloca(irb.getInt32Ty());
irb.CreateStore(irb.getInt32(0),i);
irb.CreateCondBr(irb.CreateICmpEQ(flag,irb.getInt8(0)),cond,end);
irb.SetInsertPoint(cond);
irb.CreateCondBr(irb.CreateICmpSLT(irb.CreateLoad(i),len),update,end);
irb.SetInsertPoint(update);
Value *pos=irb.CreateLoad(irb.CreateGEP(posArray,irb.CreateLoad(i)));
Value *key=irb.CreateGEP(keyArray,pos);
irb.CreateStore(irb.CreateXor(irb.CreateLoad(key),num),key);
irb.CreateStore(irb.CreateAdd(irb.CreateLoad(i),irb.getInt32(1)),i);
irb.CreateBr(cond);
irb.SetInsertPoint(end);
irb.CreateRetVoid();
return func;
}
IRBuilder<> irb(&*oldEntry->getFirstInsertionPt()); // generate context info key for each block
Value *visitedArray=irb.CreateAlloca(irb.getInt8Ty(),irb.getInt32(origBB.size()));
Value *keyArray=irb.CreateAlloca(irb.getInt32Ty(),irb.getInt32(origBB.size()));
irb.CreateMemSet(visitedArray,irb.getInt8(0),origBB.size(),(MaybeAlign)0);
irb.CreateMemSet(keyArray,irb.getInt8(0),origBB.size()*4,(MaybeAlign)0);
int idx=0;
std::vector<unsigned int> key_list;
DominatorTree tree(*f);
std::map<BasicBlock*,unsigned int> key_map;
std::map<BasicBlock*,unsigned int> index_map;
for(std::vector<BasicBlock *>::iterator b=origBB.begin();b!=origBB.end();b++)
{
BasicBlock *block=*b;
unsigned int num=getUniqueNumber(&key_list);
key_list.push_back(num);
key_map[block]=0;
}
for(std::vector<BasicBlock *>::iterator b=origBB.begin();b!=origBB.end();b++,idx++)
{
BasicBlock *block=*b;
std::vector<Constant*> doms;
int i=0;
for(std::vector<BasicBlock *>::iterator bb=origBB.begin();bb!=origBB.end();bb++,i++)
{
BasicBlock *block0=*bb;
if(block0!=block && tree.dominates(block,block0))
{
doms.push_back(irb.getInt32(i));
key_map[block0]^=key_list[idx];
}
}
irb.SetInsertPoint(block->getTerminator());
Value *ptr=irb.CreateGEP(irb.getInt8Ty(),visitedArray,irb.getInt32(idx));
Value *visited=irb.CreateLoad(ptr);
if(doms.size()!=0)
{
ArrayType *arrayType=ArrayType::get(irb.getInt32Ty(),doms.size());
Constant *doms_array=ConstantArray::get(arrayType,ArrayRef<Constant*>(doms));
GlobalVariable *dom_variable=new GlobalVariable(*(f->getParent()),arrayType,false,GlobalValue::LinkageTypes::PrivateLinkage,doms_array,"doms");
irb.CreateCall(FunctionCallee(updateFunc),{visited,irb.getInt32(doms.size()),irb.CreateGEP(dom_variable,{irb.getInt32(0),irb.getInt32(0)}),keyArray,irb.getInt32(key_list[idx])});
}
irb.CreateStore(irb.getInt8(1),ptr);
index_map[block]=idx;
}
irb.SetInsertPoint(block);
irb.CreateStore(irb.CreateXor(irb.CreateLoad(irb.CreateGEP(keyArray,irb.getInt32(index_map[succ]))),ConstantInt::get(sw->getCondition()->getType(),fixNum)),switchVar);
BranchInst::Create(loopEnd,block);
四
结语
https://github.com/bluesadi/Pluto-Obfuscator
该改进后的控制流平坦化混淆的代码可在这个网址(https://github.com/bluesadi/Pluto-Obfuscator/blob/main/llvm/lib/Transforms/Obfuscation/FlatteningEnhanced.cpp)见到。
看雪ID:R1mao
https://bbs.pediy.com/user-home-948449.htm
看雪CTF官网:https://ctf.pediy.com/
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
原文始发于微信公众号(看雪学苑):OLLVM控制流平坦化的改进
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论