Zp的VM比较简单,只模拟PUSH和JCC这2种类型指令,其他的全部都是明文保留,用乱序JMP连接.由于没有ANTI DUMP,所以可以补区段,把ZP的SHELL DLL所在区段以及后面的几个段一起补到DUMP+FIX IAT之后之后的程序中即可,包括VISO引擎处理过的代码也可以这样处理,一般脱壳这样也就可以了,如果被处理的代码中有我们感兴趣的地方,于是不得不寻求修复代码的方法.
由于我对VM一无所知,所以我将其当做代码变形来处理.
用DELPHI7编译个空窗体,用ZP1.49 VM掉第三个CALL
原代码如下:
VM过后:
原始地址指令用一个JMP代码 并用CC填充,然后通过一个PUSH/JMP 跳往VM主函数.
这里原本是乱序过的 跟起来比较累,处理方法有2种,一是修改主程序,强迫它不变形;第二种是提取主程序中的功能DLL,这个DLL是裸体的,修改下重定位,把对应代码贴过来即可.
把VM主函数中的乱序代码修复过后,单步跟.
关键部分如下:
009D0BAB是模拟标志,如果需要模拟的代码处理完毕,这里就会跳转通过.009D0BB9这里的FF15指向的是一个分配流程,前文提到,壳只模拟2种指令,PUSH和JCC
通过
连续4个mov edi,edi 分支判断是哪种指令,分别跳往各自的处理流程.
PUSH 流程
ECX中对应的是寄存器类型参数,对应关系如下
如果PUSH 后面的操作数是常数则不执行此流程,直接在FF15后给出数值.
jcc流程:
EAX中对应的是JCC的类型参数,对应关系如下
总共有10种类型JCC,我全部列出来了.紧接着通过
判断EAX是否为0 为0则跳转未实现.在修复的过程中可以强制其为0,这样方便跟踪下面的代码,记录一下跳转地址,在全部找到之后改改即可.
还原真实寄存器和堆栈,退出VM,返回未模拟的保留指令,当有PUSH或者JCC出现时,重新跳往VM.
由此可见,只需在VM_RETN以及分支判断处下断,就可以收集全部被VM的代码了.
附件中为
delphi7例子.exe
delphi7例子.zp.exe
补区段.exe