致谢:
bughoho,forgot,shoooo

试验程序用的max speed+的VMP.

VMP是堆栈机。VMP使用真堆栈,没有V_Stack
什么是堆栈机,简单的理解就是一切的指令,中间结果,数据"放"在堆栈.准确的说应该是通过堆栈关联.
要准确的理解可以参考这本书.<<Stack Computer>>

由于VMP是堆栈机所以指令系统和运作方式不太像我们熟悉的X86系统.对分析造成一些障碍.


VMP的入口代码:

最开始是两个push imm32 jmp的组合,依次压入解码的Key和PCODE的首地址(V_PCODESEG)
然后是下面的代码.  
(nextpcode不是很确切.因为只有个别handler才jmp回nextpcode,大部分情况时handler里面自己+V_EIP)

vmp0:0040304C                                   public VM_INIT
.vmp0:0040304C                   VM_INIT:                                ; CODE XREF: start+2824j
.vmp0:0040304C 51                                push    ecx
.vmp0:0040304D 55                                push    ebp
.vmp0:0040304E 53                                push    ebx
.vmp0:0040304F 56                                push    esi
.vmp0:00403050 51                                push    ecx
.vmp0:00403051 57                                push    edi
.vmp0:00403052 52                                push    edx
.vmp0:00403053 50                                push    eax
.vmp0:00403054 9C                                pushf
.vmp0:00403055 68 00 00 00 00                    push    0
.vmp0:0040305A 8B 74 24 28                       mov     esi, [esp+28h]  ; VM_CODESEG=004037B2
.vmp0:0040305E BF 00 30 40 00                    mov     edi, offset VM_Context
.vmp0:0040305E
.vmp0:00403063
.vmp0:00403063                   VM_NextPCode:                           ; CODE XREF: V_JMP+1j
.vmp0:00403063 89 F3                             mov     ebx, esi        ; ebx:previous_pcode的地址
.vmp0:00403065 03 34 24                          add     esi, [esp]      ; esi:指向current_pcode
.vmp0:00403065                                                           ;
.vmp0:00403065                                                           ; 首次进入esi=ebx
.vmp0:00403065
.vmp0:00403068
.vmp0:00403068                   Vm_Execute:                             ; CODE XREF: V_ADD_BYTE+6j
.vmp0:00403068                                                           ; V_PUSH_ESP+1j
.vmp0:00403068                                                           ; V_LOAD_BYTE_CONTEXT+16j
.vmp0:00403068                                                           ; V_LOAD_BYTE_BY_ADDR_DS+7j
.vmp0:00403068                                                           ; V_ADD_DWORD+5j
.vmp0:00403068                                                           ; V_POP_SP+2j ...
.vmp0:00403068 8A 0E                             mov     cl, [esi]       ; cl=cipher_pcode
.vmp0:0040306A 00 D9                             add     cl, bl
.vmp0:0040306C C0 C9 05                          ror     cl, 5
.vmp0:0040306F F6 D9                             neg     cl
.vmp0:00403071 46                                inc     esi             ; esi:指向数据,或者下一个pcode
.vmp0:00403072 80 E9 52                          sub     cl, 52h
.vmp0:00403075 D0 C1                             rol     cl, 1
.vmp0:00403077 FE C1                             inc     cl
.vmp0:00403079 80 F1 78                          xor     cl, 78h
.vmp0:0040307C 80 C1 19                          add     cl, 19h
.vmp0:0040307F 00 CB                             add     bl, cl
.vmp0:00403081 0F B6 C1                          movzx   eax, cl
.vmp0:00403084 8D 14 85 89 32 40+                lea     edx, VM_HANDLER_TABLE[eax*4]
.vmp0:0040308B FF 22                             jmp     dword ptr [edx]


上面就是VMP的主循环.
在主循环和handler中esi就是V_EIP edi就是指向V_CONTEXT.ebx保持一个key.
看见看出VMP的解码和执行流程有关.解码有一定难度.直接修改PCODE好像也很困难.

VM_EXECUTE主要功能就是解码PCODE然后jmp到相应的Handler.

VMP的指令:
我觉得VMP的指令如果用类似浮点指令的方式命名可能会容易理解一些.
FPU貌似也是堆栈机(我看来是,也不清楚具体的内部结构).
比如fld fst之类的指令.
没有fmov之类的指令.因为FPU处理任何指令.都是通过浮点堆栈取数据.

VMP也类似.
在我自己的+的小试验程序里面,确实也没有发现V_MOV类似的handler.不知道http://bbs.pediy.com/showthread.php?t=54535中的V_MOV是什么样子的handler.
希望能和wangdell请教一下.

可能是我的程序太小,只有4行指令
push 0
push imm32
push imm32
call    MessageBox
是不是VMP没有生成相关的handler?


识别handler的方法我是连蒙带猜.

不过可以通过最开始的PCODE快速的确定V_STORE_CONTEXT_DWORD的Handler
我通过OD记录执行的Handler的地址.然后根据手动还原的表.打出了一份VMP保护
push 0
push imm32
push imm32
Call   MesssageBox的PCODE
如下: (还没有记录PCODE的参数).能大概熟悉一下指令系统.STORE类指令就是把数据存入某地 (CONTEXT,内存地址).LOAD类是把数据从某地push到堆栈上.

V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE_CODESEG
V_STORE_CONTEXT_WORD
V_STORE_CONTEXT_WORD
V_SHL_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE_CODESEG
V_STORE_CONTEXT_WORD
V_STORE_CONTEXT_WORD
V_SHL_DWORD
V_STORE_CONTEXT_DWORD
V_STROE_WORD_CONTEXT
V_PUSH_ESP
V_LOAD_BYTE2DWORD_CODESEG
V_ADD_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_WORD_BY_ADDR_SS
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE2DWORD_CODESEG
V_LOAD_CONTEXT_DWORD
V_ADD_DWORD
V_STORE_CONTEXT_DWORD
V_PUSH_ESP
V_LOAD_DWORD_BY_ADDR_SS
V_STORE_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_NOTA_AND_NOTB_0
V_STORE_CONTEXT_DWORD
V_LOAD_DWORD_CODESEG
V_NOTA_AND_NOTB_0
V_STORE_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_DWORD_CODESEG
V_NOTA_AND_NOTB_0
V_STORE_CONTEXT_DWORD
V_NOTA_AND_NOTB_0
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE2DWORD_CODESEG
V_ADD_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE_CODESEG
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_SHLD_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_BYTE2DWORD_CODESEG
V_ADD_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_JMP
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD
V_STORE_CONTEXT_DWORD

V_LOAD_BYTE2DWORD_CODESEG            //push 0

V_LOAD_DWORD_CODESEG                     //push "HelloVm"
V_LOAD_DWORD_CODESEG                     //push "HelloVm"

V_LOAD_BYTE2DWORD_CODESEG           //push 0

V_LOAD_CONTEXT_DWORD
V_LOAD_DWORD_CODESEG
V_ADD_DWORD                                 
V_STORE_CONTEXT_DWORD

V_LOAD_DWORD_CODESEG             //这两条指令就是从IAT取MessageBox的地址。
V_LOAD_DWORD_BY_ADDR_DS        //可见IAT地址是直接保存在PCODE数据里面。不知道能不能类推[imm32]的寻址方式

V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_LOAD_CONTEXT_DWORD
V_VM_EXIT

通过PCODE 可以大概了解VMP的指令体系和大致的运作方式.

话说静态还原:
A.能自动识别Handler.这一点我觉得有难度。因为已经有混淆版VMP出现。可能是正版也可能私版VM.
B.区分VM自身结构的代码和被保护的代码.这个我认为相当困难。也许是我经验不够。如果有大量的分析成果的积累。或许能找到某种模式。
C.逻辑表达式的自动化简。自动化简可以办到。但是要求VMP不打乱逻辑顺序。(目前我也不知道VMP有没有打乱逻辑顺序)。
如只是一个萝卜一个坑的拆指令,是有办法自动化简的。

附件中有试验程序和IDB文件

水平有限,难免错漏,请各位斧正