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.
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
上传的附件: