作者:FloatingGuy 转载请注明出处:https://floatingguy.github.io/


发现2017年代码混淆技术流行了起来,ios 和 android 上应用的都非常广泛。在这记录一下学习的一点新的,项目相关的案例要等项目接触,酌情选择一部分出来分享。

本文参考了:

Deobfuscation: recovering an OLLVM-protected program

反混淆:恢复被OLLVM保护的程序) 帮助我快速入门OLLVM。

简介


OLLVM 混淆技术 & 恢复手段

原始案例:

1
2
3
4
5
6
7
8
9
10
unsigned int target_function(unsigned int n)
{
unsigned int mod = n % 4;
unsigned int result = 0;
if (mod == 0) result = (n | 0xBAAAD0BF) * (2 ^ n);
else if (mod == 1) result = (n & 0xBAAAD0BF) * (3 + n);
else if (mod == 2) result = (n ^ 0xBAAAD0BF) * (4 | n);
else result = (n + 0xBAAAD0BF) * (5 & n);
return result;
}

混淆的点:

  • 控制流混淆
  • 基本块处理

控制流平坦化

这个概念我觉得是 OLLVM 使用的混淆技术里比较复杂的一种,不仅考验思路的严谨性,而且代码实现起来应该也很复杂。

特点

该技术 主要是针对 控制流的混淆,将原本很简单的执行流复杂化。原始条件被转换为CMOV条件传送指令,然后根据比较结果,在状态变量中设置下一个“相关块”。“相关块”就是没有经过混淆的函数的原始块。

demo 混淆以后
采用该混淆技术以后,程序的流程变成了:
序言->(进入循环)主分发器->..子分发器/相关块..->预分发器(进入一下轮循环)->返回块

控制流平坦原理图

看完上面的文字描述和 两张图以后,我其实还是挺模糊的。不理解如何保证自动化完成控制流的闭合,那么我就建议看看OLLVM 官方文档中关于Control Flow Flattening的描述。

  • 混淆前的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <stdlib.h>
    int main(int argc, char** argv) {
    int a = atoi(argv[1]);
    if(a == 0)
    return 1;
    else
    return 10;
    return 0;
    }
  • 混淆后的 伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <stdlib.h>
    int main(int argc, char** argv) {
    int a = atoi(argv[1]);
    int b = 0;
    while(1) {
    switch(b) {
    case 0:
    if(a == 0)
    b = 1;
    else
    b = 2;
    break;
    case 1:
    return 1;
    case 2:
    return 10;
    default:
    break;
    }
    }
    return 0;
    }

结合这个案例就清楚多了, 可以将混淆后的伪代码和混淆后的几个对象对应上了。
序言:3-4行
主分发器: #5 while 循环
相关块: 13-16行 混淆前就包含的逻辑
子分发器: #8 新增了一个判断条件,是控制流复杂
预分发器: #6, #9, #11 通过增加一个变量(b) 结合switch语句,是控制流的规模增加。

其实控制流平坦化主要做手脚的部分就是子分发器预分发器。通过在这2部分增加大量的无效逻辑干扰逆向人员静态分析。

恢复手段

这里我们需要一个符号执行工具来遍历代码,并尝试计算每个基本块的目标终点。当出现判断条件分支时,它将帮助我们尝试运行并获取所有分支可能到达的目标地址列表。Miasm框架包含有一个符号执行引擎(支持x86 32位等架构),其基于自身的“中间表示”(IR)实现,并且可以通过反汇编器转换二进制代码到“中间表示”(IR)。

虚假控制流

特点

该技术是对基本块进行混淆,创建一个包含”不透明谓词”的新代码块。

“不透明谓词”会生成条件跳转:可以跳转到真正的基本块或另一个包含垃圾指令的代码块。

我们可以同样使用前文中的符号执行方法,找到所有有用基本块并重建控制流。但还存在一个问题:“不透明谓词”,如果包含垃圾代码的基本块返回它的父节点块,这种情况下如果我们在符号执行过程中还按这个路径去跟踪,将导致陷入死循环。所以需要先解决“不透明谓词”问题,以避免垃圾代码块,直接找到正确的执行路径。

恢复手段

首先需要识别”不透明谓词”,Miasm框架仍然可以帮助我们完成简化不透明谓词的任务,因为框架包含了一个基于自身IR(中间表示)的表达式简化引擎。我们还需加深对不透明谓词的了解。

但是 貌似OLLVM项目中生成”不透明谓词”时存在一个bug, 具体请查看原文。

指令替换

特点

函数流程图“形状”没有改变,仍可以看到相同的条件分支,但是在“相关块”中,可以看到对输入值的计算过程变得更繁杂。

  • 不修改 函数原始的控制流
  • OLLVM项目把普通算术和布尔运算换为更复杂的操作。

从OLLVM项目官网上我们可以看到,根据运算符不同,有以下几种指令被替换:+, – ,^,| ,&

恢复手段

这个方法对于所有指令的替换恢复都是相同的。如果是在闭源的混淆器中,我们就必须手动找到它们,将所有OLLVM替换公式添加到Miasm简化引擎后,就可以得到简化过的控制流。

完整保护

特点

这种技术就是将上面 介绍的所有混淆技术:指令替换、虚假控制流、不透明谓词、控制流平坦化 全局添加进来,让所有的混淆手段相互叠加达到最大效果。比如 指令替换还可以针对不透明谓词的代码。

恢复手段


反混淆工具

反obfuscation LLVM(简称为OLLVM)的工具市面上已经有了很多家,我还没有来的急一一去评测,上文中介绍『恢复手段』时基本都是使用的Miasm 框架, 另外还有:

后续会带来 android & ios 平台 对抗OLLVM的案例。