阅读笔记 —— 《Obfuscator-LLVM — Software Protection》

LLVM

LLVM独立于语言和平台。它由以下三部分组成:

  1. 前端

    前端负责解析源码,验证源码的正确性,然后生成IR代码,交付给优化器。LLVM支持两种前端:clang和基于GNU编译器集合解析器的前端

  2. 优化器(亦称为中端)

    优化器负责删除IR代码中的冗余代码和死代码、内联函数、展开循环、删除死循环、简化控制流图等。之后将结果传送给后端。

  3. 后端

    根据目标架构生成高效的汇编代码

OLLVM

OLLVM中的大多数混淆和防护机制都是在中端进行的。它具有以下功能:

  1. 指令替换(instruction substitutions)

  2. 虚假控制流插入(bogus control-flow insertion)

  3. 基本块分割(basic block splitting)

  4. 控制流平坦化(control-flow flattening)

  5. 程序合并(procedures merging)

  6. 防篡改代码插入(insertion of code tamper-proofing)

为了使生成的代码能够多样化,使用PRNG生成随机数来决定混淆代码的生成。

指令替换

不支持浮点数运算的指令替换,因为会带来数值不准确性,即认为生成的代码不具有功能等效性。

由于指令替换很直接,并没有增加逆向的阻力,因为这种混淆很容易通过重新优化生成代码来规避。这也就是为什么指令替换pass运行在所有LLVM优化passes之后。

虚假控制流插入

包括在函数控制流图中插入条件跳转结构,该结构要么指向原始基本块,要么指向循环回条件跳转块的假基本块。

不透明谓词

干扰逆向人员但值不变的表达式,用于确保运行时只执行原始基本块,同样也使优化器不能够通过识别死代码来简化生成调用图。

控制流平坦化和基本块分割

控制流平坦化

通过移除所有易辨别的条件和循环结构来破坏函数控制流图。

基本上是使用一个大型switch结构来实现。通过路由变量(switch中用到的条件变量)控制代码流进入正确的基本块。在每个基本块的末尾,设置路由变量的正确值,使得代码流进入下一个正确的基本块。

路由变量的更新使用动态更新,这样能够迫使逆向工程师对可执行文件进行动态分析。

代码防篡改

代码防篡改机制用于确保运行时代码不被修改。它通过两种机制实现:

  1. check():负责检查代码是否被修改。
  2. respond():检测到代码被篡改后采取的措施。

这些check()和respond()分布在整个程序中,并且它们之间相互依赖。

check()

对一段代码进行32位CRC校验和的计算,其中段的开始和结束时随机选择的。

check()的结果可用于更新路由变量。路由变量不仅是动态更新的,而且依赖于编译阶段生成的静态值$s$:
$$
s=\rho⊕d_1⊕d_2⊕…⊕d_n
$$
其中$\rho$的值指向下一个基本块,$d_i$是基本块中第$i$个check()的结果,其值在编译后才能知道。

程序合并

将所有函数合并到一个唯一的新函数中,然后使用新函数merged()替换所有原始函数的调用。

机制的实现大致如下:

每个原始函数的代码被提取、放入merged()中,并作为switch-case中个一个分支。然后每个原始函数都被一个简单的包装函数替换,该函数带有标识原始函数的参数,在调用merged()时能够找到正确的原始函数。


阅读笔记 —— 《Obfuscator-LLVM — Software Protection》
http://example.com/2024/04/17/论文研读/OLLVM/Obfuscator-LLVM—Software-Protection/
作者
gla2xy
发布于
2024年4月17日
许可协议