[机翻] [ABD] 03_混淆技术详解
03 混淆技术详解
本章是课程的核心章节,详细讲解 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, 0xdeadc00d 和 mov 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 的
-sub对add指令的替换会使用形如以下模式(具体实现因版本而异):; 将 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 * 7或0x2A的某种编码形式。 - 对于字符串:将字符串逐字符分解,通过指令逐字节写入内存。
工具选项
- 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 表达式的难点在于:
- 非线性:混合了布尔运算和算术运算,难以用线性代数方法化简。
- 可叠加:可以多次嵌套,层层叠加使表达式膨胀。
- 抗符号执行:某些 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)│
└─────────────┘
- VM Entry:初始化虚拟机上下文,设置虚拟寄存器、VM stack 指针等。
- Fetch:从字节码流中取出下一条指令(opcode)。
- Decode:解码 opcode,确定要调用哪个 handler。
- 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).
反混淆思路
控制流平坦化的反混淆需要:
- 识别 dispatcher(switch)与状态变量。
- 通过数据流分析追踪状态变量的取值。
- 重建基本块之间的真实控制流边。
- 这通常结合符号执行或动态追踪完成。
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、-bcf、AddOpaque、EncodeArithmetic、Virtualize、Flatten。 -fla不适用,因为代码过于简单,基本块数量不足(*1)。EncodeLiterals不适用,因为没有文字常量(*3)。
- 适合
-
test-hello.c(字符串打印):- 适合
-bcf、AddOpaque、EncodeLiterals、Virtualize、Flatten。 -sub和EncodeArithmetic不适用,因为没有算术运算可替换(*2)。-fla不适用,基本块不足(*1)。
- 适合
-
test-mod2.c(取模分支):- 大多数技术都适用。
-sub和EncodeArithmetic标记为 *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)则需要更复杂的符号执行与等价性检查。
- 技术可叠加:多种混淆技术可以组合使用,形成多层防护。
- 没有万能混淆:不同混淆技术适用于不同代码特征,需要根据目标代码选择合适的技术组合。
下一章将介绍如何使用这些混淆工具构建实验环境。

浙公网安备 33010602011771号