源程序
1 |
|
动态分析
首先有几个寄存器需要关注:
| 寄存器 | 所存储数据 |
|---|---|
| R12 | Handler表(未赋值) |
| R13 | Handler基址 |
| RBP | 栈顶指针 |
| RSI | 字节码缓冲区(偏移) |
| RDI | 伪寄存器组 |
| RBX | 解密Seed |
VMProtect首先需要将所需要的寄存器环境进行初始化,下面一次是:赋值handler表,字节码缓冲区操作,取操作码, 操作码解密

接下来跳转到下方代码,使用操作码索引handler偏移,然后加上基址,跳转到handler处

跟着跳转来到这里,红框中取操作数

可以看到是一个POP操作,将栈中数据POP到一个伪寄存器(BYTE:[RSI-1]指示了要POP到哪个寄存器)

紧跟着跳回这里,操作数索引伪寄存器组,赋值为rdx

到这里第一个handler就完成了,接下来的操作就是继续重复上面的取操作码,索引handler表,然后跳转到新的handler。
新的handler如下,取四字节操作数

下面应该是对操作数进行解码

然后push rax


第一条jmp会跳到如下位置,ja操作其实是栈溢出错误检查(如果溢出了,那rbp会超过rdi+0xe0),结合上图中的红框代码,可以知道RDI指向的位寄存器组大小为0xE0


小总结
上面的一些handler大概都是一些vmp虚拟的push、pop、jmp的操作,也就是和前面笔记所说的一样,就是把基于寄存器的CPU代码,改造成基于堆栈的CPU的伪代码。
vmp3与vmp1和vmp2的最大区别,解析bytescode不在由VMDispatcher 分发下一个指令执行什么了(每个指令记为一个handle) 而是有vm_bytescode掌管,执行上一个指令才能得到下一个指令地址这样一来代码的膨胀可想而知。在VM_Instruct内部应该是没有CALL指令的。