You are on page 1of 33

DEROKO OF ARTEAM

ASProtect VM Analyze
July 2007
1. Introduction
Well it was nearly a year since I wrote tutorial on how to fix Poly Oep
in ASProtect. That method worked more than fine for me, but I also
saw some comments that people expected to see how VM is fully
disassembled. Have no idea why, and how, but I got interested into
ASPR once more.

This document will be about ASProtect VM, and how to fix it,
including Poly OEP, and Advanced Import protection when ASPR
emulates instructions after stolen call dwod ptr[]. Reason why Im
writing this tut is that ASPR wasnt updated for a long time, so I
doubt that there will be update after this tutorial comes out, and
yet its simple VM is good introduction to VM reversing, which
nowadays becomes a must in a world of copy protection.

Editor: deroko
ASPROTECT VM ANALYZE PAGE 2

Disclaimers
All code included with this tutorial is free to use and modify; we only ask that you mention where you found it. This
tutorial is also free to distribute in its current unaltered form, with all the included supplements.

All the commercial programs used within this document have been used only for the purpose of demonstrating
the theories and methods described. No distribution of patched applications has been done under any media or
host. The applications used were most of the times already been patched, and cracked versions were available
since a lot of time. ARTeam or the authors of the paper cannot be considered responsible damages the
companies holding rights on those programs. The scope of this tutorial as well as any other ARTeam tutorial is of
sharing knowledge and teaching how to patch applications, how to bypass protections and generally speaking
how to improve the RCE art. We are not releasing any cracked application.

Verification
ARTeam.esfv can be opened in the ARTeamESFVChecker to verify all files have been released by ARTeam and
are unaltered. The ARTeamESFVChecker can be obtained in the release section of the ARTeam site:
http://releases.accessroot.com

Table of Contents
1. Introduction.......................................................................................................................................................................... 1
1. Tools and Target .................................................................................................................................................................. 3
2. Poly OEP VM ........................................................................................................................................................................ 4
3. AIP Recovery ..................................................................................................................................................................... 15
4. Conclusion ......................................................................................................................................................................... 33
5. References ......................................................................................................................................................................... 33
6. Greetings ............................................................................................................................................................................ 33
PAGE 3 ASPROTECT VM ANALYZE

1. Tools and Target


Targets that we are going to reverse are ASProtect demo from www.aspack.com , we are also
going to use 2 ASProtect crackmes that were submitted at ARTeam forum by EVOLUTION. The
reason why I need 2 crackmes is that one is Delphi where is need to fix Delphi Init procedures,
and 2nd is MSVC protected crackme which has AIP, and as we know MSVC uses call dword ptr[]
so this is good target to reverse VM which emulates a few instructions after stolen call dword
ptr[].

Tools that we are going to use are:

- Debugger of your choice


- IDA
- AsprDllDumper (should be with this document)
- Tasm32

When it comes to debugger, I prefer SoftICE, but, Olly will work too.

Aslo you will need my tutorial about ASPR from last year as I will refer to it several times, so just
make sure that you have it with you

If you are using Olly, make sure to use Manual Load in IDA so you can adjust your dll to proper
base. This is required for fast unpacking, so offsets in IDA and loaded dll in olly will be the same.
To find base address of loaded ASPR dll, simply break at 2nd VirtualAlloc, and write down that
address, as this is the place where ASPR allocates memory for virtual.dll. 1st break is allocation of
memory which will be used as decompression buffer

Ok you are now all setup


ASPROTECT VM ANALYZE PAGE 4

2. Poly OEP VM
Before we even start we need virtua dll to analyze ASPR protection. If you really want to know
how to dump it, read my tutorial from last year. But now we will use AsprDllDumper utility, dont
laugh at code It was 5am when I wrote it, and I didnt have any Red Bull left. Most important
thing is that it works as expected

First you should dump virtual dll from unpackme.exe target, as I will cover Poly OEP analyze
based on that example. Later on you will see how to make this approach generic for any Aspr
2.3 target.

To find PolyOepDispatcher you will have to use common technique of setting memory break
point on code section, once you break there, step out of code section, and trace trough poly
oep until you see call in it. This call will lead you to obsfucated memory buffer whose job is to
save registers, eflags, address of call, and to pass to PolyOepDispatcher data needed to
process VM instruction. There are other easier ways to locate PolyOEP EntryPoint but that is not
important atm, as Im going to cover only VM in this document. You may also use byte search to
find it.

If you have dumped your target dll on WinXP SP2 you will not have problems following tutorial
and offsets in it, otherwise you will have to force IDA to load dumped dll to different image base,
which you may accomplish by selecting Manual Load, anyhow learn method, and dont follow
offsets blindly.

Poly OEP dispatcher hasnt changed since last year, well because in meanwhile there werent
any updates, and it is same in 2.2x targets.

.CODE:009F95EC ASPR_VMInterpreter proc near


.CODE:009F95EC VmProcsIndex = dword ptr -8
.CODE:009F95EC OpcodeID = dword ptr -4
.CODE:009F95EC vmstructs = dword ptr 8
.CODE:009F95EC VMcall_plus_pid = dword ptr 0Ch
.CODE:009F95EC eflags = dword ptr 10h
.CODE:009F95EC register_struct = dword ptr 14h
.CODE:009F95EC oldesp = dword ptr 18h
.CODE:009F95EC
.CODE:009F95EC push ebp
.CODE:009F95ED mov ebp, esp
.CODE:009F95EF add esp, 0FFFFFFF8h
.CODE:009F95F2 push ebx
.CODE:009F95F3 push esi
.CODE:009F95F4 push edi
.CODE:009F95F5 mov ebx, [ebp+vmstructs]
.CODE:009F95F8 jmp short loc_9F95FB
.CODE:009F95F8 ; ------------------------------------------------------------
---------------
.CODE:009F95FA db 9Ah
.CODE:009F95FB ; ------------------------------------------------------------
---------------
.CODE:009F95FB
.CODE:009F95FB loc_9F95FB: ; CODE XREF:
ASPR_VMInterpreter+Cj
.CODE:009F95FB mov eax, [ebp+oldesp]
PAGE 5 ASPROTECT VM ANALYZE

It is very, very important to log data passed on stack to PolyOEPDispatcher or, as I have named
it, VM_Interpreter. You may see this arguments in above listing.

Here you may see code which obtains address of call __callpolyoep, and that address is used to
calculate unique ID of VM ocpde. I hope that I dont have to mention that ASPR can emulate
call/jmp/jcc/cmp,jcc in poly OEP.

.CODE:009F9627 mov esi, [ebx+30h] ; vm opcodes


.CODE:009F962A mov edi, [ebx+14h] ; number of VM entries
.CODE:009F962D mov eax, api_pointers
.CODE:009F9632 mov eax, [eax+34h]
.CODE:009F9635 call eax ; GetCurrentProcessId
.CODE:009F9637 sub [ebp+VMcall_plus_pid], eax
.CODE:009F963A mov eax, [ebp+VMcall_plus_pid] ; VMcall
address subed with PID
.CODE:009F963D sub eax, [ebx+VM_Struct.PolyOepEntryPoint]
.CODE:009F9640 sub eax, [ebx+VM_Struct.VM_key2] ; VM_key2
.CODE:009F9643 mov [ebp+OpcodeID], eax
.CODE:009F9646 lea eax, [ebx+VM_Struct.VmProcsIndex]
.CODE:009F9649 mov [ebp+VmProcsIndex], eax
.CODE:009F964C test edi, edi
.CODE:009F964E jbe short loc_9F96B3

OpcodeID is calculated by substracting call address with PolyOepVa and VM_Key2 which is
located at offset EBX+68h or VmData.68h. After that search for right opcode occurs here:

.CODE:009F9653 mov eax, [ebp+VmProcsIndex]


.CODE:009F9656 movzx eax, byte ptr [eax]
.CODE:009F9659 mov edx, [ebx+eax*4+VM_Struct.VmProcs]
.CODE:009F965D mov eax, esi
.CODE:009F965F call edx ; ecxtract VM id (eax, [eax])
.CODE:009F9661 cmp eax, [ebp+OpcodeID]
.CODE:009F9664 jnz short __checknextopcode

.CODE:009F96AB dec edi
.CODE:009F96AC add esi, [ebx+6Ch] ; size of VM_opcode
.CODE:009F96AF test edi, edi
.CODE:009F96B1 ja short __find_vm_opcode

Aspr cycles trough array of opcodes until it finds right one, if no opcode is found then Protection
error is generated, and ASPR will terminate its execution. I hope that you see how you may
extract exact position of call in poly OEP? Simple, take VM_OpcodeID, add to it VmData.68h +
PolyOepVA, and you will get address that needs to be fixed.

When good opcode is found ASPR will call handle opcode:

.CODE:009F9672 mov eax, [ebp+eflags]


.CODE:009F9675 push eax ; eflags saved
.CODE:009F9676 mov eax, [ebp+register_struct]
.CODE:009F9679 push eax ; register struct
.CODE:009F967A call GetSehChain
.CODE:009F967F push eax ; current SEH
.CODE:009F9680 mov ecx, esi
.CODE:009F9682 mov edx, [ebp+oldesp]

.CODE:009F9685 mov eax, ebx


.CODE:009F9687 call HandleVmOpcode
ASPROTECT VM ANALYZE PAGE 6

And emulation begins:

.CODE:009F8EFC HandleVmOpcode proc near


.CODE:009F8EFC return_from_4thcall= dword ptr -10h
.CODE:009F8EFC return_from_3rdcall= dword ptr -0Ch
.CODE:009F8EFC vmstructs_vmdata= dword ptr -8
.CODE:009F8EFC OldEsp = dword ptr -4
.CODE:009F8EFC currentSEH = dword ptr 8
.CODE:009F8EFC register_struct = dword ptr 0Ch
.CODE:009F8EFC eflags = dword ptr 10h
.CODE:009F8EFC
.CODE:009F8EFC push ebp
.CODE:009F8EFD mov ebp, esp
.CODE:009F8EFF add esp, 0FFFFFFF0h
.CODE:009F8F02 push ebx
.CODE:009F8F03 push esi
.CODE:009F8F04 push edi
.CODE:009F8F05 mov esi, ecx ; VM_opcode in esi
.CODE:009F8F07 mov [ebp+OldEsp], edx
.CODE:009F8F0A mov [ebp+vmstructs_vmdata], eax
.CODE:009F8F0D mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8F10 lea edi, [eax+24h]
.CODE:009F8F13 xor eax, eax
.CODE:009F8F15 mov al, [edi+1]
.CODE:009F8F18 mov edx, [ebp+vmstructs_vmdata]
.CODE:009F8F1B mov ebx, [edx+eax*4+40h]
.CODE:009F8F1F mov eax, esi ; VMOpcode in eax
.CODE:009F8F21 call ebx ; 2nd call (movzx eax, [eax+14])
.CODE:009F8F23 mov ebx, eax ; opcode type

Dont pay attention to comments 2nd call (movzx eax, [eax+14]) as those are specific only for
Delphi target unpackme.exe, those are changed from one to another application, as are VM
opcode size, and format. Still this can be fixed in generic way. You may for instance inject dll into
ASPR code, and perform decoding in its context. For the record I dont use this method in my
generic unpacker for ASPR 2.3 but it is up to you to figure how you will do this task. You will need
to call those procs located at VmData.40h using indexed from VmData.24h. Think about it

Next code to be extracted from VmOpcode is:

.CODE:009F8F23 mov ebx, eax ; opcode type


.CODE:009F8F25 xor eax, eax
.CODE:009F8F27 mov al, [edi+2]
.CODE:009F8F2A mov edx, [ebp+vmstructs_vmdata]
.CODE:009F8F2D mov edx, [edx+eax*4+40h]
.CODE:009F8F31 mov eax, esi ; VMOpcode in eax
.CODE:009F8F33 call edx ; 3rd call
.CODE:009F8F35 mov [ebp+return_from_3rdcall], eax
.CODE:009F8F38 xor eax, eax
.CODE:009F8F3A mov al, [edi+3]
.CODE:009F8F3D mov edx, [ebp+vmstructs_vmdata]
.CODE:009F8F40 mov edx, [edx+eax*4+40h]
.CODE:009F8F44 mov eax, esi ; VMOpcode in eax
.CODE:009F8F46 call edx ; 4th call
.CODE:009F8F48 mov [ebp+return_from_4thcall], eax
.CODE:009F8F4B sub bl, 2
.CODE:009F8F4E jb __CallOrJmp
.CODE:009F8F54 jz short __emulate_jcc_only
PAGE 7 ASPROTECT VM ANALYZE

.CODE:009F8F56 dec bl
.CODE:009F8F58 jnz __protection_error
.CODE:009F8F5E mov ecx, esi
.CODE:009F8F60 mov edx, [ebp+register_struct]
.CODE:009F8F63 mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8F66 call EmulateRealOpcode

You dont really need to know how those data are extracted from VmOpcode, well you may
disassemble each one of this procedures but remember it will be different in next target. Thats
what I figured after writing PolyOep analyzer for one target, and when I tested it on different
target, well guess what, it was different, so till this point you need to identify 4 procedures that is
ASPR calling to extract various data:

VmProcIndexes = VmData+24h

Index 0 opcode ID, this index is not really necessary as opcodes are stored in array.
Index 1 Opcode Type
Index 2 DataType3 (just remember it like that)
Index 3 DataType4 (again, just remember it like that)

At offset 9F8F46 you may see that ASPR is able to emulate a few instructions with this indexes:

0/1 = jmp or call, it doesnt really matter as you may identify call or jmp based on fact that
previous to call there was push or not. If push was there, then this was call, otherwise it is jmp.
2 = jcc emulation
3 = cmp,jcc emulation

Call/jmp Decoding:

.CODE:009F9022 __CallOrJmp:
.CODE:009F9022 mov eax, [ebp+vmstructs_vmdata]
.CODE:009F9025 mov edx, [eax+VM_Struct.VM_key2]
.CODE:009F9028 mov eax, edx
.CODE:009F902A add eax, [ebp+return_from_3rdcall]
.CODE:009F902D mov ecx, [ebp+vmstructs_vmdata]
.CODE:009F9030 mov ecx, [ecx+70h]
.CODE:009F9033 add eax, ecx
.CODE:009F9035 cmp eax, 0FFFFFFFFh
.CODE:009F9038 jnz short __JmpCallIsInPolyOep
.CODE:009F903A mov eax, edx
.CODE:009F903C add eax, [ebp+return_from_4thcall]
.CODE:009F903F mov edx, [ebp+vmstructs_vmdata]
.CODE:009F9042 add eax, [edx+VM_Struct.ImageBase]
.CODE:009F9045 add eax, ecx
.CODE:009F9047 jmp short __gobacktocode
.CODE:009F9049
.CODE:009F9049
.CODE:009F9049 __JmpCallIsInPolyOep:
.CODE:009F9049 mov edx, [ebp+vmstructs_vmdata]
.CODE:009F904C add eax, [edx+VM_Struct.PolyOepEntryPoint]
.CODE:009F904F jmp short __gobacktocode
ASPROTECT VM ANALYZE PAGE 8

If you take a closer look ASPR has to decide if call is going to stolen procedure which is in
PolyOEP, or it goes to the code section, or any other section out of poly oep range.

To perform this ASPR does this calculation:

TempVar = VmData.70h + VmData.68h + DataType3;

if (TempVar == -1)
TempVar = VmData.70h + VmData68h + DataType4 + ImageBase;
else
TempVar = TempVar + PolyOepVA;

For ASPR 2.2 VmData.70h is not used to calculate destinations. VmData.70h is introduces in ASPR
2.3 SKE.

Thats how call/jmp destinations are decoded. Simple as that

Jcc decoding is located here:

.CODE:009F8FCB __emulate_jcc_only:
.CODE:009F8FCB xor eax, eax
.CODE:009F8FCD mov al, [edi+4]
.CODE:009F8FD0 mov edx, [ebp+vmstructs_vmdata]
.CODE:009F8FD3 mov ebx, [edx+eax*4+40h]
.CODE:009F8FD7 mov eax, esi ; VM opcode
.CODE:009F8FD9 call ebx ; extract jcc type
.CODE:009F8FDB mov ebx, eax
.CODE:009F8FDD mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8FE0 xor bl, [eax+70h] ; decode jcc by xoring
it with byte at 70h
.CODE:009F8FE3 mov ecx, [ebp+eflags]
.CODE:009F8FE6 mov edx, ebx
.CODE:009F8FE8 mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8FEB call EmulateJcc ; decides if jcc is
taken or not
.CODE:009F8FF0 test al, al
.CODE:009F8FF2 jz short _jcc_not_taken
.CODE:009F8FF4 mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8FF7 mov eax, [eax+VM_Struct.PolyOepEntryPoint]
.CODE:009F8FFA add eax, [ebp+return_from_4thcall]
.CODE:009F8FFD mov edx, [ebp+vmstructs_vmdata]
.CODE:009F9000 add eax, [edx+VM_Struct.VM_key2]
.CODE:009F9003 mov edx, [ebp+vmstructs_vmdata]
.CODE:009F9006 add eax, [edx+70h]
.CODE:009F9009 jmp short __gobacktocode
.CODE:009F900B
.CODE:009F900B
.CODE:009F900B _jcc_not_taken:
.CODE:009F900B mov eax, [ebp+vmstructs_vmdata]
.CODE:009F900E mov eax, [eax+VM_Struct.PolyOepEntryPoint]
.CODE:009F9011 add eax, [ebp+return_from_3rdcall]
.CODE:009F9014 mov edx, [ebp+vmstructs_vmdata]
.CODE:009F9017 add eax, [edx+VM_Struct.VM_key2]
.CODE:009F901A mov edx, [ebp+vmstructs_vmdata]
.CODE:009F901D add eax, [edx+70h]
.CODE:009F9020 jmp short __gobacktocode
PAGE 9 ASPROTECT VM ANALYZE

Jcc Type is extracted from index 4, and in ASPR 2.3 SKE it is xored with byte at VmData.70h, this
xoring is not present in ASPR 2.2 SKE.

Depending on this value we enter into EmulateJcc procedure, and there bit test occurs.

.CODE:009F9338 EmulateJcc proc near


.CODE:009F9338 push ebx
.CODE:009F9339 push esi
.CODE:009F933A mov esi, ecx ; eflags
.CODE:009F933C xor ebx, ebx
.CODE:009F9341 xor eax, eax
.CODE:009F9343 mov al, dl ; jcc type
.CODE:009F9345 cmp eax, 0Fh ; switch 16 cases
.CODE:009F9348 ja loc_9F9593
.CODE:009F934E jmp ds:off_9F9355[eax*4] ; switch jump
.CODE:009F934E
.CODE:009F9355 off_9F9355 dd offset jcc_jo
.CODE:009F9355 dd offset jcc_jno
.CODE:009F9355 dd offset jcc_jc_jcc_jb
.CODE:009F9355 dd offset jcc_jnc_jcc_jnb
.CODE:009F9355 dd offset jcc_jz
.CODE:009F9355 dd offset jcc_jnz
.CODE:009F9355 dd offset jcc_jp
.CODE:009F9355 dd offset jcc_jnp
.CODE:009F9355 dd offset jcc_js
.CODE:009F9355 dd offset jcc_jns
.CODE:009F9355 dd offset jcc_jbe
.CODE:009F9355 dd offset jcc_ja
.CODE:009F9355 dd offset jmp_jl
.CODE:009F9355 dd offset jmp_jge
.CODE:009F9355 dd offset jcc_jle
.CODE:009F9355 dd offset jcc_jg

Lets take a look at one example of testing eflags:

.CODE:009F93C7 jcc_jc_jcc_jb:
.CODE:009F93C7 xor edx, edx ; case 0x2
.CODE:009F93C9 mov eax, esi
.CODE:009F93CB call BitTest_1ifset
.CODE:009F93D0 mov ebx, eax
.CODE:009F93D2 jmp loc_9F959D

BitTest is simple procedure of this format:

bt eax, edx
sbb eax, eax
and eax, 1

What it does, is that it checks if certain bit(edx) is set in elfags(eax), if bit is set, then CF will be set
too, and sbb will set eax to -1, otherwise if bit is not set then CF wont be set, and sbb eax, eax
will result into 0, and, of course, 0 will be return value.
ASPROTECT VM ANALYZE PAGE 10

Ok now lets analyze this code:

edx is bit position, so bit 0 is CF in eflags, and this code checks if CF is set, and returns proper
value back. So if CF is set then take jmp which means that this is jb or jc.

.CODE:009F93DA jcc_jnc_jcc_jnb:
.CODE:009F93DA xor edx, edx ; case 0x3
.CODE:009F93DC mov eax, esi
.CODE:009F93DE call BitTest_1ifset
.CODE:009F93E3 mov ebx, eax
.CODE:009F93E5 xor bl, 1
.CODE:009F93E8 jmp loc_9F959D

Here we have quite different situation, if CF is set then return value is 0 which means that jmp is
not taken. Only instructions that jmps if this bit(CF) is not set are jnb and jnc. Remember that ja !=
jnb as ja is taken only when ZF and CF are 0, while jnb is taken if CF == 0.

You may do this to reconstruct other jccs or simply use my labels.

If you take a look at code which deals with jcc emulation (one where JccType is extracted) you
will see also how destination is calculated depending if jcc is taken or not:

JccDestination = 0;
if (JccTaken)
JccDestination = PolyEntryPoint + VmData.68h + VmData70h + DataType4;
else
JccDestination = PolyEntryPoint + VmData.68h + VmData70h + DataType3;

Remember that VmData70h is not present in 2.2 so calculation is same, except you dont use
VmData70h.

Lets focus now on decoding of CMP instruction, jcc which follows cmp instruction is always
decoded in the same way as if there was no cmp.

.CODE:009F8F48 mov [ebp+return_from_4thcall], eax


.CODE:009F8F4B sub bl, 2
.CODE:009F8F4E jb __CallOrJmp
.CODE:009F8F54 jz short __emulate_jcc_only
.CODE:009F8F56 dec bl
.CODE:009F8F58 jnz __protection_error
.CODE:009F8F5E mov ecx, esi
.CODE:009F8F60 mov edx, [ebp+register_struct]
.CODE:009F8F63 mov eax, [ebp+vmstructs_vmdata]
.CODE:009F8F66 call EmulateRealOpcode

Dont ask me why I have named EmulateCmp as EmulateRealOpcode. This name gives
impression that jccs are not opcodes, but that wasnt my intention

This procedure is huge so Ill split it into several parts. 1st part is extraction of DataSrc and RegSrc:
PAGE 11 ASPROTECT VM ANALYZE

.CODE:009F8CB0 EmulateRealOpcode proc near


.CODE:009F8CB0 RegisterStruct = dword ptr -14h
.CODE:009F8CB0 var_10 = dword ptr -10h
.CODE:009F8CB0 var_C = dword ptr -0Ch
.CODE:009F8CB0 var_8 = dword ptr -8
.CODE:009F8CB0 save_decoded_data0= dword ptr -4
.CODE:009F8CB0
.CODE:009F8CB0 push ebx
.CODE:009F8CB1 push esi
.CODE:009F8CB2 push edi
.CODE:009F8CB3 push ebp ; ecx = vm opcode
.CODE:009F8CB4 add esp, 0FFFFFFECh ; esi = vm opcode
.CODE:009F8CB7 mov edi, ecx ; edx = register
struct
.CODE:009F8CB9 mov [esp], edx ; eax = vmstruct_data
.CODE:009F8CBC mov ebx, eax ; ebx = vm_structdata
.CODE:009F8CBE lea esi, [ebx+24h]
.CODE:009F8CC1 xor ebp, ebp
.CODE:009F8CC3 xor eax, eax
.CODE:009F8CC5 mov [esp+8], eax
.CODE:009F8CC9 xor eax, eax
.CODE:009F8CCB mov al, [esi+8]
.CODE:009F8CCE mov edx, [ebx+eax*4+40h]
.CODE:009F8CD2 mov eax, edi ; edi = VM_Opcode
.CODE:009F8CD4 call edx ; call emulate code 1
.CODE:009F8CD6 sub eax, [ebx+70h] ; decode data
.CODE:009F8CD9 mov [esp+DataEmulateCall1], eax ; save
emulatecodedata1
.CODE:009F8CDD xor eax, eax
.CODE:009F8CDF mov al, [esi+6]
.CODE:009F8CE2 mov edx, [ebx+eax*4+40h]
.CODE:009F8CE6 mov eax, edi
.CODE:009F8CE8 call edx ; call emulate code 2
.CODE:009F8CEA mov edx, eax ; extract register
index
.CODE:009F8CEC sub dl, 8
.CODE:009F8CEF setb dl
.CODE:009F8CF2 cmp dl, 1
.CODE:009F8CF5 jnz short __notregister
.CODE:009F8CF7 mov ecx, eax
.CODE:009F8CF9 mov edx, [esp+RegisterStruct] ; register
struct
.CODE:009F8CFC mov eax, ebx
.CODE:009F8CFE call GetRegisterValueByIndex
.CODE:009F8D03 mov [esp+SourceRegister], eax ; value from
register index
.CODE:009F8D07
.CODE:009F8D07 __notregister:

Index 8 is used to extract SrcData, depending on comparation later, this can be memory
address or displacement when indexing is used. I guess I dont have to remind you that
VmData.70h which is used here is NOT used in Aspr 2.2.

Index 6 on other hand is used to extract index of register which is SrcRegister, if this value is < 8
then there is no register used in this instruction, and depending on cmp type you can decide if
this is immediate value or memory address.

Aspr then performs similar code to extract DstData and DstRegister:


ASPROTECT VM ANALYZE PAGE 12

.CODE:009F8D07 xor eax, eax ; if no register


[esp+8] = 0
.CODE:009F8D09 mov al, [esi+7]
.CODE:009F8D0C mov edx, [ebx+eax*4+40h]
.CODE:009F8D10 mov eax, edi
.CODE:009F8D12 call edx ; call emulate 3 code
(mov eax, [eax+5])
.CODE:009F8D14 sub eax, [ebx+70h] ; VM_DATA.70
.CODE:009F8D17 mov [esp+DataEmulateCall3], eax
.CODE:009F8D1B xor eax, eax
.CODE:009F8D1D mov al, [esi+5]
.CODE:009F8D20 mov edx, [ebx+eax*4+40h]
.CODE:009F8D24 mov eax, edi
.CODE:009F8D26 call edx ; call emulate 4 code
.CODE:009F8D28 mov edx, eax
.CODE:009F8D2A sub dl, 8
.CODE:009F8D2D setb dl
.CODE:009F8D30 cmp dl, 1
.CODE:009F8D33 jnz short __notregister1
.CODE:009F8D35 mov ecx, eax
.CODE:009F8D37 mov edx, [esp+RegisterStruct] ; register
struct
.CODE:009F8D3A mov eax, ebx ; extracting DST
register or memory
.CODE:009F8D3C call GetRegisterValueByIndex
.CODE:009F8D41 mov ebp, eax ; ebp = register value
from callEmulate4
.CODE:009F8D43
.CODE:009F8D43 __notregister1:

Index 7 is used to extract DstData which can be displacement or memory address, it cant be
immediate when Dst is used.

Index 5 on other hand is used to extract Dst register, if it is < 8 then there is no Dst Register used.

ASPR then advances to calculating Dst and Src, and performs comparation:

.CODE:009F8D43 add ebp, [esp+DataEmulateCall3] ; register


value + dataEmulateCall3
.CODE:009F8D47 mov eax, [esp+DataEmulateCall1] ; eax =
dataEmulateCall1
.CODE:009F8D4B add eax, [esp+SourceRegister] ; value of
1st extracted register
.CODE:009F8D4F mov [esp+SourceData], eax ; esp = value of
1st extracted register or immidaite
.CODE:009F8D53 xor eax, eax
.CODE:009F8D55 mov al, [esi+9]
.CODE:009F8D58 mov edx, [ebx+eax*4+40h]
.CODE:009F8D5C mov eax, edi
.CODE:009F8D5E call edx ; call emulate 5 code
.CODE:009F8D60 and eax, 7Fh
.CODE:009F8D63 cmp eax, 4 ; switch 5 cases
.CODE:009F8D66 ja short __protection_error0
.CODE:009F8D68 jmp ds:off_9F8D6F[eax*4] ; switch jump
.CODE:009F8D6F off_9F8D6F dd offset DestinationIsMemory
.CODE:009F8D6F dd offset SourceIsMemory
.CODE:009F8D6F dd offset DesinationIsByte
.CODE:009F8D6F dd offset SourceIsByte
.CODE:009F8D6F dd offset __perfrom_comparation
PAGE 13 ASPROTECT VM ANALYZE

Index 9 is used to extract type of cmp instruction. One thing that doesnt seem logic here is
when SourceIsByte which means:

Cmp reg, byte ptr[], in IA32 there is no such instruction, as it is impossible to compare 32bit
register with 8bit. Simply this opcode cant be executed ever, and if it occurs in your decoding,
then you will know that something went terribly wrong

Example when DestinationIsMemory:

.CODE:009F8D99 mov ebp, [ebp+0] ; case 0x0


.CODE:009F8D9C jmp short __perfrom_comparation

EBP has address which is computed as DstReg + DstData, while eax has SourceData calculated
as SrcReg + SrcData (well if SrcReg index < 8 then SrcReg is 0).

To decode this properly you will have to write bunch of if/else to isolate good opcode, here is
one example:

switch(CmpType){
case 0: //cmp [],
if (RegSrc < 8 && RegDst <8){
_snprintf(OpcodeBuffer, 256, "cmp [%s], %s", RegisterNames[RegDst],
RegisterNames[RegSrc]);
break;
}
if (RegSrc <8){
_snprintf(OpcodeBuffer, 256, "cmp dword ptr[%.08X], %s", DataDst,
RegisterNames[RegSrc]);
break;
}
if (RegDst <8){
_snprintf(OpcodeBuffer, 256, "cmp dword ptr[%s], %.08X",
RegisterNames[RegDst], DataSrc);
break;
}
_snprintf(OpcodeBuffer, 256, "cmp dword ptr[%.08X], %.08X", DataDst, DataSrc);
break;

Well some instruction combinations have less options, as you may see I dont decode indexing,
but it is possible to occur. For example when RegDst < 8 && DataDst != 0.

Ok thats pretty much all about Poly OEP decoding, here is reminder of indexes for data
extraction:

0 Vm Opcode ID needed to find call address in PolyOEP


1 Instruction Type
2 DataType3
3 DataType4
4 JccType
5 Extract Destination Register Index
6 Extract Source Register Index
7 Extract Destination Data
8 Extract Source Data
9 Extract cmp type
ASPROTECT VM ANALYZE PAGE 14

If you think this was hard, then what are you going to say when we hit AIP emulation This was
piece of cake
PAGE 15 ASPROTECT VM ANALYZE

3. AIP Recovery
Before we continue, I have to note that at this point Im switching to msvc protected application
dumped dll as it is good for tracing, and recovering Data from AIP. Delphi is not good example
for tracing AIP as those are all jmp dword [], and no instruction emulation.

API redirection done by ASPR can be separated into what I have named normal and AIP
redirection. Normal redirection is very simple as it consist of call to allocated memory block
which will then jmp to yet another block of memory where are stored a few stolen bytes and
push/ret to rest of API, to fix this you have several choices:

1. Use AKIRA tracers to hook Export Directory of loaded modules


2. Inline hook all loaded dlls, this could represent a little problem, as some exports from
kernel32.dll are too small for inline hook
3. Simple patch ASPR Lde as I have described in previous tutorial so it will always assemble
push API_Address/ret and trace till your EIP isnt in memory of loaded modules.

Whichever method you chose you are facing one big problem. You dont know if this was call
dword ptr[] or jmp dword ptr[], sure, you may push arguments on stack and check if those are
same (call) or 1st argument is erased from stack (jmp dword[]) or simply while tracing, check if in
1st virtual buffer there is add esp, 4 or lea esp, [esp+4] which indicated jmp dword ptr[]. This is to
much hustle, and ASPR 2.3 gives us chance to know if this was jmp or call dword ptr[] by simply
analyzing AIP interpreter.

To retrieve API when AIP is used you have also several choices, but before I say anything about
them, AIP interpreter will call LoadLibraryA and custom implementation of GetProcAddress. As
this implementation doesnt support ordinals you know that this custom GetProcAddress will
receive name of the API. Methods to obtain API are:

1. Use AKIRA tracers


2. Inline hook dlls (same problem might occur as mentioned above)
3. Ohh this is really neet trick BPX LoadLibraryA, and obtain module base which is loaded.
Now set PAGE_GUARD on whole range of that module, and wait till EIP jmps there, thats
your API
4. BPX LoadLibraryA and @Sysutils@StrIComp$qqrpxct1_0 both of them will have in eax
name of dll which is loaded, and name of the API, so if you are writing own import
reconstruction engine you may use this method as it will give you names of dll and API
w/o additional work

One major question arises, and thats how to identify if API redirection is normal or AIP, well, well,
you will need to find code responsible for writing redirections, you may do this by setting HWBP
on any redirection, and depending if it is Normal or AIP you will land here:

.CODE:00A0C1C3 mov [ebp+0], eax ; normal api


redirection
.CODE:00A0C1C6 mov eax, [esp+10h]
.CODE:00A0C1CA mov eax, [eax]
.CODE:00A0C1CC mov [esp+14h], eax
.CODE:00A0C1D0 jmp short loc_A0C1DE
.CODE:00A0C1D2
.CODE:00A0C1D2 __write_AIP:
.CODE:00A0C1D2
.CODE:00A0C1D2 mov eax, [ebx+2Ch] ; ebx+2c = call_AIP
code
ASPROTECT VM ANALYZE PAGE 16

.CODE:00A0C1D5 sub eax, ebp ; ebp where to apply


write
.CODE:00A0C1D7 sub eax, 5
.CODE:00A0C1DA inc ebp
.CODE:00A0C1DB mov [ebp+0], eax ; write AIP
redirection

You are at this point in the loop which decides what API redirection should be written, here is
where you extract needed data:

.CODE:00A0BFFF mov edx, [ebx+54h] ; ebx+54 AIP opcodes


.CODE:00A0C002 test edx, edx
.CODE:00A0C004 jz __NothingToPerform
.CODE:00A0C00A cmp dword ptr [ebx+0E4h], 0
.CODE:00A0C011 jz __NothingToPerform
.CODE:00A0C017 cmp dword ptr [ebx+18h], 0
.CODE:00A0C01B jz __NothingToPerform
.CODE:00A0C021 cmp dword ptr [ebx+24h], 0
.CODE:00A0C025 jz __NothingToPerform
.CODE:00A0C02B cmp dword ptr [ebx+2Ch], 0
.CODE:00A0C02F jz __NothingToPerform
.CODE:00A0C035 cmp dword ptr [ebx+30h], 0
.CODE:00A0C039 jz __NothingToPerform
.CODE:00A0C03F mov esi, edx
.CODE:00A0C041 mov eax, [ebx+18h] ; total number of
redirections
.CODE:00A0C044 mov [esp], eax
.CODE:00A0C047 mov eax, [ebx+0E0h] ; size of opcode
.CODE:00A0C04D mov [esp+14h], eax ; save size of opcode
.CODE:00A0C051 lea edi, [ebx+40h] ; proc indexes buffer
.CODE:00A0C054 cmp dword ptr [esp], 0

.CODE:00A0C060 mov al, [edi] ; ebx+40h
.CODE:00A0C062 lea eax, [eax+eax*2]
.CODE:00A0C065 mov ebp, [ebx+eax*4+68h]
.CODE:00A0C069 mov eax, esi ; array of opcodes
.CODE:00A0C06B call ebp
.CODE:00A0C06D mov ebp, eax
.CODE:00A0C06F add ebp, [ebx+24h]
.CODE:00A0C072 add ebp, [ebx+0E0h] ; ebp = address of
redirection

.CODE:00A0C07B xor eax, eax
.CODE:00A0C07D mov al, [edi+9]
.CODE:00A0C080 lea eax, [eax+eax*2]
.CODE:00A0C083 mov edx, [ebx+eax*4+68h]
.CODE:00A0C087 mov eax, esi
.CODE:00A0C089 call edx ; check if it is AIP
redirection
.CODE:00A0C08B cmp byte ptr [ebx+20h], 0 ; ebx + 20h if 1
all are AIP
.CODE:00A0C08F jnz __write_AIP ; ebx+2c = call_AIP
code
.CODE:00A0C095 cmp al, 1
.CODE:00A0C097 jnz __write_AIP ; ebx+2c = call_AIP
code
PAGE 17 ASPROTECT VM ANALYZE

Oki doki, EBX has data which I have named AIPData (similar as VMData when it comes to
PolyOEP). Similar concept is used here, using indexes to extract API data, API addresses, type of
redirection, is emulation needed, where is emulation opcode etc

You should note that index 9 in AipProcs is used to determine what type of redirection this is, and
index 0 is used to extract where is address to which redirection should be applied. Also you
needed data pointed by EBX because without it is impossible to fix AIP.

This 2 indexs combined (0 and 9) will give you enough to get started with fixing AIP. One quick
note before we fix AIP is that instructions which can be emulated in AIP are:

call
jmp
mov
add
cmp/jcc
cmp

Each one of them has its own decoding logic, mov for example has several decodings
depending on what is moved, cmp/jcc is pretty much same, call/jmp are on other hand really
easy.

Once you have found one AIP simply follow it till it enters AIP_Dispatcher:

.CODE:009DBAD4 AIP_Dispatcher proc near


.CODE:009DBAD4
.CODE:009DBAD4 var_28 = dword ptr -28h
.CODE:009DBAD4 var_24 = dword ptr -24h
.CODE:009DBAD4 var_20 = dword ptr -20h
.CODE:009DBAD4 var_1C = dword ptr -1Ch
.CODE:009DBAD4 AIPProcIndexes = dword ptr -18h
.CODE:009DBAD4 var_14 = dword ptr -14h
.CODE:009DBAD4 LastError = dword ptr -10h
.CODE:009DBAD4 TotalNumberOfAipOpcodes= dword ptr -0Ch
.CODE:009DBAD4 AIP_ID = dword ptr -8
.CODE:009DBAD4 AipOpcodeAddress= dword ptr -4
.CODE:009DBAD4 AIP_Data = dword ptr 8
.CODE:009DBAD4 unknown = dword ptr 0Ch
.CODE:009DBAD4 CalledFrom = dword ptr 10h
.CODE:009DBAD4 eflags = dword ptr 14h
.CODE:009DBAD4 RegisterStruct = dword ptr 18h
.CODE:009DBAD4 EspBeforeCallAip= dword ptr 1Ch

It is very important to remember arguments passed to it. When it comes to unknown I think it was
SEH before enter to AIP_Dispatcher, cant remember, well anyway this value is not important at
all for decoding AIP.

.CODE:009DBB52 mov eax, off_9E0AB4


.CODE:009DBB57 mov eax, [eax+34h]
.CODE:009DBB5A call eax ; GetCurrentProcessId
.CODE:009DBB5C sub [ebp+CalledFrom], eax
.CODE:009DBB5F mov eax, [ebp+CalledFrom]
.CODE:009DBB62 sub eax, [ebx+AIP_Data.ImageBase]
.CODE:009DBB65 mov edx, [ebp+CalledFrom]
.CODE:009DBB68 sub edx, [ebx+AIP_Data.VA_of_1st_section]
.CODE:009DBB6B sub edx, [ebx+0E0h]
ASPROTECT VM ANALYZE PAGE 18

.CODE:009DBB71 mov [ebp+AIP_ID], edx


.CODE:009DBB74 cmp eax, [ebx+AIP_Data.MaxSize1stSection]
.CODE:009DBB77 jnb __protection_error

Here you may see how AIP_ID is calculated, it is not very important as we are recovering API
based on data which is located in EBX (AipData). Each opcode held in this structure will tell us
everything we need to know about AIP, and as such we dont have to take care of AIP_ID.

Next thing important to note is how is calculated offset of AIP opcode, again, for fixing this is not
needed but this text is more on ASProtect VM, then about unpacking:

.CODE:009DBB7D lea edx, [ebx+AIP_Data.AIPProcIndexes]


.CODE:009DBB80 mov [ebp+AIPProcIndexes], edx

.CODE:009DBB86 mov edx,


[ebx+AIP_Data.TotalNumberOfAipOpcodes]
.CODE:009DBB89 mov [ebp+TotalNumberOfAipOpcodes], edx
.CODE:009DBB8C mov edx, [ebp+CalledFrom]
.CODE:009DBB8F add edx, 5
.CODE:009DBB92 mov dl, [edx] ; one byte after AIP
redirection
.CODE:009DBB94 xor dl, [ebx+0E0h] ; xor it with
AIPDataE0
.CODE:009DBB9A mov edi, edx
.CODE:009DBB9C and edi, 0FFh ; edi = byte after
call ^ AIPDataE0
.CODE:009DBBA2 and eax, 0FFh ; eax = RVA of call
.CODE:009DBBA7 xor edi, eax
.CODE:009DBBA9 cmp edi, [ebp+TotalNumberOfAipOpcodes]
.CODE:009DBBAC ja loc_9DBC51
.CODE:009DBBB2
.CODE:009DBBB2 __find_aip_opcode_loop:
.CODE:009DBBB2 mov eax, [ebx+AIP_Data.AipOpcodeSize]
.CODE:009DBBB8 imul edi
.CODE:009DBBBA add eax, [ebx+AIP_Data.AipOpcodes]
.CODE:009DBBBD mov [ebp+AipOpcodeAddress], eax
.CODE:009DBBC0 jmp short loc_9DBBC3

To find correct AIP opcode ASPR uses one byte after relative call, and xors that byte with data at
AipData.E0h, this is now index into array of AipOpcodes, and AipOpcode is obtained by adding
AipData.AipOpcodeSize (E4h) + AipData.AipOpcodes (54h). Now ASPR can enter into
AIP_Interpreter and handle opcode.

While API is being resolved it isnt smart to set breakpoints, as before dll name and API name are
extracted there is checksum check on certain portion of code. So make sure that you are using
hardware breakpoints to step over calls or you will end up with protection error.

.CODE:009DAED4 AIP_Interpreter2 proc near


.CODE:009DAED4
.CODE:009DAED4 var_34 = dword ptr -34h
.CODE:009DAED4 CalculatedCheksum= dword ptr -30h
.CODE:009DAED4 IsEmulationNeeded= dword ptr -2Ch
.CODE:009DAED4 AipOpcodeForEmulation= dword ptr -28h
.CODE:009DAED4 var_24 = dword ptr -24h
.CODE:009DAED4 DataIndex7 = dword ptr -20h
.CODE:009DAED4 DataIndex5 = byte ptr -19h
PAGE 19 ASPROTECT VM ANALYZE

.CODE:009DAED4 DataIndex2 = dword ptr -18h


.CODE:009DAED4 DataIndex1 = byte ptr -11h
.CODE:009DAED4 CalledFrom = dword ptr -10h
.CODE:009DAED4 AIP_Data = dword ptr -0Ch
.CODE:009DAED4 EspBeforeCallAip= dword ptr -8
.CODE:009DAED4 ApiAddress = dword ptr -4
.CODE:009DAED4 LastError = dword ptr 8
.CODE:009DAED4 unknown = dword ptr 0Ch
.CODE:009DAED4 RegisterStruct = dword ptr 10h
.CODE:009DAED4 eflags = dword ptr 14h
.CODE:009DAED4 pAipOpcodeAddress= dword ptr 18h
.CODE:009DAED4
.CODE:009DAED4 push ebp
.CODE:009DAED5 mov ebp, esp
.CODE:009DAED7 add esp, 0FFFFFFCCh
.CODE:009DAEDA push ebx
.CODE:009DAEDB push esi
.CODE:009DAEDC push edi
.CODE:009DAEDD mov [ebp+EspBeforeCallAip], ecx
.CODE:009DAEE0 mov [ebp+CalledFrom], edx
.CODE:009DAEE3 mov [ebp+AIP_Data], eax
.CODE:009DAEE6 mov esi, [ebp+pAipOpcodeAddress]
.CODE:009DAEE9 mov eax, [ebp+AIP_Data]
.CODE:009DAEEC lea ebx, [eax+40h]
.CODE:009DAEEF jmp short loc_9DAEF2

Here you may see how local variables are filled with needed data, and that opposite to
PolyOep interpreter, which received VmOpcode, here AipInterpreter receives pointer to
AipOpcode. ASPR proceeds with extracting data needed for each AIPOpcode:

.CODE:009DAEF2 mov eax, off_9E0A24


.CODE:009DAEF7 mov byte ptr [eax], 0CBh
.CODE:009DAEFA xor eax, eax
.CODE:009DAEFC mov al, [ebx+2] ; index 2 extracts
needed data to decide if emlation is used or not
.CODE:009DAEFF lea eax, [eax+eax*2]
.CODE:009DAF02 mov edx, [ebp+AIP_Data]
.CODE:009DAF05 mov edi, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAF09 mov eax, [esi]
.CODE:009DAF0B call edi
.CODE:009DAF0D mov [ebp+DataIndex2], eax
.CODE:009DAF10 xor eax, eax
.CODE:009DAF12 mov al, [ebx+3]
.CODE:009DAF15 lea eax, [eax+eax*2]
.CODE:009DAF18 mov edx, [ebp+AIP_Data]
.CODE:009DAF1B mov edi, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAF1F mov eax, [esi]
.CODE:009DAF21 call edi
.CODE:009DAF23 mov edi, eax ; DataIndex3
.CODE:009DAF25 xor eax, eax
.CODE:009DAF27 mov al, [ebx+5]
.CODE:009DAF2A lea eax, [eax+eax*2]
.CODE:009DAF2D mov edx, [ebp+AIP_Data]
.CODE:009DAF30 mov edx, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAF34 mov eax, [esi]
.CODE:009DAF36 call edx
.CODE:009DAF38 mov [ebp+DataIndex5], al
.CODE:009DAF3B jmp short loc_9DAF3E
ASPROTECT VM ANALYZE PAGE 20

This time, each data extracted from certain index is named DataIndexX, for easier revesing I
chose it to be like that. You may use any other naming for local variables and retrived data.

.CODE:009DAF3E xor eax, eax


.CODE:009DAF40 mov al, [ebx+1]
.CODE:009DAF43 lea eax, [eax+eax*2]
.CODE:009DAF46 mov edx, [ebp+AIP_Data]
.CODE:009DAF49 mov edx, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAF4D mov eax, [esi]
.CODE:009DAF4F call edx
.CODE:009DAF51 mov [ebp+DataIndex1], al
.CODE:009DAF54 mov eax, [ebp+AIP_Data]
.CODE:009DAF57 mov eax, [eax+0E0h]
.CODE:009DAF5D add eax, [ebp+DataIndex2]
.CODE:009DAF60 mov [ebp+IsEmulationNeeded], eax

Here Aspr sets local variable IsEmulationNeeded. Actually it is index of emulation opcode, and if
this index is -1 then there is no emulation used, otherwise we have to decode emulation
instructions and this is where fun really begins.

IsEmulationNeeded or EmuOpcodeIndex is calculated like this then:

IsEmulationNeeded = DataIndex2 + AipData.E0h

Now we come to code responsible for retrieving API, you may skip it, as you may easily find API
using some of mentioned methods above.

To determine if this is call dword ptr[] or jmp dword ptr[] ASPR uses this code:

.CODE:009DB01F mov eax, [ebp+AIP_Data]


.CODE:009DB022 mov al, [eax+4Ah]
.CODE:009DB025 cmp al, [ebp+DataIndex1]
.CODE:009DB028 jnz __notcalldword

And this code for jmp dword ptr[]

.CODE:009DB0CA __notcalldword:
.CODE:009DB0CA mov eax, [ebp+AIP_Data]
.CODE:009DB0CD mov al, [eax+4Bh]
.CODE:009DB0D0 cmp al, [ebp+DataIndex1]
.CODE:009DB0D3 jnz short __nojmpdword_protection_error

So if DataIndex1 = AipData.4Ah we have call dword ptr[], else if DataIndex1 = AipData.4Bh then
we have jmp dword ptr[] otherwise we have error

How did I find this? Well if you trace ASPR a little bit you will see 2 very similar procedures but with
one huge huge difference:

.CODE:009DB048 lea eax, [ebp+unknown]


.CODE:009DB04B push eax
.CODE:009DB04C lea eax, [ebp+var_34]
.CODE:009DB04F push eax
.CODE:009DB050 lea eax, [ebp+var_24]
.CODE:009DB053 push eax
.CODE:009DB054 mov eax, [ebp+ApiAddress]
PAGE 21 ASPROTECT VM ANALYZE

.CODE:009DB057 push eax


.CODE:009DB058 mov eax, [ebp+EspBeforeCallAip]
.CODE:009DB05B push eax
.CODE:009DB05C mov eax, [ebp+RegisterStruct] ; call dword
.CODE:009DB05F push eax
.CODE:009DB060 mov eax, [ebp+AIP_Data]
.CODE:009DB063 push eax
.CODE:009DB064 call CallApiTroughObsfucation

And the one for jmp:

.CODE:009DB0EF lea eax, [ebp+unknown]


.CODE:009DB0F2 push eax
.CODE:009DB0F3 lea eax, [ebp+var_34]
.CODE:009DB0F6 push eax
.CODE:009DB0F7 lea eax, [ebp+var_24]
.CODE:009DB0FA push eax
.CODE:009DB0FB mov eax, [ebp+ApiAddress]
.CODE:009DB0FE push eax
.CODE:009DB0FF mov eax, [ebp+EspBeforeCallAip]
.CODE:009DB102 add eax, 4
.CODE:009DB105 push eax
.CODE:009DB106 mov eax, [ebp+RegisterStruct]
.CODE:009DB109 push eax
.CODE:009DB10A mov eax, [ebp+AIP_Data]
.CODE:009DB10D push eax
.CODE:009DB10E call CallApiTroughObsfucation ; jmp dword[]

And CallApiTroughObsfucation:

.CODE:009DADD8 push ebp


.CODE:009DADD9 mov ebp, esp
.CODE:009DADDB add esp, 0FFFFFFF4h
.CODE:009DADDE push ecx
.CODE:009DADDF push edx ; eax = aip_data
.CODE:009DADE0 push ebx
.CODE:009DADE1 push esi
.CODE:009DADE2 push edi
.CODE:009DADE3 mov eax, [ebp+arg_18]
.CODE:009DADE6 mov eax, [eax]
.CODE:009DADE8 mov large fs:0, eax
.CODE:009DADEF push ebp
.CODE:009DADF0 push 0EB04EB05h
.CODE:009DADF8 mov ecx, 3Ch
.CODE:009DADFD sub esp, ecx
.CODE:009DADFF mov [ebp+save_esp], esp
.CODE:009DAE02 mov esi, [ebp+old_esp]
.CODE:009DAE05 mov edi, esp
.CODE:009DAE07 rep movsb
.CODE:009DAE09 push offset CallApiReturn
.CODE:009DAE0E push [ebp+ApiAddress]
.CODE:009DAE11 push [ebp+RegisterStruct]
.CODE:009DAE14 mov eax, [ebp+AIP_Data]
.CODE:009DAE17 jmp dword ptr [eax+38h]

Note here how arguments from OldEsp are copied to new stack location, how new ret address is
stored on stack and API is called.
ASPROTECT VM ANALYZE PAGE 22

Well this is exactly how you may distinguish jmp dword ptr[] and call dword ptr[]. When call
dword ptr[] is used in AIP then arguments are located at OldStackLocation, but when jmp dword
ptr[] is used then arguments are located at OldEsp+4 because we have to take care of call
which called jmp dword ptr[].

When API is executed we are now going to check if emulation is needed or not. In case of jmp
dwod ptr[] ASPR checks if emulation is needed, but this will never occur. What we are interested
are call dword ptr[] because only and only then emulation of next instruction can occur.

.CODE:009DB170 __checkifemuationisneeded:
.CODE:009DB170 cmp [ebp+IsEmulationNeeded], 0FFFFFFFFh
.CODE:009DB174 jz short __noemulationneeded
.CODE:009DB176 mov edx, [ebp+IsEmulationNeeded]
.CODE:009DB179 mov eax, [ebp+AIP_Data]
.CODE:009DB17C call CalcualteAipOpcodeForEmulation ; =
IsEmulationNeeded *AipOpcodeSize(AipData.E4) + AipBaseOfOpcodes (AipData.58h)
.CODE:009DB181 mov [ebp+AipOpcodeForEmulation], eax
.CODE:009DB184 lea eax, [ebp+AipOpcodeForEmulation]
.CODE:009DB187 push eax
.CODE:009DB188 mov eax, [ebp+eflags]
.CODE:009DB18B push eax
.CODE:009DB18C mov eax, [ebp+RegisterStruct]
.CODE:009DB18F push eax
.CODE:009DB190 mov eax, [ebp+unknown]
.CODE:009DB193 push eax
.CODE:009DB194 mov eax, [ebp+LastError]
.CODE:009DB197 push eax
.CODE:009DB198 mov ecx, [ebp+EspBeforeCallAip]
.CODE:009DB19B mov edx, [ebp+CalledFrom]
.CODE:009DB19E mov eax, [ebp+AIP_Data]
.CODE:009DB1A1 call EmulateCmpAndJccAndMove

Now ASPR has to check if emulation is needed, if so, it calculates Address of EmuOpcode,
EmuOpcode array is located at AipData.58h, and size of this opcodes is same as AipOpcode, or
AipData.E4h.

Now we enter into real stuff, emulation of instructions in AIP:

.CODE:009DB1EC EmulateCmpAndJccAndMove proc near


.CODE:009DB1EC SourceRegisterIndex= byte ptr -16h
.CODE:009DB1EC DstRegisterIndex= byte ptr -15h
.CODE:009DB1EC DataIndex3 = dword ptr -14h
.CODE:009DB1EC DataIndex2 = dword ptr -10h
.CODE:009DB1EC CalledFrom = dword ptr -0Ch
.CODE:009DB1EC AIP_Data = dword ptr -8
.CODE:009DB1EC EspBeforeAipCall= dword ptr -4
.CODE:009DB1EC LastError = dword ptr 8
.CODE:009DB1EC oldsehhandler = dword ptr 0Ch
.CODE:009DB1EC RegisterStruct = dword ptr 10h
.CODE:009DB1EC eflags = dword ptr 14h
.CODE:009DB1EC pAipOpcodeForEmulation= dword ptr 18h
.CODE:009DB1EC
.CODE:009DB1EC push ebp
.CODE:009DB1ED mov ebp, esp
.CODE:009DB1EF add esp, 0FFFFFFE8h
.CODE:009DB1F2 push ebx
.CODE:009DB1F3 push esi
.CODE:009DB1F4 push edi
.CODE:009DB1F5 mov [ebp+EspBeforeAipCall], ecx
PAGE 23 ASPROTECT VM ANALYZE

.CODE:009DB1F8 mov [ebp+CalledFrom], edx


.CODE:009DB1FB mov [ebp+AIP_Data], eax
.CODE:009DB1FE mov eax, [ebp+AIP_Data]
.CODE:009DB201 lea esi, [eax+40h] ; AipProcIndexes
.CODE:009DB204 mov eax, [ebp+AIP_Data]
.CODE:009DB207 mov eax, [eax+0E4h] ; eax = size of
AipOpcode
.CODE:009DB20D call @System@@GetMem$qqrv
.CODE:009DB212 mov edi, eax
.CODE:009DB214 mov eax, [ebp+AIP_Data]
.CODE:009DB217 mov ecx, [eax+0E4h] ; ecx = size of
AipOpcode
.CODE:009DB21D mov edx, [ebp+pAipOpcodeForEmulation]
.CODE:009DB220 mov edx, [edx]
.CODE:009DB222 mov eax, edi
.CODE:009DB224 call @System@Move ; System::Move
.CODE:009DB229 push 10h
.CODE:009DB22B mov ecx, offset DecryptionConstants
.CODE:009DB230 mov eax, [ebp+AIP_Data]
.CODE:009DB233 mov edx, [eax+0E4h]
.CODE:009DB239 mov eax, edi
.CODE:009DB23B call DecryptAipOpcode
.CODE:009DB240 mov ebx, edi ; ebx = decrypted AIP
opcode

Till this point everything was more than good, but now we are facing a little problem.
EmuOpcode is crypted with certain Constants. Here we have a little problem in writing generic
unpacker for ASProtect, we have to find way to decrypt EmuOpcode. It is up to you to think how
you will decrypt them. One simple way it to locate those constants in ASPR code and simply
execute DecryptAipOpcode in your tool.

After that it is much simpler, again ASProtect extracts various data using AipProcIndexes and
AipProc but when those are used on EmuOpcodes effects are quite different:

.CODE:009DB24D xor eax, eax


.CODE:009DB24F mov al, [esi+2]
.CODE:009DB252 lea eax, [eax+eax*2]
.CODE:009DB255 mov edx, [ebp+AIP_Data]
.CODE:009DB258 mov edx, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DB25C mov eax, ebx
.CODE:009DB25E call edx
.CODE:009DB260 mov [ebp+DataIndex2], eax
.CODE:009DB263 xor eax, eax
.CODE:009DB265 mov al, [esi+3]
.CODE:009DB268 lea eax, [eax+eax*2]
.CODE:009DB26B mov edx, [ebp+AIP_Data]
.CODE:009DB26E mov edx, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DB272 mov eax, ebx
.CODE:009DB274 call edx
.CODE:009DB276 mov [ebp+DataIndex3], eax

And we are going for jmp/call emulation


.CODE:009DB27C xor eax, eax
.CODE:009DB27E mov al, [esi+1]
.CODE:009DB281 lea eax, [eax+eax*2]
.CODE:009DB284 mov edx, [ebp+AIP_Data]
.CODE:009DB287 mov edx, [edx+eax*4+68h]
.CODE:009DB28B mov eax, ebx
.CODE:009DB28D call edx
ASPROTECT VM ANALYZE PAGE 24

In DataIndex1 we get type of emulated opcode:

.CODE:009DB28F mov edx, [ebp+AIP_Data]


.CODE:009DB292 cmp al, [edx+4Ah]
.CODE:009DB295 jz short loc_9DB2A2 ; emulated jmp
.CODE:009DB297 mov edx, [ebp+AIP_Data]
.CODE:009DB29A cmp al, [edx+4Bh] ; data index 1
.CODE:009DB29D jnz short __checkotherconitions0

.CODE:009DB2A2 mov edx, [ebp+AIP_Data] ; emulated jmp


.CODE:009DB2A5 mov ebx, [edx+0E0h]
.CODE:009DB2AB add ebx, [ebp+DataIndex3]
.CODE:009DB2AE mov edx, [ebp+AIP_Data]
.CODE:009DB2B1 add ebx, [edx+AIP_Data.ImageBase]
.CODE:009DB2B4 mov edx, [ebp+AIP_Data]
.CODE:009DB2B7 cmp al, [edx+4Ah] ; data index 1
.CODE:009DB2BA jnz __freeEmuOpcodeMemory
.CODE:009DB2C0 sub [ebp+EspBeforeAipCall], 4 ; then
emulated call
.CODE:009DB2C4 mov eax, [ebp+AIP_Data]
.CODE:009DB2C7 mov eax, [eax+0E0h]
.CODE:009DB2CD add eax, [ebp+DataIndex2]
.CODE:009DB2D0 add eax, [ebp+CalledFrom]
.CODE:009DB2D3 mov edx, [ebp+EspBeforeAipCall]
.CODE:009DB2D6 mov [edx], eax ; emulated call
.CODE:009DB2D8 jmp __freeEmuOpcodeMemory

As you may see this code is really simple, ASPR updated EIP, and if we have call then and only
then ESP is updated with ret address. It is very simple example, so for now we know this:

If DataIndex1 == AipData.4A -> jmp


If DataIndex1 == AipData.4B -> call

I think that from above disassembly it is pretty much clear how Destination is calculated.
Remember you only need to assemble jmp/call, DataIndex2 is used to calculate ret address and
when you assemble call to righ position, this is not needed at all

Next Emulated opcode is add reg, imm32

.CODE:009DB2DD mov edx, [ebp+AIP_Data]


.CODE:009DB2E0 cmp al, [edx+4Fh] ; data index 1
.CODE:009DB2E3 jnz short __not_MovRegImm
.CODE:009DB2E5 xor eax, eax
.CODE:009DB2E7 mov al, [esi+5] ; extract DstRegister
.CODE:009DB2EA lea eax, [eax+eax*2]
.CODE:009DB2ED mov edx, [ebp+AIP_Data]
.CODE:009DB2F0 mov edx, [edx+eax*4+68h]
.CODE:009DB2F4 mov eax, ebx
.CODE:009DB2F6 call edx
.CODE:009DB2F8 mov [ebp+DstRegisterIndex], al

.CODE:009DB2FE xor eax, eax


.CODE:009DB300 mov al, [esi+7] ; DstData index
.CODE:009DB303 lea eax, [eax+eax*2]
.CODE:009DB306 mov edx, [ebp+AIP_Data]
.CODE:009DB309 mov edx, [edx+eax*4+68h]
PAGE 25 ASPROTECT VM ANALYZE

.CODE:009DB30D mov eax, ebx


.CODE:009DB30F call edx
.CODE:009DB311 mov ebx, eax ; EBX = dst_data
.CODE:009DB316 cmp [ebp+DstRegisterIndex], 4 ; is DstReg
esp?

.CODE:009DB31A jz short __itisesp ;


.CODE:009DB31C mov cl, [ebp+DstRegisterIndex]
.CODE:009DB31F mov edx, [ebp+RegisterStruct]
.CODE:009DB322 mov eax, [ebp+AIP_Data]
.CODE:009DB325 call ExtractRegisterData
.CODE:009DB32A add eax, ebx ; DstData+DstRegister
.CODE:009DB32C push eax
.CODE:009DB32D mov cl, [ebp+DstRegisterIndex]
.CODE:009DB330 mov edx, [ebp+RegisterStruct]
.CODE:009DB333 mov eax, [ebp+AIP_Data]
.CODE:009DB336 call MovDataToRegister ;

.CODE:009DB33B jmp short loc_9DB340


.CODE:009DB33D __itisesp:
.CODE:009DB33D add [ebp+EspBeforeAipCall], ebx
.CODE:009DB340
.CODE:009DB340 loc_9DB340:
.CODE:009DB340 mov ebx, [ebp+DataIndex2]
.CODE:009DB343 add ebx, [ebp+CalledFrom]
.CODE:009DB346 mov eax, [ebp+AIP_Data]
.CODE:009DB349 add ebx, [eax+0E0h]
.CODE:009DB34F jmp __freeEmuOpcodeMemory

This emulated opcode is very simple, by looking at comments and my labels you may see that it
extracts Data from Destination Register and adds to it Destination Data, and then that result
moves to Destination Register. Special case is handled when ESP is updated, this occurs when
there is __cdecl API called such as wsprintfA for example. In this case valid ESP is not stored in
RegisterStruct, it is stored in EspBeofreAipCall, so thats the one that needs to be updated.

Lets advance to next emulated opcode:

.CODE:009DB354 mov edx, [ebp+AIP_Data]


.CODE:009DB357 cmp al, [edx+50h]
.CODE:009DB35A jnz short __not_movregreg
.CODE:009DB35C xor eax, eax
.CODE:009DB35E mov al, [esi+5]
.CODE:009DB361 lea eax, [eax+eax*2]
.CODE:009DB364 mov edx, [ebp+AIP_Data]
.CODE:009DB367 mov edx, [edx+eax*4+68h]
.CODE:009DB36B mov eax, ebx
.CODE:009DB36D call edx
.CODE:009DB36F mov [ebp+DstRegisterIndex], al
.CODE:009DB375 xor eax, eax
.CODE:009DB377 mov al, [esi+6]
.CODE:009DB37A lea eax, [eax+eax*2]
.CODE:009DB37D mov edx, [ebp+AIP_Data]
.CODE:009DB380 mov edx, [edx+eax*4+68h]
.CODE:009DB384 mov eax, ebx
.CODE:009DB386 call edx
.CODE:009DB388 mov [ebp+SourceRegisterIndex], al
.CODE:009DB38B mov cl, [ebp+SourceRegisterIndex]
.CODE:009DB38E mov edx, [ebp+RegisterStruct]
.CODE:009DB391 mov eax, [ebp+AIP_Data]
ASPROTECT VM ANALYZE PAGE 26

.CODE:009DB394 call ExtractRegisterData


.CODE:009DB39C push eax ; data from
SrcRegister
.CODE:009DB39D mov cl, [ebp+DstRegisterIndex]
.CODE:009DB3A0 mov edx, [ebp+RegisterStruct]
.CODE:009DB3A3 mov eax, [ebp+AIP_Data]
.CODE:009DB3A6 call MovDataToRegister ; mov RegDst, RegSrc
.CODE:009DB3AB mov ebx, [ebp+DataIndex2]
.CODE:009DB3AE add ebx, [ebp+CalledFrom]
.CODE:009DB3B1 mov eax, [ebp+AIP_Data]
.CODE:009DB3B4 add ebx, [eax+0E0h]
.CODE:009DB3BA jmp __freeEmuOpcodeMemory

This one is very simple, ASPR extracts Data from SrcRegisters and stores it into DstRegister, just
plain mov reg, reg.

Next in our list is mov [], reg

.CODE:009DB3BF mov edx, [ebp+AIP_Data]


.CODE:009DB3C2 cmp al, [edx+51h]
.CODE:009DB3C5 jnz short __not_movMemReg

.CODE:009DB3CA xor eax, eax


.CODE:009DB3CC mov al, [esi+5] ; extract DstRegister
.CODE:009DB3CF lea eax, [eax+eax*2]
.CODE:009DB3D2 mov edx, [ebp+AIP_Data]
.CODE:009DB3D5 mov edx, [edx+eax*4+68h]
.CODE:009DB3D9 mov eax, ebx
.CODE:009DB3DB call edx
.CODE:009DB3DD mov [ebp+DstRegisterIndex], al

.CODE:009DB3E3 xor eax, eax


.CODE:009DB3E5 mov al, [esi+7] ; extract DST data
.CODE:009DB3E8 lea eax, [eax+eax*2]
.CODE:009DB3EB mov edx, [ebp+AIP_Data]
.CODE:009DB3EE mov edx, [edx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DB3F2 mov eax, ebx
.CODE:009DB3F4 call edx
.CODE:009DB3F6 mov ebx, eax ; ebx = DstData (RVA
where to apply mod)
.CODE:009DB3F8 mov cl, [ebp+DstRegisterIndex]
.CODE:009DB3FB mov edx, [ebp+RegisterStruct]
.CODE:009DB3FE mov eax, [ebp+AIP_Data]
.CODE:009DB401 call ExtractRegisterData
.CODE:009DB406 mov edx, [ebp+AIP_Data]
.CODE:009DB409 add ebx, [edx+AIP_Data.ImageBase]
.CODE:009DB40C mov [ebx], eax ; mov [DstData],
DstRegister
.CODE:009DB40E jmp short __exitemulation_jmp

Here you may see how ASPR extracts data from DstRegister, then it extracts RVA from DstData
and adds to it ImageBase. After that you see mov [ebx], eax which is mov [mem], reg. Easy to
decode, not problem so far.
PAGE 27 ASPROTECT VM ANALYZE

Next on our list is mov [reg+disp8/32], reg

.CODE:009DB434 __EmulateMovRegMem_Reg:
.CODE:009DB434 xor eax, eax
.CODE:009DB436 mov al, [esi+5]
.CODE:009DB439 lea eax, [eax+eax*2]
.CODE:009DB43C mov edx, [ebp+AIP_Data]
.CODE:009DB43F mov edx, [edx+eax*4+68h]
.CODE:009DB443 mov eax, ebx
.CODE:009DB445 call edx
.CODE:009DB447 mov [ebp+DstRegisterIndex], al
.CODE:009DB44A xor eax, eax
.CODE:009DB44C mov al, [esi+6]
.CODE:009DB44F lea eax, [eax+eax*2]
.CODE:009DB452 mov edx, [ebp+AIP_Data]
.CODE:009DB455 mov edx, [edx+eax*4+68h]
.CODE:009DB459 mov eax, ebx
.CODE:009DB45B call edx
.CODE:009DB45D mov [ebp+SourceRegisterIndex], al

Extract SourceRegIndex and DestinationRegIndex, nothing hard so far

.CODE:009DB463 xor eax, eax


.CODE:009DB465 mov al, [esi+7]
.CODE:009DB468 lea eax, [eax+eax*2]
.CODE:009DB46B mov edx, [ebp+AIP_Data]
.CODE:009DB46E mov edx, [edx+eax*4+68h]
.CODE:009DB472 mov eax, ebx
.CODE:009DB474 call edx
.CODE:009DB476 mov ebx, eax ; if zero mov [reg],
reg, else mov [reg+dstData], reg
.CODE:009DB478 mov cl, [ebp+DstRegisterIndex]
.CODE:009DB47B mov edx, [ebp+RegisterStruct]
.CODE:009DB47E mov eax, [ebp+AIP_Data]
.CODE:009DB481 call ExtractRegisterData
.CODE:009DB486 mov esi, eax ; esi =
DstRegisterData
.CODE:009DB488 mov cl, [ebp+SourceRegisterIndex]
.CODE:009DB48B mov edx, [ebp+RegisterStruct]
.CODE:009DB48E mov eax, [ebp+AIP_Data]
.CODE:009DB491 call ExtractRegisterData
.CODE:009DB496 add esi, ebx
.CODE:009DB498 mov [esi], eax ; mov [reg], reg, or
mov [reg+x], reg

Now we see that ASPR extracts Destination Data, and adds it to the content of DstRegister, this is
done so ASPR can form memory address, after that content of SourceRegister is moved to this
memory location. Remember that here you may have 3 completely different opcodes:

If (DataDst == 0)
mov [RegDst], RegSrc
if (DataDst <= 0xFF)
mov [RegDst+disp8], RegSrc
else
mov [RegDst+disp32], RegSrc
ASPROTECT VM ANALYZE PAGE 28

We are getting closer

.CODE:009DB4B1 mov edx, [ebp+AIP_Data]


.CODE:009DB4B4 cmp al, [edx+4Ch]
.CODE:009DB4B7 jnz short __emualtecmp_ONLY
.CODE:009DB4B9 jmp short __emualtecmp_and_jcc

.CODE:009DB520 __emualtecmp_ONLY:
.CODE:009DB520 mov edx, [ebp+AIP_Data]
.CODE:009DB523 cmp al, [edx+4Dh]
.CODE:009DB526 jnz short ProtectionError_0

Order of jccs here is the same as in PolyOEP (when it comes to switch/case), cmp is a little bit
different as it now uses indexes from 2-6 instead 0-4.

Lets see how cmp is emulated this time:

.CODE:009DB4BC __emualtecmp_and_jcc:
.CODE:009DB4BC mov ecx, ebx
.CODE:009DB4BE mov edx, [ebp+RegisterStruct]
.CODE:009DB4C1 mov eax, [ebp+AIP_Data]
.CODE:009DB4C4 call EmulateCmp
.CODE:009DB4C9 mov [ebp+eflags], eax
.CODE:009DB4CC jmp short loc_9DB4CF

Note data used within EmulateCmp:

.CODE:009DAC38 EmulateCmp proc near


.CODE:009DAC38
.CODE:009DAC38 var_1C = dword ptr -1Ch
.CODE:009DAC38 SrcRegister = dword ptr -18h
.CODE:009DAC38 DstRegister = dword ptr -14h
.CODE:009DAC38 CmpType_DataIndex9= byte ptr -0Dh
.CODE:009DAC38 SrcData = dword ptr -0Ch
.CODE:009DAC38 DstData = dword ptr -8
.CODE:009DAC38 RegistersStruct = dword ptr -4

Locating this data is not nuclear physic is it only patience, and correctly naming variables in
code that you are analyzing.

It is preaty much same code as one we have already analyzed:

.CODE:009DAC60 mov [ebp+DstRegister], eax


.CODE:009DAC63 xor eax, eax
.CODE:009DAC65 mov [ebp+SrcRegister], eax

First ASPR sets DstRegister, and SrcRegister to 0, and then extracts needed data from
EmuOpcode.
PAGE 29 ASPROTECT VM ANALYZE

.CODE:009DAC6B xor eax, eax


.CODE:009DAC6D mov al, [esi+7]
.CODE:009DAC70 lea eax, [eax+eax*2]
.CODE:009DAC73 mov edx, [ebx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAC77 mov eax, edi
.CODE:009DAC79 call edx
.CODE:009DAC7B mov [ebp+DstData], eax
.CODE:009DAC7E xor eax, eax
.CODE:009DAC80 mov al, [esi+5]
.CODE:009DAC83 lea eax, [eax+eax*2]
.CODE:009DAC86 mov edx, [ebx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DAC8A mov eax, edi
.CODE:009DAC8C call edx ; extract Dst register
.CODE:009DAC8E mov edx, eax
.CODE:009DAC90 sub dl, 8
.CODE:009DAC93 setb dl
.CODE:009DAC96 cmp dl, 1
.CODE:009DAC99 jnz short loc_9DACAA
.CODE:009DAC9B mov ecx, eax
.CODE:009DAC9D mov edx, [ebp+RegistersStruct]
.CODE:009DACA0 mov eax, ebx
.CODE:009DACA2 call ExtractRegisterData
.CODE:009DACA7 mov [ebp+DstRegister], eax

Basicaly if Index of DstRegister is >= 8 then only thing used here is DestinationData. Yes, order of
extracting data is not same as is in PoleOep, where ASPR extracted first SrcData, and
SrcRegister, but it is still same stuff. Also note that Source and Destination data are not modified
with KEY as is done in ASPR Poly Oep.

In the middle of data extraction, ASPR extracts type of cmp instruction:

.CODE:009DACAA xor eax, eax


.CODE:009DACAC mov al, [esi+9]
.CODE:009DACAF lea eax, [eax+eax*2]
.CODE:009DACB2 mov edx, [ebx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DACB6 mov eax, edi
.CODE:009DACB8 call edx
.CODE:009DACBA mov [ebp+CmpType_DataIndex9], al

Index 9 is what ASPR uses to extract type of instruction. ASPR then advances to extraction of
SrcData, and SrcRegister, remember that if SrcRegister is >= 8, only SrcData is used:

.CODE:009DACC0 xor eax, eax


.CODE:009DACC2 mov al, [esi+8]
.CODE:009DACC5 lea eax, [eax+eax*2]
.CODE:009DACC8 mov edx, [ebx+eax*4+AIP_Data.AIPPRocs]
.CODE:009DACCC mov eax, edi
.CODE:009DACCE call edx
.CODE:009DACD0 mov [ebp+SrcData], eax
.CODE:009DACD3 xor eax, eax
.CODE:009DACD5 mov al, [esi+6]
.CODE:009DACD8 lea eax, [eax+eax*2]
.CODE:009DACDB mov edx, [ebx+eax*4+68h]
.CODE:009DACDF mov eax, edi
.CODE:009DACE1 call edx ; Extract Src Register
.CODE:009DACE3 mov edx, eax
.CODE:009DACE5 sub dl, 8
.CODE:009DACE8 setb dl
ASPROTECT VM ANALYZE PAGE 30

.CODE:009DACEB cmp dl, 1


.CODE:009DACEE jnz short loc_9DAD02
.CODE:009DACF0 mov ecx, eax
.CODE:009DACF2 mov edx, [ebp+RegistersStruct]
.CODE:009DACF5 mov eax, ebx
.CODE:009DACF7 call ExtractRegisterData
.CODE:009DACFC mov [ebp+SrcRegister], eax

After that ASPR tells us more about cmp decoding in following code. By loking in debugger, and
analyzing it ASPR reveals to us that depending on cmp instruction type, SrcData, and DstData
are threated as RVA or imm32. I didnt see this kind of SrcData and DstData adjustment in ASPR
poly oep, so I can assume that Poly OEP in dll cant be used, or it can be used without cmp/jcc
emulation, or those offsets are rearranged earlier in ASPR layer

.CODE:009DAD02 cmp [ebp+DstData], 0


.CODE:009DAD06 jz short loc_9DAD14
.CODE:009DAD08 cmp [ebp+CmpType_DataIndex9], 2 ; if
Destination is memory and DstData != 0 then this is RVA
.CODE:009DAD0C jnz short loc_9DAD14
.CODE:009DAD0E mov eax, [ebx+AIP_Data.ImageBase]
.CODE:009DAD11 add [ebp+DstData], eax
.CODE:009DAD14
.CODE:009DAD14 loc_9DAD14:

.CODE:009DAD14 cmp [ebp+SrcData], 0


.CODE:009DAD18 jz short loc_9DAD26
.CODE:009DAD1A cmp [ebp+CmpType_DataIndex9], 3 ; if
SourceIsMemory and SrcData != 0, it is RVA
.CODE:009DAD1E jnz short loc_9DAD26
.CODE:009DAD20 mov eax, [ebx+AIP_Data.ImageBase]
.CODE:009DAD23 add [ebp+SrcData], eax
.CODE:009DAD26

.CODE:009DAD26 loc_9DAD26:
.CODE:009DAD26 mov ebx, [ebp+DstData]
.CODE:009DAD29 add ebx, [ebp+DstRegister]
.CODE:009DAD2C mov esi, [ebp+SrcData]
.CODE:009DAD2F add esi, [ebp+SrcRegister]

Once ASPR rearnages all data, it goes to switch/case and decides which cmp to perform:

.CODE:009DAD35 xor eax, eax


.CODE:009DAD37 mov al, [ebp+CmpType_DataIndex9] ; type of
oepration
.CODE:009DAD3A cmp eax, 6
.CODE:009DAD3D ja short ProtectionErrorInEmulation
.CODE:009DAD3F jmp ds:off_9DAD46[eax*4]
.CODE:009DAD46 off_9DAD46 dd offset ProtectionErrorInEmulation
.CODE:009DAD46 dd offset ProtectionErrorInEmulation
.CODE:009DAD46 dd offset DestinationIsMemory
.CODE:009DAD46 dd offset SourceIsMemory
.CODE:009DAD46 dd offset DestinationIsBytePtr
.CODE:009DAD46 dd offset SourceIsBytePointer
.CODE:009DAD46 dd offset just_compare_it
PAGE 31 ASPROTECT VM ANALYZE

I didnt mention earlier, but just_compare_it label, is just comparing two values, so thats how it
got its name. As you may see, now cmp indexes are 2-6 instead 0-4 as it was in Poly OEP. Again
you may see bug here, as there is not supported IA32 comparation used where 32bit register is
compared with 8bit. So if you decode some instruction like this, well you got an error. I have no
idea if this is a decoy, or just error in programming, as ASPR logic doesnt allow this instruction to
be executed ever.

One last thing left to look at is how jcc type is extracted, I wont cover its decoding as it is
exactly the same as in Poly Oep.

.CODE:009DB4BC __emualtecmp_and_jcc:
.CODE:009DB4BC mov ecx, ebx
.CODE:009DB4BE mov edx, [ebp+RegisterStruct]
.CODE:009DB4C1 mov eax, [ebp+AIP_Data]
.CODE:009DB4C4 call EmulateCmp
.CODE:009DB4C9 mov [ebp+eflags], eax

.CODE:009DB4CF xor eax, eax


.CODE:009DB4D1 mov al, [esi+4]
.CODE:009DB4D4 lea eax, [eax+eax*2]
.CODE:009DB4D7 mov edx, [ebp+AIP_Data]
.CODE:009DB4DA mov edx, [edx+eax*4+68h]
.CODE:009DB4DE mov eax, ebx
.CODE:009DB4E0 call edx ; extract jcc type
.CODE:009DB4E2 mov ebx, eax
.CODE:009DB4E4 mov ecx, [ebp+eflags]
.CODE:009DB4E7 mov edx, ebx
.CODE:009DB4E9 mov eax, [ebp+AIP_Data]
.CODE:009DB4EC call EmulateJcc
.CODE:009DB4F1 test al, al
.CODE:009DB4F3 jz short __jccnottaken

Index 4 is used to extract type of Jcc, and then that extracted index is used to decide if jcc is
taken or not. Jcc emulation is same as in poly oep, so no need to show it twice.

.CODE:009DB4F8 mov eax, [ebp+AIP_Data]


.CODE:009DB4FB mov ebx, [eax+0E0h] ; DataE0
.CODE:009DB501 add ebx, [ebp+DataIndex3] ; + DataIndex3
.CODE:009DB504 mov eax, [ebp+AIP_Data]
.CODE:009DB507 add ebx, [eax+AIP_Data.ImageBase]
.CODE:009DB50A jmp short __freeEmuOpcodeMemory

.CODE:009DB50F __jccnottaken:
.CODE:009DB50F mov eax, [ebp+AIP_Data]
.CODE:009DB512 mov ebx, [eax+0E0h]
.CODE:009DB518 add ebx, [ebp+DataIndex2]
.CODE:009DB51B add ebx, [ebp+CalledFrom]
.CODE:009DB51E jmp short __freeEmuOpcodeMemory

Calculation of jcc destination is simple:

JccDestination = 0;
if (JccTaken)
JccDestination = DataIndex3 + AipData.E0 + AipData.ImageBase;
else
JccDestination = CalledFrom + DataIndex2 + AipData.E0;
ASPROTECT VM ANALYZE PAGE 32

Here we only have to take care about destination when Jcc is taken, because we have to
assemble only jcc __destination_if_taken after fixed call dword ptr[].

Well thats all I have to say


PAGE 33 ASPROTECT VM ANALYZE

4. Conclusion
Well as you may see to do all of this by hand would be really crazy, thats why you need to write
own tools for automatization. World of protectors is moving toward Virtual Machines, although,
ASPR virtual machine is simple it is good exercise for RCE skills, and good introduction to
wonderful world of Virtual Machines. I also hope, that you, as a reader, learnt how it is
important to write tools to automate process of unpacking.

5. References
ASProtect SKE Unpacking Apprach, deroko of ARTeam, http://tutorials.accessroot.com
Unpacking with Tracers I, NCR, http://tutorials.accessroot.com
Unpacking with Tracers II, NCR, http://tutorials.accessroot.com

6. Greetings
I wish to thank to all my mates in ARTeam, 29a group for one of the best e-zine, great unpackers
from www.unpack.cn, softworm, fly, shoooo, ColdFever for his hard work on UnThemida project,
and, of course, you for reading this document.

, deroko of ARTeam

You might also like