首页
社区
课程
招聘
[原创]OLLVM (三)控制流平坦化
发表于: 2天前 109

[原创]OLLVM (三)控制流平坦化

2天前
109

前言

最近学习了OLLVM,代码工程链接。我对原有代码添加了注释并做了代码分析。

目标

  1. 将除了返回基本块的所有基本块的终止指令替换成跳转到一个中间基本块,这个中间基本块根据当前的变量值来确定跳转到哪个目标基本块。
  2. 将函数中的 switch 指令降级为一系列比较和跳转指令。

混淆步骤

  1. 预处理:将函数中的 switch 指令降级为一系列比较和跳转指令。通过LegacyLowerSwitch.cpp内的LowerSwitch类。

  2. 初始化平坦化上下文。

    1. 初始化一个随机密钥,用于打乱 case 值以增加反混淆难度,存储到ScramblingKey变量内。
    2. 获取函数第一个基本块指针,存储到FirstBasicBlock变量内。
    3. 遍历函数内所有基本块存放到名为OrigBb的列表(vector)内。
      1. 获取所有基本块列表后:
        1. 移除第一个基本块(通常为主入口),后面会重新安排流程
        2. 如果第一个基本块以条件分支结束,则拆分它以便插入控制流结构
      2. 这一步如果出现这几种情况,会直接退出控制流平坦化:
        1. 如果存在 invoke 指令则放弃混淆
        2. 如果只有一个基本块,无需平坦化
    4. 删除 第一个基本块 的最后一条指令,为插入新的控制流结构做准备。
    5. 第一个基本块 最后插入新的 switch 控制变量,名为**%switchVar**。
    6. 第一个基本块 最后插入 store 命令,将加密后的值存储到**%switchVar**。加密秘钥是ScramblingKey,下面所有加密秘钥都是它。
    7. 创建loopEntryloopEnd基本块。在当前步骤后,loopEnd 会添加跳转到 loopEntry 的语句,形成循环。
    8. 在loopEntry的最后插入load命令,load被switch命令用到的条件变量**%switchVar**,插入前基本块是空的。例如:%switchVar14 = load i64, ptr %switchVar, align 8
    9. switchDefault基本块:
      1. 创建 switchDefault 基本块,它是默认 case 块所跳转的地方,它插入到loopEnd之后。
      2. switchDefault 最后插入跳转到 loopEnd 的指令,例如:br label %loopEnd
  3. 第一个基本块 最后插入指令,跳转到loopEntry

  4. loopEnd跳回loopEntry,构成循环。

  5. loopEntry基本块创建 switch 命令。

    1. 创建空switch指令。例如:switch i64 %switchVar14, label %switchDefault []

    2. 将所有原始基本块加入switch的case中(除了第一个基本块,因为它已经从OrigBb删除)。执行完成后举例:

      switch i64 %switchVar14, label %switchDefault [
        i64 -9068860717773574370, label %SwConvNodeBlock_1_
        i64 -9068860718768112474, label %SwConvNodeBlock_2_11
        i64 -9068860719814512285, label %SwConvLeafBlock_3_9
        i64 -9068860717911456551, label %SwConvLeafBlock_3_7
        ......
        i64 -9068860717544530469, label %sw.bb
        i64 -9068860717742850501, label %sw.bb2
        ......
        i64 -9068860717718176324, label %if.then
        i64 -9068860718702902671, label %if.else
        i64 -9068860718179539988, label %NewDefault
        i64 -9068860718325703097, label %sw.default
        i64 -9068860718244552267, label %return
      ]
      
  6. 更改所有基本块的终止指令(排除第一个基本块,因为它已经跳转到loopEntry,排除返回基本块)。

    1. 获取后继块并删除终结指令。
    2. **查找后继目标基本块在第5步中对应的case值。**例如上面的:i64 -9068860717773574370i64 -9068860718768112474
    3. 将case值存储到**%switchVar**中,第4步会用到,指令例如:store i64 %27, ptr %switchVar, align 8
      1. 提升对抗强度:可以对case值进行加密,然后在store指令前生成解密指令,解密后再存储到**%switchVar**中。
    4. 跳转到loopEndloopEnd会调转到loopEntry,会运行在上面第2.8和第5步中创建的指令,这些指令会用到上一步存储到**%switchVar**中的值,它是switch的条件值,根据它选择下一个要跳转到的目标块。
  7. 将寄存器变量降级为栈变量:我们在上面新增命令的时候可能破坏了SSA信息,识别到寄存器变量,然后通过调用DemoteRegToStackDemotePHIToStack函数将寄存器变量改为由alloc分配的变量。逻辑详情见《将寄存器变量降级为栈变量》一节。

  8. 再次调用第1步相同的逻辑,将函数中的 switch 指令降级为一系列比较和跳转指令。

将寄存器变量降级为栈变量

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
void fixStack(Function *const F) {
  // 存放需要降级的 PHI 节点
  // Try to remove phi node and demote reg to stack
  std::vector<PHINode *>     TmpPhi;
  // 存放需要降级为栈的寄存器变量
  std::vector<Instruction *> TmpReg;
  // 函数入口块
  BasicBlock *               const BbEntry = &*F->begin();
 
  do {
    TmpPhi.clear();
    TmpReg.clear();
 
    // 遍历函数中的所有基本块和指令
    for (Function::iterator TmpBBIter = F->begin(); TmpBBIter != F->end();
         ++TmpBBIter) {
 
      for (BasicBlock::iterator TmpInstIter = TmpBBIter->begin();
           TmpInstIter != TmpBBIter->end(); ++TmpInstIter) {
 
        // 如果是 PHI 指令
        if (isa<PHINode>(TmpInstIter)) {
          PHINode *const Phi = cast<PHINode>(TmpInstIter);
          // 加入 PHI 列表
          TmpPhi.push_back(Phi);
          continue;
        }
 
        // 如果不是入口块中的 alloca 指令,并且该指令逃逸了(跨块使用)
        const bool IsAllocaInst = isa<AllocaInst>(TmpInstIter);
        const bool IsEntryBB = TmpInstIter->getParent() == BbEntry;
 
        if (IsAllocaInst && IsEntryBB) {
          continue;
        }
 
        const bool IsValueEscapes = valueEscapes(&*TmpInstIter);
        const bool IsUsedOutsideOfBlock =
            TmpInstIter->isUsedOutsideOfBlock(&*TmpBBIter);
 
//        outs() << "IsAllocaInst:" << IsAllocaInst << ",IsEntryBB:" << IsEntryBB
//               << ",IsValueEscapes:" << IsValueEscapes
//               << ",IsUsedOutsideOfBlock:" << IsUsedOutsideOfBlock << "\n\n";
 
        if (IsValueEscapes || IsUsedOutsideOfBlock) {
          // 加入寄存器列表
          TmpReg.push_back(&*TmpInstIter);
          continue;
        }
      }
    }
 
    // 将收集到的寄存器变量降级为栈变量
    for (unsigned int I = 0; I != TmpReg.size(); ++I) {
      Instruction *const Inst = TmpReg.at(I);
      DemoteRegToStack(*Inst);
    }
 
    // 将收集到的 PHI 节点降级为栈变量
    for (unsigned int I = 0; I != TmpPhi.size(); ++I) {
      PHINode *const TmpPHINode = TmpPhi.at(I);
      DemotePHIToStack(TmpPHINode);
    }
 
    // 循环直到没有更多可降级内容
  } while (TmpReg.size() != 0 || TmpPhi.size() != 0);
}

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回