Code Virtualizer虚拟机保护初探

Code Virtualizer简介
Code Virtualizer是由Oreans开发的一款代码虚拟机保护软件,用于保护软件不被逆向工程,同传统的加密/压缩壳不同,该虚拟机保护软件并没有对目标程序的代码和数据进行压缩和加密处理,而是将源程序的指令代码进行混淆与乱序处理并转换成语义等价的虚拟机伪指令,然后由虚拟机调度执行。由于是对原有指令代码的虚拟化处理,因此可能会降低原有代码的执行效率,Code Virtualizer支持宏包裹的方式对原程序的部分代码片段进行保护处理,从而使得开发人员可以只对部分关键的代码片段进行虚拟化处理,而不是必须对所有的代码进行虚拟化。
虚拟机分析
虚拟机保护会把源程序的X86指令变成自定义的伪指令,等到执行的时候,他会模拟CPU“取指-译码-执行”这样的步骤,Code Virtualizer内置在保护程序中的VM就会启动,读取伪指令,然后解析执行,执行完毕后,当前系统的寄存器/EFLAGS以及堆栈状态同未虚拟化时的执行结果一致。
Code Virtualizer是一个堆栈虚拟机,它的一切操作都是基于堆栈传递的。有的虚拟机保护是自己创建的堆栈,而Code Virtualizer则直接使用的是当前程序自身的堆栈。在Code Virtualizer中,伪指令就是一个个的handler,VM中有一个核心的Dispatch部分,本质就是一个大的循环+SWITCH语句,它通过读取程序的伪指令,然后在DispatchTable里面定位到不同的handler中执行。绝大多数情况下,在一个handler中执行完成后,程序将回到Dispatch起始,取得并解析下一条指令,然后到next handler中执行。
该虚拟机是基于语义的,对汇编指令转化为等价语义的代码,然后虚拟执行。
假设虚拟机执行前的虚拟机环境为:

 esp    init_esp
 ebp    init_ebp
 eax    init_eax
 ebx    init_ebx
 ecx    init_ecx
 edx    init_edx
 esi    init_esi
 edi    init_edi
 zf 0

... ...

在该虚拟机执行的语句为xor eax,eax则执行完后的虚拟机环境应该为:

 esp    init_esp
 ebp    init_ebp
 eax    0
 ebx    init_ebx
 ecx    init_ecx
 edx    init_edx
 esi    init_esi
 edi    init_edi
 zf 1

经过Code Virtualizer加密的X86指令,一条简单的指令被分解成数条伪指令,它按照自己的伪指令排列去实现原指令的功能,再加上垃圾指令以及指令乱序,使得逆向工程人员将无法看到源程序的指令。
测试代码
我们用汇编语言写了一段简单的代码用来测试Code Virtualizer的处理情况,因为代码比较简单,因此更能使我们将分析集中在虚拟机本身。
.data
MsgBoxCaption db "Hello",0
MsgBoxText db "Hello Code Virtualizer!",0

.code
start:

VIRTUALIZER_START
mov eax, 0deadbeefh
VIRTUALIZER_END
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,0
end start
上面代码中加粗的部分是用Code Virtualizer SDK的宏来包装要虚拟化的代码,也就是只有VIRTUALIZER_START和VIRTUALIZER_END包围的代码才会被虚拟化。
虚拟机初始化
进入程序入口点后,程序首先将虚拟机伪指令地址压入栈中,然后跳转到VM_INIT部分去执行。
.v_lizer:00407548 000 push offset VM_CODE
.v_lizer:0040754D 004 jmp VM_INIT
进入VM_INIT后, 就是先把EFLAGS和通用寄存器压入堆栈,然后对当前代码进行重定位处理,这是因为如果Code Virtualizer需要对DLL/SYS进行处理的话,需要将自身的Dispatcher Handler进行重定位才行。
.v_lizer:004034EC 004 pusha
.v_lizer:004034ED 024 pushf
.v_lizer:004034EE 028 cld
.v_lizer:004034EF 028 call $+5
.v_lizer:004034F4
DELTA:
.v_lizer:004034F4 02C pop edi
.v_lizer:004034F5 028 sub edi, offset DELTA
.v_lizer:004034FB 028 mov eax, edi
.v_lizer:004034FD 028 add edi, offset VM_CONTEXT
.v_lizer:00403503 028 cmp eax, [edi+VM_CONTEXT.delta_offset]
...
.v_lizer:0040350A 028 mov [edi+VM_CONTEXT.delta_offset], eax
上面代码中的VM_CONTEXT是Code Virtualizer的核心数据结构,用于保存虚拟机的执行环境与Dispatch Handler表,对于其中的重要字段我们在下面的分析中会进行解析。接着就对VM_CONTEXT中的调度表中的Handler进行重定位处理:
.v_lizer:0040350A mov [edi+_VM_CONTEXT.delta_offset], eax
.v_lizer:0040350D mov ecx, 0A8h ; Handler数量
.v_lizer:00403512 jmp short loc_403521
.v_lizer:00403514 jmp short loc_40351C
.v_lizer:00403516 add [edi+ecx*4+58h], eax
.v_lizer:0040351A jmp short loc_403520
.v_lizer:0040351C add [edi+ecx*4+48h], eax
.v_lizer:00403520 dec ecx
.v_lizer:00403521 or ecx, ecx
.v_lizer:00403523 jnz short loc_403514
VM_INIT初始化执行完毕后,对应的几个关键寄存器说明如下:
ESI 指向伪指令的起始,每次执行Handler,ESI根据指令长度和指令的内部处理不断增加。
EDI 指向VM_CONTEXT结构。
EAX就是从虚拟机指令码未解码之前的数据,通过运算得到Handler Index。
EBX是参与指令解码的,和虚拟机指令进行运算得到对应的Handler Index,值得注意的是某些Handler内部也会改变EBX的值。
EDX是一个临时寄存器,在虚拟机的堆栈操作中,经常使用EDX来作为临时变量存放数据。
指令解码
完成后基本的环境初始化后,就开始对虚拟机伪指令进行解码,解码的代码如下:
.v_lizer:00403556 lodsb
===========> 取出一字节(取出伪指令)
.v_lizer:00403557 add al, 0E5h
.v_lizer:00403559 push ebx
.v_lizer:0040355A mov bl, 0E4h
.v_lizer:0040355C inc bl
.v_lizer:0040355E sub bl, 0FBh
.v_lizer:00403561 sub bl, 8Eh
.v_lizer:00403564 sub al, bl
.v_lizer:00403566 pop ebx
.v_lizer:00403567 sub al, bl
.v_lizer:00403569 add al, 5Ch
.v_lizer:0040356B sub al, 0E5h
===========>等价于sub al, bl
.v_lizer:00403B22 push ebx
.v_lizer:004053B3 mov bl, 33h
.v_lizer:004053B5 push ebx
.v_lizer:004053B6 mov bh, 0AEh
.v_lizer:004053B8 shl bh, 7
.v_lizer:004053BB sub bh, 0ABh
.v_lizer:004053BE add bh, 0A7h
.v_lizer:004053C1 sub al, bh
.v_lizer:004053C3 pop ebx
.v_lizer:004053C4 sub al, bl
.v_lizer:004053C6 add al, 0FCh
.v_lizer:004053C8 pop ebx
===========>等价于sub al, 33h
.v_lizer:004053C9 push ebx
.v_lizer:004053CA mov bl, 0E9h
.v_lizer:004053CC push dx
.v_lizer:004053CE mov dl, 1
.v_lizer:00404E63 add bl, dl
.v_lizer:00404E65 pop dx
.v_lizer:00405588 and bl, 97h
.v_lizer:0040558B or bl, 7
.v_lizer:004056AE xor bl, 0D1h
.v_lizer:004056B1 sub al, bl
.v_lizer:004056B3 pop ebx
===========>等价于sub al,56h
.v_lizer:004056B4 sub bl, 0ADh
.v_lizer:004056B7 add bl, al

.v_lizer:004056B9 push 75ECh
.v_lizer:004056BE mov [esp], edx
==========>等价于push edx

.v_lizer:00403AC3 mov dl, 0EBh
.v_lizer:00403AC5 dec dl
.v_lizer:00403AC7 neg dl
.v_lizer:00403AC9 shl dl, 4
.v_lizer:0040672F add dl, 4Dh
.v_lizer:00406732 add bl, dl
==========>等价于add bl,0ADh

.v_lizer:00403D2D pop edx
.v_lizer:00403D2E movzx eax, al
.v_lizer:00403D31 jmp dword ptr [edi+eax*4]
==========>这里就跳转到对应的Handler去执行

上面的代码对代码进行的混淆处理,但实际要做的事情很简单,就是把VM CODE转换成对应的Dispatch Handler的索引,然后跳转到对应的指令处理例程去执行相应的逻辑。
分析上述代码后得知,索引的转换规则如下:
Handler Index = al - bl - 0x33 - 0x56
bl += al
因此,可以看到Code Virtualizer的虚拟机伪指令是经过简单的变换处理,目的是防止很容易的就分析出虚拟机伪指令的功能,因此我们可以通过编写调试器脚本将原始指令(索引值)转换为明文的伪指令。需要注意的是,与某些虚拟机不同,Code Virtualizer的虚拟机的指令并非等长,因此我们在处理的时候需要知道当前指令的全长是多少,从而跳过operand部分。如果Handler中修改了bl的值,那么也应该注意。
代码混淆与乱序
在Code Virtualizer中大量使用了垃圾指令和代码乱序来达到干扰逆向工程人员分析的目的。
什么是代码乱序?
代码乱序是将一系列的代码序列分散打乱分布在PE映像中,中间穿插跳转指令以及不改变环境的垃圾代码,从而扰乱正常分析流程。一般的来讲,连接指令以无条件的跳转jmp、变形的短跳转call、对称的条件跳转指令([jz、jnz];[jc、jnc]…)等实现,前提是不改变其他环境以免破坏代码正常功能。
Code Virtualizer的代码乱序是通过将指令序列分散打乱分布,然后使用大量的绝对跳转指令JMP来实现代码片段的连接。如图1所示:

而对于Code Virtualizer中的代码混淆,主要就是把简单的运算膨胀成复杂的、但有些计算是相互抵消的指令,增加大量的冗余代码。 打个简单的比方,就是把本来的1+2=3改写成11+11(0.5+0.5)+2(1/2) = 3这样的形式。因此我们在分析的时候,就需要特别注意代码最后哪些关键寄存器的值发生了改变,以及堆栈的变化,围绕这两点进行分析就不容易被混淆的垃圾代码所迷惑。
比如某个Handler的部分代码如下所示:
.v_lizer:00403999 000 sub esi, 15F6572Eh *
.v_lizer:0040583F 000 sub esi, 28315364h *
.v_lizer:00405E16 000 add esi, eax
.v_lizer:00405E18 000 add esi, 28315364h *
.v_lizer:00405E1E 000 push ecx *
.v_lizer:00405E1F 004 mov ecx, 15F6572Eh *
.v_lizer:004046C9 004 add esi, ecx *
.v_lizer:004046CB 004 pop ecx *

经过分析可以得知,上面一大段的代码仅仅等价于add esi, eax,其中加*的代码都是冗余的垃圾指令。
Dispatch Handler分析
根据对VM_CONTEXT的分析,Code Virtualizer一共包含了160多个Dispatch Handler。因此我们如果要完整的分析Code Virtualizer,那么就需要对这160多个Dispatch Handler进行分析。本文尝试分析当前程序使用到的Handler,对这些Handler有了一定了解后,分析剩下的Handler就基本是体力劳动了。
对于我们用来测试的例子来说,虚拟化后的伪指令如图2:

由于做了简单的数值转换,因此即使是相同的伪指令在这个指令代码片段中也显示的是不同的值。然后我们用调试器一步一步的跟踪,分析出相关的伪指令的含义。
当然我们只是分析了一小部分,全部的伪指令一共有150多个,通过分析少数相关的虚拟机伪指令,我们就可以窥豹一斑,大致了解虚拟机伪指令的工作过程。下面以其中一条指令为例:
Handler Index = 0x2D

.v_lizer:00403608 lodsd
========>读取四字节
.v_lizer:00403609 add eax, 2317011Eh
.v_lizer:0040360E sub eax, 367E736Ch
.v_lizer:00403613 sub eax, ebx
.v_lizer:00403615 add eax, 367E736Ch
.v_lizer:0040361A push esi
.v_lizer:0040361B mov esi, 2317011Eh
.v_lizer:00403620 sub eax, esi
.v_lizer:00403622 pop esi
========>等价于sub eax, ebx
.v_lizer:00403623 push esi
.v_lizer:00403624 mov esi, 1C9811AFh
.v_lizer:00403629 add eax, 1A36C00h
.v_lizer:004062FD sub eax, esi
.v_lizer:004062FF sub eax, 1A36C00h
.v_lizer:00406304 mov esi, [esp]
.v_lizer:00406307 add esp, 4
========>等价于sub eax, 1C9811AFh
.v_lizer:0040630D push 0Ah
.v_lizer:00406312 mov [esp], ebx
.v_lizer:00406315 mov ebx, 21307C1Eh
.v_lizer:0040631A add ebx, 321175AFh
.v_lizer:00406320 xor ebx, 3769313Ah
.v_lizer:00406326 sub eax, 0C57480Eh
.v_lizer:00404562 add eax, ebx
.v_lizer:00404564 add eax, 0C57480Eh
.v_lizer:00404569 pop ebx
========>等价于add eax, 6428C0F7h
.v_lizer:0040456A xor ebx, eax
.v_lizer:0040456C push 64C5h
.v_lizer:00404571 mov [esp], eax
========>等价于
xor ebx, eax
push eax
综上分析,得出该段代码实际功能为
lodsd
sub eax, ebx
sub eax, 1c9811afh
add eax, 6428c0f7h
xor ebx, eax
push eax
在本文测试的样本文件中,lodsd读取的内容是0xB8AFC4D7,ebx为0x40741B,经过上述计算后变为4,实际上也就是把指令字后面的4字节做如下变换后压入堆栈:
real IMM32= IMM32 - ebx - 1C9811AFh + 6428C0F7h
代码片段的功能相当于PUSH IMM32。
示例代码手工还原
push OFFSET vm.offset0x1C
pop edx
pop [edx]
push OFFSET vm.offset0x0
pop edx
pop [edx]
push OFFSET vm.offset0x18
pop edx
pop [edx]
push OFFSET vm.offset0x10
pop edx
pop [edx]
push OFFSET vm.offset0x0C
pop edx
pop [edx]
push OFFSET vm.offset0x0C
pop edx
pop [edx]
push OFFSET vm.offset0x04
pop edx
pop [edx]
push OFFSET vm.offset0x08
pop edx
pop [edx]
push OFFSET vm.offset0x14
pop edx
pop [edx]

因为在进入虚拟机调度之前,程序调用了以下指令
pushad
pushfd

因此执行后栈的内容如下
EDI
ESI
EBP
ESP
EBX
EDX
ECX
EAX
EFLAGS

我们可以知道
push OFFSET vm.field
pop edx
pop [edx]
这三句代码实际上等价于
Lea edx, OFFSET [vm.field]
Pop [edx]
也就是把栈中的DWORD弹出到edx指向的内存中。因此我们可以推测出vm中的相关的未知字段是什么了。

struct VM_CONTEXT
{
DWORD vm_edi; +0
DWORD vm_edx; +4
DWORD vm_ecx; +8
DWORD vm_ebx; +0c
DWORD vm_ebp; +10
DWORD vm_eax; +14
DWORD vm_esi; +18
DWORD vm_eflag; +1c
DWORD VM_off_20;
DWORD VM_off_24;
DWORD VM_off_28;
DWORD delta_offset;
DWORD VM_lock;
DWORD VM_off_34;
DWORD VM_off_38;
DWORD VM_off_3c;
DWORD VM_off_40;
DWORD VM_off_44;
DWORD VM_off_48;
DWORD VM_dispatch_xxx1;
DWORD VM_dispatch_xxx2;
DWORD VM_dispatch_xxx3;
...
}
经过对不同的样本进行不同的虚拟化处理,可以看到寄存器对应关系并不是固定的,但同一样本的寄存器位置是固定的。根据上面分析的Handler对应的语义,我们手工将伪指令转换成对应的X86代码:
mov edx, esp
push edx
push 4
pop eax
add dword ptr [esp], 4
mov esp, dword ptr [esp]
push 0xDEADBEEF
把我们赋值的数据压入堆栈
push offset vm.eax
将VM的EAX寄存器也压入堆栈
pop edx
push edx
push edx
push 4
pop eax
add dword ptr [esp], 4
pop edx
push 0x44a6
push 0xdfe8
pop edx
pop edx
pop [edx]
push 0x40100f
add dword ptr [esp], vm.delta_offset
其中0x40100f就是虚拟机执行完后要继续执行的原始代码的位置,加上vm.delta_offset是为了重定位。看到上面一大堆代码,实际上也是Code Virtualizer加入的混淆指令,经分析精简后实际上完成的功能就是
mov eax, 0xDEADBEEF
push 0x40100F
add dword ptr [esp], vm.delta_offset
继续分析其余的代码,
lodsd
add esi, eax
mov ebx, 0
push offset vm.eflags
pop edx
push dword ptr [edx]

push offset vm.eax
pop edx
push dword ptr [edx]

push offset vm.ecx
pop edx
push dword ptr [edx]

push offset vm.edx
pop edx
push dword ptr [edx]

push offset vm.ebx
pop edx
push dword ptr [edx]

push offset vm.ebx
pop edx
push dword ptr [edx]

push offset vm.ebp
pop edx
push dword ptr [edx]

push offset vm.esi
pop edx
push dword ptr [edx]

push offset vm.edi
pop edx
push dword ptr [edx]
这一大段就是把原来PUSHAD与PUSHFD指令保存的标志位和寄存器数据再原样压入堆栈。
恢复好环境后,就进入了尾声,跳转到VM_EXIT代码段执行,VM_EXIT也包含了一些混淆的代码,但其最关键的也就是最后三条代码:
.v_lizer:004042D2 popa
.v_lizer:004042D3 popf
.v_lizer:004042D4 retn
因此执行完上面三条语句就退出了虚拟机保护。虚拟机执行完后程序的寄存器环境如下图所示:

可见EAX被设为了0xDEADBEEF,而且堆栈也恢复了平衡。经过上面的分析我们可以知道,Code Virtualizer并不是直接对要保护的X86指令做VM处理,而是先对加入必要的保存和恢复环境的代码,然后对原始的代码进行混淆,最后才会这部分代码做VM处理。
参考文献
Code Virtualizer 1.3.8.0版虚拟机分析 nEINEI
Ryosuke的分析
http://bbs.pediy.com/showthread.php?t=62447&highlight=Virtualizer

你可能感兴趣的:(Code Virtualizer虚拟机保护初探)