ASProtect 1.4 11.20 DEMO主程序的分析(1)脱壳
Dedicated to those who never give up in their lives


[分析]

今天我们的目标是最新版的ASProtect 1.4 Demo。它是在aspack.com上免费下载的试用版。主程序名为asprotect.exe,其修改日期是“2007年11月20日, 15:57:36” ,其版本信息为“Version 1.4 Build 11.20 Release 1999-2005 ASPack Software”,被PeID 0.94识别为“ASProtect 1.2x - 1.3x [Registered] -> Alexey Solodovnikov。”,被vera 0.15识别为“Version: [ Unknown! ], Signature: [ 0DCBD2DA ], E-Mail: [ PE_Kill@mail.ru ]”,被Die 0.64识别为“ASProtect 2.3 SKE”,Die 0.64的结果还比较靠谱。因为是和ASProtect SKE 2.4 DEMO同一时间发布,我估计多半是ASProtect SKE 2.4 DEMO的壳。

[阅读线索]
壳的执行流程:Loader1.exe -> loader2.exe -> protector.dll
protector.dll的组成:emulator模拟器,checker检查器,obfuscator混淆器…

I. PeiD检查到的是Loader1.exe

[略语表]
AIP, Advanced Import Protection
INT, Import Name Table
IID, Image Import Descriptor
ESSF, Emulate Standard System FUnctions

文件头,12个输出函数,12个节section,有一个节名叫JCLDEBUG表明程序/壳可能是由Delphi编写的,最后两个节由asprotect增加,名字分别为.data和.adata。原程序的TLS目录移入.data中,但TLSCallBack空。其它没有什么值得注意的地方。


[loader1.exe]
    为了对付逆向者,壳中有壳:如果视此时执行的代码段为第一阶段的话,表示为Loader1.exe,功能为装载第二阶段的代码入内存、为第二阶段的执行作准备工作初始化参数、重建输入函数表、重定位…
入口点在0x401000处,挺普通的一个值,但其实这处代码只是一个跳板:跳转到asprotect增加的节.data中。

——————————————————————————————
seg023:00401000                EP_loader_exe proc near
seg023:00401000 68 01 00 59 00 push    offset sub_590001
seg023:00401005 E8 01 00 00 00 call    nullsub_1
seg023:0040100A C3             retn
seg023:0040100A                EP_loader_exe endp ;
——————————————————————————————

壳的自我保护机制有密文存放指令和数据,边执行、边还原、边执行。
Decrypt Code           Decrypt Target        Base     Step
0x005900fd-0x00590177  00590178-00590AFF     EDX      EDI
0x005901b2-0x00590212  0059021A-00590AFE     EAX      ECX
0x00590255-0x005902E7  00590320-00590AFC     EDI      EBX
0x00590355-0x005903D7  00593DD-00590AFD     EAX     ECX


如果上面四段循环正常执行、便来到此处。获取kernel32的七个输出函数。注意,紧接着它的就是还回被偷走的0x401000处的代码片段。
——————————————————————————————
.data:00590456 E8 9C 02 00 00 call    GetIB_Kernel32_dll
.data:0059045B FC             cld
.data:0059045C 8D B5 8C 00 00+lea     esi, [ebp+8Ch]
.data:00590462 AD             lodsd
.data:00590463 0B C0          or      eax, eax
.data:00590465 74 1B          jz      short loc_590482
.data:00590467 8B F8          mov     edi, eax
.data:00590469 B9 0C 00 00 00 mov     ecx, 0Ch
.data:0059046E F3 A4          rep movsb
.data:00590470 EB 10          jmp     short loc_590482
——————————————————————————————

GetIB_Kernel32_dll()自己实现GetProcAddress()来获取输入函数地址。GetProcAddress()的实现:遍历表AddressOfNames,算每一个函数名字的Hash值,找到匹配项后,算出表AddressOfNameOrdinals的索引号,利用此号在表AddressOfFunctions查找函数的指针,找到后返回指针值。
第一个要查找的函数为kernel32!GetProcAddress(),你在程序中却找不到字符串“GetProcAddress”!因为它以Hash的方式存储,也以Hash的方式查找:在kernel32.dll的输出名字表中寻找名字的Hash值为0xB72551A7的函数。
为了方便读者对照,我把函数中每一段功能的开头列出来。
------------------------------------------------------------------------
; 获取DLL文件的基址
.data:005906F7 8B 44 24 24       mov     eax, [esp+24h]
.data:005906FB 25 00 00 FF FF    and     eax, 0FFFF0000h
.data:00590700 05 00 00 01 00    add     eax, offset unk_10000
.data:00590705 2D 00 00 01 00    sub     eax, offset unk_10000
.data:0059070A 66 81 38 4D 5A    cmp     word ptr [eax], 5A4Dh
.data:0059070F 75 F4             jnz     short loc_590705

; 获取DLL文件的输入表
.data:00590705 2D 00 00 01 00    sub     eax, offset unk_10000
.data:0059070A 66 81 38 4D 5A    cmp     word ptr [eax], 5A4Dh

; 循环,调用自定义的GetProcAddress获取函数地址
.data:00590734 8B 33             mov     esi, [ebx]
.data:00590736 89 B5 7C 03 00 00 mov     [ebp+37Ch], esi ; 目标函数名的Hash值
.data:0059073C E8 0B 00 00 00    call    GetProcAddress
.data:00590741 AB                stosd
.data:00590742 83 C3 04          add     ebx, 4
.data:00590745 83 3B 00          cmp     dword ptr [ebx], 0
.data:00590748 75 EA             jnz     short loc_590734
-----------------------------------------------------------------------


在还原0x401000处被移走的代码后,调用VirtualAlloc()分配两块大小为5A000的堆。一块临时存放与硬盘上的文件几乎一样的Loader2exe的内存映像,文件头全以零填充(有些域没有用零填,里面藏有玄机),另一块存放此文件Loader2.exe如同被windows加载后的内存映象。

--------------------------------------------------------------------
debug031:00FD0000 A4 88 05 00       dd 588A4h       ; 三个函数LoadL,GetMH,GetPA的存放位置
debug031:00FD0004 00 80 05 00       dd 58000h        ; Loader2.exe入口点RVA
debug031:00FD0008 FC 01 04 00       dd 401FCh       ; DllProc()入口点
debug031:00FD000C 00                db    0
; Loader2.exe的文件头:大部分域用零填充,少部分有数据

debug031:00FD01F8 01 00 00 00 00+dd 1, 0, 41000h, 1000h, 1DC00h, 400h, 4 dup(0)
debug031:00FD0220 01 00 00 00 00+dd 1, 0, 1000h, 42000h, 600h, 1E000h, 4 dup(0)
debug031:00FD0248 01 00 00 00 00+dd 1, 0, 0F000h, 43000h, 0, 1E600h, 4 dup(0)
debug031:00FD0270 01 00 00 00 00+dd 1, 0, 2000h, 52000h, 800h, 1E600h, 4 dup(0)
debug031:00FD0298 01 00 00 00 00+dd 1, 0, 1000h, 54000h, 800h, 1EE00h, 4 dup(0)
debug031:00FD02C0 01 00 00 00 00+dd 1, 0, 3000h, 55000h, 2600h, 1F600h, 4 dup(0)
debug031:00FD02E8 01 00 00 00 00+dd 1, 0, 1000h, 58000h, 0A00h, 21C00h, 4 dup(0)
debug031:00FD0310 01 00 00 00 00+dd 1, 0, 1000h, 59000h, 0, 22600h, 4 dup(0)
; Loader2.exe的节头
-----------------------------------------------------------------

跳转到堆中继续执行,因为是堆,基址在不同类型的机器上可能会变化,所以此处的1088000h在你的机器上可能不同,下面不再说明。
————————————————————————————————
.data:005905AC 68 00 80 08 01    push    1088000h
.data:005905B1 C3                retn
————————————————————————————————

[loader2.exe]
Loader2.exe的功能是加载protector.dll。
这就是新世界(堆)的入口点。
———————————————————————————————

debug032:01088000 90                nop
debug032:01088001 60                pusha
debug032:01088002 E8 40 06 00 00    call    near ptr unk_1088647
———————————————————————————————

在这段代码丛林中的旅行如同重温一遍aspack.exe壳,在此不在赘述,请参考《aspack的分析和手工脱壳》一文。
———————————————————————————————
动态解码0x001088101  0x001088647(0x546)后,跳到0x108830D处继续执行
debug032:010880D6 50                push    eax
debug032:010880D7 53                push    ebx
debug032:010880D8 E8 74 05 00 00    call    DecipherCode   ; 解码函数
debug032:010880DD 8B C8             mov     ecx, eax
debug032:010880DF 8D BD 45 2A 44 00 lea     edi, [ebp+442A45h]
debug032:010880E5 8B B5 75 29 44 00 mov     esi, [ebp+442975h]
debug032:010880EB F3 A4             rep movsb
debug032:010880ED 8B 85 75 29 44 00 mov     eax, [ebp+442975h]
debug032:010880F3 68 00 80 00 00    push    8000h
debug032:010880F8 6A 00             push    0
debug032:010880FA 50                push    eax
debug032:010880FB FF 95 7D 29 44 00 call    dword ptr [ebp+44297Dh]
debug032:01088101 8D 85 51 2C 44 00 lea     eax, [ebp+442C51h]
debug032:01088107 50                push    eax
debug032:01088108 C3                retn

; 跳转到protector.dll处继续执行。
debug032:010885CC 68 50 11 07 01    push    1071150h
debug032:010885D1 C3                retn
———————————————————————————————

[protector.dll]
protector.dll的功能是加载目标程序。从protector.dll的编写风格,可以看出它是由Delphi编写的。
函数流是_InitLib -> _StartLib -> InitUnits -> _Halt0 -> ExitDll -> DllProc

Protector.dll入口点
--------------------------------------------------------------
debug032:01071150
debug032:01071150                         EP_protector_dll proc near
debug032:01071150 55                      push    ebp
debug032:01071151 8B EC                   mov     ebp, esp
debug032:01071153 83 C4 B4                add     esp, 0FFFFFFB4h
debug032:01071156 B8 28 0E 07 01          mov     eax, offset InitTable 
debug032:0107115B E8 80 4C FC FF          call    _InitLib
debug032:01071160 E8 B3 24 FC FF          call    _Halt0
debug032:01071160                         EP_protector_dll endp
--------------------------------------------------------------

Delphi DLL中的DllProc类似于VC++中的DllMain。下面是DllProc的入口点,可以看见它往栈中存放了若干函数地址,执行的顺序与函数压栈的顺序相反。
-----------------------------------------------------------------------
debug032:010701FC                         DllProc proc near
debug032:010701FC 55                      push    ebp
debug032:010701FD 8B EC                   mov     ebp, esp
debug032:010701FF 53                      push    ebx
debug032:01070200 56                      push    esi
debug032:01070201 57                      push    edi
debug032:01070202 A1 E0 2B 07 01          mov     eax, ds:off_1072BE0
…...略去几行
debug032:01070230 68 60 ED 03 01          push    offset Fun7
debug032:01070235 68 98 02 07 01          push    offset Fun6
debug032:0107023A 68 BC F9 06 01          push    offset Fun5
debug032:0107023F 68 F4 F9 06 01          push    offset Fun4
debug032:01070244 68 04 F4 06 01          push    offset Fun3
debug032:01070249 68 78 EF 06 01          push    offset Fun2
debug032:0107024E 68 3C FF 06 01          push    offset Fun1
debug032:01070253 C3                      retn
debug032:01070253                         DllProc endp
————————————————————————————————

为内存中某一特定地址处赋值以表示某一段代码的开始,好象生产线上的流水号,执行到某一步,打一个标记。(但也可能是Flags, Sign, Mask, whatever…)
----------------------------------Fun1()开头
debug032:0106FF5D A1 8C 2B 07 01          mov     eax, ds:off_1072B8C
debug032:0106FF62 C6 00 C9                mov     byte ptr [eax], 0C9h
---------------------------------Fun2()开头
debug032:0106EF7D A1 8C 2B 07 01          mov     eax, ds:off_1072B8C
debug032:0106EF82 C6 00 CD                mov     byte ptr [eax], 0CDh

程序中有一张大表TTableX,其中的每一个数据项Item又是一个表,存储运行所需的一种数据信息,如protector.dll自需的输入表、protectee.exe需要的输入表、protectee.exe中AIP表、…大致是以TItemX的结构存储,当然每一个具体的TItemX的内部结构又不相同。利用函数TObjX_1_Fun1_Search()来查找。
程序执行到某一阶段时,总有例程会解密这张表的某个表项,读取其内容。记住这一点,在适当地方下断点编写脚本读取内容,可以节省大量的人工。

   TTableX = record
   Hdr: HdrX
   Body: array[0..9999] of  TItemX;
   End;
—————————————————
   THdrX = record
   sign:char;
   size:integer;
   unknown:integer;
   end;
—————————————————
  TItemX = record
      Hdr: THdrX;
      Bdy: array[0..9999] of char;
   end;
—————————————————— 调用查找例程 
debug032:0106F66F A1 0C 2B 07 01          mov     eax, ds:ppObj_1_Fun1
debug032:0106F674 8B 00                   mov     eax, [eax]
debug032:0106F676 B2 0F                   mov     dl, 0Fh
debug032:0106F678 E8 5B 65 FF FF          call    TObjX_1_Fun1_Search 
——————————————————

Fun1()的工作任务:创建了若干全局对象,组建了protector.dll自需的输入表,…
所有后续调用的函数都是这其中某一对象的方法。

; 全局对象列表如下,除了TH\TH2\TH3是程序中已经注明的外,其它都是我编的名字。其中的各个域只是跟踪时的笔记,读者无需详究。
------------------------------------------------------------------
TObjX_1_Fun1, 0x14, class(TObject)
+04 pMem in loader1.exe
+08 VirMem for pMem in loader1.exe
+1C Num
TObjX_1_1_Fun1, 0x28
TAsEmulator, 0x58, class(TObject)
+04  pTItemX
+08  
+28  Checksum(Mem in protector.dll)
+2C  
+30  pTObjX_2_1_Fun1, 0x24, class(TObject)
+10, +11, +12 init to 110
+34-+3C sign
+40  pLoadLibrary
+44  pLoadLibrary XOR First4 bytes in LoadLibrary
+48  pGetProcAddress
+4C  pGetProcAddress XOR First4 bytes in GetProcAddress
+50  ImageBase 400000
TObjX_3_Fun1, 0x30, class(TObject)
TObjX_4_Fun1, 0x98, class(TObject)
TObjX_5_Fun1, 0x1B8, class(TObject)
+08  
+14  400000
+18  Number of AIP
+24  401000
+20  bDoSth
+28  Size
+54  TIATx
  010B18B  VirMem_Size     分配20个200字节的堆
TH, 0x01, class(TObject)
TH2, 0x70, class(TH)
TH3, 0x70, class(TH), 10E07D8
+04 sizeofseed
+08 - +10 from Seed in 5924CC
+40 big sizeofseed 
VirMethod 00        Init
VirMethod 02        Checksum
TH4, 0x70, class(TH)
-----------------------------------------------------------------------
比如会利用TH3虚拟函数02号来校验代码段。

Fun1在最后调用函数进入虚拟机中执行检测,其实很简单,就是调用IsDebuggerPresent()来确定调试器是否存在,如果存在,提示并退出MessageBox(), ExitProcess()。
———————————————————提示信息
Debugger detected  - please close it down and restart!(\r\n\r\n)Window
s NT users: Please note that having the..WinIce/SoftIce service installed means that you are(\r\n)running a debugger!
———————————————————提示信息

——————————————————————Fun1()中调用Wired()检测调试器的存在
debug032:0107019E E8 1D FC FF FF          call    Wired
debug032:010701A3 E8 94 ED FF FF          call    nullsub_25
debug032:010701A8 33 C0                   xor     eax, eax
debug032:010701AA 5A                      pop     edx
——————————————————————Wired()中原型
debug032:0106FF0B A1 F8 29 07 01          mov     eax, ds:dword_10729F8
debug032:0106FF10 E8 D3 EC FF FF          call    DetectDebugger


函数Fun2()完成对原程序的解压缩,函数Fun3()复原近调用(E8 X)和Jmp近跳转(E9 X)。


这是解压缩(解密)原程序需要的节信息
[起址,解压缩后大小,解压缩前大小]
-----------------------------------------------------------------------
.data:00590A13 00 10 00 00 00 80 0F 00+Section_Hdr_Target dd 1000h, 0F8000h, 5DC2Ah; 0
.data:00590A13 2A DC 05 00 00 90 0F 00+dd 0F9000h, 0C600h, 81D7h; 3
.data:00590A13 00 C6 00 00 D7 81 00 00+dd 10A000h, 2E00h, 2902h; 6
.data:00590A13 00 A0 10 00 00 2E 00 00+dd 1202DCh, 42524h, 1F88Ah; 9
.data:00590A13 02 29 00 00 DC 02 12 00+dd 163000h, 2CE00h, 1663Ah; 12
-----------------------------------------------------------------------

Fun4()检测程序是否被更改。
在这个版本中,Fun5()负责相当相当重要的工作,包括重建IAT和AIP(Advanced Import Protection),重建obfuscatee例程,…
在Fun1()中,在对象TAsEmulator的数据域中保存了GetProcAddress和LoadLibrary的首地址和前四个字节的XOR值,在Fun5()中检查器检查这几个值是否一样,将结果参与计算。

流程
HandleIAT()
GetIAT()
AIP()

流程
GetIAT()
GetpFirstThunk()
GetRandInt()      用随机数覆盖pFirstThunk
GetStrLen()       获取字符串DLL名的长度,如kernel32.dll的长度是12
GetProcAddr()     不是kernel32.dll中的那个函数


--------------------------------------------------------------------
debug032:0106F9BC                         Fun5 proc near
debug032:0106F9BC 68 DC 2A B0 33          push    33B02ADCh
debug032:0106F9C1 68 FC 54 00 00          push    54FCh      ; 特殊点2 106B8B0
debug032:0106F9C6 68 B4 63 03 00          push    363B4h     ; 特殊点2 363B4+54FC = 3B8B0
debug032:0106F9CB 68 98 03 00 00          push    398h       ; 特殊点1 
debug032:0106F9D0 68 20 F6 03 00          push    3F620h     ; 特殊点1 3F620+398 = 3F9B8
debug032:0106F9D5 68 00 50 05 00          push    55000h     ; 
debug032:0106F9DA FF 35 D4 34 07 01       push    ds:HInstance   ; ImageBase_protector.dll 
debug032:0106F9E0 E8 C7 C4 FC FF          call    sub_103BEAC
debug032:0106F9E5 31 04 24                xor     [esp], eax
debug032:0106F9E8 8B 05 D4 34 07 01       mov     eax, ds:HInstance
debug032:0106F9EE 01 04 24                add     [esp], eax
debug032:0106F9F1 C3                      retn               ; 跳转到HandleIAT()继续执行
debug032:0106F9F1                         Fun5 endp
-------------------------------------------------------------------- HandleIAT()中调用GetIAT()重建输入表
debug032:0106F752 A1 F4 2A 07 01          mov     eax, ds:ppObj_2_Fun1
debug032:0106F757 8B 00                   mov     eax, [eax]
debug032:0106F759 E8 02 5E FF FF          call    near ptr GetIAT()
——————————————————————————在获取到FirstThunk值后马上用随机数覆盖
debug032:010655A2 6A FF                   push    0FFFFFFFFh
debug032:010655A4 E8 87 17 FE FF          call    GetRandInt
debug032:010655A9 40                      inc     eax
debug032:010655AA 89 03                   mov     [ebx], eax
—————————————————————————检查器计算XOR值,将结果参与计算
debug032:010655C1 8B 46 48                mov     eax, [esi+48h]
debug032:010655C4 8B 00                   mov     eax, [eax]
debug032:010655C6 33 46 48                xor     eax, [esi+48h]
debug032:010655C9 2B 46 4C                sub     eax, [esi+4Ch]
debug032:010655CC E8 0B FF FF FF          call    sub_10654DC
debug032:010655D1 25 FF 00 00 00          and     eax, 0FFh       ; 正常情况下应该始终为0
debug032:010655D6 03 F8                   add     edi, eax 

流程
Fun7() -> sub_106EEE4 -> VirMem 0x02070000 -> OEP
——————————————————————————————————
debug032:0106FDA0 E8 13 C0 FF FF    call    sub_106BDB8
debug032:0106FDA5 E8 02 43 FF FF    call    sub_10640AC
debug032:0106FDAA E8 35 F1 FF FF    call    sub_106EEE4   ; 进入这个函数,离OEP不远了
debug032:0106FDAF 83 C4 24          add     esp, 24h
debug032:0106FDB2 5F                pop     edi
debug032:0106FDB3 5E                pop     esi
debug032:0106FDB4 5B                pop     ebx
debug032:0106FDB5 C3                retn
——————————————————————————————————
debug276:02070000                   loc_2070000:        ; 0x02070000入口
debug276:02070000 0F 82 06 00 00 00 jb      loc_207000C
debug276:02070006 81 EF 5F 2B D9 3F sub     edi, 3FD92B5Fh
debug276:0207000C
---------------------------------------------------------------最后一跳,跳向OEP
debug276:02070110 03 C3             add     eax, ebx       ; ebx是OEP的RVA
debug276:02070112 5C                pop     esp
debug276:02070113 FF E0             jmp     eax



[脱壳]

OEP的AVA为4F861C,RVA为0x000F861C。
因为OEP没有被偷代码,在到达OEP时直接转储文件,修改文件头。

Fun5()中会调用GetIAT()来重建IAT表,在这一过程中会访问存储protectee.exe输入表。前面提到,这张输入表是以TIATx的形式存储在TTableX中。每一个TDllX对应一个IID。结构如下。
TIATx = record
  Hdr = record
   Sign5E;           { 始终是5E }
   Magic: array[1..9] of Byte;
  End;
  Dlls : array[1…999] of TDllX;
End;

TDllX = record
  pFirstThunk : int;             ; FirstThunk的地址
  Magic: array[1..3] of byte
  DllId: word                ; 每一个DLL用一个标记来表示
  DllName : string              ; Dll Name
  Procs : array[0…9999] of TProcX    ; FirstThunk中的所有函数,遍历可知FirstThunk的函数数目
End;
TProcX = record
  Magic: Byte;
  ProcId: Word;
  ProcName: array[1..9999] of char;
End;

——————————————————————此处下断,edi指向TIATx
debug032:01065688                   loc_1065688:        
debug032:01065688 8B DF             mov     ebx, edi
debug032:0106568A 8B 03             mov     eax, [ebx]
debug032:0106568C 85 C0             test    eax, eax
debug032:0106568E 0F 85 0A FFFFFF    jnz     oc_106559E
—————————————————————— 部分 TDllX 
debug038:011244A1 CC A1 10 00       pFirstThunk dd 10A1CCh ; pFirstThunk of kernel32.dll
debug038:011244A5 7F 03 00 00       dd 37Fh
debug038:011244A9 F2                db 0F2h ; ?
debug038:011244AA F8                db 0F8h ; ?
debug038:011244AB 6B 65 72 6E 65 6C+apKernel32_dll_0 db 'kernel32.dll',0 ; DLL的名字
debug038:011244B8 81                db  81h ; ?        ; sign  第一个函数    
debug038:011244B9 9D                db  9Dh ; ?
debug038:011244BA FB                db 0FBh ; ?
debug038:011244BB 13 00 AA 94 44 9A+db 13h, 0, 0AAh, 94h, 44h     ; 第一个函数被加密的名字
debug038:011244BB 19 89 8C 41 33 B8+db 9Ah, 19h, 89h, 8Ch, 41h
debug038:011244BB B9 99 42 BC 0D 9F+db 33h, 0B8h, 0B9h, 99h, 42h
debug038:011244BB B7 40 5D          db 0BCh, 0Dh, 9Fh, 0B7h, 40h
debug038:011244BB                   db 5Dh              
debug038:011244D0 9F                db  9Fh ; ?            ; sign 第二个函数
debug038:011244D1 95                db  95h ; ?
debug038:011244D2 17                db  17h
debug038:011244D3 16 00 07 83 FB C2+db 16h, 0, 7, 83h, 0FBh; 0   ; 第二个函数被加密的名字
debug038:011244D3 45 33 E9 A6 64 FB+db 0C2h, 45h, 33h, 0E9h, 0A6h; 5


我们读取这张表可以直接获取到的信息包括:Dll Name, FirstThunk, FirstThunk中函数的数目。通过这三种信息我们就可以重建Dll名字表,所有的IID。
——————————————————————运行脚本重建的Dll 名字表[部分]
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000   6B 65 72 6E 65 6C 33 32  2E 64 6C 6C 00 00 00 00   kernel32.dll....
00000010   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000020   75 73 65 72 33 32 2E 64  6C 6C 00 00 00 00 00 00   user32.dll......
00000030   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000040   61 64 76 61 70 69 33 32  2E 64 6C 6C 00 00 00 00   advapi32.dll....
00000050   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000060   6F 6C 65 61 75 74 33 32  2E 64 6C 6C 00 00 00 00   oleaut32.dll....
——————————————————————

在运行到OEP时,所有的FirstThunk都被protector.dll填好了,513个输入函数有114个函数被AIP,也就是程序中调用这111个函数的地方全部指向堆中的protector.dll例程。不管怎样,有400个函数指向正确的输入函数地址,写脚本遍历所有的FirstThunk、重建INT。

——————————————————————运行脚本重建的INT,全零处是被AIP的函数
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000010   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000020   00 00 44 65 6C 65 74 65  43 72 69 74 69 63 61 6C   ..DeleteCritical
00000030   53 65 63 74 69 6F 6E 00  00 00 00 00 00 00 00 00   Section.........
00000040   00 00 4C 65 61 76 65 43  72 69 74 69 63 61 6C 53   ..LeaveCriticalS
00000050   65 63 74 69 6F 6E 00 00  00 00 00 00 00 00 00 00   ection..........
00000060   00 00 45 6E 74 65 72 43  72 69 74 69 63 61 6C 53   ..EnterCriticalS
00000070   65 63 74 69 6F 6E 00 00  00 00 00 00 00 00 00 00   ection..........

现在就开始处理程序中所有AIP了。 

[AIP]

AIP处形如
E8 XX XX XX XX        call    near ptr unk_2090000   
调用的对象由API函数转成了protector.dll在堆中的例程

完成这一任务的工作主要在Fun5()中完成,相关的函数为sub_106E424。
流程
Fun5()
 Fun5_1Fun
BuildIAT()
AIP()
  sub_106E424

存储AIP信息的叫做AIP表,其中每一个表项代表一处AIP,表项的数据结构是TAIPx,大小共41个字节。每一处AIP 的位置pAIP = Magic1 + Magic2 + 0x401000,Magic1, Magic2在每一个程序中都不同,Int2是相对固定值、在此程序中为0x1E9069F5,Int2随着AIP位置的不同而不同。
+00     byte
+09     Magic1
+12     bDoEvil         0 or 1, is a problem!
+16     byte
+1C     

在函数sub_106E424中适当位置debug032:0106E474处编写脚本可知AIP表的位置和表项的数目0x72,遍历AIP表可知所有的AIP地址。
--------------------------------------------------------------------  
debug032:0106E46F 8B F2                   mov     esi, edx       ; esi指向AIP表
debug032:0106E471 8B 43 18                mov     eax, [ebx+18h]  ; eax为AIPNum
debug032:0106E474 89 04 24                mov     [esp+34h+AIPNum], eax  ; 计数器赋值
debug032:0106E477 8B 83 E0 00 00 00       mov     eax, [ebx+0E0h]
debug032:0106E47D 89 44 24 14             mov     [esp+34h+Magic2], eax   ; Magic2
debug032:0106E481 8D 7B 40                lea     edi, [ebx+40h]
debug032:0106E484 83 3C 24 00             cmp     [esp+34h+AIPNum], 0
debug032:0106E488 0F 86 AB 01 00 00       jbe     loc_106E639
---------------------------------------------------------------------
debug032:0106E602 8B 43 2C                mov     eax, [ebx+2Ch]     ; 取0x02090000
debug032:0106E605 2B C5                   sub     eax, ebp
debug032:0106E607 83 E8 05                sub     eax, 5
debug032:0106E60A 45                      inc     ebp               
debug032:0106E60B 89 45 00                mov     [ebp+0], eax       ; 存入0x02090000
-----------------------------------------------------------------------
 Get()函数
debug032:0106E4AD 8A 47 09                mov     al, [edi+9]
debug032:0106E4B0 8D 04 40                lea     eax, [eax+eax*2]
debug032:0106E4B3 8B 54 83 68             mov     edx, [ebx+eax*4+68h]
debug032:0106E4B7 8B C6                   mov     eax, esi
debug032:0106E4B9 FF D2                   call    edx
----------------------------------------------------------------


混淆前的AIP例程,实际工作是在sub_106DF04中完成,sub_2090000只是一个跳板。

———————————————————sub_2090000原型
sub_2090000  proc near 
  push edi
  pushf
  sub esp, 20h
  mov edi, esp
  mov [edi+1Ch], edi
  mov [edi+18h], esi
  mov [edi+14h], ebp
  mov [edi+0Ch], ebx
  mov [edi+08h], edx
mov [edi+04h], ecx
mov [edi+00h], eax
  mov eax, esp
  add eax, 2C   
  push eax       ; Argument Frame
  push edi       ; GReg Frame
  push [edi+20h] ; Eflags
  mov esi, [edi+28h] ; retaddr
  sub esi, 05h
  add esi, ProcessId
  push esi           ; esi = caller addr + ProcessId
  push fs:[0]
  push pTObjX_5_Fun1
  call sub_106DF04
sub_2090000 endp

Sub_2090000的原理是通过查询每一处AIP的TAIPx表项,获取DllId,ProcId,以这两个值作为参数调用GetProcAddr()在TIATx中查询,找到对应的函数。
我们的思路是在程序执行到sub_2090000时中止程序运行。往堆栈中填入AIP地址值,在sub_2090000处理过程中,在适当位置下断点,获取解密后的API函数名,API函数在INT中的位置。

---------------------------------------------  GetProcAddr()的参数cx是DllId,dx是ProcId,eax指向对象,对象中第二数据成员就是TIATx的指针
debug032:0106D41D 66 8B 4D E0             mov     cx, word ptr [ebp+DllId]
debug032:0106D421 8B D7                   mov     edx, edi
debug032:0106D423 8B 45 F4                mov     eax, [ebp+obj]
debug032:0106D426 E8 E5 05 00 00          call    near ptr GetProcAddr()
debug032:0106D42B 84 C0                   test    al, al
-----------------------------------------------  在GetProcAddr()中、0x0106DB95处edi指向解密的API函数名
debug032:0106DB88 8B D3                   mov     edx, ebx
debug032:0106DB8A 8B 45 E8                mov     eax, [ebp+var_18]
debug032:0106DB8D E8 AA 7E FD FF          call    ToString        ; return
debug032:0106DB8D                                                 ; edi - pProcName
debug032:0106DB92 8B 7D E8                mov     edi, [ebp+var_18]
debug032:0106DB95 8B 45 F4                mov     eax, [ebp+var_C]
-----------------------------------------------------------------


在debug032:0106E474处中断获取所有AIP位置的脚本
----------------------------------------------------------------------<code>


#include "idc.idc"
#define  FstVirAddr  0x00401000
#define  magic1offset   0x09

static main(void)
{
  auto haip, i,j, obj, AIPNum, Magic2, pAIPTable, itemsize;

  obj = GetRegValue("ebx");
  AIPNum = Dword(obj+0x18);
  Magic2 = Dword(obj+0xE0);
  itemsize = Dword(obj+0xE4);
  haip = fopen("d:\\aip.txt","w");
  pAIPTable = GetRegValue("esi");
  for(i=0;i<AIPNum;i++)
  {
    writelong(haip,Dword(pAIPTable+i*itemsize+magic1offset)+Magic2+FstVirAddr,0);
  }

  // ending sign
  writelong(haip,BADADDR,0);
  fclose("haip");
  return;
}
----------------------------------------------------------------------</code>

获取到的AIP地址表局部
————————————————————————————
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000   28 12 40 00 30 12 40 00  38 12 40 00 40 12 40 00   (.@.0.@.8.@.@.@.
00000010   60 12 40 00 78 12 40 00  90 12 40 00 A0 12 40 00   `.@.x.@.?@.?@.
00000020   A8 12 40 00 B8 12 40 00  C0 12 40 00 D8 12 40 00   ?@.?@.?@.?@.
00000030   E0 12 40 00 F0 12 40 00  F8 12 40 00 00 13 40 00   ?@.?@.?@...@.
00000040   20 13 40 00 30 13 40 00  38 13 40 00 40 13 40 00    .@.0.@.8.@.@.@.                                              
————————————————————————————

程序中大概有几十处左右无法用这种方法获取函数名字:向堆栈中填入调用地址+0x05,调用sub_2090000。它们或是被偷代码,或是ESSP。这没有什么快捷的方法,花点功夫,只是一个体力活。
在处理ESSP时,一个比较有意思的例子是comctl32.dll 中的InitCommonControls(),因为它代码实现就一句指令mov eax, eax,所以跳到堆中又立即返回调用处继续执行。我差一点将其nop掉。


原程序会注册不同窗口组件的回调函数,在这些函数中进行检测AIP处的操作码是否为E8。这些地方在原程序编写时就已经嵌入,没有现成的表可以查询它们的位置。但有机器码特征可以查询。不需要程序出错时,一处一处修改解决。

[位置]
(1)004DF054             004DEF68, FOnCreate()
(2)004E5710 (E8: 4e5727) 004E565C, OnActivate()
(3)004E220C             004E2198, FOnClick()    Help -> About
(4)004E2C4F 
(5)004E27F5
 (6)004E250F( E8: 4e2528)
(7)E8: 4df523
(8)4E0aa3(4e0ac5)
(9)4e1546(E8: 4e1564)
(10)4e5ec4(E8: 4e5ee6)

检查代码段的原型
Sub_checkE8 proc
push edx
push edi
mov edx, offset Fun
push [edx]
pop edx
movzx edi, dl            ; 正常情况下应该是E8

lea edi, [edi-0D69B23Eh]
mov edx,0D69B156h
add edi, edx
test edi, edi
jz GoodBoy
BadBoy:
…     ;  Do some crazy thing
GoodBoy:
Pop edi
Pop edx
Ret 
Sub_checkE8 endp

获取程序中E8检测处
---------------------------------------------------------------------------------<code>
#include "idc.idc"

static main(void)
{

  auto i,j,he8;

  he8 = fopen("d:\\e8.txt","w");  
  if ( !he8 )
  {
    Message("file not open");
    return;
  }
 
  for(i=0x401000;i<0x401000+0x1ee000;i++)
  {
    if( (Dword(i) == 0x000000E9) && (Byte(i+4)==0x0) )
    {
      if ( ((Byte(i+5)&0xf0) == 0x50)&&((Byte(i+6)&0xf0) ==  
0x50) )
      {
        if ( Byte(i+7) == 0xE9 )
          fprintf(he8,"E8: %x\r\n",i);
      }
    }
  }

  fclose(he8);
  return;
}
---------------------------------------------------------------------------------</code>

[obfuscatee]

Asprotect混淆了一些Delphi库函数。
程序中被混淆的库函数,入口处有两种类型的代码指令,如下
68 00 00 23 02          push    2230000h
C3                      retn

E9 F7 69 E3 01          jmp     near ptr 2240000h

重建这些指令段的任务在Fun5()中完成。
----------------------------------------       填充第一种类型
debug032:0106EA10 C6 03 68                mov     byte ptr [ebx], 68h
debug032:0106EA13 8D 43 01                lea     eax, [ebx+1]
debug032:0106EA16 8B 55 F8                mov     edx, [ebp+var_8]
debug032:0106EA19 89 10                   mov     [eax], edx
debug032:0106EA1B 83 C3 05                add     ebx, 5
debug032:0106EA1E C6 03 C3                mov     byte ptr [ebx], 0C3h
—————————————        填充第二种类型
debug032:0106EA23 8B 45 F8                mov     eax, [ebp+var_8]
debug032:0106EA26 2B C3                   sub     eax, ebx
debug032:0106EA28 83 E8 05                sub     eax, 5
debug032:0106EA2B 8D 53 01                lea     edx, [ebx+1]
debug032:0106EA2E 89 02                   mov     [edx], eax
debug032:0106EA30 C6 03 E9                mov     byte ptr [ebx], 0E9h
----------------------------------------       原程序中一共有25处
debug032:01081BB8 00 00 23 02       MemBlock_LenA4 dd 2230000h 
debug032:01081BBC 00 00 24 02       dd offset unk_2240000
debug032:01081BC0 00 00 25 02       dd offset unk_2250000
debug032:01081BC4 00 00 26 02       dd offset unk_2260000
debug032:01081BC8 00 00 27 02       dd offset unk_2270000
debug032:01081BCC 00 00 28 02       dd offset unk_2280000
debug032:01081BD0 00 00 29 02       dd offset unk_2290000
debug032:01081BD4 00 00 2A 02       dd offset unk_22A0000
debug032:01081BD8 00 00 2B 02       dd offset unk_22B0000
debug032:01081BDC 00 00 2C 02       dd offset unk_22C0000
debug032:01081BE0 00 00 2D 02       dd offset unk_22D0000
debug032:01081BE4 00 00 2E 02       dd offset unk_22E0000
debug032:01081BE8 00 00 2F 02       dd offset unk_22F0000
debug032:01081BEC 00 00 30 02       dd offset unk_2300000
debug032:01081BF0 00 00 31 02       dd offset unk_2310000

对付这种方法,可以还原或补节的方法来完成。很简单很无聊,不用多讲。
获取原程序中被混淆处的地址:在Fun5()中合适位置下断点,或在原程序中遍历。
———————————————————————————— 在原程序中遍历实现
#include "idc.idc"
#define   MinVirMem     0x2230000
#define   MaxVirMem     0x23b0000

static main(void)
{

  auto i,j;

  for(i=0x401000;i<=0x401000+0xF8000;i++)
  {
    if ( Byte(i) == 0xE9 )
    {
      if ( ((Dword(i+1)+i+5) <= MaxVirMem) && ((Dword(i+1)+i+5) >= MinVirMem) ){
        Message("er:%x,ee:%x\r\n", i, Dword(i+1)+i+5);
        i = i+4;
      }
    }
    else if ( (Byte(i) == 0x68) && (Byte(i+5) == 0xC3) )
    {
       if ( (Dword(i+1) >= MinVirMem) && (Dword(i+1)<=MaxVirMem)  ){
         Message("er:%x,ee:%x\r\n",i, Dword(i+1));
         i = i+5;
       }
    }
  }

  return;
}
————————————————————————————



[emulator]

还原虚拟代码,人工是不可能完成的任务,只有依靠脚本的力量。请参见附件。
题外话:如果虚拟代码也按照补区段的方法去完成,还有意义吗?那不正达到壳隐藏信息的目的了吗?

protector.dll中有两种虚拟机,一种模拟自己代码段,另一种模拟原程序的代码段。前一种中规中矩,后一种很黄很暴力:操作码全部换成伪操作码,在VM中又嵌入第二层VM。这第二层VM模拟了mov, sub, add, cmp一共四种类型的指令,但没有实现带有SIB字节的指令和reg8为目标操作数的指令。这里的指令类型不同于具体的操作码,比如Inc操作码,Dec操作码都可以分别模拟成add,sub类型指令。

VM的工作主要由两个函数来完成,Executor()执行已经被模拟化的代码段(函数),Emulator()模拟化代码段。

如果你在程序中发现这样的代码段,这样的代码段就是被虚拟机模拟执行。
68 00 00 00 00          push    0
68 XX XX XX XX          push    offset Emulatee
68 XX XX XX XX          push    offset EmulRec
E8 XX XX XX XX          call    Executor

虚拟器一共有三个重要的结构:AsEmulIns, AsEmulInsHdr, AsEmulContext。
不同的Aspr版本,三个结构会有很大的变化:伪opcode和真实opcode的换算关系、同一意义数据域有不同的偏移量(同一偏移量处的数域有不同的意义)、...

被模拟的代码段由一个AsEmulInsHdr和若干AsEmulInsItem来表示,每一段代码行用一个AsEmulInsItem来表示。
当前CPU的执行状态用AsEmulContext来表示,虚拟机运行的结果反映在AsEmulContext各个域的变化上。

模拟器按照操作码,分情况来处理。如
push ebp(55)的处理函数流: Op5X() -> Push()
pop ebp(5C)的处理函数流:Op5X() -> Pop()

我所见到的这个版本的VM没有实现浮点运算指令和0F为前缀码的大部分指令,不知道原因是demo版本的限制,还是模拟器仍在进化中。

------------------------------------------------------取操作码Opcode,跳转
debug032:0106B269 8A 83 B4 00 00 00       mov     al, [ebx+0B4h]
debug032:0106B26F 02 46 50                add     al, [esi+50h]
debug032:0106B272 25 FF 00 00 00          and     eax, 0FFh
debug032:0106B277 C1 E8 04                shr     eax, 4
debug032:0106B27A 83 E0 0F                and     eax, 0Fh
debug032:0106B27D 83 F8 0F                cmp     eax, 0Fh    ; switch 16 cases
debug032:0106B280 0F 87 27 01 00 00       ja      gameover   
debug032:0106B286 FF 24 85 8D B2 06 01    jmp     ds:off_106B28D[eax*4] ; switch jump
---------------------------------------------------------------------取指令位
debug032:01069219 8A 9D B4 00 00 00 mov     bl, [ebp+0B4h]
debug032:0106921F 02 5E 50          add     bl, [esi+50h]
debug032:01069222 80 E3 0F          and     bl, 0Fh
-----------------------------------------------------------判断是否是7字头的操作码(0)
80 C1 90                add     cl, 90h
80 E9 10                sub     cl, 10h
72 0E                   jb      Opcode_7X
-----------------------------------------------------------判断是否是7字头的操作码(1)
80 E9 70                sub     cl, 70h
80 E9 10                sub     cl, 10h
72 0E                   jb      Opcode_7X
------------------------------------------判断操作码是否为C2或C3
add     al, 3Eh
sub     al, 2
jb      short C2C3
------------------------------------------判断操作码是否为06或07
debug032:01054540 04 FA                   add     al, 0FAh
debug032:01054542 2C 02                   sub     al, 2
debug032:01054544 72 03                   jb      short lo
—————————————————————判断操作码是否是E0, E1, E2, E3
04 20                   add     al, 20h
2C 04                   sub     al, 4
0F 92 C0                setb    al


在原程序中的Emulatee一共要进入二次,第一次进是为第二次作准备工作。
seg024:004E200E 68 14 26 68 6A    push    6A682614h        ; ???
seg024:004E2013 68 BD 46 11 6A    push    6A1146BDh         ; RVA = 6A1F66B3 XOR 6A1146BD
seg024:004E2018 68 88 8B 12 01    push    offset off_1128B88  ; 指针,指向ObjX_1_Fun1
seg024:004E201D E8 F2 48 B7 00    call    Step1

函数Step1()的工作无它,为第二步真正执行作好准备工作。
ObjX_1_Fun1中的数据成员对我们有帮助
  + 18          pAspEmulHdr的数组
  + 1C          程序中一共有几处Emulatee

------------------------------------------------------------  程序中一共有五处Emulatee
debug266:0112A890 B4 8B 12 01       dd offset dword_1128BB4 ; E1382
debug266:0112A894 4C 92 12 01       dd 112924Ch       ; E200E
debug266:0112A898 58 94 12 01       dd 1129458h        ; E50BB
debug266:0112A89C FC 9B 12 01       dd 1129BFCh      ; E53EC
debug266:0112A8A0 70 A7 12 01       dd unk_112A770   ; E6878
------------------------------------------------------------
(1)004E200E          FOnClick         “Project” -> “New Project” Menu Item
(2)004E1382          FOnClick         “File To Protect”
(3) 004E50BB                          “Registration” -> “Generate” Button
(4)004E53EC                           “Registration” -> “Check” Button
(5)004E6878                           “Protect Original OEP” Checkbox

第二次进
seg024:004E2008 53                push    ebx
seg024:004E2009 56                push    esi
seg024:004E200A 8B F2             mov     esi, edx
seg024:004E200C 8B D8             mov     ebx, eax
seg024:004E200E 68 00 00 00 00    push    0                  ; pVirMem
seg024:004E2013 68 BD 46 51 6A    push    6A5146BDh       ; RVA = 6A5146BD XOR 6A1F66B3h
seg024:004E2018 68 18 AC 12 01    push    offset AspEmulHdr  ; 
seg024:004E201D E8 1E 49 B7 00    call    Step2



/*

  [Type List]
  _Aspr_Emul_2_4_11_20_Type_1
  used for protector.dll self
  
  _Aspr_Emul_2_4_11_20_Type_2
  used for protectee

*/






#ifdef _Aspr_Emul_2_4_11_20_Type_1
// used for protector.dll self


struct  _AsprEmulInsHdr{
  DWORD  InsHdr_cpTAsprEmul;     //  + 0x000
  DWORD  InsHdr_pInsBody;        //  + 0x004
  DWORD  InsHdr_InsItemNum;      //  + 0x008
  BYTE   InsHdr_FuncInds1[0x0A]; //  + 0x00C
  BYTE   InsHdr_FuncInds2[0x0A]; //  + 0x016
  DWORD  InsHdr_GetFuncs[0x0A];  //  + 0x020
  DWORD  InsHdr_InsItemSize;     //  + 0x048
  DWORD  InsHdr_ImageBase;       //  + 0x04C
  DWORD  InsHdr_RandInt;         //  + 0x050
  DWORD  InsHdr_ProcId ;         //  + 0x054
  DWORD  InsHdr_InsBodySize;     //  + 0x058
} AsprEmulInsHdr, *PAsprEmulInsHdr;


typedef  struct  _AsprEmulInsItem{
    BYTE   SIB;           // + 000
                          // + 001
    BYTE   ModRM          // + 006
    DWORD                 // + 007
    BYTE   FakeOpcode0;   // + 00B
    BYTE                  // + 00C
    DWORD  FakeEip;       // + 00D
                          // + 011
    BYTE   FakeOpcode1;   // + 014
                          // + 015
    DWORD  Disp;          // + 01A
                          // + 01E
    DWORD FakeImm0;       // + 023
    BYTE                  // + 026
    BYTE                  // + 027
    BYTE  Misc;           // + 028
    BYTE                  // + 02C
    BYTE  bAdjust;        // + 02D
                          // + 02E
} AsprEmulInsItem, *PAsprEmulInsItem; 
                          // + 033


typedef struct _AsprEmulContext{
                             // + 000

 
    DWORD  Eax;              // + 020       
    DWORD  Ecx;              // + 024
    DWORD  Edx;              // + 028
    DWORD  Ebx;              // + 02C
    DWORD  Esp;              // + 030
    DWORD  Ebp;              // + 034
    DWORD  Esi;              // + 038
    DWORD  Edi;              // + 03C
    DWORD                    // + 040
    DWORD  Eip;              // + 044
    DWORD  Eflags;           // + 048
                             // + 04C

    DWORD  CsBase;           // + 060
    DWORD  SsBase;           // + 064
    DWORD  DsBase;           // + 068
    DWORD  EsBase;           // + 06C
    DWORD  FsBase;           // + 070
    DWORD  GsBase;           // + 074
    WORD   Cs;               // + 078
    WORD   Ss;               // + 07A
    WORD   Ds;               // + 07C
    WORD   Es;               // + 07E
    WORD   Fs;               // + 080
    WORD   Gs;               // + 082
                             // + 084

    DWORD  LowOpcodeNum;     // + 090
    DWORD  HighOpcodeNum;    // + 094
    DWORD  curSegBase;       // + 098
    BYTE   bAdjust;          // + 09C
    DWORD  pOrigERR;         // + 09D
    BYTE   b2BOpcode;        // + 0A1
    DWORD  SrcOperandInfo;   // + 0A2
    BYTE   SrcOperandMod;    // + 0A6
    DWORD  DestOperandInfo;  // + 0A7
    BYTE   DestOperandMod;   // + 0AB
    DWORD  OperandSize;      // + 0AC
    BYTE   PrefixFlags;      // + 0B0
    BYTE                     // + 0B1
    BYTE   FakeOpcode;       // + 0B4
    DWORD  curInsItem;       // + 0B5
    DWORD  nextInsItem;      // + 0B9
} AsprEmulContext, * PAsprEmulContext; 
                           // + 0BD


#endif  // #ifdef _Aspr_Emul_2_4_11_20_Type_1


#ifdef _Aspr_Emul_2_4_11_20_Type_2
// used for protectee


struct  _AsprEmulInsHdr{
  DWORD  InsHdr_cpTAsprEmul;            //  + 0x000
  DWORD  InsHdr_RandInt;                //  + 0x004
  DWORD  InsHdr_pcpTManualInstall;      //  + 0x008
  DWORD  InsHdr_InsItemNum;             //  + 0x00C
  DWORD  InsHdr_ImageBase;              //  + 0x010
  DWORD  InsHdr_ProcId;                 //  + 0x014
  DWORD  InsHdr_pInsBody;               //  + 0x018
  DWORD                                 //  + 0x01C
} AsprEmulInsHdr, *PAsprEmulInsHdr;
                                        //  + 0x020



typedef  struct  _AsprEmulInsItem{
  Byte  FakeOpcode0H4;     // + 000
  Byte  FakeOpcode0L4;     // + 001
  Byte  bInsHdrASM;        // + 002
  Byte  SIB;               // + 003
  DWORD FakeImm0;          // + 004          
  DWORD FakeEip;           // + 008
  BYTE                     // + 00C
  BYTE  ModRM;             // + 00D
  DWORD Disp;              // + 00E                          
  Byte  FakeOpcode1H4;     // + 012
  Byte  FakeOpcode1L4;     // + 013
  Byte  bAdjust;           // + 014
  Byte  Opcode1Flags;      // + 015
  Byte  Opcode0Flags;      // + 016
  BYTE  Misc;              // + 017
} AsprEmulInsItem, *PAsprEmulInsItem; 
                           // + 018


typedef  struct _AsprEmulInsHdrASM{
                              // + 000
  
  BYTE  bInsHdrASM;           // + 002

  DWORD FakeEip;              // + 008
  BYTE  InsItemNum;           // + 00C
  BYTE  CodeSize;             // + 00D
  DWORD InsItemIndex;         // + 00E
                              // 

}  AsprEmulInsHdrASM, *PAsprEmulInsHdrASM;
                              // + 018


typedef struct  _AsprEmulInsItemASM{
  BYTE   WorkType;            // + 000
  BYTE   AddrMod;             // + 001
  BYTE   Flags;               // + 002
  BYTE   DestRegCode;         // + 003
  BYTE   SrcRegCode;          // + 004
  DWORD  DestDisp;            // + 005
  DWORD  SrcDispImm;          // + 009
} AsprEmulInsItemASM, *PAsprEmulInsItemASM;
                              // + 00D

typedef struct _AsprEmulContext{
  DWORD  Eflags;               // + 000
                               // + 004
  BYTE                         // + 00D
  DWORD  curSegBase;           // + 018
  Byte   bAdjust;              // + 01C                           
  DWORD  pOrigERR;             // + 01D
  DWORD  Eip;                  // + 021
  DWORD  LowOpcodeNum;         // + 025
  DWORD  HighOpcodeNum;        // + 029
  DWORD  nextInsItem;          // + 02D  
  DWORD  pOpcodeFlags;         // + 031
  BYTE   b2BOpcode;            // + 035
                               // + 036
                               
  DWORD  SrcOperandInfo;       // + 03C
  Byte   SrcOperandMod;        // + 040
  DWORD  pFakeOpcodeH4;        // + 041
  BYTE   OpcodeL4;             // + 045
  
  DWORD  DestOperandInfo;      // + 06C
  Byte   DestOperandMod;       // + 070 
  WORD   Cs;                   // + 071
  WORD   Ss;                   // + 073
  WORD   Ds;                   // + 075
  WORD   Es;                   // + 077
  WORD   Fs;                   // + 079
  WORD   Gs;                   // + 07B
  DWORD  CsBase;               // + 07D
  DWORD  SsBase;               // + 081
  DWORD  DsBase;               // + 085
  DWORD  EsBase;               // + 089
  DWORD  FsBase;               // + 08D
  DWORD  GsBase;               // + 091
  DWORD  curInsItem;           // + 095
  DWORD  OperandSize;          // + 099
  DWORD                        // + 09D                         
  DWORD  Eax;                  // + 0A1       
  DWORD  Ecx;                  // + 0A5
  DWORD  Edx;                  // + 0A9
  DWORD  Ebx;                  // + 0AD
  DWORD  Esp;                  // + 0B1
  DWORD  Ebp;                  // + 0B5
  DWORD  Esi;                  // + 0B9
  DWORD  Edi;                  // + 0BD
  DWORD  PrefixFlags;          // + 0C1                        
} AsprEmulContext, * PAsprEmulContext; 
                               // + 0C5

#endif  // #ifdef _Aspr_Emul_2_4_11_20_Type_2



[VirtualProtect()]
InitUnits在执行各单元的初始化例程时,有一个调皮的初始化例程调用VirtualProtect()将输入表中的表项(0x50A064)的前四个字节的属性设为MEM_READWRITE,然后在原处Hook了自己的一个函数。在此说明一下。


到此为此,脱壳结束,程序可以正常运行,无任何差错。关于程序中的限制、demo版能否解成release版、如何解、Aspr所使用的算法,Delphi资源,…,那是另一篇文章的主题了。
附件为两个还原两种类型VM的IDC脚本。附图为“tnttools到此一游”
如果发现脚本有什么bug请站内短信联系,在此先谢过。


[致谢] 
Alexey Solodovnikov ( for making such a tough toy )
Blackeyes (Asprotect 中的 X86 虚拟机代码分析)
shoooo(Asprotect 中的 X86 的VM分析)
Pediy.com( a good playground )
My family ( though you all will never have interest in taking a look of this shit. )


没有你们的贡献,我这篇文章永远不可能完成。


TnTTools, The Art of Reverse Engineering
Enjoy it.

上传的附件 unaspr.rar