此文章是我学习VMProtect虚拟机加密程序的心得,关于VMProtect宏观理论,参考了N多的网络文章和资料,做个笔记,以后好回顾,O(∩_∩)O哈哈~
软件:正版VMProtect2.04加密的Win98记事本
加密选项:除了 编译--调试模式与水印 以外,全部打钩;虚拟计算机--数量为默认值1;编译类型:超级(变异+虚拟)
说明:即使是同一个程序,VMProtect每次加密出来的代码都是不同的,所以如果你也去加密一个Win98记事本,是不会得到我下面的代码的。
一、VMProtect虚拟机概述
虚拟机加密,就是说像VMP这样的保护程序,它会把源程序的X86指令变成自定义的伪指令,等到执行的时候,VMP内置在保护程序中的VM就会启动,读取伪指令,然后解析执行。
VMP是一个堆栈虚拟机,它的一切操作都是基于堆栈传递的。在VMP中,伪指令就是一个个的handler,VM中有一个核心的Dispatch部分,它通过读取程序的bytecode,然后在DispatchiTable里面定位到不同的handler中执行。绝大多数情况下,在一个handler中执行完成后,程序将回到Dispatch部分,然后到next handler中执行。
在上面的框架中,核心的部件就是Dispatch部分,下面并列的部件就是handlers。
经过VMP加密的X86指令,一条简单的指令被分解成数条VMP的伪指令,它按照自己的伪指令排列去实现原指令的功能,在加上其他的花指令混乱等等,你将完全看不到源程序指令。VMP自带的各种机制都不再是以X86指令的形式去实现,而是用自己的伪指令去测试。
二、VMProtect虚拟机具体解析
1)到达VM的Dispatch部分
VMP要保存程序当前的context状况,然后进入虚拟代码执行
以下的代码完整粘贴自OD。有*标记的是程序的流程代码。其他的是junk code
CPU Disasm
Address Hex dump Command Comments
00437817 . 68 B59DF9FC PUSH FCF99DB5
0043781C .^ E9 F320FFFF JMP 00429914
00429914 > /C70424 0A4142 MOV DWORD PTR SS:[ESP],E342410A ; *
0042991B . |56 PUSH ESI
0042991C . |E8 E3C50000 CALL 00435F04
;开头放入了1个数字,这是基本数据。当然它现在是加密的。第一个push的数据并没有用,因为第二个push就覆盖掉了它,在动态调试中比较容易看出。
00435F04 $ E8 60160000 CALL 00437569 ; \NOTEPAD.00437569
00437569 /$ C64424 04 87 MOV BYTE PTR SS:[ARG.1],87 ; NOTEPAD.00437569(guessed Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8,Arg9,Arg10,Arg11,Arg12,Arg13,Arg14,Arg15,Arg16,Arg17,Arg18,Arg19,Arg20)
0043756E |. C74424 08 D3A MOV DWORD PTR SS:[ARG.2],B433AFD3 ; *
00437576 |. 60 PUSHAD
00437577 |. 9C PUSHFD
00437578 |. 8D6424 2C LEA ESP,[ESP+2C] ;压入再多的数据都没用,这条指令直接修改了ESP指针
0043757C \. E9 015C0000 JMP 0043D182
;又放入了一个数据,当然它依然是加密的
;当前堆栈情况:
;0013FFBC B433AFD3 ӯ3
;0013FFC0 E342410A .AB
;0013FFC4 7C817077 wp| ; RETURN to kernel32.7C817077
0043D182 \> /E9 31020000 JMP 0043D3B8
0043D3B8 /> \60 PUSHAD
0043D3B9 |. 9C PUSHFD
0043D3BA |. 68 82589578 PUSH 78955882
0043D3BF |. 897424 24 MOV DWORD PTR SS:[ESP+24],ESI ; *
0043D3C3 |. 68 07421102 PUSH 2114207
0043D3C8 |. 68 8098AB76 PUSH 76AB9880
0043D3CD |. 57 PUSH EDI
0043D3CE |. 8D6424 30 LEA ESP,[ESP+30]
0043D3D2 \. E9 6C040000 JMP 0043D843
;我们现在可以来认识一点VMP的伎俩:
;1、虽然有很多的push指令,但是通过使用lea esp,[esp+xx]这样的指令,前面的所有的push都将作废
;2、为了跳过前面的junk push指令,在真正需要往堆栈放入数据的时候就使用mov dword ptr ss:[esp+xx],esi这样的指令,跳过了堆栈的xx空间来存储
;保存esi寄存器
0043D843 |> >E8 B6F6FFFF CALL 0043CEFE ; \NOTEPAD.0043CEFE
0043CEFE $ BE 054674C6 MOV ESI,C6744605 ; NOTEPAD.0043CEFE(guessed Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8,Arg9,Arg10,Arg11,Arg12,Arg13,Arg14,Arg15,Arg16,Arg17,Arg18,Arg19,Arg20)
0043CF03 . 8D6424 04 LEA ESP,[ESP+4]
0043CF07 . 0F80 D5050000 JO 0043D4E2
0043CF0D . 57 PUSH EDI ; *
0043CF0E . 68 EF0D9B40 PUSH 409B0DEF
0043CF13 . 0FCF BSWAP EDI
0043CF15 . 5F POP EDI
0043CF16 . 53 PUSH EBX
0043CF17 . 891424 MOV DWORD PTR SS:[ESP],EDX ; *
0043CF1A . 0FB6F1 MOVZX ESI,CL
0043CF1D . 50 PUSH EAX ; *
0043CF1E . 9C PUSHFD
0043CF1F . E9 C41A0000 JMP 0043E9E8
;保存edi edx eax
0043E9E8 |> \E8 8AF7FFFF CALL 0043E177 ; \NOTEPAD.0043E177
0043E177 /$ 66:0FCF BSWAP DI ; Undocumented instruction or encoding
0043E17A |. 896C24 04 MOV DWORD PTR SS:[ARG.1],EBP ; *
0043E17E \.^ E9 BEF5FFFF JMP 0043D741
;保存ebp
0043D741 |> /871C24 XCHG DWORD PTR SS:[ESP],EBX ; *
0043D744 |. |9C PUSHFD
0043D745 |. |60 PUSHAD
0043D746 |. |894C24 20 MOV DWORD PTR SS:[ESP+20],ECX ; *
0043D74A |. |60 PUSHAD
0043D74B |. |9C PUSHFD
0043D74C |. |66:891C24 MOV WORD PTR SS:[ESP],BX ; |Arg1
0043D750 |. |875424 40 XCHG DWORD PTR SS:[ESP+40],EDX ; *
0043D754 |. |E8 D50E0000 CALL 0043E62E ; \NOTEPAD.0043E62E
;保存ebx ecx 再次的保存edx
0043E62E /$ 9C PUSHFD ; *
0043E62F |. 8F4424 40 POP DWORD PTR SS:[ESP+40] ; *
0043E633 |. 66:89C6 MOV SI,AX
0043E636 |. FF35 89D24300 PUSH DWORD PTR DS:[43D289] ; *
0043E63C |. 8F4424 3C POP DWORD PTR SS:[ESP+3C] ; *
;保存eflags。和读取了一个数字0保存,这个0非常重要
0043E640 |. 0FCD BSWAP EBP
0043E642 |. 66:FFC7 INC DI
0043E645 |. 47 INC EDI
0043E646 |. C74424 38 000 MOV DWORD PTR SS:[ESP+38],0 ; *
;再次放入了一个0
;我们必须要在这里停顿一下了。由于是静态的看文章,O__O"…,让我们来看一下堆栈:
0013FF90 00000000 .... ; 0
0013FF94 00000000 .... ; 数字0
0013FF98 00000246 F.. ; eflags
0013FF9C 7C92E514 | ; edx
0013FFA0 0013FFB0 . ; ecx
0013FFA4 7FFDF000 .0 ; ebx
0013FFA8 0013FFF0 . ; ebp
0013FFAC 00000000 .... ;eax
0013FFB0 7C92E514 | ;edx
0013FFB4 00500065 e.P. ;edi
0013FFB8 00640061 a.d. ;esi
0013FFBC B433AFD3 ӯ3 ;最开始的2个基础加密数据
0013FFC0 E342410A .AB
0013FFC4 7C817077 wp| ; RETURN to kernel32.7C817077
;除了动态的esp寄存器,其他的寄存器已经保存完毕了,
0043E64E |. 0FCD BSWAP EBP
0043E650 |. 66:87D5 XCHG BP,DX
0043E653 |. 66:897C24 08 MOV WORD PTR SS:[ESP+8],DI
0043E658 |. 66:F7D3 NOT BX
0043E65B |. 8B7424 68 MOV ESI,DWORD PTR SS:[ESP+68] ; *
;它读取的是第一个基础数据
0013FFC0 E342410A .AB ;*
0013FFC4 7C817077 wp| ; RETURN to kernel32.7C817077
0043E65F |. 66:0FCD BSWAP BP ; Undocumented instruction or encoding
0043E662 |. 0F98C6 SETS DH
0043E665 |. 0FCE BSWAP ESI ; *
0043E667 |. 66:F7D2 NOT DX
0043E66A |. 66:0FBEEB MOVSX BP,BL
0043E66E |. 4E DEC ESI ; *
0043E66F |. 66:FFC5 INC BP
0043E672 |. 20F6 AND DH,DH
0043E674 |. 66:C1D2 0E RCL DX,0E
0043E678 |. 66:81FE 3214 CMP SI,1432 ; *
0043E67D |. 81F6 63A1000A XOR ESI,0A00A163 ; *
;到这里esi的计算完成,esi==0041E381,在VMP里,esi都是指向bytecode的,看来马上就要开始读取了,然后跳转到handler了,哈哈。。还有一些准备工作的,就是要划分堆栈空间的,理解好VMP的堆栈空间划分是至关重要的。
0043E683 |. 45 INC EBP
0043E684 |. 80FD EF CMP CH,0EF
0043E687 |. 66:0FB3D5 BTR BP,DX
0043E68B |. 5A POP EDX
0043E68C |. 8D6C24 34 LEA EBP,[ESP+34] ; *
;这里给ebp传递0013FF90,切下第一刀
0043E690 |. 86F8 XCHG AL,BH
0043E692 |. 81EC 8C000000 SUB ESP,8C ; *
;分配大段的堆栈空间
0043E698 |. 66:0FBEFA MOVSX DI,DL
0043E69C |. 89E7 MOV EDI,ESP ; *
;这里给edi传递0013FED0,切下第二刀
0043E69E |> C0FE 04 SAR DH,4
0043E6A1 |. 0F9FC3 SETG BL
0043E6A4 |. FEC8 DEC AL
0043E6A6 |. C0C0 02 ROL AL,2
0043E6A9 |. 89F3 MOV EBX,ESI ; *
;在ebx中备份一下esi的0041e381
0043E6AB |. 0FBAF2 05 BTR EDX,5
0043E6AF |. F7DA NEG EDX
0043E6B1 |. 0FCA BSWAP EDX
0043E6B3 |. 66:0FBAEA 0E BTS DX,0E
0043E6B8 |. 0375 00 ADD ESI,DWORD PTR SS:[EBP] ; *
;[ebp]中的是堆栈0013FF90里面的0,看来这是一个偏移量,用来修正bytecode地址
0043E6BB |> 66:0FA5FA SHLD DX,DI,CL ; Dispatch
0043E6BF |. 8A46 FF MOV AL,BYTE PTR DS:[ESI-1] ; *
;开始读取bytecode,看来准备工作完成。它没有读取[esi],而是[esi-1]
0043E6C2 |. 88FA MOV DL,BH
0043E6C4 |. 30D8 XOR AL,BL ; * 在ebx中备份bytecode地址;原来是要地址参与到计算中
0043E6C6 |. FECA DEC DL
0043E6C8 |. 8D95 88EB93F7 LEA EDX,[EBP+F793EB88]
0043E6CE |. F6D0 NOT AL ; *
0043E6D0 |. 42 INC EDX
0043E6D1 |. F7D2 NOT EDX
0043E6D3 |. 0F9FC2 SETG DL
0043E6D6 |. FEC8 DEC AL ; *
0043E6D8 |. D0CA ROR DL,1
0043E6DA |. C0C8 07 ROR AL,7 ; * 一直在计算al值,算好了
0043E6DD |. F5 CMC
0043E6DE |. C0E6 06 SHL DH,6
0043E6E1 |. 83EE 01 SUB ESI,1 ; * 指针减1,
0043E6E4 |. D0D2 RCL DL,1
0043E6E6 |. 66:0FB6D1 MOVZX DX,CL
0043E6EA |. 66:D1DA RCR DX,1
0043E6ED |. 30C3 XOR BL,AL ; * bl可是参与到计算的,bl帮al算完了,现在al来帮bl算好下一次的值
0043E6EF |. 60 PUSHAD
0043E6F0 |. 66:0FBAE1 06 BT CX,6
0043E6F5 |.^ E9 32E9FFFF JMP 0043D02C
0043D02C |> /C0DE 07 RCR DH,7
0043D02F |. |0FB6C0 MOVZX EAX,AL ; *
0043D032 |. |F5 CMC
0043D033 |. |E9 E7200000 JMP 0043F11F
0043F11F |> \38F5 CMP CH,DH
0043F121 |. 66:FFC2 INC DX
0043F124 |. 8B1485 DBE143 MOV EDX,DWORD PTR DS:[EAX*4+43E1DB] ; *等你等到我心痛呀
;终于开始,DispatchTable原来是在43E1DB,获得的是加密的地址,如:49C4C29F,数据窗口去43E1DB看看,茫茫多的地址呀,有200多个,不过这个不代表着有200多个handler洒,应该有一些是指向相同的handler。
0043F12B |. F9 STC
0043F12C |. 84F4 TEST AH,DH
0043F12E |. 60 PUSHAD
0043F12F |.^ E9 CCEFFFFF JMP 0043E100
0043E100 |> /81C2 6B197FB6 ADD EDX,B67F196B ; *原来只用一条ADD指令就解出来了,上面的49C4C29F=》0043DC0A,很简单的解密,自己可以去DispatchTable计算出所有的
0043E106 |. |C60424 46 MOV BYTE PTR SS:[ESP],46
0043E10A |. |895424 3C MOV DWORD PTR SS:[ESP+3C],EDX ; * handler地址存一下,存到哪里呢?看看堆栈吧
;0013FECC 0043DC0A .C. ; 存到这里
;0013FED0 E5064000 .@ ;这里是EDI指向的空间,看一下前面的堆栈划分图
0043E10E |. |9C PUSHFD
0043E10F |. |68 A42795AD PUSH AD9527A4
0043E114 |. |FF7424 0C PUSH DWORD PTR SS:[ESP+0C]
0043E118 |. |FF3424 PUSH DWORD PTR SS:[ESP]
0043E11B |. |FF7424 4C PUSH DWORD PTR SS:[ESP+4C] ; *
0043E11F |. |C2 5000 RETN 50 ; *
;这两条*指令要一起看,第一条push的就是0013FECC 0043DC0A这个handler地址,第二条就跑去找handler
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
上传的附件: