OLLVM控制流平坦化源码分析+魔改

本文详细介绍了在LLVMIR层面上如何进行控制流平坦化(ControlFlowFlattening)以及函数合并,主要涉及到的基本块、函数和指令操作,包括创建switch结构、处理不同类型的跳转指令,以及如何通过模块Pass实现函数合并,以增强代码的混淆性。

前置知识

Module是指模块,Function模块下的函数,BasicBlock函数下的基本块,Instruction 基本块下的IR指令

Flattening::flatten(Function *f)

 for (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;
    }
  }

把函数分成很多个基本块,并且push到vector类型的 origBB中。

判断里面基本块是否大于1,不大于1的话就没有意义去进行混淆:

 if (origBB.size() <= 1) {
   
   
    return false;
  }

需要把vertor里面的第一个基本块即入口基本块单独拿出来进行处理:对入口基本块进行判断,如果是无条件跳转则不进行任何处理,否则需要找到最后一条指令,将整个if结构给split,split之后两个块之间会自动添加跳转指令,然后就可以把原来的split后的if结构给它扔进要处理的基本块列表。

  origBB.erase(origBB.begin());

  // Get a pointer on the first BB
  Function::iterator tmp = f->begin(); //++tmp;
  BasicBlock *insert = &*tmp;

  // If main begin with an if
  BranchInst *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);
  }

如果是条件跳转的话这里是把上面自动添加那个跳转指令给删除,如果不是的话,那么也是需要把它删除,因为跳转点目标还不能确定:

// Remove jump
  insert->getTerminator()->eraseFromParent();

源代码

 %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #3
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  br label %NodeBlock8

目前代码

entry:
  %.reg2mem = alloca i32
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #3
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  store i32 %2, i32* %.reg2mem
  %switchVar = alloca i32

创建一个switchvar变量,然后去获取一个随机整数创建store指令塞给switchvar中

 switchVar =
      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);

也就是在switchvar添了如下这一行:

  store i32 157301900, i32* %switchVar

创建switch

创建两个block,其它的基本块插入它们之间

  loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);
  loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);

如下:

loopEntry:                                      
 
loopEnd:                                          

目标基本块里面啥内容也没有。

在loopEntry里面新建一个load指令,并且把switchVar

 load = new LoadInst(switchVar, "switchVar", loopEntry);

目前loopentry指令如下:

loopEntry:                                        ; preds = %entry, %loopEnd
  %switchVar10 = load i32, i32* %switchVar

把insert插入到loopEntry之前,这里的insert就是entry基本块,再创建两个跳转指令,从insert(即第一个基本块)跳转到loopEntry;从loopend跳转到loopEntry

  // Move first BB on top
  insert->moveBefore(loopEntry);
  BranchInst::Create(loopEntry, insert);
  // loopEnd jump to loopEntry
  BranchInst::Create(loopEntry, loopEnd);

这里结束后,entry模块就完整了,如下:

entry:
  %.reg2mem = alloca i32
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #3
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  store i32 %2, i32* %.reg2mem
  %switchVar = alloca i32
  store i32 157301900, i32* %switchVar
  br label %loopEntry

而loopend模块也有了一条指令(其实也是完整了):

loopEnd:                                        
br label %loopEntry

紧接着创建一个基本块,然后在基本块里面创建一个跳转指令,从switchDefault跳转到loopend中

 BasicBlock *swDefault =
      BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
  BranchInst::Create(loopEnd, swDefault);

多了一个switchDefault基本块,指令如下:

switchDefault:                                    ; preds = %loopEntry
  br label %loopEnd

创建一个switch指令,位置是在loopentry基本块下,且创建了0个case,然后设置了条件为load,就上面的load。

 switchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry);
  switchI->setCondition(load);

把entry最后一行跳转指令删除后再创建了一个跳转指令,从entry跳转到loopentry

  f->begin()->getTerminator()->eraseFromParent();
    BranchInst::Create(loopEntry, &*f->begin());
  for (std::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 switch
    numCase = cast<ConstantInt>(ConstantInt::get(
        switchI->getCondition()->getType(),
        llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key)));
    switchI->addCase(numCase, i);
  }

目前代码:

entry:
  %.reg2mem = alloca i32
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #3
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  store i32 %2, i32* %.reg2mem
  %switchVar = alloca i32
  store i32 157301900, i32* %switchVar
  br label %loopEntry

loopEntry:     
%switchVar10 = load i32, i32* %switchVar
  switch i32 %switchVar10, label %switchDefault [
  ]

switchDefault:                                    ; preds = %loopEntry
  br label %loopEnd

loopEnd:                                        
br label %loopEntry

创建case

  for (std::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 switch
    numCase = cast<ConstantInt>(ConstantInt::get(
        switchI->getCondition()->getType(),
        llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key)));
    switchI->addCase(numCase, i);
  }

这里的i就是指剩下的那些case分支代码基本块,i->moveBefore(loopEnd),把某个代码基本块置于loopend之前。比如某个基本块是这样:

NodeBlock8:                                       ; preds = %entry
  %Pivot9 = icmp slt i32 %2, 2
  br i1 %Pivot9, label %LeafBlock, label %NodeBlock

然后下面的这些代码就是创建一个numcase,就是case分支里面的case值,这个值它是随机生成的,种子的话是Entry.cpp里面的那个AesSeed值,如果确定AesSeed的话,那么这里随机生成的case每次都是固定的。
switchI->addCase(numCase, i);紧接着在switch里面增加一个case值,跳转到NodeBlock8里面。
目前switch执行完一次后,loopentry基本bolck块如下:

loopEntry:     
%switchVar10 = load i32, i32* %switchVar
  switch i32 %switchVar10, label %switchDefault [
   i32 157301900, label %NodeBlock8
  ]

当循环执行结束后:

目前代码

entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #3
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  br label %NodeBlock8

NodeBlock8:                                       ; preds = %entry
  %Pivot9 = icmp slt i32 %2, 2
  br i1 %Pivot9, label %LeafBlock, label %NodeBlock

NodeBlock:                                        ; preds = %NodeBlock8
  %Pivot = icmp slt i32 %2, 3
  br i1 %Pivot, label %sw.bb2, label %LeafBlock6

LeafBlock6:                                       ; preds = %NodeBlock
  %SwitchLeaf7 = icmp eq i32 %2, 3
  br i1 %SwitchLeaf7, label %sw.bb4, label %NewDefault

LeafBlock:                                        ; preds = %NodeBlock8
  %SwitchLeaf = icmp eq i32 %2, 1
  br i1 %SwitchLeaf, label %sw.bb, label %NewDefault

sw.bb:                                            ;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦&之璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值