VMProtect reversing(1)

Eddy 发布于2010-8-9 11:6:23 分类: 加密解密 已浏览loading 网友评论0条 我要评论

VMProtect, Part 0: Basics

Author: RolfRolles

VMProtect is a virtualization protector. Like other protections in the genre, among others
ReWolf's x86 Virtualizer and CodeVirtualizer, it works by disassembling the x86 bytecode of
the target executable and compiling it into a proprietary, polymorphic bytecode which is
executed in a custom interpreter at run-time. This is unlike the traditional notions of
packing, in which the x86 bytecode is simply encrypted and/or compressed: with virtualization,
the original x86 bytecode in the protected areas is gone, never to be seen again. Or so the
idea goes.

If you've never looked at VMProtect before, I encourage you to take a five-minute look in
IDA (here's a sample packed binary). As far as VMs go, it is particularly skeletal and
easily comprehended. The difficulty lies in recreating working x86 bytecode from the VM
bytecode. Here's a two-minute analysis of its dispatcher.

push edi ; push all registers
push ecx
push edx
push esi
push ebp
push ebx
push eax
push edx
pushf
push 0 ; imagebase fixup
mov esi, [esp+8+arg_0] ; esi = pointer to VM bytecode
mov ebp, esp ; ebp = VM's "stack" pointer
sub esp, 0C0h
mov edi, esp ; edi = "scratch" data area

VM__FOLLOW__Update:
add esi, [ebp+0]

VM__FOLLOW__Regular:
mov al, [esi] ; read a byte from EIP
movzx eax, al
sub esi, -1 ; increment EIP
jmp ds:VM__HandlerTable[eax*4] ; execute instruction handler

A feature worth discussing is the "scratch space", referenced by the register edi throughout
the dispatch loop. This is a 16-dword-sized area on the stack where VMProtect saves the
registers upon entering the VM, modifies them throughout the course of a basic block,
and from whence it restores the registers upon exit. For each basic block protected by the VM,
the layout of the registers in the scratch space can potentially be different.

Here's a disassembly of some instruction handlers. Notice that A) VMProtect is a stack machine
and that B) each handler -- though consisting of scant few instructions -- performs several
tasks, e.g. popping several values, performing multiple operations, pushing one or more values.

#00: x = [EIP-1] & 0x3C; y = popd; [edi+x] = y

.text:00427251 and al, 3Ch ; al = instruction number
.text:00427254 mov edx, [ebp+0] ; grab a dword off the stack
.text:00427257 add ebp, 4 ; pop the stack
.text:0042725A mov [edi+eax], edx ; store the dword in the scratch space

#01: x = [EIP-1] & 0x3C; y = [edi+x]; pushd y

.vmp0:0046B0EB and al, 3Ch ; al = instruction number
.vmp0:0046B0EE mov edx, [edi+eax] ; grab a dword out of the scratch space
.vmp0:0046B0F1 sub ebp, 4 ; subtract 4 from the stack pointer
.vmp0:0046B0F4 mov [ebp+0], edx ; push the dword onto the stack

#02: x = popw, y = popw, z = x + y, pushw z, pushf

.text:004271FB mov ax, [ebp+0] ; pop a word off the stack
.text:004271FF sub ebp, 2
.text:00427202 add [ebp+4], ax ; add it to another word on the stack
.text:00427206 pushf
.text:00427207 pop dword ptr [ebp+0] ; push the flags

#03: x = [EIP++]; w = popw; [edi+x] = Byte(w)

.vmp0:0046B02A movzx eax, byte ptr [esi] ; read a byte from EIP
.vmp0:0046B02D mov dx, [ebp+0] ; pop a word off the stack
.vmp0:0046B031 inc esi ; EIP++
.vmp0:0046B032 add ebp, 2 ; adjust stack pointer
.vmp0:0046B035 mov [edi+eax], dl ; write a byte into the scratch area

#04: x = popd, y = popw, z = x << y, pushd z, pushf

.vmp0:0046B095 mov eax, [ebp+0] ; pop a dword off the stack
.vmp0:0046B098 mov cl, [ebp+4] ; pop a word off the stack
.vmp0:0046B09B sub ebp, 2
.vmp0:0046B09E shr eax, cl ; shr the dword by the word
.vmp0:0046B0A0 mov [ebp+4], eax ; push the result
.vmp0:0046B0A3 pushf
.vmp0:0046B0A4 pop dword ptr [ebp+0] ; push the flags

#05: x = popd, pushd ss:[x]

.vmp0:0046B5F7 mov eax, [ebp+0] ; pop a dword off the stack
.vmp0:0046B5FA mov eax, ss:[eax] ; read a dword from ss
.vmp0:0046B5FD mov [ebp+0], eax ; push that dword

Part 1: Bytecode and IR
Author: RolfRolles

The approach I took with ReWolf's x86 Virtualizer is also applicable here, although a
more sophisticated compiler is required. What follows is some preliminary notes on the
design and implementation of such a component. These are not complete details on breaking
the protection; I confess to having only looked at a few samples, and I am not sure which
protection options were enabled.

As before, we begin by constructing a disassembler for the interpreter. This is immediately
problematic, since the bytecode language is polymorphic. I have created an IDA plugin that
automatically constructs OCaml source code for a bytecode disassembler. In a
production-quality implementation, this should be implemented as a standalone component that
returns a closure.

The generated disassembler, then, looks like this:

let disassemble bytearray index =
match (bytearray.(index) land 0xff) with
0x0 -> (VM__Handler0__PopIntoRegister(0),[index+1])
| 0x1 -> (VM__Handler1__PushDwordFromRegister(0),[index+1])
| 0x2 -> (VM__Handler2__AddWords,[index+1])
| 0x3 -> (VM__Handler3__StoreByteIntoRegister(bytearray.(index+1)),[index+2])
| 0x4 -> (VM__Handler0__PopIntoRegister(4),[index+1])
| 0x5 -> (VM__Handler1__PushDwordFromRegister(4),[index+1])
| 0x6 -> (VM__Handler4__ShrDword,[index+1])
| 0x7 -> (VM__Handler5__ReadDword__FromStackSegment,[index+1])
| ... -> ...

Were we to work with the instructions individually in their natural granularity, depicted
above, the bookkeeping on the semantics of each would likely prove tedious. For illustration,
compare and contrast handlers #02 and #04. Both have the same basic pattern: pop two values
(words vs. dwords), perform a binary operation (add vs. shr), push the result, then push the
flags. The current representation of instructions does not express these, or any, similarities.

Handler #02: Handler #04:
mov ax, [ebp+0] mov eax, [ebp+0]
sub ebp, 2 mov cl, [ebp+4]
add [ebp+4], ax sub ebp, 2
pushf shr eax, cl
pop dword ptr [ebp+0] mov [ebp+4], eax
pushf
pop dword ptr [ebp+0]

Therefore, we pull a standard compiler-writer's trick and translate the VMProtect instructions
into a simpler, "intermediate" language (hereinafter "IR") which resembles the pseudocode
snippets atop the handlers in part zero. Below is a fragment of that language's abstract
syntax.

type size = B | W | D | Q
type temp = int * size
type seg = Scratch | SS | FS | Regular
type irbinop = Add | And | Shl | Shr | MakeQword
type irunop = Neg | MakeByte | TakeHighDword | Flags
type irexpr = Reg of register
| Temp of int
| Const of const
| Deref of seg * irexpr * size
| Binop of irexpr * irbinop * irexpr
| Unop of irexpr * irunop

type ir =
DeclareTemps of temp list
| Assign of irexpr * irexpr
| Push of irexpr
| Pop of irexpr
| Return

A portion of the VMProtect -> IR translator follows; compare the translation for handlers #02
and #04.

let make_microcode = function
VM__Handler0__PopIntoRegister(b) -> [Pop(Deref(Scratch, Const(Dword(zero_extend_byte_dword(b land 0x3C))), D))]
| VM__Handler2__AddWords -> [DeclareTemps([(0, W);(1, W);(2, W)]);
Pop(Temp(0));
Pop(Temp(1));
Assign(Temp(2), Binop(Temp(0), Add, Temp(1)));
Push(Temp(2));
Push(Unop(Temp(2), Flags))]
| VM__Handler4__ShrDword -> [DeclareTemps([(0, D);(1, W);(2, D)]);
Pop(Temp(0));
Pop(Temp(1));
Assign(Temp(2), Binop(Temp(0), Shr, Temp(1)));
Push(Temp(2));
Push(Unop(Temp(2), Flags))]
| VM__Handler7__PushESP -> [Push(Reg(Esp))]
| VM__Handler23__WriteDwordIntoFSSegment -> [DeclareTemps([(0, D);(1, D)]);
Pop(Temp(0));
Pop(Temp(1));
Assign(Deref(FS, Temp(0), D), Temp(1))]
| ... -> ...

To summarize the process, below is a listing of VMProtect instructions, followed by the assembly
code that is executed for each, and to the right is the IR translation.

VM__Handler1__PushDwordFromRegister 32

and al, 3Ch ; al = 32
mov edx, [edi+eax]
sub ebp, 4
mov [ebp+0], edx Push (Deref (Scratch, Const (Dword 32l), D));

VM__Handler7__PushESP

mov eax, ebp
sub ebp, 4
mov [ebp+0], eax Push (Reg Esp);

VM__Handler0__PopIntoRegister 40

and al, 3Ch
mov edx, [ebp+0]
add ebp, 4
mov [edi+eax], edx Pop (Deref (Scratch, Const (Dword 40l), D));

VM__Handler19__PushSignedByteAsDword (-1l)

movzx eax, byte ptr [esi] ; *esi = -1
sub esi, 0FFFFFFFFh
cbw
cwde
sub ebp, 4
mov [ebp+0], eax Push (Const (Dword (-1l)));

VM__Handler9__PushDword 4525664l

mov eax, [esi] ; *esi = 4525664l
add esi, 4
sub ebp, 4
mov [ebp+0], eax Push (Const (Dword 4525664l));

VM__Handler9__PushDword 4362952l};

mov eax, [esi] ; *esi = 4362952l
add esi, 4
sub ebp, 4
mov [ebp+0], eax Push (Const (Dword 4362952l));

VM__Handler19__PushSignedByteAsDword 0l};

movzx eax, byte ptr [esi] ; *esi = 0
sub esi, 0FFFFFFFFh
cbw
cwde
sub ebp, 4
mov [ebp+0], eax Push (Const (Dword (0l)));

VM__Handler42__ReadDwordFromFSSegment};

mov eax, [ebp+0] DeclareTemps([(0,D)]); Pop (Temp 0);
mov eax, fs:[eax]
mov [ebp+0], eax Push (Deref (FS, Temp 0, D));

已经有(0)位网友发表了评论,你也评一评吧!
原创文章如转载,请注明:转载自Eddy Blog
原文地址:http://www.rrgod.com/decryption/500.html     欢迎订阅Eddy Blog

关于 VMProtect reversing  的相关文章

记住我的信息,下次不用再输入 欢迎给Eddy Blog留言