Obfuscation-llvm混淆flatten源码分析

1. Control Flow Flattening算法

源码层面的control flow flattening
源码层面的控制流图

图(a)为原始代码,图(b)为经过control flow flattening的代码,图(c)为原始代码的控制流图,图(d)为经过control flow flattening的代码的控制流图。[1]

2. IR层Control Flow Flattening的源码分析

2.1 IR层简介

llvm总体架构

其中IR(intermediate representation)是前端语言生成的中间代码,也是Pass操作的对象,它主要包含四个部分:

  • Module:比如一个.c或.cpp文件
  • Function:代表文件中的一个函数
  • BasicBlock:每个函数会被划分为一些block,它的划分标准是:一个block只有一个入口和一个出口。
  • Instruction:具体的指令。
    它们之间的关系可以用下图来表示:


    IR中各部分的关系

2.2 源码分析

使用一个简单的例子来测试Control Flow Flattening的处理流程,测试代码如下:

int func1(int a,int b)
{
     int result;
     if(a>0){
            result=a+b;
     }
     else{
            result=a-b;
     }
     return result;
}

这段测试代码生成的IR代码如下图2所示:

func1生成的IR

2.2.1 入口函数runOnFunction

Control Flow Flattening Pass处理的对象是函数,所以重载函数runOnFunction,如果Pass处理的的对象时Module,可以重载runOnModule。

bool runOnFunction(Function &F) override {
    Function *tmp = &F;
    // Do we obfuscate
    if (flatten(tmp)) {
        //++Flattened;
    }
    
    return false;
}
2.2.2 处理函数flatten

将函数中的所有BasicBlock保存到vector容器originBB中,当originBB的数量小于等于1时,返回false。

// f为flatten参数: Function *f
// vector<BasicBlock *> origBB;

// Save all original BB
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;
  }
}

// Nothing to flatten
if (origBB.size() <= 1) {
  return false;
}

将originBB中的第一个BasicBlock删除掉,并通过f->begin()获取函数的第一个BasicBlock;如果第一个BasicBlock的最后一条指令是否是跳转指令,如果是跳转指令的话转换为跳转类型指令。

// Remove first BB
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());
}

如果是跳转指令是条件跳转,或者是第一个BasicBlock的后继BaseBlock大于1个(非条件跳转指令的后继BasicBlock为1个,条件跳转指令的后继BasicBlock为2个,return指令的后继BasicBlock为0个)。获取跳转指令的地址,然后通过splitBasicBlock将第一个BasicBlock一分为2,后边划分出来的BasicBlock起名叫"first",然后将"first"插入到originBB的起始位置。
这样做是为了将变量声明部分与控制流部分分割开,因为first部分是要放进switch-case分支中的,防止变量作用域错误。

  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);
  }

此时的IR图如下所示:


拆分第一个BasicBlock的结果图

接着将第一个BasicBlock(即entry BB)与其下一个BasicBlock(即first)的跳转关系解除:

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

解除之后的IR图如下所示:


解除entry跳转first

然后在第一个BasicBlock创建switchVar变量,并赋一个随机的值。

// AllocaInst *switchVar;

// Create switch variable and set as it
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);

接着创建while循环需要的"loopEntry"和"loopEnd"2个BasicBlock,在loopEntry中读取switchVar的值:

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

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

将第一个BasicBlock(即entry,也就是insert)放在loopEntry之前,并设置第一个BasicBlock的最后一条指令跳转loopEntry。
设置loopEnd的最后一条指令跳转loopEntry。

// Move first BB on top    // insert现在是entry basicblock,将entry BB放在loopEntry BB之前。
insert->moveBefore(loopEntry);
BranchInst::Create(loopEntry, insert);   // entry bb的最后一个指令为br loopEntry

// loopEnd jump to loopEntry
BranchInst::Create(loopEntry, loopEnd);  // loopEnd bb的最后一个指令为br loopEntry

接着创建switch-case中的default跳转对应的BasicBlock swDefault,swDefault在loopEnd之前,并在swDefault中添加一条跳转loopEnd的指令。

// 创建switch-case的default分支BB,default分支的指令只有br loopEnd
BasicBlock *swDefault =
BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
BranchInst::Create(loopEnd, swDefault);

然后在loopEntry中创建了switch-case的指令,指定default block为swDefault,并设置switch-case的变量switchVar。

// Create switch instruction itself and set condition
// 在loopEntry中创建switch-case,并设置条件值
switchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry);
switchI->setCondition(load);

然后将第一个BasicBlock的最后跳转指令设置为跳转loopEntry:

// Remove branch jump from 1st BB and make a jump to the while
f->begin()->getTerminator()->eraseFromParent();
// 函数的第一个bb,也就是entry bb,的最后一条指令为br loopEntry
BranchInst::Create(loopEntry, &*f->begin());

上述流程创建了while循环相关的2个BasicBlock,switch-case指令,和其对应的switchDefault BasicBlock,并修改各个BasicBlock之间的跳转关系,如下图所示:


添加while循环和switch-case

接下来需要将所有保存在容器originBB中的BasicBlock添加到switch-case中,每一个BasicBlock对应一个case,并且每个case的值都是一个随机值。代码如下:

  // Put all BB in the switch
  for (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);
  }

此时的IR图如下所示:

增加case后的IR图

添加了全部的BasicBlock之后,需要修改switch-case层级的BasicBlock之间的跳转关系,使得每个BasicBlock执行完毕之后,会重新设置switchVar值,然后通过while循环再次进入switch-case中的下一个case中,知道程序执行完毕。
如果BasicBlock的最后指令的后继BB数量为0(return语句的指令后继BB只有0个),不需要处理;
如果BasicBlock的最后指令的后继BB数量为1,找到后继BB,从switch-case指令中找到该BB对应的case号码,将其赋值给switchVar,然后将最后的指令替换为跳转loopEnd。

  // Recalculate switchVar
  for (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end();
       ++b) {
      BasicBlock *i = *b;
      ConstantInt *numCase = NULL;
      
      // getNumSuccessors是获取后续BB的个数,Ret BB后继BB为0个,...
      
      // Ret BB, Ret BB的最后不用加br loopEnd
      if (i->getTerminator()->getNumSuccessors() == 0) {
          continue;
      }
      
      // If it's a non-conditional jump
      if (i->getTerminator()->getNumSuccessors() == 1) {
          // Get successor and delete terminator
          BasicBlock *succ = i->getTerminator()->getSuccessor(0);
          i->getTerminator()->eraseFromParent();
          
          // Get next case
          numCase = 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 loop,BB结尾跳转loopEnd
          new StoreInst(numCase, load->getPointerOperand(), i);
          BranchInst::Create(loopEnd, i);
          continue;
      }

     // If it's a conditional jump
    if (i->getTerminator()->getNumSuccessors() == 2) {
         ...
     }
  }

设置完跳转关系的IR图如下所示:


设置case跳转关系

参考文献

[1]: OBFUSCATING C++ PROGRAMS VIA CONTROL FLOW FLATTENING
[2]: Obfuscator-llvm源码分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容