初探VMProtect 3.x虚拟保护壳原理(学习中。。。)
本文应该算作个人学习VMP保护的笔记,所以内容较为空泛。详细的VMP虚拟保护壳的学习可以参考文末的参考资料。
0x01 外层壳保护
使用VMProtect保护后的程序添加了两个新节区
壳代码也是虚拟化过的
寻找OEP的方法
- 通过对ZwProtectVirtualMemory下断,观察栈顶
- 对代码节下硬件执行断点(感觉略鸡肋,得知道原始函数在大致哪个范围
- 对mainCRTstartup内使用的一些API下断点(IsProcessorFeaturePresent, GetSystemTimeAsFileTime),然后回溯找到OEP
修复IAT表
虽然能看到程序使用了哪些API,却不能通过交叉引用来静态分析,因为VMP保护后的程序导入地址是运行时动态计算的
参考源哥用Unicorn还原IAT表的文章
mov ebx, offset byte_407DD1
mov ebx, [ebx+198694h]
lea ebx, [ebx+44C25846h]
xchg ebx, [esp+0]
retn
[407DD1 + 198694] + 44C25846 = IAT(MessageBoxW)
0x02 代码混淆引擎
代码混淆引起所使用的指令都是不常见的指令,我们可以一眼就识别出来比如 rcr,bt,btc,sbb,lahf等。
0x03 虚拟化引擎
这一部分比较复杂,我主要参考的是这篇文章 ,以x86的VMP保护为讲解例子,如果启动了VMP加外层壳VMP的handle和混淆变异的代码会在.vmp1这个节区里,否则都在.vmp0节区。
进入虚拟机的标志是push uint32 加上 call function 跳转到.vmp1的节区进行操作,在大多数情况下这个call是不会返回的,更像是一个跳转。
其中这个push的32位数是虚拟opcode表起始位置加密后的值。
call进去后就开始依次执行每一个handle了,在每个handle里面都存在的大量的代码混淆阻碍逆向分析。
原文中的例子
======================================================================
0x7ae901: mov ecx, dword ptr [esi]
0x7ae905: lea esi, [esi + 4]
0x7ae914: movzx eax, byte ptr [ebp]
0x83e3c2: lea ebp, [ebp + 1]
0x7d7bf8: mov dword ptr [esp + eax], ecx
...
0x8429bf: add edi, ecx
0x6d015e: jmp edi
======================================================================
0x755912: mov ecx, dword ptr [esi]
0x75591a: lea esi, [esi + 4]
0x755925: movzx eax, byte ptr [ebp]
0x6c94c6: mov dword ptr [esp + eax], ecx
0x6c94d7: lea ebp, [ebp + 4]
...
0x79cdbd: add edi, ecx
0x79cdbf: push edi
0x79cdc0: ret
======================================================================
0x7b821a: mov ecx, dword ptr [esi]
0x7b8222: lea esi, [esi + 4]
0x7b822b: movzx eax, byte ptr [ebp]
0x7b695c: mov dword ptr [esp + eax], ecx
0x7b6966: lea ebp, [ebp + 4]
...
0x7637cb: add edi, ecx
0x78cc6a: jmp edi
我们可以详细分析每个handle的实际作用,在这篇文章中较为详细的分析了VPUSH16 [VCTX + *]
这个handle具体实现方式
0x45bf82: VUNKNOWN: (VIP = esi, VSP = ebp)
# update VIP to point on operand (current VIP is pointing on opcode offset)
0x45bf82: lea esi, [esi - 1]
# get the ciphered operand (1 byte)
0x45bf8c: movzx eax, byte ptr [esi]
# mutated operand decryption (keychain)
# NOTE : ebx contain the rolling key
0x45bf94: xor al, bl
0x45bf99: ror al, 1
0x40a4fa: dec al
0x40a505: not al
0x40a507: dec al
0x40a514: xor bl, al
# push a value into vm stack from vm context
# eax = 8; VCTX[8] -> [VSP-2] = VPUSH R8
0x40a51a: movzx dx, byte ptr [esp + eax]
0x40a51f: sub ebp, 2
0x40a529: mov word ptr [ebp], dx
# update VIP to the next ciphered opcode offset
0x40a531: lea esi, [esi - 4]
# get next ciphered opcode offset
0x40a537: mov ecx, dword ptr [esi]
# mutated next handle offset decryption routine (keychain)
# NOTE : ebx contain the rolling key
0x40a53e: xor ecx, ebx
0x438108: sub ecx, 0x5eac74dd
0x43810e: cmc
0x43810f: not ecx
0x41743d: bswap ecx
0x41743f: rol ecx, 1
0x4513d8: neg ecx
0x4513da: stc
0x4513db: xor ebx, ecx
# update absolute handle position with the next handle offset
0x4513e0: add edi, ecx
# reset the next rolling key operand
0x4752b4: lea ecx, [esp + 0x60]
# jump to the next handle
0x461417: push edi
0x461418: ret
VM的体系结构
VIP和VSP是存储在一个随机的寄存器中(register base),下图为VMP 上下文的包含关系图。
一条指令由两部分组成,一个加密的handle偏移量和它加密后的参数(操作码和操作数)
某段VMP例程以一个VENTER指令开始,以一个VEXIT指令结束,以下为常见的VM指令。
VENTER, VEXIT, VADDU*, VNANDU*, VNORU*, VPUSHV, VPOPR, VPOPVSP, VPUSHVSP, VPUSHI*
VFETCH*, VJUMP_*, VMOV*, VSHLU*, VSHRU*, VMULU*, VDIVU*, ....