ollvm混淆有哪些,怎么实现的,分析的方法有哪些,分别怎么做?

深入解析 OLLVM 混淆:原理、实现与破解思路

在面对逆向分析时,OLLVM(Obfuscator-LLVM)是一种非常常见的代码混淆工具。它的强大之处在于基于 LLVM IR(中间表示)进行混淆,这意味着无论是 C 还是 C++ 代码,都能在编译过程中被深度改造,变得更加难以理解。今天,我想聊聊 OLLVM 的主要混淆手段,它们是如何实现的,以及在实际分析中如何应对它们。


控制流平坦化(Control Flow Flattening)

这项技术的核心思路是打乱代码原有的执行顺序。正常情况下,一个函数的执行流是线性的,按照 if-elseswitchfor 这些结构来决定下一个执行路径。但是 OLLVM 让代码变成一个状态机,所有的基本块都会存放在一个循环里,并通过一个 state 变量控制跳转。

代码改造方式
假设原始代码如下:

void example(int x) {
    if (x > 0)
        doSomething();
    else
        doSomethingElse();
}

经过 OLLVM 处理后,它会变成这样:

void example(int x) {
    int state = 0;
    while (true) {
        switch (state) {
            case 0:
                if (x > 0) state = 1;
                else state = 2;
                break;
            case 1:
                doSomething();
                state = 3;
                break;
            case 2:
                doSomethingElse();
                state = 3;
                break;
            case 3:
                return;
        }
    }
}

这样一来,代码的执行路径变得难以预测,控制流图(CFG)在 IDA 里看上去会像一团乱麻。

应对方法

  • 动态调试:直接在 GDB/Frida 里观察 state 变量是如何变化的。
  • 静态分析:用 IDA 或 Ghidra,把 switch-case 结构还原成原始的 if-else 逻辑。
  • 自动去混淆:一些工具(如 Tigress deobfuscator)可以自动检测并优化平坦化代码。

指令替换(Instruction Substitution)

OLLVM 会用等价但更复杂的数学运算来替换简单的指令。例如,a + b 可能会被替换成 ((a ^ b) + 2 * (a & b)),虽然它们计算结果相同,但更难以直接理解。

代码改造方式
原始代码:

int sum(int a, int b) {
    return a + b;
}

OLLVM 混淆后:

int sum(int a, int b) {
    return ((a ^ b) + 2 * (a & b)); 
}

这只是最简单的替换方式,更多的可能涉及 ROL(循环左移)、ROR(循环右移)等位运算技巧,让分析变得更加棘手。

应对方法

  • 模式匹配:可以用 IDA Python 或 Ghidra 脚本批量检测常见的数学变换模式。
  • 符号执行:用 Z3 Solver 或 angr 这样的工具,把复杂表达式还原成最简形式。
  • AI 识别:有些研究会用机器学习来自动识别这种模式。

虚假控制流(Bogus Control Flow)

OLLVM 会插入无意义的 if-else 逻辑,让反编译器生成的控制流图变得更加复杂,但实际上这些逻辑永远不会被执行。

代码改造方式

void example() {
    if (randomValue() > 1000) {  
        doSomething();  
    } else {  
        doEvilStuff();  
    }
}

如果 randomValue() 在编译时就可以确定为固定值,比如总是返回 500,那么 doEvilStuff() 这一分支就永远不会执行。但 IDA 仍然会把它当成正常分支去分析,导致混淆。

应对方法

  • 静态分析:看看 if 里的变量是否是恒定的,如果是,直接简化控制流。
  • 符号执行:用 angr 执行模拟,看看分支是否真的有可能被触发。
  • 动态调试:直接让程序跑起来,看看哪些代码真的会被执行。

整数混淆(Opaque Predicate)

类似于虚假控制流,这种混淆方式会让 if 语句看起来像是可变的,但实际上结果是恒定的。例如:

if ((x * 0x12345678) & 0xFFFFFFF0 == 0) {  
    doSomething();  
}

这个条件看起来很复杂,但可能 x 的范围在编译时已知,这样一来 if 语句的结果是固定的。

应对方法

  • 符号执行:尝试化简 if 语句里的数学表达式。
  • 动态调试:看看 if 语句在运行时的结果是否总是一样的。

垃圾代码插入(Dead Code Insertion)

在代码里插入永远不会被执行的 if 语句、死循环、无用的函数调用等,增加代码复杂度。

应对方法

  • 代码优化:用 opt -O2 这样的 LLVM 工具自动优化掉无用代码。
  • 静态分析:用 IDA/Ghidra 识别死代码并删除。

间接跳转(Indirect Branching)

OLLVM 可能会把 call func() 变成间接调用,比如用一个函数指针:

void example() {  
    void (*func_ptr)() = getFunction();  
    func_ptr();  
}

这样 IDA 里不会直接显示 call doSomething(),而是 call func_ptr,导致分析变难。

应对方法

  • 动态 Hook:用 Frida 拦截 getFunction(),看看它返回了哪个函数。
  • 间接调用分析:用 IDA 交叉引用功能,找到可能的 func_ptr 值。

总结:OLLVM 混淆与破解思路

混淆方式 分析方法
控制流平坦化 动态调试、自动去混淆
指令替换 符号执行、模式匹配
虚假控制流 静态分析、动态执行
整数混淆 逻辑化简、符号执行
垃圾代码 代码优化、死代码检测
间接跳转 Hook 分析、交叉引用检查

面对 OLLVM 混淆,最好的策略是结合静态分析、动态调试和自动化工具,逐步拆解代码结构。只要方法得当,破解 OLLVM 其实并没有那么可怕。

你可能感兴趣的:(安卓逆向面试题,android,安卓逆向,面试)