Cr4ckm3 发表于 2017-3-10 00:36

VMProtect 3.09 虚拟机架构浅析

本帖最后由 Cr4ckm3 于 2017-3-10 09:59 编辑

0x00 写在前面
最近两周一直在研究VMProtect相关的东西,发现VMProtect 2.x 被研究的比较充分,FKVMP、Zeus、VMP分析插件1.4等神器也出来了很久。但VMProtect 3 没有在论坛内找到相关的资料,这里写一点简单分析作为抛砖引玉之用。新人第一贴,求论坛的大大们多多鼓励。

根据官方公告,VMProtect 3使用了新的虚拟机架构,本文通过分析自己加虚拟化的简单程序,介绍一下VMProtect 3虚拟机的基本架构,并与VMProtect 2.x进行简单对比。
这里强调一下,本贴只介绍虚拟机架构方面的内容,关于脱壳、IAT恢复、过anti等等请参考其他大佬的文章。

0x01 初步分析
分析的样本是用MASM32编写的小程序,源代码如下:

include \masm32\include\masm32rt.inc

.data?
value dd ?
int_array dd 100 dup(?)

.data
;item dd 0
text db "Hello Text",0
caption db "Hello Caption",0

.code

start:

call main
push NULL
call ExitProcess


main proc

mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
mov eax, 0deadbeefh
ret
main endp

end start


编译完成后大小1.50 KB (1,536 字节)

使用VMProtect 1.81 demo版本对main函数(地址0x40100C)加虚拟化,最快速度,关掉一切其他保护。
这里之所以使用 1.81 demo这么低的版本,主要原因是该版本VMP中的虚拟机代码没有混淆,生成的bytecode也比较规则,没有2.x版本的冗余分支,同时又能被VMP分析插件1.4完美支持,因此非常适合拿来学习VMP虚拟机结构。
加密后文件大小5.00 KB (5,120 字节)

再使用VMProtect 2.12.3版本加虚拟化,选项相同 ,关掉所有保护。
加密后文件大小15.0 KB (15,360 字节)

再使用论坛的VMProtect 3.09版本加虚拟化,选项相同,关掉所有保护。
加密后文件大小516 KB (528,896 字节)

可见VMProtect 3.x版本加密后的代码量有非常大的变化,注意这里关掉了所有保护选项的情况,因此这些多出来的代码不是壳代码,也不是调试器检测的代码,就是实实在在虚拟化后的指令。
这么为什么会多出这么多,这里简单分析一下。

需要用到OD的追踪功能(很好用的功能,但大家经常忽略掉他)
OD分别打开两个文件,Ctrl+F11 跟踪步入,64位电脑先将ntdll.dll和kernel32.dll标记为系统DLL,避免记录到trace中。
跟踪完成后分析两个程序产生trace的区别。

console.vmp_1.81_demo.exe生成527条trace
用OD统计下指令数(注意在选项中关掉统计时将相邻指令合并的选项),前几条是这样
Count Address Command Comment
48. 00403025 mov al,byte ptr ds:
48. 00403027 movzx eax,al
48. 0040302A sub esi,-0x1
48. 0040302D jmp dword ptr ds:

console.vmp_2.12.3.exe 生成14277条trace
用OD统计下指令数,前几条是这样
Count Address Command Comment
149. 004046E5 mov ecx,dword ptr ds:
149. 004046EC pushad
149. 004046ED lea esp,dword ptr ss:
149. 004046F1 jle console_.00404622
149. 00404833 neg cx
149. 00404836 sub cx,0xFBDF

console.vmp_3.09.exe 生成2238条Trace
Count Address Command Comment
18. 00421B90 jmp edi
18. 0042D77B lea eax,dword ptr ss:
18. 0042D77F cmp sp,0x6934
18. 0042D784 cmp ebp,eax
18. 0042D786 ja console_.00421B90
2. 00408854 jmp edi
2. 004099CF inc eax
2. 004099D0 ror eax,0x2
2. 004099D3 jmp console_.0041442D

如果了解VMP架构的小伙伴应该发现问题了。我们具体比较一下
先说1.81,由于是demo版本,所以没有混淆,所以trace数相当的少。
2.12版本加了大量垃圾指令,同时也会额外生成几条bytecode,所以Trace数量猛增。
关键是3.09版本,其文件大小是2.12版本的几十倍,但trace数却只是2.12版本的几分之一。
再看指令数统计,答案就很明白了。

我们知道VMP 1.x 2.x的虚拟机结构都是典型的取指令、解码opcode、根据opcode进行dispatch的过程,整个虚拟机是个大循环结构,handler表最多0xFF大小,所有x86指令都由这0xFF(由于有重复,实际会少)个handler模拟完成。
执行次数最多的jmp dword ptr ds:和mov ecx,dword ptr ds:都是典型的dispatch指令。
由于循环的存在,很少的指令也会生成较多的Trace

反观3.x 可以看到只有5条指令重复执行了18次,剩下的指令最多只执行过2次。可以说3.x循环的部分是相关少的。那么这些取指令和dispatch的过程都是独立的。最为可怕的是handler本身也不是复用的。也就是说两条相同的指令会生成不同的handler代码,尽管他们实现的功能是完全相同的。因此文件大小会发生极大的变化。
(这里可能不确切,更大的可能是VMProtect的handler表变大了,由于测试指令过少,所以没有发生重复。如果真的每条指令都单独生成一系列handler,那加密指令多的时候文件会膨胀的可怕。但确定的一点是,原本复杂的循环结构消失了。)

0x02 版本对比

前面已经说过没有了循环结构,那么具体分析一下3.x的虚拟机是如何工作的。
首先简单说一下2.x的虚拟机,资料很多,不多说了,只提几个要点。

VMP虚拟机是栈式机,与x86体系结构的寄存器式虚拟机不同。x86的add eax, ebx指令,在VMP中会变成push ebx, push eax, add,pop eax的形式。这里的栈称为虚拟栈。
EBP指向虚拟机栈顶,充当x86中的ESP功能,即虚拟机的vm_esp。
虚拟机执行的字节码是编码在程序中的,ESI指向其地址,ESI也就是vm_eip
EDI指向寄存器数组,也就是vm_context。2.x版本的vm_context也在栈中,在EBP上方。(其实最特别早期的VMP版本中vm_context也曾位于全局数据区,过段时间会写篇关于VMP各版本变化过程的文章,敬请期待)
vm_context包含16个4字节寄存器(32位的情况下)
栈结构是这样:
栈底--->ebp------>edi(vm_context)---> esp
EBX 经常参与解密,保存密钥。

说了关于旧版本的废话,这里直接抛出新版本的结构对比:
相同点:
ESI依然是vm_eip,指向要解析的字节码。
EBP依然是vm_esp,指向虚拟栈顶
EBX 依然经常参与解密。
不同点:
栈结构是这样:
栈底--->ebp------>esp(vm_context)
ESP成为vm_context指针,EDI作为跳转寄存器,统一的dispatcher消失,每次跳转需要从ESI取出4字节进行解密加到EDI上,然后jmp edi(或push edi,ret 功能一样)
这里ESP成为vm_context指针的设计对我们来说是个好消息,因为vm_context不能随便移动,因此涉及到栈操作的垃圾指令大大减少了。
但jmp edi的跳转方式很让人头疼。
FKVMP和VMP分析插件等神器的基本逻辑是找到dispatcher 即jmp dword ptr ds:类似的代码
然后枚举eax的值0~0xFF即可得到所有handler的入口,根据指令特征识别handler。依次解密出bytecode。

3.x不再有统一的dispatch过程,使得之前的神器从架构上无法应用到3.x版本。


0x3 具体分析

前面是为了有个统一的印象。
接下来具体分析。

我们直接看前面OD得到的Trace,实际的trace中有大量的垃圾指令,但正如前面所说,多不涉及栈操作,因此比较简单,以下的分析均是去了垃圾指令的,所以会发现地址不太连续。

虚拟机初始化部分


<ModuleEntryPoint>call console_.0040100C                  ; ESP=0019FF80

; jmp push call入口, 和vmp 2.x差不多
0040100C Main   jmp console_.00481F2C
00481F2C Main   push 0x6D3BB7D7                           ; ESP=0019FF7C
00481F31 Main   call console_.00436946                  ; ESP=0019FF78

;寄存器入栈
00436946 Main   push eax                                  ; ESP=0019FF74
00436948 Main   push edi                                  ; ESP=0019FF70
00436949 Main   push ebp                                  ; ESP=0019FF6C
0043694A Main   pushfd                                    ; ESP=0019FF68
0043694C Main   push edx                                  ; ESP=0019FF64
00436953 Main   push ebx                                  ; ESP=0019FF60
00436958 Main   push ecx                                  ; ESP=0019FF5C
0043695C Main   push esi                                  ; ESP=0019FF58
0043695D Main   mov eax,0x0                               ; EAX=00000000
00436965 Main   push eax                                  ; ESP=0019FF54


; ESI = 入口处压入的常数,解密ESI
00436968 Main   mov esi,dword ptr ss:         ; ESI=6D3BB7D7
0043696C Main   neg esi                                 ; FL=CAS, ESI=92C44829
0043696E Main   not esi                                 ; ESI=6D3BB7D6
0043697C Main   neg esi                                 ; FL=CAS, ESI=92C4482A
00436984 Main   bswap esi                                 ; ESI=2A48C492
0043698A Main   not esi                                 ; ESI=D5B73B6D
0043698C Main   xor esi,0x2A080CB4                        ; FL=S, ESI=FFBF37D9
00436996 Main   not esi                                 ; ESI=0040C826
0043699B Main   lea esi,dword ptr ds:
0043699E Main   inc al                                    ; FL=C, EAX=00000001

; 分配栈空间,仍为0xC0
; ebp仍为栈指针, esp指向context
004369A7 Main   mov ebp,esp                               ; EBP=0019FF54
004369B0 Main   sub esp,0xC0                              ; ESP=0019FE94

; 初始化EBX
004369BB Main   mov ebx,esi                               ; EBX=0040C826


; edi = 0x4369DE 初始化EDI
004369DE Main   lea edi,dword ptr ds:         ; EDI=004369DE



跳转到第一个handler



; 取ESI的4字节
004369EA Main   mov eax,dword ptr ds:                ; EAX=D7FE101E
; vEIP += 4

004369F0 Main   add esi,0x4                               ; FL=0, ESI=0040C82A

; 解码EAX
004369FE Main   xor eax,ebx                               ; FL=S, EAX=D7BED838
00436A00 Main   inc eax                                 ; FL=PS, EAX=D7BED839
0045D528 Main   ror eax,0x2                               ; FL=PS, EAX=75EFB60E
00410CB9 Main   inc eax                                 ; FL=P, EAX=75EFB60F
00410CBD Main   xor eax,0x75EF373B                        ; FL=0, EAX=00008134
0044A61F Main   dec eax                                 ; FL=P, EAX=00008133
00449463 Main   xor ebx,eax                               ; FL=0, EBX=00404915

; 跳转到 edi+eax
00449465 Main   add edi,eax                               ; FL=PA, EDI=0043EB11
00438226 Main   push edi                                  ; ESP=0019FE90
00438227 Main   retn                                    ; ESP=0019FE94



这里就开始是handler部分了,与2.x 一样,最开始的几条bytecode指令是把栈中的真实寄存器pop到虚拟寄存器数组vm_context中




; ======================= handler
; vPopReg4 R14 (0x38)
; 这里handler命令规则采用VMP分析插件的命名风格,表示从栈中弹出4字节数据到寄存器R13中(以R0开头)
; 解码寄存器下标 al
0043EB11 Main   movzx eax,byte ptr ds:               ; EAX=000000F3
0043EB14 Main   add esi,0x1                               ; FL=P, ESI=0040C82B
0043EB1D Main   xor al,bl                                 ; FL=S, EAX=000000E6
0043EB2A Main   not al                                    ; EAX=00000019
0043EB38 Main   xor al,0x7D                               ; FL=0, EAX=00000064
0043EB3F Main   inc al                                    ; FL=P, EAX=00000065
0043EB43 Main   xor al,0xF6                               ; FL=PS, EAX=00000093
0043EB49 Main   ror al,1                                  ; EAX=000000C9
0043EB4E Main   dec al                                    ; FL=CS, EAX=000000C8
0043EB58 Main   neg al                                    ; FL=CA, EAX=00000038
0043EB64 Main   xor bl,al                                 ; EBX=0040492D

; 取出栈顶的值,退出4字节栈空间,赋到vm_context中
; 明显的pop操作,除了寄存器使用不同,实现方法其实和2.x是一样的
0043EB6B Main   mov ecx,dword ptr ss:                ; ECX=00000000
0042B819 Main   add ebp,0x4                               ; FL=0, EBP=0019FF58
004539B2 Main   mov dword ptr ss:,ecx


第一条handler结束后,又是跳转
虽然功能完全一样,但VMP又实现出了完全独立了另一套代码
以此规避掉了循环


; 接下来又从ESI取4字节,解密并跳转上次解密代码的地址是004369EA
004539B5 Main   mov eax,dword ptr ds:                ; EAX=D7FCBE81
004539B7 Main   lea esi,dword ptr ds:            ; ESI=0040C82F
004539BE Main   xor eax,ebx                               ; FL=PS, EAX=D7BCF7AC
0044BADF Main   inc eax                                 ; FL=S, EAX=D7BCF7AD
0044BAE2 Main   ror eax,0x2                               ; EAX=75EF3DEB
0043D2C3 Main   inc eax                                 ; FL=0, EAX=75EF3DEC
0043D2C6 Main   xor eax,0x75EF373B                        ; FL=P, EAX=00000AD7
0045507E Main   dec eax                                 ; FL=0, EAX=00000AD6
00455081 Main   xor ebx,eax                               ; FL=0, EBX=004043FB
0045508A Main   add edi,eax                               ; FL=P, EDI=0043F5E7
0045508C Main   push edi                                  ; ESP=0019FE90
0045508D Main   retn                                    ; ESP=0019FE94


这里又是一个pop handler。


; ======================= handler
; vPopReg4 R8 (0x20)
0043F5E7 Main   movzx eax,byte ptr ds:               ; EAX=0000004D
0043F5EA Main   add esi,0x1                               ; FL=PA, ESI=0040C830
0043F5F6 Main   xor al,bl                                 ; FL=S, EAX=000000B6
0043F5FD Main   not al                                    ; EAX=00000049
0043F5FF Main   xor al,0x7D                               ; FL=0, EAX=00000034
0043F605 Main   inc al                                    ; FL=P, EAX=00000035
0043F60E Main   xor al,0xF6                               ; FL=PS, EAX=000000C3
0043F616 Main   ror al,1                                  ; FL=CPS, EAX=000000E1
0043F61D Main   dec al                                    ; FL=CS, EAX=000000E0
0043F626 Main   neg al                                    ; FL=C, EAX=00000020
0043F628 Main   xor bl,al                                 ; FL=PS, EBX=004043DB

0043F630 Main   mov ecx,dword ptr ss:                ; ECX=00401000
0043F63A Main   add ebp,0x4                               ; EBP=0019FF5C
0043F643 Main   mov dword ptr ss:,ecx


这里又是一个跳转。


; 跳转到下条指令,与之前代码位置又都不同
0043F64E Main   mov eax,dword ptr ds:                ; EAX=28020B6A
0043F652 Main   lea esi,dword ptr ds:            ; ESI=0040C834
0043F65D Main   xor eax,ebx                               ; FL=P, EAX=284248B1
00467C71 Main   inc eax                                 ; EAX=284248B2
00467C72 Main   ror eax,0x2                               ; FL=CP, EAX=8A10922C
00426C32 Main   inc eax                                 ; FL=CPS, EAX=8A10922D
00426C34 Main   xor eax,0x75EF373B                        ; FL=S, EAX=FFFFA516
00426C39 Main   dec eax                                 ; EAX=FFFFA515
00426C3A Main   xor ebx,eax                               ; EBX=FFBFE6CE
00426C3C Main   add edi,eax                               ; FL=CP, EDI=00439AFC
00408854 Main   jmp edi


这里又是一个pop handler


; ======================= handler
; vPopReg4
00439AFC Main   movzx eax,byte ptr ds:               ; EAX=00000068
00439B06 Main   lea esi,dword ptr ds:            ; ESI=0040C835
00439B0C Main   xor al,bl                                 ; EAX=000000A6
00439B18 Main   not al                                    ; EAX=00000059
00439B21 Main   xor al,0x7D                               ; FL=P, EAX=00000024
0047EF23 Main   inc al                                    ; FL=0, EAX=00000025
0047EF2C Main   xor al,0xF6                               ; FL=S, EAX=000000D3
0047EF32 Main   ror al,1                                  ; FL=CS, EAX=000000E9
0047EF3D Main   dec al                                    ; FL=CPS, EAX=000000E8
0047EF3F Main   neg al                                    ; FL=CPA, EAX=00000018
0047EF49 Main   xor bl,al                                 ; FL=S, EBX=FFBFE6D6

0047EF50 Main   mov ecx,dword ptr ss:                ; ECX=00401000
0047EF55 Main   lea ebp,dword ptr ss:            ; EBP=0019FF60
0047EF5B Main   mov dword ptr ss:,ecx


基本模式:
ESI保存bytecode和跳转地址,每条Bytecode前有4字节跳转地址
进行跳转时将跳转地址读出来,进行解密,解密后的值再加上EDI
就是最终的跳转地址,即handler地址。
按照惯例,虚拟机入口pop出所有寄存器到vm_context
应该会产生大量的pop指令,不一一分析了,这里直接跳过。

跳过的bytecode如下

vPopReg4 R1 (0x4)   
vPopReg4 R3 (0xC)
vPopReg4 R0 (0x0)
vPopReg4 R11 (0x2C)
vPopReg4 R12
vPopReg4 R2
vPopReg4 R13


执行完10个pop,这里终于出现了新handler, 是压入立即数指令,也就是mov eax, 0deadbeefh对应的bytecode。

; vPushImm4 DEADBEEF
; 取立即数并解密
0047D362 Main   mov eax,dword ptr ds:                ; EAX=39A96DBB
0040F037 Main   lea esi,dword ptr ds:            ; ESI=0040C865
0040F03D Main   xor eax,ebx                               ; FL=S, EAX=C612D09B
0040F044 Main   neg eax                                 ; FL=CPA, EAX=39ED2F65
0040F047 Main   rol eax,0x2                               ; EAX=E7B4BD94
0040F04C Main   add eax,0x4B906ADA                        ; FL=C, EAX=3345286E
00477C1C Main   xor eax,0x7A4740E4                        ; FL=0, EAX=4902688A
00477C23 Main   sub eax,0x69850CCD                        ; FL=CPAS, EAX=DF7D5BBD
00477C28 Main   ror eax,1                                 ; EAX=EFBEADDE
00477C2A Main   bswap eax                                 ; EAX=DEADBEEF
00477C31 Main   xor ebx,eax                               ; FL=P, EBX=211603CF
; 压到栈中
00477C38 Main   sub ebp,0x4                               ; FL=A, EBP=0019FF7C
00477C42 Main   mov dword ptr ss:,eax



又是跳转,但这次稍有不同,因为是push指令,需要检查栈是否发生溢出,这与2.x是一致的

; 取下次跳转的地址
00477C46 Main   mov eax,dword ptr ds:                ; EAX=095A81E6
0043B383 Main   lea esi,dword ptr ds:            ; ESI=0040C869
0043B391 Main   xor eax,ebx                               ; FL=0, EAX=284C8229
0043ED4B Main   inc eax                                 ; EAX=284C822A
0043ED4C Main   ror eax,0x2                               ; FL=C, EAX=8A13208A
00436549 Main   inc eax                                 ; FL=CPS, EAX=8A13208B
00436550 Main   xor eax,0x75EF373B                        ; FL=S, EAX=FFFC17B0
00418A8E Main   dec eax                                 ; FL=PAS, EAX=FFFC17AF
00418A91 Main   xor ebx,eax                               ; FL=PS, EBX=DEEA1460
00418A93 Main   add edi,eax                               ; FL=CPA, EDI=0043EB11
; 由于是push指令,为了避免栈中数据与vm_context重合,需要检查栈溢出,这与2.x是一致的
; 检查栈溢出
0042D77B Main   lea eax,dword ptr ss:         ; EAX=0019FEF4
0042D784 Main   cmp ebp,eax                              
0042D786 Main   ja console_.00421B90


这部分代码不在trace中,因为实际不会溢出,这里单独从OD中拿出来作为分析,同样都去掉了垃圾指令


; 如果发生溢出
0042D78C    8BD4            mov edx,esp
0042D790    B9 40000000   mov ecx,0x40
; 分配0x80空间
0042D79C    8D4425 80       lea eax,dword ptr ss:
00472FEA    24 FC         and al,0xFC
00472FEC    2BC1            sub eax,ecx   
; 上抬vm_context位置
004783AE    8BE0            mov esp,eax
004061B8    57            push edi                                 ; console_.<ModuleEntryPoint>
004061B9    9C            pushfd
00474547    56            push esi                                 ; console_.<ModuleEntryPoint>
00474548    8BF2            mov esi,edx                              ; console_.<ModuleEntryPoint>
0043AA70    8BF8            mov edi,eax
; 复制vm_context内容
004307BC    FC            cld   
00416140    F3:A4         rep movs byte ptr es:,byte ptr ds:


00416148    5E            pop esi                                  ; kernel32.755362C4
004166E8    9D            popfd
004166EC    5F            pop edi                                  ; kernel32.755362C4
00456F23    57            push edi                                 ; console_.<ModuleEntryPoint>
00456F24    C3            retn



接下来回到原本的分析过程中,由于重复了10遍mov eax, 0deadbeefh,
因此会出现10遍同样的handler,但地址各不相同


vPopReg4 R15(0x3C)
vPushImm4 DEADBEEF
vPopReg4 R4 (0x10)
vPushImm4 DEADBEEF
vPopReg4 R12(0x30)
vPushImm4 DEADBEEF
vPopReg4 R13(0x34)
vPushImm4 DEADBEEF
vPopReg4 R2 (0x08)
vPushImm4 DEADBEEF
vPopReg4 R9 (0x24)
vPushImm4 DEADBEEF
vPopReg4 R10(0x28)
vPushImm4 DEADBEEF
vPopReg4 R15(0x3C)
vPushImm4 DEADBEEF
vPopReg4 R4 (0x10)
vPushImm4 DEADBEEF
vPopReg4 R7 (0x1C)


这里可以看到同样的mov eax, 0deadbeefh ,pop的寄存器是不同的,可见2.x的寄存器轮转机制并没有变化。


新跳转

00450F03 Main   mov eax,dword ptr ds:                ; EAX=D7E129A1
00450F08 Main   lea esi,dword ptr ds:            ; ESI=0040C8E3
00450F0E Main   xor eax,ebx                               ; FL=0, EAX=2858C061
0043F841 Main   inc eax                                 ; EAX=2858C062
0042D4DE Main   ror eax,0x2                               ; FL=C, EAX=8A163018
00444518 Main   inc eax                                 ; FL=CS, EAX=8A163019
0044451A Main   xor eax,0x75EF373B                        ; FL=PS, EAX=FFF90722
0044451F Main   dec eax                                 ; EAX=FFF90721
00442C79 Main   xor ebx,eax                               ; FL=P, EBX=0040EEE1
00442C83 Main   add edi,eax                               ; FL=C, EDI=0040AC3D
00442C85 Main   jmp edi

新的handler ,这次是push,用于退出虚拟机时保存虚拟寄存器到栈中

; vPushReg4 R7(0x1C)
; 解密寄存器下标
0040AC3D Main   movzx eax,byte ptr ds:               ; EAX=0000005F
00454BB7 Main   add esi,0x1                               ; FL=P, ESI=0040C8E4
00454BBD Main   xor al,bl                                 ; FL=PS, EAX=000000BE
00475727 Main   not al                                    ; EAX=00000041
00413C0A Main   xor al,0x7D                               ; FL=P, EAX=0000003C
00456F67 Main   inc al                                    ; FL=0, EAX=0000003D
00456F6B Main   xor al,0xF6                               ; FL=S, EAX=000000CB
00456F6E Main   ror al,1                                  ; EAX=000000E5
00447E46 Main   dec al                                    ; FL=CPS, EAX=000000E4
00447E48 Main   neg al                                    ; FL=CA, EAX=0000001C
00481E64 Main   xor bl,al                                 ; FL=S, EBX=0040EEFD
; push reg
00481E67 Main   mov eax,dword ptr ss:            ; EAX=DEADBEEF
00481E71 Main   lea ebp,dword ptr ss:            ; EBP=0019FF7C
00481E7A Main   mov dword ptr ss:,eax


接下来的bytecode都是push指令,不一一分析了

vPushReg4 R11(0x2C)
vPushReg4 R0(0x0)
vPushReg4 R3(0xC)
vPushReg4 R1(0x4)
vPushReg4 R5(0x14)
vPushReg4 R6(0x18)
vPushReg4 R8(0x20)


又是一段跳转代码

00429FBE Main   mov eax,dword ptr ds:                ; EAX=D7F040B7
00429FC0 Main   lea esi,dword ptr ds:            ; ESI=0040C90B
00429FC6 Main   xor eax,ebx                               ; FL=S, EAX=D7B04B20
00429FC8 Main   inc eax                                 ; FL=PS, EAX=D7B04B21
00429FC9 Main   ror eax,0x2                               ; EAX=75EC12C8
00425BD2 Main   inc eax                                 ; FL=P, EAX=75EC12C9
00425BD4 Main   xor eax,0x75EF373B                        ; FL=0, EAX=000325F2
00425BD9 Main   dec eax                                 ; EAX=000325F1
00425BDA Main   xor ebx,eax                               ; FL=P, EBX=00432E66
00425BDF Main   add edi,eax                               ; FL=0, EDI=0045E09E
0042D77B Main   lea eax,dword ptr ss:         ; EAX=0019FEF4
0042D784 Main   cmp ebp,eax                               ; FL=PA
0042D786 Main   ja console_.00421B90
00421B90 Main   jmp edi



新的handler
vRet, 功能与之前一样,从栈中将寄存器弹出到真实寄存器中。

0045E09E Main   mov esp,ebp                               ; ESP=0019FF60
0045E0AA Main   pop esi                                 ; ESP=0019FF64, ESI=00401000
0045E0AB Main   pop ecx                                 ; ECX=00401000, ESP=0019FF68
0045E0B2 Main   pop ebx                                 ; EBX=003F7000, ESP=0019FF6C
0045E0BA Main   pop edx                                 ; EDX=00401000, ESP=0019FF70
0045E0C7 Main   popfd                                     ; FL=PZ, ESP=0019FF74
0045E0CD Main   pop ebp                                 ; ESP=0019FF78, EBP=0019FF94
0045E0D6 Main   pop edi                                 ; ESP=0019FF7C, EDI=00401000
0045E0DB Main   pop eax                                 ; EAX=DEADBEEF, ESP=0019FF80
004822D1 Main   retn                                    ; ESP=0019FF84

; 这里已经是非虚拟化的代码了
00401005 Main   push 0x0                                  ; ExitCode = 0x0, ESP=0019FF80
00401007 Main   call <jmp.&kernel32.ExitProcess>          ; FL=0, EAX=00401000, ECX=00000000, EDX=00000000, EBX=7707F9A0, ESP=0019FE8C, EBP=0019FF64, ESI=00000000, EDI=00000000
; 程序退出


我们把1.81 demo的bytecode提取出来,这里用到VMP分析插件1.4这个神器(由于是demo版本,FKVMP是不行的)


00403765 VMP_0040100C/$78         vPopReg4 vR14      DWORD _t0 = 0
00403766               |.6B F70C72B7vPushImm4 0B7720CF7DWORD _t1 = 0B7720CF7
0040376B               |.39         vAdd4                DWORD _t2 = 0B7720CF7; DWORD _t3 = AddFlag(_t1, check00000000)
0040376C               |.74         vPopReg4 vR13      DWORD _t4 = _t3
0040376D               |.5C         vPopReg4 vR7         DWORD _t5 = 0B7720CF7
0040376E               |.40         vPopReg4 vR0         DWORD _t6 = EAX
0040376F               |.48         vPopReg4 vR2         DWORD _t7 = EDX
00403770               |.64         vPopReg4 vR9         DWORD _t8 = EBP
00403771               |.50         vPopReg4 vR4         DWORD _t9 = EDX
00403772               |.74         vPopReg4 vR13      DWORD _t10 = ECX
00403773               |.44         vPopReg4 vR1         DWORD _t11 = EDI
00403774               |.54         vPopReg4 vR5         DWORD _t12 = ESI
00403775               |.70         vPopReg4 vR12      DWORD _t13 = EFL
00403776               |.68         vPopReg4 vR10      DWORD _t14 = EBX
00403777               |.4C         vPopReg4 vR3         DWORD _t15 = 401016
00403778               |.58         vPopReg4 vR6         DWORD _t16 = 403765
00403779               |.FA EFBEADDEvPushImm4 0DEADBEEFDWORD _t17 = 0DEADBEEF
0040377E               |.4C         vPopReg4 vR3         DWORD _t18 = 0DEADBEEF
0040377F               |.E9 EFBEADDEvPushImm4 0DEADBEEFDWORD _t19 = 0DEADBEEF
00403784               |.58         vPopReg4 vR6         DWORD _t20 = 0DEADBEEF
00403785               |.23 EFBEADDEvPushImm4 0DEADBEEFDWORD _t21 = 0DEADBEEF
0040378A               |.40         vPopReg4 vR0         DWORD _t22 = 0DEADBEEF
0040378B               |.B4 EFBEADDEvPushImm4 0DEADBEEFDWORD _t23 = 0DEADBEEF
00403790               |.60         vPopReg4 vR8         DWORD _t24 = 0DEADBEEF
00403791               |.23 EFBEADDEvPushImm4 0DEADBEEFDWORD _t25 = 0DEADBEEF
00403796               |.4C         vPopReg4 vR3         DWORD _t26 = 0DEADBEEF
00403797               |.6B EFBEADDEvPushImm4 0DEADBEEFDWORD _t27 = 0DEADBEEF
0040379C               |.7C         vPopReg4 vR15      DWORD _t28 = 0DEADBEEF
0040379D               |.B4 EFBEADDEvPushImm4 0DEADBEEFDWORD _t29 = 0DEADBEEF
004037A2               |.4C         vPopReg4 vR3         DWORD _t30 = 0DEADBEEF
004037A3               |.6B EFBEADDEvPushImm4 0DEADBEEFDWORD _t31 = 0DEADBEEF
004037A8               |.6C         vPopReg4 vR11      DWORD _t32 = 0DEADBEEF
004037A9               |.E9 EFBEADDEvPushImm4 0DEADBEEFDWORD _t33 = 0DEADBEEF
004037AE               |.40         vPopReg4 vR0         DWORD _t34 = 0DEADBEEF
004037AF               |.23 EFBEADDEvPushImm4 0DEADBEEFDWORD _t35 = 0DEADBEEF
004037B4               |.58         vPopReg4 vR6         DWORD _t36 = 0DEADBEEF
004037B5               |.28         vPushReg4 vR10       EBX DWORD v0 = EBX
004037B6               |.30         vPushReg4 vR12       EFL DWORD v1 = EFL
004037B7               |.14         vPushReg4 vR5      ESI DWORD v2 = ESI
004037B8               |.04         vPushReg4 vR1      EDI DWORD v3 = EDI
004037B9               |.34         vPushReg4 vR13       ECX DWORD v4 = ECX
004037BA               |.10         vPushReg4 vR4      DWORD _t42 = _t9
004037BB               |.24         vPushReg4 vR9      EBP DWORD v5 = EBP
004037BC               |.08         vPushReg4 vR2      EDX DWORD v6 = EDX
004037BD               |.18         vPushReg4 vR6      EAX DWORD v7 = 0DEADBEEF
004037BE               |.30         vPushReg4 vR12       DWORD _t46 = _t13
004037BF               |.1C         vPushReg4 vR7      DWORD _t47 = 0B7720CF7
004037C0               \.A4         vRet               return Stack(34, 4)


对比可以发现其实bytecode是基本一致的,但是内部实现已经发生了巨大变化。

这里简单分析就结束了。
至于条件跳转等其他实现细节,以后再慢慢讨论

0x04 写在最后

从今年2月分开始的连续几个月时间可能都要研究虚拟机保护相关的内容,因此也会陆续发些心得笔记上来。
这里希望论坛大大们可以给些指点,好少走点弯路。
也希望其他坛友们能多多回帖讨论。


相关文件都放在附件中了
解压密码www.52pojie.cn
深夜发贴,求鼓励
以上

Cr4ckm3 发表于 2017-3-27 20:27

拉酷 发表于 2017-3-26 17:56
请问如何把ntdll.dll设置为系统dll?
OD中切换到 “E" 窗口,右键对应的DLL 选择”设为系统DLL”即可

Cr4ckm3 发表于 2017-5-13 15:00

生鲜喵 发表于 2017-4-28 19:40
那楼主,作为小白的我问下,微软打hyper-y虚拟机是否和这个一个机制??作为系统自带的程序功能,是否会在 ...

hyper-y是与VMWare和VirtualBox一样的虚拟机软件。 VMProtect是用于软件保护的。这和VMProtect的虚拟机只是名称相似实际上没关系。VMProtect的虚拟机,实际是一种程序语言解释器,和Java虚拟机是同一种类型的概念。

酒醒黄昏 发表于 2017-3-10 10:21

我来抢个沙发但是我真没有看懂

ugvnui 发表于 2017-3-10 10:22

纯好贴,,高质量。。。。喜欢。。想你看起!!

Hmily 发表于 2017-3-10 10:26

好文,VMP3.x的架构变了,本文是我看到第一篇公开分析的科普文章,新版handle也更多了,期待更多分析和工具的出现,加精鼓励!

pxhb 发表于 2017-3-10 10:30

先收藏,以后有时间看看,不错的好东西

Cr4ckm3 发表于 2017-3-10 10:31

Hmily 发表于 2017-3-10 10:26
好文,VMP3.x的架构变了,本文是我看到第一篇公开分析的科普文章,新版handle也更多了,期待更多分析和工具 ...

感谢H大的评价

crack晓晓 发表于 2017-3-10 10:39

厉害了我的C

chinasmu 发表于 2017-3-10 11:03

楼主6到没朋友

ygh23 发表于 2017-3-10 11:13

好东西啊。

掂软心内 发表于 2017-3-10 11:21

膜拜,这个真的厉害了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: VMProtect 3.09 虚拟机架构浅析