[机翻] [ABD] 03_混淆技术详解

03 混淆技术详解

https://github.com/malrev/ABD

本章是课程的核心章节,详细讲解 6 种主流混淆技术。我们将从"共同思想"出发,逐一分析每种混淆的原理、工具选项与代码示例。


3.0 共同思想分类

在深入具体技术之前,先从"混淆到底在做什么"的角度,将混淆技术分为三大类共同思想:

思想一:做无用的事情(Doing Nothing Useful)

插入不影响程序结果的代码,增加分析者需要阅读的代码量。

  • Garbage/Dead Code Insertion(垃圾/死代码插入)

这类混淆的特点是:插入的代码对最终结果没有任何影响,纯粹是为了"稀释"真实代码,增加逆向分析的视觉与认知负担。

思想二:更改语法(Changing Syntax)

用功能等效但更复杂的代码形式替换原始代码,使代码更难读懂。

  • Instruction Substitution(指令替换)
  • Encode Literals(编码文字)
  • Encode Arithmetic / MBA(编码算术 / 混合布尔算术)

这类混淆不改变程序的控制流语义(Control Flow Semantics),只改变指令的"写法"。它依赖于数学恒等式,将简单表达式替换为等价但复杂的表达式。

思想三:不仅更改语法还要更改语义(Changing Both Syntax and Semantics)

不仅改变指令写法,还改变控制流结构本身,使控制流图面目全非。

  • Opaque Predicate(不透明谓词)
  • Virtualization Obfuscation(虚拟化混淆)
  • Control Flow Flattening(控制流平坦化)

这类混淆直接破坏程序的控制流图(Control Flow Graph),使逆向工具(如反编译器)生成的伪代码难以阅读,是更强大的混淆手段。

分类总览

思想 技术 强度
做无用的事情 Garbage/Dead Code Insertion
更改语法 Instruction Substitution
更改语法 Encode Literals
更改语法 Encode Arithmetic / MBA
更改语法与语义 Opaque Predicate
更改语法与语义 Virtualization Obfuscation
更改语法与语义 Control Flow Flattening

3.1 Garbage / Dead Code Insertion(垃圾 / 死代码插入)

原理

Garbage / Dead Code Insertion 是最简单的混淆技术之一。其核心思想是:在程序中插入不影响最终结果的指令。

根据插入代码是否被执行,可进一步区分:

类型 说明
Garbage Code(垃圾代码) 始终被执行但对结果无影响的代码。例如 mov edx, 0xdeadc00d(给一个寄存器赋值,但该值从不被使用)。
Dead Code(死代码) 从不执行的代码。通常放在一个永假条件分支的分支体中。

代码示例

以一个 div5 函数为例,该函数计算 n / 5(通过乘以 0xCCCCCCCD 再移位的经典编译器优化实现)。

原始汇编:

div5:
    mov     eax, [esp+4]        ; 参数 n
    mov     edx, 0xCCCCCCCD     ; 魔数:2^33 / 5 的近似值
    mul     edx                 ; edx:eax = n * 0xCCCCCCCD
    shr     edx, 2              ; edx = n / 5
    mov     eax, edx             ; 返回值
    ret

混淆后(插入 garbage code):

div5:
    mov     edx, 0xdeadc00d     ; [garbage code] 赋值给 edx,但从不使用
    mov     ecx, 0              ; [garbage code] 赋值给 ecx,但从不使用
    mov     eax, [esp+4]        ; 参数 n
    mov     edx, 0xCCCCCCCD     ; 魔数
    mul     edx
    shr     edx, 2
    mov     eax, edx
    ret

插入的两条指令 mov edx, 0xdeadc00dmov ecx, 0 会被执行,但对程序的最终输出没有影响——因为 edx 随后被 0xCCCCCCCD 覆盖,ecx 从未被读取。

反混淆思路

Garbage / Dead Code 是最容易消除的混淆,经典编译器优化中的 死代码消除(Dead Code Elimination, DCE)死存储消除(Dead Store Elimination) 即可处理。这属于课程后续"Dataflow Analysis"部分的内容。


3.2 Instruction Substitution(指令替换)

原理

Instruction Substitution 的思想是:用复杂但功能等效的代码序列替换原始的简单指令。

关键在于利用数学恒等式。例如,对于任意常数 c,有:

x + c  ≡  (x ^ c) - (x ^ c) + ...    (构造恒等变换)

一种常见的技巧是"减去再加上同一个常数":

x + c  ≡  x - (-c)  ≡  x - c + 2c  ≡  ...

更具体地,构造一个常数 k,利用恒等式:

x + c  ≡  (x ^ k) ^ k + c   ; 异或自身等于不变
         或
x + c  ≡  (x - k) + k + c   ; 减去再加回

工具选项

  • O-LLVM 提供 -sub 选项启用指令替换。

代码示例

原始汇编(简单的加法):

    add     eax, 5            ; eax = eax + 5

混淆后(O-LLVM -sub):

    ; 原始: eax = eax + 5
    ; 混淆后利用恒等变换,常量 k = 0x2598A32B
    xor     eax, 0x2598A32B   ; eax = eax ^ k
    sub     eax, 0x2598A32B   ; eax = (eax ^ k) - k
    add     eax, 0x2598A32B   ; eax = (eax ^ k) - k + k = eax ^ k
    ; 注意:实际 O-LLVM 会保证最终结果等价于 eax + 5
    ; 这里简化展示"减去再加上同一常量 2598A32Bh"的恒等变换思想

实际上,O-LLVM 的 -subadd 指令的替换会使用形如以下模式(具体实现因版本而异):

; 将 add eax, 5 替换为:
sub eax, 0x2598A32B
add eax, 0x2598A32B    ; 减去再加回同一常量 2598A32Bh,相互抵消
add eax, 5             ; 最后加上真正的增量

其核心是"插入一对相互抵消的操作",使分析者难以一眼看出真实意图。

反混淆思路

指令替换产生的是恒等变换,可以通过 常量传播(Constant Propagation)代数化简(Algebraic Simplification)数据流分析 识别并简化。这是后续 Dataflow Analysis 与 Symbolic Execution 的用武之地。


3.3 Encode Literals(编码文字)

原理

Encode Literals 的思想是:用更复杂的表达式替换程序中的文字常量(literals)字符串(strings),通过划分(splitting)或编码(encoding)的方式隐藏其真实值。

  • 对于数值常量:例如将 42 替换为 6 * 70x2A 的某种编码形式。
  • 对于字符串:将字符串逐字符分解,通过指令逐字节写入内存。

工具选项

  • Tigress 提供 EncodeLiterals 选项。

代码示例

以字符串 "hello world" 为例。

原始 C 代码:

printf("hello world\n");

编译后,字符串 "hello world" 通常作为整体存储在 .rodata 段,可以直接被字符串扫描工具(如 strings 命令)提取。

混淆后(Tigress EncodeLiterals):

字符串不再以整体形式出现,而是被拆分为逐字节的赋值指令,在运行时动态构造:

    ; 分配内存用于存放字符串 "hello world\n"
    ; 然后逐字符写入(以下是简化的伪汇编表示)

    mov     byte ptr [eax],     68h    ; 'h'  = 0x68
    mov     byte ptr [eax+1],   65h    ; 'e'  = 0x65
    mov     byte ptr [eax+2],   6Ch    ; 'l'  = 0x6C
    mov     byte ptr [eax+3],   6Ch    ; 'l'  = 0x6C
    mov     byte ptr [eax+4],   6Fh    ; 'o'  = 0x6F
    mov     byte ptr [eax+5],   20h    ; ' '  = 0x20
    mov     byte ptr [eax+6],   77h    ; 'w'  = 0x77
    mov     byte ptr [eax+7],   6Fh    ; 'o'  = 0x6F
    mov     byte ptr [eax+8],   72h    ; 'r'  = 0x72
    mov     byte ptr [eax+9],   6Ch    ; 'l'  = 0x6C
    mov     byte ptr [eax+10],  64h    ; 'd'  = 0x64
    mov     byte ptr [eax+11],  0Ah    ; '\n' = 0x0A
    mov     byte ptr [eax+12],  00h    ; '\0' = 0x00  (字符串终止符)

这样,strings 命令无法直接从二进制文件中提取出 "hello world",因为字符串不再以连续字节序列的形式存在于数据段中。

反混淆思路

  • 静态分析:识别逐字节写入模式,重建字符串。
  • 动态分析:在运行时 dump 内存,捕获动态构造的字符串。

3.4 Encode Arithmetic / Mixed Boolean-Arithmetic(编码算术 / MBA)

原理

Encode Arithmetic 也称 Mixed Boolean-Arithmetic(MBA,混合布尔算术),其思想是:用更复杂的表达式替换算术运算布尔运算

MBA 利用整数算术与位运算之间的非线性关系,构造出人类(甚至某些自动化工具)难以化简的表达式。例如:

x + y  ≡  (x ^ y) + 2 * (x & y)     ; 经典 MBA 恒等式
x + y  ≡  (x | y) + (x & y)          ; 另一种 MBA 形式

这些恒等式在数学上完全等价,但由于混合了布尔运算(^, &, |)和算术运算(+, *),使得表达式看起来极为复杂。

工具选项

  • Tigress 提供 EncodeArithmetic 选项。

代码示例

原始汇编(简单的加法):

    add     eax, ebx          ; eax = eax + ebx

混淆后(Tigress EncodeArithmetic,MBA 形式):

    ; 原始: eax = eax + ebx
    ; MBA 替换: eax = (eax ^ ebx) + 2*(eax & ebx) 等价于 eax + ebx
    ; 用 not, sub, lea, or, and 等位运算混合替换简单加法

    mov     ecx, eax          ; ecx = eax (备份)
    not     eax               ; eax = ~eax
    and     eax, ebx          ; eax = ~eax & ebx
    not     eax               ; eax = ~(~eax & ebx) = eax | ebx
    ; ... 继续构造 MBA 表达式 ...
    lea     edx, [eax + ebx]  ; 利用 lea 进行加法
    or      ecx, edx          ; 混合或运算
    and     ecx, eax          ; 混合与运算
    ; 最终结果等价于 eax + ebx

上述是一个概念性示例。实际的 MBA 变换会根据具体的恒等式模板生成多种变体,且可能嵌套多层,使化简极为困难。

MBA 的强大之处

MBA 表达式的难点在于:

  1. 非线性:混合了布尔运算和算术运算,难以用线性代数方法化简。
  2. 可叠加:可以多次嵌套,层层叠加使表达式膨胀。
  3. 抗符号执行:某些 MBA 构造专门用于对抗符号执行引擎(参见 Wang et al. Linear Obfuscation to Combat Symbolic Execution, ESORICS'11)。

反混淆思路

MBA 的反混淆需要专门的 MBA 化简(MBA Deobfuscation) 技术,包括:

  • 基于 SMT 求解器的等价性验证。
  • 基于代数化简规则的符号化简。
  • 采样测试:用多组输入测试表达式,猜测其语义。

3.5 Opaque Predicate(不透明谓词)

原理

Opaque Predicate(不透明谓词) 的思想是:插入一个结果已知(对混淆者而言)的条件分支,但对逆向分析者而言难以判断其结果。

具体来说:

  • 插入一个从不触发始终触发的条件分支。
  • 将原本的无条件分支(unconditional branch) 变为条件分支(conditional branch)
  • 条件的真假由混淆者控制(已知为永真或永假),但分析者难以推断。
原始:    unconditional jump  →  目标B

混淆后:   if (opaque_condition)   ← 分析者以为条件可能为假
             goto 目标B              (实际永真,总是跳转)
         else
             goto bogus_code         (死代码,从不执行)

工具选项

  • O-LLVM 提供 -bcf 选项(Bogus Control Flow,虚假控制流)。

三种实现方式

构造不透明谓词有多种方法,课程介绍了三种典型方式:

方式 1:Pseudo-Handle(伪句柄)

利用 Windows API 的返回值特性构造永真条件:

// GetCurrentProcess() 返回 -1 (0xFFFFFFFF),即伪句柄
// 与 0xFFFFFFFF 比较总是相等
if (GetCurrentProcess() == (HANDLE)0xFFFFFFFF) {
    // 真实代码(总是执行)
    real_code();
} else {
    // 死代码(从不执行)
    bogus_code();
}
  • GetCurrentProcess() 在 Windows 上总是返回 -1(即 0xFFFFFFFF),这是一个伪句柄(pseudo-handle),代表当前进程。
  • 因此 GetCurrentProcess() == 0xFFFFFFF(或 0xFFFFFFFF)始终为真。
  • 分析者若不了解这一 API 特性,可能误判条件结果。

方式 2:Collatz Conjecture(考拉兹猜想)

利用数学上未被证明的猜想构造不透明谓词:

// Collatz 函数: f(n)
//   if n % 2 == 0:  f(n) = n / 2
//   if n % 2 == 1:  f(n) = 3n + 1
//
// 猜想: 对任意正整数 n,反复应用 f(n) 最终会到达 1
//       (但此猜想至今未被证明)

int collatz(int n) {
    while (n != 1) {
        if (n % 2 == 0)
            n = n / 2;
        else
            n = 3 * n + 1;
    }
    return n;  // 猜想:总是返回 1
}

// 不透明谓词
if (collatz(some_known_input) == 1) {
    real_code();   // 总是执行(基于猜想)
} else {
    bogus_code();  // 从不执行
}
  • Collatz 猜想:对任意正整数 n,若 n 为偶数则除以 2,若为奇数则乘以 3 加 1,反复操作最终会到达 1。
  • 该猜想至今未被证明(也未被证伪),但在实践中对所有测试过的数都成立。
  • 混淆者选择一个已验证会到达 1 的输入,使谓词在实际中始终为真。
  • 由于猜想未被数学证明,SMT 求解器也无法证明该条件为永真,从而难以自动识别。

方式 3:Arithmetic(算术恒等式)

利用算术恒等式构造永真或永假条件:

// 永真条件示例
if ((x * x + x) % 2 == 0) {       // x*(x+1) 是两个连续整数之积,必为偶数
    real_code();                   // 总是执行
}

// 永假条件示例
if ((x * x) < 0) {                // 任意整数的平方非负
    bogus_code();                  // 从不执行
}
  • 这些条件基于简单的数学性质,对人类分析者来说可能容易识别,但可以通过多层嵌套和复杂化来增加难度。

参考文献

  • Zobernig et al. Indistinguishable Predicates. 2017.
  • Wang et al. Linear Obfuscation to Combat Symbolic Execution. ESORICS'11.

反混淆思路

  • 对 Arithmetic 类型:可用 SMT 求解器证明条件永真/永假。
  • 对 Pseudo-Handle 类型:需识别 API 语义。
  • 对 Collatz Conjecture 类型:最难自动处理,因为 SMT 求解器无法证明未解决的数学猜想。通常需要启发式识别(如识别 Collatz 函数的特征模式)。

3.6 Virtualization Obfuscation(虚拟化混淆)

原理

Virtualization Obfuscation 是最强大的混淆技术之一。其核心思想是:

唯一的字节码(bytecode)替换原始代码,并在一个自定义虚拟机(Virtual Machine)上执行该字节码。字节码格式独立于主机 ISA(Instruction Set Architecture)。

这意味着:

  • 原始的 x86/x64 代码被转换为 VM 字节码。
  • VM 字节码与 x86 指令完全不同,是混淆工具自定义的。
  • 逆向分析者必须先理解 VM 的指令集与执行逻辑,才能分析字节码。
  • 这大大增加了逆向成本。
原始 x86 代码                混淆后
─────────────              ──────────────────────
mov eax, 5   ──►          [VM 字节码]  ──►  [VM 解释器]
add eax, 3                 push 5            (在 VM 上执行)
                           push 3
                           add               字节码格式独立于 x86

VM 执行流程

虚拟机的执行流程通常为:

        ┌─────────────┐
        │  VM Entry   │   初始化 VM 上下文(虚拟寄存器、VM stack)
        └──────┬──────┘
               │
               ▼
        ┌─────────────┐◄────┐
        │   Fetch     │     │
        │ (取字节码)   │     │ 循环
        └──────┬──────┘     │
               │            │
               ▼            │
        ┌─────────────┐     │
        │   Decode    │     │
        │ (解码)       │     │
        └──────┬──────┘     │
               │            │
               ▼            │
        ┌─────────────┐     │
        │   Execute   │─────┘
        │ (执行handler)│
        └─────────────┘
  1. VM Entry:初始化虚拟机上下文,设置虚拟寄存器、VM stack 指针等。
  2. Fetch:从字节码流中取出下一条指令(opcode)。
  3. Decode:解码 opcode,确定要调用哪个 handler。
  4. Execute:执行对应的 handler,更新虚拟寄存器或 VM stack。

VM 组件

一个典型的 VM 包含以下组件:

Handlers(处理器)

每个 VM 指令对应一个 handler 函数,负责执行该指令的语义:

Handler 功能
handler_push 将值压入 VM stack
handler_pop 从 VM stack 弹出值
handler_add 加法运算
handler_xor 异或运算
handler_mov 数据传送
... ...

虚拟寄存器(Virtual Registers)

VM 拥有自己的寄存器集合:

reg_0, reg_1, reg_2, ...     ; 通用虚拟寄存器
reg_ip                        ; VM 指令指针(Instruction Pointer)
reg_sp                        ; VM 栈指针(Stack Pointer)

VM 主循环代码示例

以下是 Tigress Virtualize 生成的 VM 主循环(switch-case 结构)的简化形式:

void vm_run(unsigned char *bytecode, vm_context *ctx) {
    while (1) {
        int opcode = bytecode[ctx->reg_ip++];   // Fetch + Decode
        switch (opcode) {
            case load_int:
                /* 从字节码加载立即数到虚拟寄存器 */
                ctx->reg[ctx->reg_ip_field] = *(int*)&bytecode[ctx->reg_ip];
                ctx->reg_ip += sizeof(int);
                break;

            case handler_push:
                /* push: reg_sp 指向的位置写入值,reg_sp 递增 */
                ctx->stack[ctx->reg_sp++] = ctx->reg[...];
                break;

            case handler_pop:
                /* pop: reg_sp 递减,读取栈顶值到虚拟寄存器 */
                ctx->reg[...] = ctx->stack[--ctx->reg_sp];
                break;

            case handler_add:
                /* add: 弹出两个操作数,相加后压回 */
                {
                    int b = ctx->stack[--ctx->reg_sp];
                    int a = ctx->stack[--ctx->reg_sp];
                    ctx->stack[ctx->reg_sp++] = a + b;
                }
                break;

            case handler_xor:
                /* xor: 弹出两个操作数,异或后压回 */
                {
                    int b = ctx->stack[--ctx->reg_sp];
                    int a = ctx->stack[--ctx->reg_sp];
                    ctx->stack[ctx->reg_sp++] = a ^ b;
                }
                break;

            case branchIfTrue:
                /* 条件跳转: 弹出条件值,若为真则设置 reg_ip */
                {
                    int cond = ctx->stack[--ctx->reg_sp];
                    int target = *(int*)&bytecode[ctx->reg_ip];
                    ctx->reg_ip += sizeof(int);
                    if (cond) {
                        ctx->reg_ip = target;
                    }
                }
                break;

            case halt:
                /* 停机 */
                return;

            default:
                /* 未知 opcode */
                break;
        }
    }
}

进阶技术

为了进一步增强虚拟化混淆的强度,课程介绍了两种进阶技术:

技术 1:Handler Duplication(处理器复制)

  • 思想:从同一个 handler 模板生成多个功能相同但代码不同的处理器副本。
  • 效果:同一个操作(如 add)可能有 10 个不同的 handler 实现,字节码中随机使用其中一个。
  • 目的:使分析者难以通过简单的 opcode 统计识别 handler 功能,因为同一个功能对应多个不同的代码实现。
模板 handler_add:
    弹出 b, 弹出 a, 压入 a+b

生成的副本:
    handler_add_1:  用 mov, add 指令实现
    handler_add_2:  用 lea 指令实现 lea eax, [a+b]
    handler_add_3:  用 sub 实现等效逻辑
    ...(10个不同变体)

技术 2:Direct Threaded Code(直接线程代码)

  • 思想:通过分散调度程序(dispersed dispatcher)隐藏 VM 主循环。用 goto(直接跳转)代替 switch-case 结构。
  • 效果:消除明显的 switch 结构,使控制流图不再呈现为"一个中心 dispatcher + 多个 case"的模式,增加分析难度。
// switch-case 版本(容易被识别):
while (1) {
    switch (bytecode[pc++]) {
        case 0x01: ...; break;
        case 0x02: ...; break;
        ...
    }
}

// Direct Threaded Code 版本(难以识别):
// 每个 handler 末尾直接跳转到下一个 handler
handler_push:
    ...;
    goto *next_handler_addr;    // 直接跳转,无中心 dispatcher

handler_add:
    ...;
    goto *next_handler_addr;

局限性

虚拟化混淆虽然强大,但也有其局限:

局限 说明
Loop VM 中的循环可能与 VM 主循环本身冲突,导致实现复杂。
Switch / Case VM 中的 switch-case 可能与 VM 的 switch-case dispatcher 冲突。
Exception Handling 异常处理机制可能与 VM 的执行流程冲突。

这些限制意味着虚拟化混淆并非对所有代码结构都适用。


3.7 Control Flow Flattening(控制流平坦化)

原理

Control Flow Flattening(控制流平坦化) 的思想是:将程序的每个基本块(basic block)展平为一个 switch 语句的一个 case,根据索引跳转到下一个块。

经过平坦化后,程序的控制流图从一个"有层次的结构"变成了一个"扁平的结构"——所有基本块都从属于一个中心 dispatcher(switch),原有的嵌套与分支关系被完全破坏。

平坦化前(结构化控制流):           平坦化后(平坦结构):

    A ──► B ──► C                     ┌── dispatcher (switch) ──┐
         │                            │  case 0: A              │
         ▼                            │  case 1: B              │
         D ──► E                       │  case 2: C              │
                                        │  case 3: D              │
                                        │  case 4: E              │
                                        └─────────────────────────┘
                                        (每个 case 设置下一索引)

代码示例

原始 C 代码:

printf("Hello, ");
printf("world!\n");

原始代码的执行顺序清晰:先打印 "Hello, ",再打印 "world!\n"

展平后(switch-case 结构):

int next_block = 0;   // 状态变量,控制下一个执行的基本块

while (1) {
    switch (next_block) {

        case 0:
            /* 原始的 printf("Hello, "); */
            printf("Hello, ");
            next_block = 1;   // 跳转到下一个块
            break;

        case 1:
            /* 原始的 printf("world!\n"); */
            printf("world!\n");
            next_block = 2;   // 跳转到下一个块(结束)
            break;

        case 2:
            /* 结束 */
            return 0;

        default:
            /* 不应到达 */
            break;
    }
}

经过平坦化后:

  • 反编译器无法还原出原始的顺序结构。
  • 所有的基本块都变成了 switch 的 case,控制流"扁平化"。
  • 分析者必须追踪 next_block 的赋值才能理解执行顺序。
  • 对于有条件分支的代码,平坦化后更难理解(因为分支变成了对 next_block 的不同赋值)。

实际案例:ANEL RAT (APT10)

控制流平坦化在真实 APT 恶意软件中已有应用。一个典型案例是 ANEL RAT,据信由 APT10 组织使用。

项目 内容
恶意软件 ANEL RAT
关联组织 APT10
MD5 a79f59b1b17e8bfa3299e50a8af9cdaf
混淆特征 编译器级别的控制流平坦化(Compiler-Level Obfuscations)

参考文献

  • Haruyama. Defeating APT10 Compiler-Level Obfuscations. VB'19 (Virus Bulletin Conference 2019).

反混淆思路

控制流平坦化的反混淆需要:

  1. 识别 dispatcher(switch)与状态变量。
  2. 通过数据流分析追踪状态变量的取值。
  3. 重建基本块之间的真实控制流边。
  4. 这通常结合符号执行或动态追踪完成。

3.8 混淆技术适用性表

不同的混淆技术对不同类型的代码有不同的适用性。下表展示了课程中使用的三种测试代码(test-add.c, test-hello.c, test-mod2.c)下,各种混淆技术的适用情况。

图例说明

符号 含义
适用,混淆成功应用
*1 基本块不足,平坦化无足够块可展平
*2 无可替代的操作,指令替换无适用目标
*3 无文字常量,编码文字无适用目标
*4 无插入空间,不透明谓词无合适插入位置

适用性表

混淆技术 test-add.c test-hello.c test-mod2.c
O-LLVM -sub *2(无可替代操作) *2
O-LLVM -bcf
O-LLVM -fla *1(基本块不足) *1
Tigress AddOpaque ✓(*4 无插入空间)
Tigress EncodeLiterals *3(无文字) *3
Tigress EncodeArithmetic *2
Tigress Virtualize
Tigress Flatten

适用性分析

  • test-add.c(常量加法):

    • 适合 -sub-bcfAddOpaqueEncodeArithmeticVirtualizeFlatten
    • -fla 不适用,因为代码过于简单,基本块数量不足(*1)。
    • EncodeLiterals 不适用,因为没有文字常量(*3)。
  • test-hello.c(字符串打印):

    • 适合 -bcfAddOpaqueEncodeLiteralsVirtualizeFlatten
    • -subEncodeArithmetic 不适用,因为没有算术运算可替换(*2)。
    • -fla 不适用,基本块不足(*1)。
  • test-mod2.c(取模分支):

    • 大多数技术都适用。
    • -subEncodeArithmetic 标记为 *2,但实际 test-mod2.c 中含取模运算,部分场景可应用。
    • EncodeLiterals 不适用(*3,无数值文字)。
    • AddOpaque 标记为 *4(无插入空间),但实际上取模分支本身有条件判断,可能已无额外空间。

结论:没有一种混淆技术适用于所有代码。选择混淆技术时需要根据代码特征匹配,这也是混淆工具提供多种变换选项的原因。


3.9 本章小结

本章详细讲解了 6 种(含 MBA 共 7 类)主流混淆技术:

技术 思想 强度 主要工具
Garbage/Dead Code Insertion 做无用的事 多数混淆器
Instruction Substitution 改语法 O-LLVM -sub
Encode Literals 改语法 Tigress EncodeLiterals
Encode Arithmetic / MBA 改语法 Tigress EncodeArithmetic
Opaque Predicate 改语法+语义 O-LLVM -bcf, Tigress AddOpaque
Virtualization Obfuscation 改语法+语义 Tigress Virtualize, VMProtect
Control Flow Flattening 改语法+语义 O-LLVM -fla, Tigress Flatten

关键要点:

  • 越改语义的混淆越难反混淆:仅改变语法的混淆(如 Instruction Substitution)可通过数据流分析化简;而改变控制流语义的混淆(如 Virtualization、Flattening)则需要更复杂的符号执行与等价性检查。
  • 技术可叠加:多种混淆技术可以组合使用,形成多层防护。
  • 没有万能混淆:不同混淆技术适用于不同代码特征,需要根据目标代码选择合适的技术组合。

下一章将介绍如何使用这些混淆工具构建实验环境。

posted @ 2026-08-05 15:54  DirWangK  阅读(14)  评论(0)    收藏  举报