好友
阅读权限 30
听众
最后登录 1970-1-1
本帖最后由 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编写的小程序,源代码如下:
[Asm] 纯文本查看 复制代码
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统计下指令数(注意在选项中关掉统计时将相邻指令合并的选项),前几条是这样
[Asm] 纯文本查看 复制代码
Count Address Command Comment
48. 00403025 mov al,byte ptr ds:[esi]
48. 00403027 movzx eax,al
48. 0040302A sub esi,-0x1
48. 0040302D jmp dword ptr ds:[eax*4+0x403103]
console.vmp_2.12.3.exe 生成14277条trace
用OD统计下指令数,前几条是这样
[Asm] 纯文本查看 复制代码
Count Address Command Comment
149. 004046E5 mov ecx,dword ptr ds:[eax*4+0x405254]
149. 004046EC pushad
149. 004046ED lea esp,dword ptr ss:[esp+0x2C]
149. 004046F1 jle console_.00404622
149. 00404833 neg cx
149. 00404836 sub cx,0xFBDF
console.vmp_3.09.exe 生成2238条Trace
[Asm] 纯文本查看 复制代码
Count Address Command Comment
18. 00421B90 jmp edi
18. 0042D77B lea eax,dword ptr ss:[esp+0x60]
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:[eax*4+0x403103]和mov ecx,dword ptr ds:[eax*4+0x405254]都是典型的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*4+0x403103]类似的代码
然后枚举eax的值0~0xFF即可得到所有handler的入口,根据指令特征识别handler。依次解密出bytecode。
3.x不再有统一的dispatch过程,使得之前的神器从架构上无法应用到3.x版本。
0x3 具体分析
前面是为了有个统一的印象。
接下来具体分析。
我们直接看前面OD得到的Trace,实际的trace中有大量的垃圾指令 ,但正如前面所说,多不涉及栈操作,因此比较简单,以下的分析均是去了垃圾指令 的,所以会发现地址不太连续。
虚拟机初始化部分
[Asm] 纯文本查看 复制代码
<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:[esp+0x28] ; 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:[esi+eax]
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:[0x4369DE] ; EDI=004369DE
跳转到第一个handler
[Asm] 纯文本查看 复制代码
; 取ESI的4字节
004369EA Main mov eax,dword ptr ds:[esi] ; 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中
[Asm] 纯文本查看 复制代码
; ======================= handler
; vPopReg4 R14 (0x38)
; 这里handler命令规则采用VMP分析插件的命名风格,表示从栈中弹出4字节数据到寄存器R13中(以R0开头)
; 解码寄存器下标 al
0043EB11 Main movzx eax,byte ptr ds:[esi] ; 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:[ebp] ; ECX=00000000
0042B819 Main add ebp,0x4 ; FL=0, EBP=0019FF58
004539B2 Main mov dword ptr ss:[esp+eax],ecx
第一条handler结束后,又是跳转
虽然功能完全一样,但VMP又实现出了完全独立了另一套代码
以此规避掉了循环
[Asm] 纯文本查看 复制代码
; 接下来又从ESI取4字节,解密并跳转 上次解密代码的地址是004369EA
004539B5 Main mov eax,dword ptr ds:[esi] ; EAX=D7FCBE81
004539B7 Main lea esi,dword ptr ds:[esi+0x4] ; 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。
[Asm] 纯文本查看 复制代码
; ======================= handler
; vPopReg4 R8 (0x20)
0043F5E7 Main movzx eax,byte ptr ds:[esi] ; 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:[ebp] ; ECX=00401000
0043F63A Main add ebp,0x4 ; EBP=0019FF5C
0043F643 Main mov dword ptr ss:[esp+eax],ecx
这里又是一个跳转。
[Asm] 纯文本查看 复制代码
; 跳转到下条指令,与之前代码位置又都不同
0043F64E Main mov eax,dword ptr ds:[esi] ; EAX=28020B6A
0043F652 Main lea esi,dword ptr ds:[esi+0x4] ; 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
[Asm] 纯文本查看 复制代码
; ======================= handler
; vPopReg4
00439AFC Main movzx eax,byte ptr ds:[esi] ; EAX=00000068
00439B06 Main lea esi,dword ptr ds:[esi+0x1] ; 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:[ebp] ; ECX=00401000
0047EF55 Main lea ebp,dword ptr ss:[ebp+0x4] ; EBP=0019FF60
0047EF5B Main mov dword ptr ss:[esp+eax],ecx
基本模式:
ESI保存bytecode和跳转地址,每条Bytecode前有4字节跳转地址
进行跳转时将跳转地址读出来,进行解密,解密后的值再加上EDI
就是最终的跳转地址,即handler地址。
按照惯例,虚拟机入口pop出所有寄存器到vm_context
应该会产生大量的pop指令,不一一分析了,这里直接跳过。
跳过的bytecode如下
[Asm] 纯文本查看 复制代码
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。
[Asm] 纯文本查看 复制代码
; vPushImm4 DEADBEEF
; 取立即数并解密
0047D362 Main mov eax,dword ptr ds:[esi] ; EAX=39A96DBB
0040F037 Main lea esi,dword ptr ds:[esi+0x4] ; 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:[ebp],eax
又是跳转,但这次稍有不同,因为是push指令,需要检查栈是否发生溢出,这与2.x是一致的
[Asm] 纯文本查看 复制代码
; 取下次跳转的地址
00477C46 Main mov eax,dword ptr ds:[esi] ; EAX=095A81E6
0043B383 Main lea esi,dword ptr ds:[esi+0x4] ; 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:[esp+0x60] ; EAX=0019FEF4
0042D784 Main cmp ebp,eax
0042D786 Main ja console_.00421B90
这部分代码不在trace中,因为实际不会溢出,这里单独从OD中拿出来作为分析,同样都去掉了垃圾指令
[Asm] 纯文本查看 复制代码
; 如果发生溢出
0042D78C 8BD4 mov edx,esp
0042D790 B9 40000000 mov ecx,0x40
; 分配0x80空间
0042D79C 8D4425 80 lea eax,dword ptr ss:[ebp-0x80]
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:[edi],byte ptr ds:[esi]
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,但地址各不相同
[Asm] 纯文本查看 复制代码
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的寄存器轮转机制并没有变化。
新跳转
[Asm] 纯文本查看 复制代码
00450F03 Main mov eax,dword ptr ds:[esi] ; EAX=D7E129A1
00450F08 Main lea esi,dword ptr ds:[esi+0x4] ; 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,用于退出虚拟机时保存虚拟寄存器到栈中
[Asm] 纯文本查看 复制代码
; vPushReg4 R7(0x1C)
; 解密寄存器下标
0040AC3D Main movzx eax,byte ptr ds:[esi] ; 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:[esp+eax] ; EAX=DEADBEEF
00481E71 Main lea ebp,dword ptr ss:[ebp-0x4] ; EBP=0019FF7C
00481E7A Main mov dword ptr ss:[ebp],eax
接下来的bytecode都是push指令,不一一分析了
[Asm] 纯文本查看 复制代码
vPushReg4 R11(0x2C)
vPushReg4 R0(0x0)
vPushReg4 R3(0xC)
vPushReg4 R1(0x4)
vPushReg4 R5(0x14)
vPushReg4 R6(0x18)
vPushReg4 R8(0x20)
又是一段跳转代码
[Asm] 纯文本查看 复制代码
00429FBE Main mov eax,dword ptr ds:[esi] ; EAX=D7F040B7
00429FC0 Main lea esi,dword ptr ds:[esi+0x4] ; 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:[esp+0x60] ; EAX=0019FEF4
0042D784 Main cmp ebp,eax ; FL=PA
0042D786 Main ja console_.00421B90
00421B90 Main jmp edi
新的handler
vRet, 功能与之前一样,从栈中将寄存器弹出到真实寄存器中。
[Asm] 纯文本查看 复制代码
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是不行的)
[Asm] 纯文本查看 复制代码
00403765 VMP_0040100C /$ 78 vPopReg4 vR14 DWORD _t0 = 0
00403766 |. 6B F70C72B7 vPushImm4 0B7720CF7 DWORD _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 EFBEADDE vPushImm4 0DEADBEEF DWORD _t17 = 0DEADBEEF
0040377E |. 4C vPopReg4 vR3 DWORD _t18 = 0DEADBEEF
0040377F |. E9 EFBEADDE vPushImm4 0DEADBEEF DWORD _t19 = 0DEADBEEF
00403784 |. 58 vPopReg4 vR6 DWORD _t20 = 0DEADBEEF
00403785 |. 23 EFBEADDE vPushImm4 0DEADBEEF DWORD _t21 = 0DEADBEEF
0040378A |. 40 vPopReg4 vR0 DWORD _t22 = 0DEADBEEF
0040378B |. B4 EFBEADDE vPushImm4 0DEADBEEF DWORD _t23 = 0DEADBEEF
00403790 |. 60 vPopReg4 vR8 DWORD _t24 = 0DEADBEEF
00403791 |. 23 EFBEADDE vPushImm4 0DEADBEEF DWORD _t25 = 0DEADBEEF
00403796 |. 4C vPopReg4 vR3 DWORD _t26 = 0DEADBEEF
00403797 |. 6B EFBEADDE vPushImm4 0DEADBEEF DWORD _t27 = 0DEADBEEF
0040379C |. 7C vPopReg4 vR15 DWORD _t28 = 0DEADBEEF
0040379D |. B4 EFBEADDE vPushImm4 0DEADBEEF DWORD _t29 = 0DEADBEEF
004037A2 |. 4C vPopReg4 vR3 DWORD _t30 = 0DEADBEEF
004037A3 |. 6B EFBEADDE vPushImm4 0DEADBEEF DWORD _t31 = 0DEADBEEF
004037A8 |. 6C vPopReg4 vR11 DWORD _t32 = 0DEADBEEF
004037A9 |. E9 EFBEADDE vPushImm4 0DEADBEEF DWORD _t33 = 0DEADBEEF
004037AE |. 40 vPopReg4 vR0 DWORD _t34 = 0DEADBEEF
004037AF |. 23 EFBEADDE vPushImm4 0DEADBEEF DWORD _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
深夜发贴,求鼓励
以上
免费评分
查看全部评分
本帖被以下淘专辑推荐:
· 学习及教程 | 主题: 1131, 订阅: 1123
· 杂七杂八 | 主题: 1074, 订阅: 320
· 破解教程 | 主题: 126, 订阅: 213
· 优秀逆向文 | 主题: 238, 订阅: 93