VMP_virtual_ISA
0.VMP的工作原理
1.VMP实现虚拟化的方案
- 虚拟机的寄存器:在内存开辟一段连续的区域当成虚拟机的寄存器,业界称之为VM_CONTEXT,某些版本的VMP用EDI指向这个区域
- 虚拟机的堆栈: 这个和物理机是一样的,直接在内存开辟就好。VMP还是用EBP指向栈顶
- 虚拟机的指令:不同版本VMP的指令是不一样的,这样可以在一定程度上防止VMP本身被破解,业界俗称VM_DATA
- 虚拟机的EIP:业界俗称veip,某些版本的VMP用ESI替代,指向VM_DATA,用以读取虚拟CPU需要执行的指令;
2、VMP虚拟机的执行流程
(1)想想启动VT时,是不是要先开辟一段内存空间,把当前guestOS部分寄存器的值保存好?VMP也一样,先保存物理寄存器的值,后续退出VM后才能还原
(2)让vEIP从VM_DATA读取虚拟机的指令(pcode)
(3)由于虚拟机的指令和物理CPU完全不同,那么在指令读取后,该怎么去执行了?举个栗子:比如0x1表示入栈,0x2表示出栈,0x3表示寄存器之间互相传数据(当然实际的指令可能不会这么简单,VMP每个版本的指令集都不同),这些指令该怎么执行了?在VMP中,有个概念叫handler,专门根据不同的指令执行不同的操作(当然这些操作VMP事先都定义好了)。这个和VT中VMX的handler作用类似:根据不同的异常有不同的处理方法(我个人猜测VMP的作者肯定借鉴了VT的原理和思路);
为了达到这种不同指令执行不同handler分支的效果,编码实现层面通常用switch+case实现,用于将不同的指令跳转到不同的分支执行,业界俗称dispatcher。具体到汇编代码,switch+case一般的汇编形式为:mov ecx,dword ptr ds:[eax*4+base](注意寄存器可能会变成其他的,但这 xxx*4+基址的形式不会变) , 这是比较明显的特征,用以用来定位VMP的dispatcher。
(4)执行完一个handler,vEIP接着指向下VM_DATA的下一个指令,然后重复(2)-(4)这几个步骤;
以VMP1.1版本的加密为例子
1 |
|
对test()函数内部代码进行VMP加密,x32dbg调试
1.mov eax 0x12345678指令进行了加密
0x40AFDD 就是pcode的地址
加密逻辑
下面讲一下VMP中虚拟指令Handler的分类,并介绍了一部分比较重要的指令
1、元指令
来看一个具体的例子,vPushImm4,这是1.7Demo版本没有混淆的Handler实现:
虚拟机栈顶Vesp就是ebp
再来看一个虚拟寄存器入栈的指令:vPushReg4
2、算术运算指令
接下来我们来看一下算术运算指令。最经典的当属加法指令vAdd4:
这样一来,这条vAdd4虚拟指令执行完毕之后,栈顶第一个值就是最新的eflags,第二个值就是加法的和了。
再来看一个逻辑右移指令vShr4:
这个Handler中,从栈顶读取4个字节的被操作数放到eax寄存器,从栈顶+4的位置读取1个字节的移位计数放到cl寄存器。然后抬高栈顶2个字节,执行移位操作,把移位的结果放到新的栈顶+4的位置。最后和加法一样,移位操作同样会涉及eflags寄存器的修改,也要把最新的eflags保存到栈顶。
这个图描绘了指令之前之前和执行完成的堆栈变化情况:
3、内存操作指令
接下来这一组是内存操作相关的虚拟指令,我们看一个例子vReadMemSs4:
第一步:从堆栈顶部读取4个字节装入eax
第二步:以刚刚读取到的eax的值作为指针,读取指向的内存区域,大小是4个字节,同样装入eax寄存器中。这里读取内存的时候,使用的段寄存器是ss,所以这个Handler命名里面有个Ss。
vReadMemSs4相当于mov [ebp],[[ebp]]
4、逻辑运算指令
他的操作流程就是
1 | mov [ebp+4],and((not)[ebp],not([ebp+4])); |
5、跳转指令
直接从栈顶的位置取了4个字节赋值给了esi寄存器,然后ebp+4,更新栈顶指针位置就完成了。
