VMProtect 2 - Detailed Analysis of The Virtual Machine Architecture - Back Engineering
VMProtect 2 - Detailed Analysis of The Virtual Machine Architecture - Back Engineering
VMProtect 2 - Detailed Analysis of The Virtual Machine Architecture - Back Engineering
Table Of Contents
https://back.engineering/17/05/2021/#terminology 1/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
https://back.engineering/17/05/2021/#terminology 2/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Samuel Chevet
Inside VMProtect 2
Inside VMProtect 2 Slides
Rolf Rolles
Unpacking Virtualization Obfuscators
VMProtect 2 - Reverse Engineering
Anatoli Kalysch
VMAttack IDA PRO Plugin
HOME Doxygen Tags Researchers
https://back.engineering/17/05/2021/#terminology 3/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
HOME Doxygen Tags Researchers
Can Bölük
VTIL (Virtual-machine Translation Intermediate Language)
NoVmp - A static devirtualizer for VMProtect x64 3.x powered by VTIL.
Katy Hearthstone
VMProtect Control Flow Obfuscation (Case study: string algorithm
cryptanalysis in Honkai Impact 3rd)
IRQL0
Helped created vmprofiler v1.0, and helped with general analysis of vm
handlers.
BTBD
Providing an algorithm to handle deadstore removal with Zydis.
Before diving into this post I would like to state a few things in regards to existing
VMProtect 2 work, the purpose of this article, and my intentions, as these seem to
become misconstrued and distorted at times.
Purpose
Although there has been a lot of research already conducted on VMProtect 2, I feel
that there is still information which has not been discussed publicly nor enough source
code disclosed to the public. The information I am disclosing in this article aims to go
beyond generic architectural analysis but much lower. The level in which one could
encode their own virtual machine instructions given a VMProtect’ed binary as well as
intercept and alter results of virtual instructions with ease. The dynamic analysis
discussed in this article is based upon existing work by Samuel Chevet, my dynamic
analysis research and vmtracer project is simply an expansion upon his work
demonstrated in his presentation “Inside VMProtect”.
Intentions
This post is not intending to cast any negative views upon VMProtect 2, the creator(s)
of said software or anyone who uses it. I admire the creator(s) who clearly have
impressive skills to create such a product.
This post has also been created under the impression that everything discussed here
has most likely been discovered by private entities, and that I am not the first to find
or document such things about the VMProtect 2 architecture. I am not intending to
present this information as though it is ground breaking or something that no one else
https://back.engineering/17/05/2021/#terminology 4/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
has already discovered, quite the opposite. This is simply a collection of existing
information appended with my own research.
This being said, I humbly present to you, “VMProtect 2, Detailed Analysis of the Virtual
Machine Architecture”.
Terminology
VIP - Virtual Instruction Pointer, this equivalent to the x86-64 RIP register which
contains the address of the next instruction to be executed. VMProtect 2 uses the
native register RSI to hold the address of the next virtual instruction pointer. Thus RSI
is equivalent to VIP.
VSP - Virtual Stack Pointer, this is equivalent to the x86-64 RSP register which contains
the address of the stack. VMProtect 2 uses the native register RBP to hold the address
of the virtual stack pointer. Thus RBP is equivalent to VSP.
VM Handler - A routine which contains the native code to execute a virtual instruction.
For example, the VADD64 instruction adds two values on the stack together and
stores the result as well as RFLAGS on the stack.
Virtual Instruction - Also known as “virtual bytecode” is the bytes interpreted by the
virtual machine and subsequently executed. Each virtual instruction is composed of at
least one or more operands. The first operand contains the opcode for the instruction.
Virtual Opcode - The first operand of every virtual instruction. This is the vm handler
index. The size of a VMProtect 2 opcode is always one byte.
IMM / Immediate Value - A value encoded into a virtual instruction by which operations
are to happen upon, such as loading said value onto the stack or into a virtual register.
Virtual instructions such as LREG, SREG, and LCONST all have immediate values.
Transformations - The term “transform” used throughout this post refers specifically
to operations done to decrypt operands of virtual instructions and vm handler table
entries. These transformations consist of add, sub, inc, dec, not, neg, shl, shr, ror, rol,
and lastly BSWAP. Transformations are done with sizes of 1, 2, 4, and 8 bytes.
Transformations can also have immediate/constant values associated with them such
as “xor rax, 0x123456”, or “add rax, 0x123456”.
Introduction
VMProtect 2 is a virtual machine based x86 obfuscator which converts x86 instructions
to a RISC, stack machine, instruction set. Each protected binary has a unique set of
https://back.engineering/17/05/2021/#terminology 5/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
encrypted virtual machine instructions with unique obfuscation. This project aims to
disclose very significant signatures which are in every single VMProtect 2 binary with
the intent to aid in further research. This article will also briefly discuss different types
of VMProtect 2 obfuscation. All techniques to deobfuscate are tailor specifically to
virtual machine routines and will not work on generally obfuscated routines,
specifically routines which have real JCC’s in them.
VMProtect 2 uses two types of obfuscation for the most part, the first being
deadstore, and the second being opaque branching. Throughout obfuscated routines
you can see a few instructions followed by a JCC, then another set of instructions
followed by another JCC. Another contributing part of opaque branching is random
instructions which affect the FLAGS register. You can see these little buggers
everywhere. They are mostly bit test instructions, useless compares, as well as
set/clear flags instructions.
In this opaque branching obfuscation example I will go over what VMProtect 2 opaque
branching looks like, other factors such as the state of rflags, and most importantly
how to determine if you are looking at an opaque branch or a legitimate JCC.
.vmp0:00000001400073B8 66 0F CA bswap dx
.vmp0:00000001400073C7 F6 D8 neg al
Consider the above obfuscated code. Notice the JNO branch. If you follow this branch
in ida and compare the instructions against the instructions after the JNO you can see
that the branch is useless as both paths execute the same meaningful instructions.
loc_14000443E:
.vmp0:000000014000443E F5 cmc
https://back.engineering/17/05/2021/#terminology 6/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
If you look close enough you can see that there are a few instructions which are in
both branches. It can be difficult to determine what code is deadstore and what code
is required, however if you select a register in ida and look at all the places it is written
to prior to the instruction you are looking at, you can remove all of those other writing
instructions up until there is a read of said register. Now, back to the example, In this
case the following instructions are what matter:
Generation of these opaque branches makes it so there are duplicate instructions. For
each code path there is also more deadstore obfuscation as well as opaque conditions
and other instructions that affect RFLAGS.
VMProtect 2 deadstore obfuscation adds the most junk to the instruction stream aside
from opaque bit tests and comparisons. These instructions serve no purpose and can
be spotted and removed by hand with ease. Consider the following:
https://back.engineering/17/05/2021/#terminology 7/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
.vmp0:0000000140004158 66 F7 D7 not di
.vmp0:0000000140004161 F9 stc
.vmp0:0000000140004162 41 58 pop r8
.vmp0:0000000140004164 F5 cmc
.vmp0:0000000140004165 F8 clc
.vmp0:0000000140004175 41 59 pop r9
.vmp0:0000000140004194 F5 cmc
.vmp0:00000001400041A3 66 09 F6 or si, si
.vmp0:00000001400041AC 9D popfq
.vmp0:00000001400041AD 0F 9F C1 setnle cl
.vmp0:00000001400041B0 0F 9E C1 setle cl
.vmp0:00000001400041C2 66 F7 D6 not si
.vmp0:00000001400041CD 66 F7 D6 not si
.vmp0:0000000140007AF6 C3 retn
Let’s start from the top, one instruction at a time. The first instruction at 0x140004149
is “RCL - Rotate Left Carry”. This instruction affects the FLAGS register as well as DI.
Lets see the next time DI is referenced. Is it a read or a write? The next reference to DI
https://back.engineering/17/05/2021/#terminology 8/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
is the NOT instruction at 0x140004158. NOT reads and writes DI, so far both
instructions are valid. The next instruction that references DI is the POP instructions.
This is critical as all write’s to RDI prior to this POP can be removed from the
instruction stream.
The next instruction is POP RAX at 0x14000414C. RAX is never written too throughout
the entire instruction stream it is only read from. Since it has a read dependency this
instruction cannot be removed. Moving onto the next instruction, SHLD - double
precision shift left, a write dependency on R11, read dependency on BX. The next
instruction that references R11 is the POP R11 at 0x140004153. We can remove the
SHLD instruction as its deadstore.
Now just repeat the process for every single instruction. The end result should look
something like this:
.vmp0:0000000140004162 41 58 pop r8
.vmp0:0000000140004175 41 59 pop r9
.vmp0:00000001400041AC 9D popfq
.vmp0:0000000140007AF6 C3 retn
This method is not perfect for removing deadstore obfuscation as there is a second
POP RCX which is missing from this result above. POP and PUSH instructions are
special cases which should not be emitted from the instruction stream as these
instructions also change RSP. This method for removing deadstore is also only applied
to vm_entry and vm handlers. This cannot be applied to generically obfuscated
routines as-is. Again, this method is NOT going to work on any obfuscated routine, it’s
https://back.engineering/17/05/2021/#terminology 9/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
specifically tailored for vm_entry and vm handlers as these routines have no legitimate
JCC’s in them.
Rolling Decryption
VMProtect 2 uses a rolling decryption key. This key is used to decrypt virtual
instruction operands, which subsequently prevents any sort of hooking, as if any
virtual instructions are executed out of order the rolling decryption key will become
invalid causing further decryption of virtual operands to be invalid.
During execution inside of the virtual machine, some natiive registers are dedicated
for the virtual machine mechanisms such as the virtual instruction pointer and virtual
stack. In this section I will be discussing these native registers and their uses for the
virtual machine.
https://back.engineering/17/05/2021/#terminology 10/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
To begin, RSI is always used for the virtual instruction pointer. Operands are fetched
from the address stored in RSI. The initial value loaded into RSI is done by vm_entry.
RBP is used for the virtual stack pointer, the address stored in RBP is actually the
native stack memory. RBP is loaded with RSP prior to allocation of scratch registers.
This brings us to RDI which contains scratch registers. The address in RDI is initialized
as well in vm_entry and is set to an address landing inside of the native stack.
R12 is loaded with the linear virtual address of the vm handler table. This is done
inside of vm_entry and throughout the entire duration of execution inside of the
virtual machine R12 will contain this address.
R13 is loaded with the linear virtual address of the module base address inside of
vm_entry and is not altered throughout execution inside of the virtual machine.
RBX is a very special register which contains the rolling decryption key. After every
decryption of every operand of every virtual instruction RBX is updated by applying a
transformation to it with the decrypted operand’s value.
RAX, RCX, and RDX are used as temporary registers inside of the virtual machine,
however RAX is used for very specific temporary operations over the other registers.
RAX is used to decrypt operands of virtual instructions, AL specifically is used when
decrypting the opcode of a virtual instruction.
https://back.engineering/17/05/2021/#terminology 11/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
After this value is pushed onto the stack, a jmp is then executed to start executing
vm_entry. vm_entry is subjected to obfuscation which I explained in great detail
above. By flattening and then removing deadstore code we can get a nice clean view
of vm_entry.
https://back.engineering/17/05/2021/#terminology 12/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
As expected all registers as well as RFLAGS is pushed to the stack. The last push puts
eight bytes of zeros on the stack, not a relocation which I first expected. The ordering
in which these pushes happen are unique per-build, however the last push of eight
zero’s is always the same throughout all binaries. This is a very stable signature to
determine when the end of general register pushes is done. Below are the exact
sequences of instructions I am referring to in this paragraph.
https://back.engineering/17/05/2021/#terminology 13/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
After all registers and RFLAGS is pushed onto the stack the base address of the
module is loaded into R13. This happens in every single binary, R13 always contains
the base address of the module during execution of the VM. The base address of the
module is also pushed onto the stack.
Next, the relative virtual address of the desired virtual instructions to be executed is
decrypted. This is done by loading the 32bit RVA into ESI from RSP+0xA0. This is a very
significant signature and can be found trivially. Three transformations are then applied
to ESI to get the decrypted RVA of the virtual instructions. The three transformations
are unique per-binary. However, there are always three transformations.
Furthermore, the next notable operation that occurs is space allocated on the stack
for scratch registers. RSP is always moved to RBP always, then RSP is subtracted by
0x140. Then aligned by 16 bytes. After this is done the address is moved into RDI.
During the execution of the VM RDI always contains a pointer to scratch registers.
The next notable operation is loading the address of the vm handler table into R12.
This is done on every single VMProtect 2 binary. R12 always contains the linear virtual
address of the vm handler table. This is yet another significant signature which can be
used to find the location of the vm handler table quite trivially.
https://back.engineering/17/05/2021/#terminology 14/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Another operation is then done on RSI to calculate VIP. Inside of the PE headers, there
is a header called the “optional header”. This contains an assortment of information.
One of the fields is called “ImageBase”. If there are any bits above 32 in this field those
bits are then added to RSI. For example, vmptest.vmp.exe ImageBase field contains
the value 0x140000000. Thus 0x100000000 is added to RSI as part of the calculation. If
an ImageBase field contains less than a 32 bit value zero is added to RSI.
After this addition is done to RSI, a small and somewhat insignificant instruction is
executed. This instruction loads the linear virtual address of the virtual instructions
into RBX. Now, RBX has a very special purpose, it contains the “rolling decryption” key.
As you can see, the first value loaded into RBX is going to be the address of the virtual
instructions themselves! Not the linear virtual address but just the RVA including the
top 32bits of the ImageBase field.
Next, the base address of the vmp module is added to RSI computing the full, linear
virtual address of the virtual instructions. Remember that RBP contains the address of
RSP prior to the allocation of scratch space. The base address of the module is on the
top of the stack at this point.
This concludes the details for vm_entry, the next part of this routine is actually
referred to as “calc_vm_handler” and is executed after every single virtual instruction
besides the vm_exit instruction.
calc_jmp is part of the vm_entry routine, however it’s referred to by more than just the
vm_entry routine. Every single vm handler will eventually jump to calc_jmp (besides
vm_exit). This snippet of code is responsible for decrypting the opcode of every virtual
instruction as well as indexing into the vm handler table, decrypting the vm handler
table entry and jumping to the resulting vm handler.
https://back.engineering/17/05/2021/#terminology 15/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The first instruction of this snippet of code reads a single byte out of RSI which as you
know is VIP. This byte is an encrypted opcode. In other words it’s an encrypted index
into the vm handler table. There are 5 total transformations which are done. The first
transformation is always applied to the encrypted opcode and the value in RBX as the
source. This is the “rolling encryption” at play. It’s important to note that the first
value loaded into RBX is the RVA to the virtual instructions. Thus BL will contain the
last byte of this RVA.
The last transformation is applied to the rolling encryption key stored in RBX. This
transformation is the same transformation as the first. However the registers swap
places. The end result is the decrypted vm handler index. The value of AL is then zero
extended to the rest of RAX.
Now that the index into the vm handler table has been decrypted the vm handler
entry itself must be fetched and decrypted. There is only a single transformation
applied to these vm handler table entries. No register values are ever used in these
transformations. The register in which the encrypted vm table entry value is loaded
into is always RCX or RDX.
VIP is now advanced. VIP can be advanced either forward or backwards and the
advancement operation itself can be an LEA, INC, DEC, ADD, or SUB instruction.
https://back.engineering/17/05/2021/#terminology 16/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Lastly, the base address of the module is added to the decrypted vm handler RVA and
a JMP is then executed to start executing this vm handler routine. Again RDX or RCX is
always used for this ADD and JMP. This is another significant signature in the virtual
machine.
This concludes the calc_jmp code snippet specifications. As you can see there are some
very significant signatures which can be found trivially using Zydis. Especially the
decryption done on vm handler table entries, and fetching these encrypted values.
Unlike vm_entry, vm_exit is quite a straightforward routine. This routine simply POP’s
all registers back into place including RFLAGS. There are some redundant POP’s which
are used to clear the module base, padding, as well as RSP off of the stack since they
are not needed. The order in which the pops occur are the inverse of the order in
which they are pushed onto the stack by vm_entry. The return address is calculated
and loaded onto the stack prior to the vm_exit routine.
.vmp0:0000000140004162 41 58 pop r8
.vmp0:0000000140004175 41 59 pop r9
.vmp0:00000001400041AC 9D popfq
.vmp0:0000000140007AF6 C3 retn
https://back.engineering/17/05/2021/#terminology 17/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Vm handlers which put any new values on the stack will have a stack check after the
vm handler executes. This routine checks to see if the stack is encroaching upon the
scratch registers.
.vmp0:000000014000429D 0F 87 5B 17 00 00 ja calc_jmp
.vmp0:0000000140004672 9C pushfq
.vmp0:0000000140004A48 9D popfq
Note the usage of “movsb” which is used to copy the contents of the scratch registers.
Virtual instructions consist of two or more operands. The first operand being the
opcode of the virtual instruction. Opcodes are 8bit, unsigned values which when
decrypted are the index into the vm handler table. There can be a second operand
which is a one to eight byte immediate value.
https://back.engineering/17/05/2021/#terminology 18/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
All operands are encrypted and must be decrypted with the rolling decrypt key.
Decryption is done inside of calc_jmp as well as vm handlers themselves. Vm handlers
that do decryption will be operating on immediate values only and not an opcode.
VMProtect 2 encrypts its virtual instructions using a rolling decryption key. This key is
located in RBX and is initially set to the address of the virtual instructions. The
transformations done to decrypt operands consist of XOR, NEG, NOT, AND, ROR, ROL,
SHL, SHR, ADD, SUB, INC, DEC, and BSWAP. When an operand is decrypted the first
transformation applied to the operand includes the rolling decryption key. Thus only
XOR, AND, ROR, ROL, ADD, and SUB are going to be the first transformation applied to
the operand. Then, there are always three transformations directly applied to the
operand. At this stage, the operand is completely decrypted and the value in RAX will
hold the decrypted operand value. Lastly the rolling decryption key is updated by
transforming the rolling decryption key with the fully decrypted operand value. An
example looks like this:
This above snippet of code decrypts the first operand, which is always the instructions
opcode. This code is part of the calc_jmp routine, however the transformation format
is the same for any second operands.
https://back.engineering/17/05/2021/#terminology 19/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
VM Handlers - Specifications
VM handlers contain the native code to execute virtual instructions. Every VMProtect 2
binary has a vm handler table which is an array of 256 QWORD’s. Each entry contains
an encrypted relative virtual address to the corresponding VM handler. There are many
variants of virtual instructions such as different sizes of immediate values as well as
sign and zero extended values. This section will go over a few virtual instruction
examples as well as some key information which must be noted when trying to parse
VM handlers.
https://back.engineering/17/05/2021/#terminology 20/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
VM handlers which handle immediate values fetch the encrypted immediate value
from RSI. The traditional five transformations are then applied to this encrypted
immediate value. The transformation format follows the same as the calc_jmp
transformations. The first transformation is applied to the encrypted immediate value
with the rolling decryption key being the source of the operation. Then three
transformations are applied directly to the encrypted immediate value, this decrypts
the value fully. Lastly the rolling decryption key is updated by doing the first
transformation except with the destination and source operands swapped.
https://back.engineering/17/05/2021/#terminology 21/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Also note that vm handlers are subjected to opaque branching as well as deadstore
obfuscation.
One of the most iconic virtual machine instructions is LCONST. This virtual instruction
loads a constant value from the second operand of a virtual instruction onto the stack.
This is the deobfuscated view of LCONSTQ VM handler. As you can see this VM handler
reads the second operand of the virtual instruction out of VIP (RSI). It then decrypts
this immediate value and advances VIP. The decrypted immediate value is then put
onto the VSP.
sub rbp, 8
This virtual instruction loads a DWORD size operand from RSI, decrypts it, and extends
it to a QWORD, finally putting it on the virtual stack.
dec eax
push rbx
pop rbx
https://back.engineering/17/05/2021/#terminology 22/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
sub rbp, 8
Note, this last vm handler updates the rolling decryption key by putting the value on
the stack then applying the transformation. This is something that could cause
significant problems when parsing these VM handlers. Luckily there is a very simple
trick to handle this, always remember that the transformation applied to the rolling
key is the same transformation as the first. In the above case it’s a simple XOR.
LCONSTCBW loads a constant byte value from RSI, decrypts it, and zero extends the
result as a WORD value. This decrypted value is then placed upon the virtual stack.
add al, bl
inc al
neg al
add bl, al
inc rsi
mov [rbp], ax
LCONSTCWDE loads a constant word from RSI, decrypts it, and sign extends it to a
DWORD. Lastly the resulting value is placed upon the virtual stack.
xor ax, bx
neg ax
xor bx, ax
cwde
LCONSTDW loads a constant dword from RSI, decrypts it, and lastly places the result
upon the virtual stack. Also note that VIP advances backwards in the example below.
https://back.engineering/17/05/2021/#terminology 23/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
You can see this in the operand fetch as its subtracting from RSI prior to a dereference.
bswap eax
dec eax
neg eax
push rbx
pop rbx
Let’s look at another VM handler, this one by the name of LREG. Just like LCONST
there are many variants of this instruction, especially for different sizes. LREG is also
going to be in every single binary as it’s used inside of the VM to load register values
into scratch registers. More on this later.
LREGQ has a one byte immediate value. This is the scratch register index. A pointer to
scratch registers is always loaded into RDI. As described above many times, there are
five total transformations applied to the immediate value to decrypt it. The first
transformation is applied from the rolling decryption key, followed by three
transformations applied directly to the immediate value which fully decrypts it. Lastly
the rolling decryption key is updated by applying the first transformation on it with
the decrypted immediate value as the source.
sub al, bl
ror al, 2
not al
inc al
sub bl, al
sub rbp, 8
inc rsi
https://back.engineering/17/05/2021/#terminology 24/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
LREGDW is a variant of LREG which loads a DWORD from a scratch register onto the
stack. It has two operands, the second being a single byte representing the scratch
register index. The snippet of code below is a deobfuscated view of LREGDW.
sub al, bl
ror al, 1
neg al
sub bl, al
sub rbp, 4
Another iconic virtual instruction which is in every single binary is SREG. There are
many variants to this instruction which set scratch registers to certain sizes values. This
virtual instruction has two operands, the second being a single byte immediate value
containing the scratch register index.
SREGQ sets a virtual scratch register with a QWORD value from on top of the virtual
stack. This virtual instruction consists of two operands, the second being a single byte
representing the virtual scratch register.
sub al, bl
ror al, 2
not al
inc al
sub bl, al
add rbp, 8
SREGDW sets a virtual scratch register with a DWORD value from on top of the virtual
stack. This virtual instruction consists of two operands, the second being a single byte
representing the virtual scratch register.
xor al, bl
https://back.engineering/17/05/2021/#terminology 25/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
inc al
xor bl, al
mov [rax+rdi*1], dx
SREGW sets a virtual scratch register with a WORD value from on top of the virtual
stack. This virtual instruction consists of two operands, the second being a single byte
representing the virtual scratch register.
sub al, bl
neg al
sub bl, al
dec rsi
SREGB sets a virtual scratch register with a BYTE value from on top of the virtual stack.
This virtual instruction consists of two operands, the second being a single byte
representing the virtual scratch register.
xor al, bl
not al
neg al
xor bl, al
mov [rax+rdi*1], dl
https://back.engineering/17/05/2021/#terminology 26/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The virtual ADD instruction adds two values on the stack together and stores the
result in the second value position on the stack. RFLAGS is then pushed onto the stack
as the ADD instruction alters RFLAGS.
ADDQ adds two QWORD values stored on top of the virtual stack. RFLAGS is also
pushed onto the stack as the native ADD instruction alters flags.
pushfq
ADDW adds two WORD values stored on top of the virtual stack. RFLAGS is also
pushed onto the stack as the native ADD instruction alters flags.
add [rbp+0x08], ax
pushfq
pop [rbp]
ADDB adds two BYTE values stored on top of the virtual stack. RFLAGS is also pushed
onto the stack as the native ADD instruction alters flags.
add [rbp+0x08], al
pushfq
pop [rbp]
The virtual MUL instruction multiples two values stored on the stack together. These
vm handlers use the native MUL instruction, additionally RFLAGS is pushed onto the
stack. Lastly, it is a single operand instruction which means there is no immediate
value associated with this instruction.
MULQ multiples two QWORD values together, the result is stored on the stack at
VSP+24, additionally RFLAGS is pushed onto the stack.
mul rdx
pushfq
pop [rbp]
The virtual DIV instruction uses the native DIV instruction, the top operands used in
division are located on top of the virtual stack. This is a single operand virtual
instruction thus there is no immediate value. RFLAGS is also pushed onto the stack as
the native DIV instruction can also RFLAGS.
DIVQ divides two QWORD values located on the virtual stack. Push RFLAGS onto the
stack.
div [rbp+0x10]
pushfq
pop [rbp]
The READ instruction reads memory of different sizes. There is a variant of this
instruction to read one, two, four, and eight bytes.
READQ reads a QWORD value from the address stored on top of the stack. This virtual
instruction seems to sometimes have a segment prepended to it. However not all
READQ vm handlers have this ss associated with it. The QWORD value is now stored
on top of the virtual stack.
https://back.engineering/17/05/2021/#terminology 28/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
READDW reads a DWORD value from the address stored on top of the virtual stack.
The DWORD value is then put on top of the virtual stack. Below are two examples of
READDW, one which uses this segment index syntax and the other without it.
READW reads a WORD value from the address stored on top of the virtual stack. The
WORD value is then put on top of the virtual stack. Below is an example of this vm
handler using a segment index syntax however keep in mind there are vm handlers
without this segment index.
mov [rbp], ax
The WRITE virtual instruction writes up to eight bytes to an address. There are four
variants of this virtual instruction, one for each power of two up to and including
eight. There are also versions of each vm handler which use a segment offset type
instruction encoding. However in longmode some segment base addresses are zero.
The segment that seems to always be used is the SS segment which has the base of
zero thus the segment base has no effect here, it simply makes it a little more difficult
to parse these vm handlers.
WRITEQ writes a QWORD value to the address located on top of the virtual stack. The
stack is incremented by 16 bytes.
WRITEDW writes a DWORD value to the address located on top of the virtual stack.
The stack is incremented by 12 bytes.
The WRITEW virtual instruction writes a WORD value to the address located on top of
the virtual stack. The stack is then incremented by ten bytes.
mov ss:[rax], dx
The WRITEB virtual instruction writes a BYTE value to the address located on top of
the virtual stack. The stack is then incremented by ten bytes.
mov ss:[rax], dl
The SHL vm handler shifts a value located on top of the stack to the left by a number
of bits. The number of bits to shift is stored above the value to be shifted on the stack.
The result is then put onto the stack as well as RFLAGS.
SHLCBW shifts a byte value to the left and zero extends the result to a WORD. RFLAGS
is pushed onto the stack.
sub rbp, 6
shl al, cl
mov [rbp+8], ax
pushfq
SHLW shifts a WORD value to the left. RFLAGS is pushed onto the virtual stack.
shl ax, cl
mov [rbp+0x08], ax
pushfq
pop [rbp]
SHLDW shifts a DWORD to the left. RFLAGS is pushed onto the virtual stack.
shl eax, cl
pushfq
pop [rbp]
SHLQ shifts a QWORD to the left. RFLAGS is pushed onto the virtual stack.
https://back.engineering/17/05/2021/#terminology 31/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
shl rax, cl
pushfq
pop [rbp]
The SHLD virtual instruction shifts a value to the left using the native instruction SHLD.
The result is then put onto the stack as well as RFLAGS. There is a variant of this
instruction for one, two, four, and eight byte shifts.
SHLDQ shifts a QWORD to the left with double precision. The result is then put onto
the virtual stack and RFLAGS is pushed onto the virtual stack.
pushfq
pop [rbp]
The SHLDDW virtual instruction shifts a DWORD value to the left with double
precision. The result is pushed onto the virtual stack as well as RFLAGS.
pushfq
pop [rbp]
https://back.engineering/17/05/2021/#terminology 32/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The SHR instruction is the complement to SHL, this virtual instruction alters RFLAGS
and thus the RFLAGS value will be on the top of the stack after executing this virtual
instruction.
SHRQ shifts a QWORD value to the right. The result is put onto the virtual stack as well
as RFLAGS.
shr rax, cl
pushfq
pop [rbp]
The SHRD virtual instruction shifts a value to the right with double precision. There is a
variant of this instruction for one, two, four, and eight byte shifts. The virtual
instruction concludes with RFLAGS being pushed onto the virtual stack.
SHRDQ shifts a QWORD value to the right with double precision. The result is put onto
the virtual stack. RFLAGS is then pushed onto the virtual stack.
pushfq
pop [rbp]
SHRDDW shifts a DWORD value to the right with double precision. The result is put
onto the virtual stack. RFLAGS is then pushed onto the virtual stack.
https://back.engineering/17/05/2021/#terminology 33/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
pushfq
pop [rbp]
The NAND instruction consists of a not being applied to the values on top of the stack,
followed by the result of this not being bit wise and’ed to the next value on the stack.
The and instruction alters RFLAGS thus, RFLAGS will be pushed onto the virtual stack.
NANDW NOT’s two WORD values then bitwise AND’s them together. RFLAGs is then
pushed onto the virtual stack.
and [rbp+0x08], ax
pushfq
pop [rbp]
The READCR3 virtual instruction is a wrapper vm handler around the native mov reg,
cr3. This instruction will put the value of CR3 onto the virtual stack.
The WRITECR3 virtual instruction is a wrapper vm handler around the native mov cr3,
reg. This instruction will put a value into CR3.
https://back.engineering/17/05/2021/#terminology 34/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
PUSHVSP virtual instruction pushes the value contained in native register RBP onto
the virtual stack stack. There is a variant of this instruction for one, two, four, and eight
bytes.
PUSHVSPQ pushes the entire value of the virtual stack pointer onto the virtual stack.
PUSHVSPDW pushes the bottom four bytes of the virtual stack pointer onto the
virtual stack.
PUSVSPW pushes the bottom WORD value of the virtual stack pointer onto the virtual
stack.
mov [rbp], ax
This virtual instruction loads the virtual stack pointer register with the value at the top
of the stack.
This virtual instruction loads the virtual stack pointer register with the WORD value at
the top of the stack.
https://back.engineering/17/05/2021/#terminology 35/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
This virtual instruction loads the virtual stack pointer register with the DWORD value
at the top of the stack.
This virtual instruction loads the native flags register with the QWORD value at the
top of the stack.
push [rbp]
popfq
The virtual JMP instruction changes the RSI register to point to a new set of virtual
instructions. The value at the top of the stack is the lower 32bits of the RVA from the
module base to the virtual instructions. This value is then added to the top 32bits of
the image base value found in the optional header of the PE file. The base address is
then added to this value.
The virtual call instruction takes an address of the top of the virtual stack and then
calls it. RDX is used to hold the address so you can only really call functions with a
single parameter using this.
call rdx
https://back.engineering/17/05/2021/#terminology 36/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
Now that VMProtect 2’s virtual machine architecture has been documented, we can
reflect on the significant signatures. In addition, the obfuscation that VMProtect 2
generates can also be handled with quite simple techniques. This can make parsing the
vm_entry routine trivial. vm_entry has no legit JCC’s so everytime we encounter a JCC
we can simply follow it, remove the JCC from the instruction stream, then stop once
we hit a JMP RCX/RDX. We can remove most deadstore by following how an
instruction is used with Zydis, specifically tracking read and write dependencies on the
destination register of an instruction. Finally with the cleaned up vm_entry we can
now iterate through all of the instructions and find vm handlers, transformations
required to decrypt vm handler table entries, and lastly the transformations required
to decrypt the relative virtual address to the virtual instructions pushed onto the stack
prior to jumping to vm_entry.
One of the best, and most well known signatures is LEA r12, vm_handlers. This
instruction is located inside of the vm_entry snippet of code and loads the linear
virtual address of the vm handler table into R12. Using Zydis we can easily locate and
parse this LEA to locate the vm handler table ourselves.
vm_entry.begin(), vm_entry.end(),
return true;
return false;
);
if (result == vm_entry.end())
return nullptr;
ZydisCalcAbsoluteAddress(&result->instr,
return reinterpret_cast<std::uintptr_t*>(ptr);
https://back.engineering/17/05/2021/#terminology 37/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The above Zydis routine will locate the address of the VM handler table statically. It
only requires a vector of ZydisDecodedInstructions, one for each instruction in the
vm_entry routine. My implementation of this (vmprofiler) will deobfuscate vm_entry
first then pass around this vector.
This is easily located using Zydis. All that must be done is locate a SIB mov instruction
with RCX, or RDX as the destination, R12 as the base, RAX as the index, and lastly eight
as the index. Now, using Zydis we can find the next instruction with RDX or RCX as the
destination, this instruction will be the transformation applied to VM handler table
entries.
bool vm::handler::table::get_transform(
vm_entry.begin(), vm_entry.end(),
instr->operand_count == 2 &&
instr->operands[1].mem.scale == 8 &&
(instr->operands[0].reg.value == ZYDIS_REGISTER_RDX ||
instr->operands[0].reg.value == ZYDIS_REGISTER_RCX))
rcx_or_rdx = instr->operands[0].reg.value;
return true;
return false;
);
https://back.engineering/17/05/2021/#terminology 38/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
// check to see if we found the fetch instruction and if the next instruction
return false;
handler_fetch, vm_entry.end(),
return true;
return false;
);
if (handler_transform == vm_entry.end())
return false;
*transform_instr = handler_transform->instr;
return true;
This function will parse the vm_entry routine and return the transformation done to
decrypt VM handler table entries. In C++ each transformation operation can be
implemented in lambdas and a single function can be coded to return the
corresponding lambda routine for the transformation that must be applied.
The above code is equivalent to the below C++ code. This will decrypt vm handler
entries. To encrypt new values an inverse operation must be done. However for XOR
that is simply XOR.
vm::decrypt_handler _decrypt_handler =
};
vm::encrypt_handler _encrypt_handler =
https://back.engineering/17/05/2021/#terminology 39/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
};
The above decrypt and encrypt handlers can be dynamically generated by creating a
map of each transformation type and a C++ lambda reimplementation of this
instruction. Furthermore a routine to handle dynamic values such as byte sizes can be
created. This prevents a switch case from being created every single time a
transformation is required.
namespace transform
// ...
{ ZYDIS_MNEMONIC_ADD, _add<T> },
{ ZYDIS_MNEMONIC_XOR, _xor<T> },
{ ZYDIS_MNEMONIC_BSWAP, _bswap<T> },
};
switch (bitsize)
case 8:
case 16:
case 32:
case 64:
default:
// ...
This small snippet of code will allow for easy implementation of transformations in
C++ with overflows in mind. It’s very important that sizes are respected during
transformation as without correct size overflows as well as rolls and shifts will be
https://back.engineering/17/05/2021/#terminology 40/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
operand = transform::apply(key_decrypt->operands[0].size,
operand = transform::apply(
generic_decrypt_1->operands[0].size,
generic_decrypt_1->mnemonic, operand,
transform::has_imm(generic_decrypt_1) ?
generic_decrypt_1->operands[1].imm.value.u : 0);
operand = transform::apply(
generic_decrypt_2->operands[0].size,
generic_decrypt_2->mnemonic, operand,
transform::has_imm(generic_decrypt_2) ?
generic_decrypt_2->operands[1].imm.value.u : 0);
operand = transform::apply(
generic_decrypt_3->operands[0].size,
generic_decrypt_3->mnemonic, operand,
transform::has_imm(generic_decrypt_3) ?
generic_decrypt_3->operands[1].imm.value.u : 0);
rolling_key = transform::apply(key_decrypt->operands[0].size,
https://back.engineering/17/05/2021/#terminology 41/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
.vmp0:0000000140004605 66 F7 D8 neg ax
.vmp0:0000000140004613 98 cwde
As you can see there are two bytes read out of VIP. It’s the first instruction. This is
something we can look for in zydis. Any MOVZX, MOVSX, or MOV where RAX is the
destination and RSI is the source shows that there is an immediate value and thus we
know that five transformations are expected in the instruction stream. We can then
search for an instruction where RAX is the destination and RBX is the source. This will
be the first transformation. In the above example, the first subtraction instruction is
what we are looking for.
Next we can look for three instructions which have a write dependency on RAX. These
three instructions will be the generic transformations applied to the operand.
https://back.engineering/17/05/2021/#terminology 42/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
.vmp0:0000000140004605 66 F7 D8 neg ax
At this point the operand is completely decrypted. The only thing left is a single
transformation done to the rolling decryption key (RBX). This last transformation
updates the rolling decryption key.
vm_handler.begin(), vm_handler.end(),
(instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
util::reg::compare(instr_data.instr.operands[0].reg.value, ZYDIS_REGIST
instr_data.instr.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr_data.instr.operands[1].mem.base == ZYDIS_REGISTER_RSI)
return true;
return false;
);
if (imm_fetch == vm_handler.end())
return false;
// transform rax, rbx <--- note these registers can be smaller so we to64 them...
if (util::reg::compare(instr_data.instr.operands[0].reg.value, ZYDIS_REGIST
util::reg::compare(instr_data.instr.operands[1].reg.value, ZYDIS_REGIST
return true;
return false;
);
// last transformation is the same as the first except src and dest are swapped...
https://back.engineering/17/05/2021/#terminology 43/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
transforms[transform::type::rolling_key] = key_transform->instr;
instr_copy.operands[0].reg.value = key_transform->instr.operands[1].reg.value;
instr_copy.operands[1].reg.value = key_transform->instr.operands[0].reg.value;
transforms[transform::type::update_key] = instr_copy;
if (key_transform == vm_handler.end())
return false;
if (util::reg::compare(instr_data.instr.operands[0].reg.value, ZYDIS_RE
return true;
return false;
);
if (generic_transform == vm_handler.end())
return false;
return true;
As you can see above, the first transformation is the same as the last transformation
except the source and destination operands are swapped. VMProtect 2 takes some
creative liberties when applying the last transformation and can sometimes push the
rolling decryption key onto the stack, apply the transformation, then pop the result
back into RBX. This small, but significant inconvenience can be handled by simply
swapping the destination and source registers in the ZydisDecodedInstruction variable
as demonstrated in the above code.
The dilemma with trying to statically analyze virtual instructions is that branching
operations inside of the virtual machine are very difficult to handle. In order to
calculate where a virtual JMP is jumping to, emulation is required. I will be pursuing
this in the near future (unicorn).
https://back.engineering/17/05/2021/#terminology 44/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The first and foremost important piece of information to log when intercepting virtual
instructions is the opcode value which is located in AL. Logging this will tell us all of
the virtual instructions executed. The next value which must be logged is the rolling
decryption key value which is located in BL. This will allow vmprofiler to decrypt
operands statically.
Since we are able to, logging all scratch registers after every single virtual instruction
is an important addition to the logged information as this will paint an even bigger
picture of what values are being manipulated. Lastly, logging the top five QWORD
values on the virtual stack is done to provide even more information as again, this
virtual instruction set architecture is based off of a stack machine.
To conclude the dynamic analysis section of this post, I have created a small file format
for this runtime data. The file format is called “vmp2” and contains all runtime log
information. The structures for this file format are very simple, they are listed below.
namespace vmp2
forward,
backward
https://back.engineering/17/05/2021/#terminology 45/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
};
invalid,
v1 = 0x101
};
struct file_header
u64 epoch_time;
u64 module_base;
exec_type_t advancement;
version_t version;
u32 entry_count;
u32 entry_offset;
};
struct entry_t
u8 handler_idx;
u64 decrypt_key;
u64 vip;
union
struct
u64 r15;
u64 r14;
u64 r13;
u64 r12;
u64 r11;
u64 r10;
u64 r9;
u64 r8;
u64 rbp;
u64 rdi;
u64 rsi;
u64 rdx;
u64 rcx;
u64 rbx;
u64 rax;
u64 rflags;
};
u64 raw[16];
} regs;
union
u64 qword[0x28];
https://back.engineering/17/05/2021/#terminology 46/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
u8 raw[0x140];
} vregs;
union
u64 qword[0x20];
u8 raw[0x100];
} vsp;
};
Provided a “vmp2” file, vmprofiler will produce pseudo virtual instructions including
immediate values as well as affected scratch registers. This is not devirtualization by
any means, nor does it provide a view of multiple code paths, however it does give a
very useful trace of executed virtual instructions. Vmprofiler can also be used to
statically locate the vm handler table and determine what transformation is used to
decrypt these vm handler entries.
An example output of vmprofiler will produce all information about every vm handler
including immediate value bit size, virtual instruction name, as well as the five
transformations applied to the immediate value if there is an immediate value.
add al, bl
not al
inc al
add bl, al
=====================================================
The transformations, if any, are extracted as well from the vm handler and can be
executed dynamically to decrypt operands.
https://back.engineering/17/05/2021/#terminology 47/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
// ...
In order to display all traced information such as native register values, scratch register
values and virtual stack values I have created a very small Qt project which will allow
you to step through a trace. I felt that a console was way too restrictive and I also
found it hard to prioritize what needs to be displayed on the console, thus the need
for a GUI.
https://back.engineering/17/05/2021/#terminology 48/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
After the vm_entry routine executes, all registers that were pushed onto the stack are
then loaded into virtual machine scratch registers. This also extends to the module
base and RFLAGS which was also pushed onto the stack. The mapping of native
registers to scratch registers is not respected.
Another behavior which the virtual machine architecture exhibits is that if a native
instruction is not implemented with vm handlers a vmexit will happen to execute the
native instruction. In my version of VMProtect 2 CPUID is not implemented with vm
handlers so an exit happens.
Prior to a vmexit, values from scratch registers are loaded onto the virtual stack. The
vmexit virtual instruction will put these values back into native registers. You can see
that the scratch registers are different from the ones directly after a vmentry. This is
because like I said before scratch registers are not mapped to native registers.
https://back.engineering/17/05/2021/#terminology 49/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
For this demo I will be virtualizing a very simple binary which just executes CPUID and
returns true if AVX is supported, else it returns false. The assembly code for this is
displayed below.
.text:00007FF776A0100D cpuid
.text:00007FF776A0101C retn
When protecting this code I have opted out of using packing for simplicity of the
demonstration. I have protected the binary with “Ultra'' settings, which is just
obfuscation + virtualization. Looking at the PE header of the output file, we can see
https://back.engineering/17/05/2021/#terminology 50/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
that the entry point RVA is 0x1000, the image base is 0x140000000. We can now give
this information to vmprofiler-cli and it should give us the vm handler table RVA as
well as all of the vm handler information.
https://back.engineering/17/05/2021/#terminology 51/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
We can see that vmprofiler-cli has flattened and deobfuscated the vm_entry code as
well as located the vm handler table. We can also see the transformation done to
decrypt vm handler entities, it’s the XOR directly after mov rdx, [r12+rax*8].
We can also see that VIP advanced positively as RSI is incremented by the INC
instruction.
Armed with this information we can now compile a vmtracer program which will patch
all vm handler table entries to our trap handler which will allow us to trace virtual
instructions as well as alter virtual instruction results.
vm::decrypt_handler_t _decrypt_handler =
};
vm::encrypt_handler_t _encrypt_handler =
};
vm::handler::edit_entry_t _edit_entry =
DWORD old_prot;
PAGE_EXECUTE_READWRITE, &old_prot);
*entry_ptr = val;
old_prot, &old_prot);
};
https://back.engineering/17/05/2021/#terminology 52/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
vmp2::file_header trace_header;
trace_header.epoch_time = time(nullptr);
trace_header.advancement = vmp2::exec_type_t::forward;
trace_header.version = vmp2::version_t::v1;
trace_header.module_base = module_base;
I have omitted some of the other code such as the ofstream code and vmtracer class
instantiation, you can find that code here. The main purpose of displaying this
information is to show you how to parse a vm_entry and extract the information which
is required to create a trace.
tracer.start();
NT_HEADER(module_base)->OptionalHeader.AddressOfEntryPoint + module_base)();
tracer.stop();
Now that a trace file has been created we can now inspect the trace via vmprofiler-cli
or vmprofiler-qt. However I would suggest the latter as the program has been
explicitly created to view trace files.
When loading a trace file into vmprofiler-qt, one must know the vm_entry RVA as well
as the image base found in the optional header of the PE file. Given all of this
information as well as the original protected binary, vmprofiler-qt will display all
virtual instructions in a trace file and allow for you to “single step” through it.
Let’s look at the trace file and see if we can locate the original instructions which have
now been converted to a RISC, stack machine based architecture. The first block of
code that executes after vm_entry seems to contain no code pertaining to the original
binary. It is here simply for obfuscation purposes and to prevent static analysis of
virtual instructions as to understand where the virtual JMP instruction is going to land
would require emulation of the virtual instruction set. This first jump block is located
inside of every single protected binary.
https://back.engineering/17/05/2021/#terminology 53/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The next block following the virtual JMP instruction does a handful of interesting
math operations pertaining to the stack. If you look closely you can see that the math
operation being executed is: sub(x, y) = ~((~(x) & ~(x)) + y) & ~((~(x) & ~(x)) +
y); sub(VSP, 10).
If we simplify this math operation we can see that the operation is a subtraction done
to VSP. sub(x, y) = ~((~x) + y). This is equivalent to the native operation sub rsp,
0x10. If we look at the original binary, the one that is not virtualized, we can see that
there is in fact this instruction.
https://back.engineering/17/05/2021/#terminology 54/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
The mov eax, 1 displayed above can be seen in the virtual instructions closely after the
subtraction done on VSP. The MOV EAX, 1 is done via a LCONSTBSX and a SREGDW.
The SREG bitsize matches the native register width of 32bits, as well as the constant
value being loaded into it.
Next we see that a vmexit happens. We can see where code execution will continue
outside of the virtual machine by going to the last ADDQ prior to the vmexit. The first
two values on the stack should be the module base address and 32bit relative virtual
address to the routine that will be returned to. In this trace the RVA is 0x140008236. If
we inspect this address in IDA we can see that the instruction “CPUID” is here.
.vmp0:0000000140008236 0F A2 cpuid
As you can see, directly after the CPUID instruction, code execution enters back into
the virtual machine. Directly after setting all virtual scratch registers with native
register values located on the virtual stack a constant is loaded onto the stack with the
value of 0x1C. The resulting value from CPUID is then shifted to the right by this
constant value.
The AND operation is done with two NAND operations. The first NAND simply inverts
the result from the SHR; invert(x) = ~(x) & ~(x). This is done by loading the DWORD
value twice onto the stack to make a single QWORD.
The result of this AND operation is then set into virtual scratch register seven
(SREGDW 0x38). It is then moved into scratch register 16. If we look at the vmexit
https://back.engineering/17/05/2021/#terminology 55/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
instruction and the order in which LREGQ’s are executed we can see that this is indeed
correct.
Lastly, we can also see the ADD instruction and LVSP instruction which adds a value to
VSP. This is expected as there is an ADD RSP, 0x10 in the original binary.
From the information above we can reconstruct the following native instructions:
mov eax, 1
cpuid
and ecx, 1
ret
As you can see there are a few instructions which are missing, particularly the push’s
and pop’s of RBX, as well as the XOR to zero the contents of ECX. I assume that these
instructions are not converted to virtual instructions directly and are instead
implemented in a roundabout way.
In order to alter virtual instructions one must reimplement the entire vm handler first.
If the vm handler decrypts a second operand one must remember the importance of
the decryption key validity. Thus the original immediate value must be computed and
applied to the decryption key via the original transformation. However this value can
https://back.engineering/17/05/2021/#terminology 56/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
This virtual instruction has two operands, the first being the vm handler index to
execute and the second being the immediate value which in this case is a single byte.
Since there are two operands there will be five transformations inside of the vm
handler.
We can recode this vm handler and compare the decrypted immediate value with
0x1C, then branch to a subroutine to load a different value onto the stack. This will
then result in the SHR computing a different result. Essentially we can spoof the
CPUID results. An alternative to this would be recreating the SHR handler, however for
simplicity sake i’m just going to shift to a bit that is set. In this case bit 5 in ECX after
CPUID is set if VMX is supported and since my CPU supports virtualization this bit will
be high. Below is the new vm handler.
.data
__mbase dq 0h
public __mbase
.code
__lconstbzx proc
xor al, bl
https://back.engineering/17/05/2021/#terminology 57/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
dec al
ror al, 1
neg al
xor bl, al
je swap_val
sub rbp, 2
mov [rbp], ax
jmp rax
sub rbp, 2
mov [rbp], ax
jmp rax
__lconstbzx endp
end
If we now run the vm tracer again with this new vm handler being set to index 0x55 we
should be able to see a change in LCONSTBZX. In order to facilitate this hook, one
must set the virtual address of the new vm handler into a vm::handler::table_t
object.
_meta_data.virt = reinterpret_cast<u64>(&__lconstbzx);
handler_table.set_meta_data(0x55, _meta_data);
If we run the binary now it will return 1. You can see this below.
https://back.engineering/17/05/2021/#terminology 58/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
To begin, encoding virtual instructions requires that the vm handlers for said virtual
instructions are inside of the binary. Locating these vm handlers is done by
‘vmprofiler’. The vm handler index is the first opcode and the immediate value, if any,
is the second. Combining these two sets of operands will yield an encoded virtual
instruction. This is the first stage of assembling virtual instructions, the second is
encrypting the operands.
Once we have our encoded virtual instructions we can now encrypt them using the
inverse operations of vm handler transformations as well as the inverse operations for
calc_jmp. It’s important to note that the way in which VIP advances must be taken into
consideration when encrypting as the order of operands and virtual instructions
depends on this advancement direction.
In order to execute these newly assembled virtual instructions, one must put the
virtual instructions within a 32bit address range of the vm_entry routine, then put the
encrypted rva to these virtual instructions onto the stack, and lastly call into vm_entry.
https://back.engineering/17/05/2021/#terminology 59/61
2021/6/24 VMProtect 2 - Detailed Analysis of the Virtual Machine Architecture // Back Engineering
SIZE_T bytes_copied;
PROCESS_INFORMATION proc_info;
nullptr, false,
CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
WaitForInputIdle(proc_info.hProcess, INFINITE);
module_base + vmasm->header->offset,
vmasm->header->size,
WriteProcessMemory(proc_info.hProcess, virt_instrs,
nullptr, 0u,
module_base + vm_entry_rva,
CONTEXT thread_ctx;
GetThreadContext(thandle, &thread_ctx);
thread_ctx.Rsp -= 8;
WriteProcessMemory(proc_info.hProcess, thread_ctx.Rsp,
&vmasm->header->encrypted_rva,
SetThreadContext(thandle, &thread_ctx);
ResumeThread(thandle);
To conclude, my dynamic analysis solution is not the most ideal solution, however It
should allow for basic reverse engineering of protected binaries. With more time static
analysis of virtual instructions will become possible, however for the time being
dynamic analysis will have to do. In the future I will be using unicorn to emulate the
virtual machine handlers.
Although I have documented a handful of virtual instructions there are many more
that I have not documented. The goal of documenting the virtual instructions that I
have is to allow the reader of this article to obtain a feel for how vm handlers should
look as well as how one could alter the results of these vm handlers. The documented
virtual instructions in this article are also the most common ones. These virtual
instructions will most likely be inside of every virtual machine.
I have added a handful of reference builds inside of the repository for you to try your
hand at making them return 1 by altering vm handlers. There is also a build which uses
multiple virtual machines in a single binary.
Lastly, I would like to restate that this research has most definitely already been done
by private entities, and I am not the first to document some of the virtual machine
architecture discussed in this post. I have credited those whom I have studied the
research of already, however there are probably many more people that have done
research on VMProtect 2 that I have not listed simply because I have not come across
their work.
https://back.engineering/17/05/2021/#terminology 61/61