破解狼人 发表于 2010-10-31 22:40:53

VMP之反VM(还原代码)初探

VMP之还原VMP初探
作者:小娃崽

这应该是一篇菜鸟一看就懂,高手不用看就懂的帖子。不知道为什么,前段时间老是在想VMP这个东西,可能是因为太闲的原因吧。突然想到 如果虚拟了 MOV EAX,1这个指令最终结果还会是令EAX这个寄存器中的数据为1的。这两天休息,就下了VMP1.08回来虚拟了几个小程序,F8从头到尾把程序跑了一趟。累啊,以下是一点心得,先记录下来,免得以后忘记了。
【1】总的纲领:
汇编中操作数主要分为三类:寄存器操作数,内存操作数,立即操作数。CPU指令执行的最终结果还是改变寄存器或者内存中的数据。
比如 :
MOV EAX,1
mov ,eax
使EAX=1,=1
也可以变换一种形式改变EAX的值,
比如:
push 1
popeax
不管怎样,最终结果还是一样的,只是换了个形式,VMP也一样,只是跑了N条等价的伪指令,晃点我们的眼睛而已。

【2】VMP是堆栈机:
它有自己的"寄存器",有自己的伪指令,它对数据的操作都是通过堆栈进行的,从它的伪指令可以看出,伪指令太多了,我只看了几十条,其他的就看不下去了。
如【1】所述,指令的结果只是改变数据.
   pop   dx
   pop   ax
   pop   cx
   div   cx
   push    ax
   push    dx
   JMP   vm_execute //字除法,并把商和余数压入堆栈 v_divw

   pop   ecx
   add   dword ptr , ecx
   jmp   vm_execute         //可以看做是V_ADD

   lods    byte ptr
   add   al, bl
   sub   al, 0C6
   ror   al, 1
   not   al
   ror   al, 3
   add   bl, al
   cbw
   cwde
   push    eax
   jmp   vm_execute          //可以看做是V_PUSH


   pop   eax
   pop   dword ptr    
   jmp   vm_execute         //可以看做是和MOV ,reg 等价的伪指令


。。。。。。。。。。。。
没猜想错的话,在虚拟机内部,VMP对数据的操作转换到了VM_COMTEXT,堆栈当中,只是通过伪指令进行而已。
在论坛里还看到前辈们总结的 ,这个或那个再或那个等于异或什么的,还画有图,应该是精华部分吧,没看,因为我觉得我看不懂,以后用的上的话在看吧。
【3】VMP的构造:
    猜想进入VMP之前和退出VMP之后,堆栈的情况应该差不多的,改变不了多少。于是对VM入口下了硬件访问断点。
00404313    58            pop   eax
00404314    61            popad                     //F9几下来到这里 和VMP的入口刚好匹配得上 应该就是传说中的VM出口了。
00404315    9D            popfd
00404316    C3            retn                      //还有一条差不多的,只是这里是retnf
   F8按了好久,于是干脆就写了几句脚本,专门查看EAX的值,发现大体结构都是差不多的。

VM_START:
00401000 > $68 EB4C4000   push    00404CEB             //把PCODE压入堆栈
00401005   .- E9 A63A0000   jmp   00404AB0

VM_EXECUTE:

00404AB0    9C            pushfd
00404AB1    60            pushad                     
00404AB2    68 00000000   push    0
00404AB7    8B7424 28       mov   esi, dword ptr
00404ABB    FC            cld
00404ABC    BF 00404000   mov   edi, 00404000
00404AC1    89F3            mov   ebx, esi
00404AC3    033424          add   esi, dword ptr
00404AC6    AC            lods    byte ptr
00404AC7    00D8            add   al, bl
00404AC9    FEC0            inc   al
00404ACB    F6D0            not   al
00404ACD    C0C0 07         rol   al, 7
00404AD0    34 D3         xor   al, 0D3
00404AD2    00C3            add   bl, al
00404AD4    0FB6C0          movzx   eax, al
00404AD7    FF2485 DD404000 jmp   dword ptr     //开始执行伪指令

0040404F    AC            lods    byte ptr
00404050    00D8            add   al, bl
00404052    34 4A         xor   al, 4A
00404054    FEC8            dec   al
00404056    C0C8 05         ror   al, 5
00404059    FEC8            dec   al
0040405B    00C3            add   bl, al
0040405D    8F0487          pop   dword ptr        //把之前PUSH的寄存器保存到VM_COMTEXT,10次,V_SaveToVMContext
00404060    E9 610A0000   jmp   00404AC6

。。。。。。。。。。。。。。。。。N条伪指令

00404B32    AC            lods    byte ptr
00404B33    00D8            add   al, bl
00404B35    34 4A         xor   al, 4A
00404B37    FEC8            dec   al
00404B39    C0C8 05         ror   al, 5
00404B3C    FEC8            dec   al
00404B3E    00C3            add   bl, al
00404B40    FF3487          push    dword ptr    //把数据压入堆栈 ,10次,V_VMContextToStack
00404B43^ E9 7EFFFFFF   jmp   00404AC6

004045B6    58            pop   eax
004045B7    61            popad
004045B8    9D            popfd                            //再把堆栈的数据压入到真实的寄存器
004045B9    C3            retn

N条伪指令下来,最终执行了几条等价的汇编指令而已。以下是我自己总结的VMP大概构造:

Vm_Start
VM_Execute
V_SaveToVMContext
N条伪指令
V_VMContextToStack
V_RETN

【4】验证与猜想:
回到MOV EAX,1这条指令上来,我就虚拟了这个指令而已,然后在VM的出口下断,发现最终的结果就是EAX=1,其他寄存器与没VM之前的变化不大。以下是代码片段,我可是完整的F8几回才看到了点真相。

   V_SaveToVMContext10次
   。。。。。。。。。。。。。
   00404421    AC            lods    byte ptr
   00404422    00D8            add   al, bl
   00404424    2C C6         sub   al, 0C6
   00404426    D0C8            ror   al, 1
   00404428    F6D0            not   al
   0040442A    C0C8 03         ror   al, 3
   0040442D    00C3            add   bl, al
   0040442F    66:50         push    ax
   00404431    E9 57060000   jmp   00404A8D             //V_PUSH 5
   。。。。。。。。。。。。
   00404B17    AC            lods    byte ptr
   00404B18    00D8            add   al, bl
   00404B1A    C0C0 07         rol   al, 7
   00404B1D    FEC0            inc   al
   00404B1F    34 3E         xor   al, 3E
   00404B21    04 2D         add   al, 2D
   00404B23    00C3            add   bl, al
   00404B25    8F0487          pop   dword ptr //V_SaveToVMContext对应的V_EAX被改变
   00404B28^ E9 60FFFFFF   jmp   00404A8D
   。。。。。。。。。。。。
   V_VMContextToStack 10次
   V_RETN    //退出VM
【5】还原VMP的一点感悟
知道了事情的真相,感觉人肉还原VMP还是可行的,需要的只是时间和毅力,N条伪指令下来,才还原成几条汇编指令,我才不干!我想VMP强大的地方应该数PUSH,POP操作了太多次,有的负责解码,但大多是的时候我都觉得它们跟花指令差不多。而且HANDLE与PCODE可是随机组合的,同一段程序会VM出不同的版本。
如果MOV EAX,1VM后的结果是 :
   V_SaveToVMContext
   V_PUSH 5
   V_SaveToVMContext
   V_VMContextToStack
   V_RETN   
我想还原起来可就不费力了。
可以从VM的外部着手,观察和猜测这段VM的大体功能,像高手们说的,把它看成是一个函数,有时候不用跟进VM就能看出个大概了,比如一个虚拟了的MessageBoxA,接着进入VM内部,注意观察VM_COMTEXT,堆栈的数据变化,把无效的PUSH,POP给剔除,精简出有效的伪指令,再还原。而且对数据操作的指令(ADD,MUL,DIV之类的)在VMP内部都有等效的伪指令,而且不难看出。

【总结】:
   终于理解了什么是VM,高兴
页: [1]
查看完整版本: VMP之反VM(还原代码)初探