原理

代码混淆的原理首先看:https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/3.2.6_instruction_confusion.html

方式

1.代码变形

指将单条或者多条指令转化成等价的单挑或多条指令。其中对单挑指令的变形叫做局部变形,对多条指令的变形结合起来就叫做全局变形。

例子:

1
mov eax,12345678h

可以用下面的组合来代替

1
2
push 12345678h
pop eax

更进一步:

1
2
3
4
5
pushfd
mov eax, 1234
shl eax, 10
mov ax, 5678
popfd

pushfdpopfd 是为了保护 EFLAGS 寄存器不受变形后指令的影响。

继续替换:

1
2
3
4
5
pushfd
push 1234
pop eax
shl eax, 10
mov ax 5678

这样的结果就是简单的指令也可能会变成上百上千条指令,大大提高了理解的难度。

再看下面的例子:

1
jmp {label}

可以变成:

1
2
push {label}
ret

而且 IDA 不能识别出这种 label 标签的调用结构。

指令:

1
call {label}

可以替换成:

1
2
3
push {call指令后面的那个label}
push {label}
ret

指令:

1
push {op}

可以替换成:

1
2
sub esp, 4
mov [esp], {op}

下面我们来看看全局变形。对于下面的代码:

1
2
mov eax, ebx
mov ecx, eax

因为两条代码具有关联性,在变形时需要综合考虑,例如下面这样:

1
2
3
4
mov cx, bx
mov ax, cx
mov ch, bh
mov ah, bh

这种具有关联性的特定使得通过变形后的代码推导变形前的代码更加困难。

2.花之令

https://blog.csdn.net/m0_46296905/article/details/117336574

3.扰乱指令序列

4.多分支

5.不透明谓词

6.间接指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dummy_data1 db      100h dup (0)
message1 db 'hello world', 0

dummy_data2 db 200h dup (0)
message2 db 'another message', 0

func proc
...
mov eax, offset dummy_data1
add eax, 100h
push eax
call dump_string
...
mov eax, offset dummy_data2
add eax, 200h
push eax
call dump_string
...
func endp

这里就是通过dummy_data 来间接的引用message,但是IDA就不能正确的分析到message的引用

7.代码虚拟化(这个也就是CTF题目中的VM逆向题目)

1
2
3
4
5
6
7
8
9
10
11
+------------+
| 头部指令序列 | -------> | 代码虚拟机入口 |
|------------| |
| | | 保存代码现场 |
| | |
| 中间指令序列 | | 模拟执行中间指令序列 |
| | |
| | | 设置新的代码现场 |
|------------| |
| 尾部指令序列 | <------- | 代码虚拟机出口 |
+------------+

当原始指令执行到指令序列的开始处,就转入代码虚拟机的入口。此时需要保存当前线程的上下文信息,然后进入模拟执行阶段,该阶段是代码虚拟机的核心。有两种方案来保证虚拟机代码与原始代码的栈空间使用互不冲突,一种是在堆上开辟开辟新的空间,另一种是继续使用原始代码所使用的栈空间,这两种方案互有优劣,在实际中第二种使用较多。

对于怎样模拟原始代码,同样有两种方案。一种是将原本的指令序列转变为一种具有直接或者间接对应关系的,只有虚拟机才能理解的代码数据。例如用 0 来表示 push, 1 表示 mov 等。这种直接或间接等价的数据称为 opcode。另一种方案是将原始代码的意义直接转换成新的代码,类似于代码变形,这种方案基于指令语义,所以设计难度非常大。