Zp的VM比较简单,只模拟PUSH和JCC这2种类型指令,其他的全部都是明文保留,用乱序JMP连接.由于没有ANTI DUMP,所以可以补区段,把ZP的SHELL DLL所在区段以及后面的几个段一起补到DUMP+FIX IAT之后之后的程序中即可,包括VISO引擎处理过的代码也可以这样处理,一般脱壳这样也就可以了,如果被处理的代码中有我们感兴趣的地方,于是不得不寻求修复代码的方法.
  
  由于我对VM一无所知,所以我将其当做代码变形来处理.
  
  用DELPHI7编译个空窗体,用ZP1.49 VM掉第三个CALL
  原代码如下:

引用:
0044B168   $  55            push ebp
0044B169   .  8BEC          mov ebp,esp
0044B16B   .  51            push ecx
0044B16C   .  53            push ebx
0044B16D   .  56            push esi
0044B16E   .  57            push edi
VM过后:
引用:
0044B168   /E9 9A5E0100     jmp 00461007                             ; 00461007
0044B16D   |CC              int3
0044B16E   |CC              int3
0044B16F   |CC              int3
0044B170   |CC              int3
0044B171   |CC              int3
0044B172   |CC              int3
原始地址指令用一个JMP代码 并用CC填充,然后通过一个PUSH/JMP 跳往VM主函数.
引用:
009D0E0A    9C              pushfd
009D0E0B    83EC 3E         sub esp,3E
009D0E0E    892424          mov dword ptr ss:[esp],esp
009D0E11    894424 36       mov dword ptr ss:[esp+36],eax
009D0E15    8B4424 42       mov eax,dword ptr ss:[esp+42]
009D0E19    894424 2E       mov dword ptr ss:[esp+2E],eax
009D0E1D    8B4424 3E       mov eax,dword ptr ss:[esp+3E]
009D0E21    894424 0C       mov dword ptr ss:[esp+C],eax
009D0E25    894C24 26       mov dword ptr ss:[esp+26],ecx
009D0E29    895424 3A       mov dword ptr ss:[esp+3A],edx
009D0E2D    895C24 18       mov dword ptr ss:[esp+18],ebx
009D0E31    896C24 08       mov dword ptr ss:[esp+8],ebp
009D0E35    897424 14       mov dword ptr ss:[esp+14],esi
009D0E39    897C24 10       mov dword ptr ss:[esp+10],edi
009D0E3D    830424 46       add dword ptr ss:[esp],46
009D0E41    54              push esp
009D0E42    E8 18FFFFFF     call 009D0D5F                            ; 009D0D5F
009D0E47    C3              retn
这里原本是乱序过的 跟起来比较累,处理方法有2种,一是修改主程序,强迫它不变形;第二种是提取主程序中的功能DLL,这个DLL是裸体的,修改下重定位,把对应代码贴过来即可.
把VM主函数中的乱序代码修复过后,单步跟.

关键部分如下:
引用:
009D0BAB   /75 29           jnz short 009D0BD6                       ; 009D0BD6
009D0BAD   |8B0D A4669D00   mov ecx,dword ptr ds:[9D66A4]            ; dumped_2.00A14258
009D0BB3   |03C1            add eax,ecx
009D0BB5   |56              push esi
009D0BB6   |8946 1C         mov dword ptr ds:[esi+1C],eax
009D0BB9   |FF15 70669D00   call dword ptr ds:[9D6670]               ; dumped_2.00A50744
009D0BBF   |8B46 20         mov eax,dword ptr ds:[esi+20]
009D0BC2   |85C3            test ebx,eax
009D0BC4   |74 05           je short 009D0BCB                        ; 009D0BCB
009D0BC6   |23C5            and eax,ebp
009D0BC8   |8946 20         mov dword ptr ds:[esi+20],eax
009D0BCB   |807E 24 01      cmp byte ptr ds:[esi+24],1
009D0BCF   |74 15           je short 009D0BE6                        ; 009D0BE6
009D0BD1   |8B46 20         mov eax,dword ptr ds:[esi+20]
009D0BD4  ^|EB D3           jmp short 009D0BA9                       ; 009D0BA9
009D0BAB是模拟标志,如果需要模拟的代码处理完毕,这里就会跳转通过.009D0BB9这里的FF15指向的是一个分配流程,前文提到,壳只模拟2种指令,PUSH和JCC
通过
引用:
00A5A863    8BFF            mov edi,edi
00A5A865    E9 7A210000     jmp 00A5C9E4                             ; 00A5C9E4
连续4个mov edi,edi 分支判断是哪种指令,分别跳往各自的处理流程.

PUSH 流程
引用:
009D07FC    83F9 19         cmp ecx,19
009D07FF    7F 72           jg short 009D0873                        ; 009D0873
009D0801    74 6C           je short 009D086F                        ; 009D086F
009D0803    83F9 07         cmp ecx,7
009D0806    7F 3B           jg short 009D0843                        ; 009D0843
009D0808    74 34           je short 009D083E                        ; 009D083E
009D080A    49              dec ecx
009D080B    74 2C           je short 009D0839                        ; 009D0839
009D080D    49              dec ecx
009D080E    74 24           je short 009D0834                        ; 009D0834
009D0810    49              dec ecx
009D0811    74 1C           je short 009D082F                        ; 009D082F
009D0813    49              dec ecx
009D0814    74 14           je short 009D082A                        ; 009D082A
009D0816    49              dec ecx
009D0817    74 0C           je short 009D0825                        ; 009D0825
009D0819    49              dec ecx
009D081A    0F85 99000000   jnz 009D08B9                             ; 009D08B9
ECX中对应的是寄存器类型参数,对应关系如下

引用:
eax  25
ecx  26
edx  27
ebx  28
esp  29
ebp  2a
esi  2b
edi  2c
如果PUSH 后面的操作数是常数则不执行此流程,直接在FF15后给出数值.

jcc流程:
引用:
009D08CF    55              push ebp
009D08D0    8BEC            mov ebp,esp
009D08D2    8B45 0C         mov eax,dword ptr ss:[ebp+C]
009D08D5    3D DF000000     cmp eax,0DF
009D08DA    75 0F           jnz short 009D08EB                       ; 009D08EB
009D08DC    8B45 08         mov eax,dword ptr ss:[ebp+8]
009D08DF    0FB640 0D       movzx eax,byte ptr ds:[eax+D]
009D08E3    C1E8 03         shr eax,3
009D08E6    83E0 01         and eax,1
009D08E9    5D              pop ebp
009D08EA    C3              retn
009D08EB    3D E0000000     cmp eax,0E0
009D08F0    75 0E           jnz short 009D0900                       ; 009D0900
009D08F2    8B45 08         mov eax,dword ptr ss:[ebp+8]
009D08F5    0FB640 0D       movzx eax,byte ptr ds:[eax+D]
009D08F9    C1E8 03         shr eax,3
009D08FC    F7D0            not eax
009D08FE  ^ EB E6           jmp short 009D08E6                       ; 009D08E6
009D0900    3D E1000000     cmp eax,0E1
009D0905    75 09           jnz short 009D0910                       ; 009D0910
009D0907    8B45 08         mov eax,dword ptr ss:[ebp+8]
009D090A    0FB640 0C       movzx eax,byte ptr ds:[eax+C]
009D090E  ^ EB D6           jmp short 009D08E6                       ; 009D08E6
009D0910    3D E2000000     cmp eax,0E2
EAX中对应的是JCC的类型参数,对应关系如下
引用:
jo  DF
jno E0
jb  E1
jnb E2
je  E3
jnz E4
jbe E5
ja  E6
js  E7
jns E8
jpe E9
jpo EA
jl  EB
jge EC
jle ED
jg  EE
总共有10种类型JCC,我全部列出来了.紧接着通过
引用:
00A59957    85C0            test eax,eax
判断EAX是否为0 为0则跳转未实现.在修复的过程中可以强制其为0,这样方便跟踪下面的代码,记录一下跳转地址,在全部找到之后改改即可.

引用:
009D0BF8    8328 08         sub dword ptr ds:[eax],8
009D0BFB    8B08            mov ecx,dword ptr ds:[eax]
009D0BFD    FF70 04         push dword ptr ds:[eax+4]
009D0C00    8F41 04         pop dword ptr ds:[ecx+4]
009D0C03    FF70 0C         push dword ptr ds:[eax+C]
009D0C06    8F01            pop dword ptr ds:[ecx]
009D0C08    FF30            push dword ptr ds:[eax]
009D0C0A    FF70 10         push dword ptr ds:[eax+10]
009D0C0D    FF70 14         push dword ptr ds:[eax+14]
009D0C10    FF70 08         push dword ptr ds:[eax+8]
009D0C13    FF70 18         push dword ptr ds:[eax+18]
009D0C16    FF70 3A         push dword ptr ds:[eax+3A]
009D0C19    FF70 26         push dword ptr ds:[eax+26]
009D0C1C    FF70 36         push dword ptr ds:[eax+36]
009D0C1F    8B48 2A         mov ecx,dword ptr ds:[eax+2A]
009D0C22    C701 00000000   mov dword ptr ds:[ecx],0
009D0C28    58              pop eax
009D0C29    59              pop ecx
009D0C2A    5A              pop edx
009D0C2B    5B              pop ebx
009D0C2C    5D              pop ebp
009D0C2D    5E              pop esi
009D0C2E    5F              pop edi
009D0C2F    5C              pop esp
009D0C30    9D              popfd
009D0C31    C3              retn
还原真实寄存器和堆栈,退出VM,返回未模拟的保留指令,当有PUSH或者JCC出现时,重新跳往VM.

由此可见,只需在VM_RETN以及分支判断处下断,就可以收集全部被VM的代码了.

附件中为
delphi7例子.exe
delphi7例子.zp.exe
补区段.exe
上传的附件 zp_vm.part1.rar
zp_vm.part2.rar