指令虚拟化
虚拟机(VM)其实就是用软件来模拟硬件。我们可以仿照 x86 指令集,自己定义一套指令,在程序(解释器)中有一套函数和结构来解析自定义的指令并执行相应的功能。
虚拟化是一种基于虚拟机的代码保护技术。他将硬件支持的机器码转化为字节码指令系统,来达到不被轻易篡改和逆向的目的。
简单来说就是出题人通过实现一个小型的虚拟机,自定义一些操作码(opcode),然后在程序执行时通过解释操作码,执行对应的函数,从而实现程序原有的功能。
下图是常见的虚拟机结构:
虚拟机的主程序其实就是一个循环,这个循环不断的去读取指令(伪机器码 opcode),然后执行指令opcode 所对应的一些函数,这样下来就可以与真实的程序执行相差无几。
正向实现
想要对抗虚拟化,首先要搞清楚用于保护的虚拟机是如何实现的要想实现虚拟机,需要完成两个目标:
定义一套指令集
实现对应的解释器
结构体定义
真实赛题中的 VM 通常会实现一个类似如下的结构体,用于保存虚拟机状态:
1 | typedef struct |
opcode 定义
接着自定义一些指令,需要决定该指令集是定长的还是变长的。
这里以变长指令集为例,先列出一个表来:
书写机器码
假定希望实现的语义如下:
1 |
|
根据我们定义的指令对其进行拆分和重构,可以得到如下机器码:
1 | unsigned char code[] = { |
初始化虚拟机
在实际运行虚拟机之前,需要先对 VM 结构体进行初始化:
1 | VM* vm_new() { |
解释器编写
现在就可以来实现每条指令的 handle 以及 dispatcher 了
1 | int vm_run(VM* vm) { |
启动
于是 main 函数可以这样写:
1 | int main() { |
解题步骤
遇到 VM 类的赛题,我们一般按照如下的步骤来解题:
分析 VM 结构
- 结构体大小
- 有哪些字段(内存、寄存器)
分析指令集
- 指令长度是否可变
- 每种指令的构成
- 每种指令的含义(伪汇编)
- VM 的退出条件
编写 Python 版解释器,输出伪汇编代码
阅读伪代码,分析程序流程,写出去虚拟化的原始代码
书写解题脚本
本例中的 Python 版解释器如下:
1 | code = [0x20, 0x10, 0x48, 0x20, 0x11, 0x65, 0x20, 0x12, 0x6c, 0x20, 0x13, 0x6c, |