本文应该算作个人学习VMP保护的笔记,所以内容较为空泛。详细的VMP虚拟保护壳的学习可以参考文末的参考资料。

0x01 外层壳保护

使用VMProtect保护后的程序添加了两个新节区

壳代码也是虚拟化过的

image-20211230185633948

寻找OEP的方法

  1. 通过对ZwProtectVirtualMemory下断,观察栈顶
  2. 对代码节下硬件执行断点(感觉略鸡肋,得知道原始函数在大致哪个范围
  3. 对mainCRTstartup内使用的一些API下断点(IsProcessorFeaturePresent, GetSystemTimeAsFileTime),然后回溯找到OEP

修复IAT表

虽然能看到程序使用了哪些API,却不能通过交叉引用来静态分析,因为VMP保护后的程序导入地址是运行时动态计算的

image-20211230211131287

参考源哥用Unicorn还原IAT表的文章

image-20211230213959428

mov     ebx, offset byte_407DD1
mov     ebx, [ebx+198694h]
lea     ebx, [ebx+44C25846h]
xchg    ebx, [esp+0]
retn
[407DD1 + 198694] + 44C25846 = IAT(MessageBoxW)

image-20211231104400935

0x02 代码混淆引擎

代码混淆引起所使用的指令都是不常见的指令,我们可以一眼就识别出来比如 rcr,bt,btc,sbb,lahf等。

image-20220102150822685

code2

block

0x03 虚拟化引擎

这一部分比较复杂,我主要参考的是这篇文章 ,以x86的VMP保护为讲解例子,如果启动了VMP加外层壳VMP的handle和混淆变异的代码会在.vmp1这个节区里,否则都在.vmp0节区。

进入虚拟机的标志是push uint32 加上 call function 跳转到.vmp1的节区进行操作,在大多数情况下这个call是不会返回的,更像是一个跳转。

img

其中这个push的32位数是虚拟opcode表起始位置加密后的值。

call进去后就开始依次执行每一个handle了,在每个handle里面都存在的大量的代码混淆阻碍逆向分析。

image-20220126212411013image-20220126213041291

原文中的例子
======================================================================
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 上下文的包含关系图。

img

一条指令由两部分组成,一个加密的handle偏移量和它加密后的参数(操作码和操作数)

img

某段VMP例程以一个VENTER指令开始,以一个VEXIT指令结束,以下为常见的VM指令。

VENTER, VEXIT, VADDU*, VNANDU*, VNORU*, VPUSHV, VPOPR, VPOPVSP, VPUSHVSP, VPUSHI*
VFETCH*, VJUMP_*, VMOV*, VSHLU*, VSHRU*, VMULU*, VDIVU*, ....

0x04 参考资料

关于unicorn去搞VMP的iat那点事

Quick look around VMP 3.x

VMP导入表修复

(VMProtect 分析)跟着ida和WinDbg逛VirtualMachine

VM保护攻防