2009.07.26_小娃崽_VMP 之反 VM (还原代码) 之初初

来源:互联网 发布:telnet端口开启 编辑:程序博客网 时间:2021/05/15 18:52

http://www.unpack.cn/forum.php?mod=viewthread&tid=38633 发表于 2009-7-26 21:19:55

VMP之还原VMP初探

作者:小娃崽[CUG]

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

   lods    byte ptr [esi]
   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 [eax]   
   jmp     vm_execute         //可以看做是和MOV [XXXXXXXX],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 [esp+28]
00404ABB    FC              cld
00404ABC    BF 00404000     mov     edi, 00404000
00404AC1    89F3            mov     ebx, esi
00404AC3    033424          add     esi, dword ptr [esp]
00404AC6    AC              lods    byte ptr [esi]
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 [eax*4+4040DD]    //开始执行伪指令

0040404F    AC              lods    byte ptr [esi]
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 [edi+eax*4]       //把之前PUSH的寄存器保存到VM_COMTEXT,10次,V_SaveToVMContext
00404060    E9 610A0000     jmp     00404AC6

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

00404B32    AC              lods    byte ptr [esi]
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 [edi+eax*4]     //把数据压入堆栈 ,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_SaveToVMContext  10次
   。。。。。。。。。。。。。
   00404421    AC              lods    byte ptr [esi]
   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 [esi]
   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 [edi+eax*4]  //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,1  VM后的结果是 :
     V_SaveToVMContext  
     V_PUSH 5
     V_SaveToVMContext
     V_VMContextToStack
     V_RETN   
我想还原起来可就不费力了。
可以从VM的外部着手,观察和猜测这段VM的大体功能,像高手们说的,把它看成是一个函数,有时候不用跟进VM就能看出个大概了,比如一个虚拟了的MessageBoxA,接着进入VM内部,注意观察VM_COMTEXT,堆栈的数据变化,把无效的PUSH,POP给剔除,精简出有效的伪指令,再还原。而且对数据操作的指令(ADD,MUL,DIV之类的)在VMP内部都有等效的伪指令,而且不难看出。