代码混淆
原理
代码混淆的原理首先看:https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/3.2.6_instruction_confusion.html
方式
1.代码变形
指将单条或者多条指令转化成等价的单挑或多条指令。其中对单挑指令的变形叫做局部变形,对多条指令的变形结合起来就叫做全局变形。
例子:
1 | mov eax,12345678h |
可以用下面的组合来代替
1 | push 12345678h |
更进一步:
1 | pushfd |
pushfd
和 popfd
是为了保护 EFLAGS 寄存器不受变形后指令的影响。
继续替换:
1 | pushfd |
这样的结果就是简单的指令也可能会变成上百上千条指令,大大提高了理解的难度。
再看下面的例子:
1 | jmp {label} |
可以变成:
1 | push {label} |
而且 IDA 不能识别出这种 label 标签的调用结构。
指令:
1 | call {label} |
可以替换成:
1 | push {call指令后面的那个label} |
指令:
1 | push {op} |
可以替换成:
1 | sub esp, 4 |
下面我们来看看全局变形。对于下面的代码:
1 | mov eax, ebx |
因为两条代码具有关联性,在变形时需要综合考虑,例如下面这样:
1 | mov cx, bx |
这种具有关联性的特定使得通过变形后的代码推导变形前的代码更加困难。
2.花之令
https://blog.csdn.net/m0_46296905/article/details/117336574
3.扰乱指令序列
4.多分支
5.不透明谓词
6.间接指针
1 | dummy_data1 db 100h dup (0) |
这里就是通过dummy_data 来间接的引用message,但是IDA就不能正确的分析到message的引用
7.代码虚拟化(这个也就是CTF题目中的VM逆向题目)
1 | +------------+ |
当原始指令执行到指令序列的开始处,就转入代码虚拟机的入口。此时需要保存当前线程的上下文信息,然后进入模拟执行阶段,该阶段是代码虚拟机的核心。有两种方案来保证虚拟机代码与原始代码的栈空间使用互不冲突,一种是在堆上开辟开辟新的空间,另一种是继续使用原始代码所使用的栈空间,这两种方案互有优劣,在实际中第二种使用较多。
对于怎样模拟原始代码,同样有两种方案。一种是将原本的指令序列转变为一种具有直接或者间接对应关系的,只有虚拟机才能理解的代码数据。例如用 0
来表示 push
, 1 表示 mov
等。这种直接或间接等价的数据称为 opcode。另一种方案是将原始代码的意义直接转换成新的代码,类似于代码变形,这种方案基于指令语义,所以设计难度非常大。