[分析]
Aspack是Alexey Solodovnikov设计的一款压缩软件。你可以到http://www.aspack.com下载试用版。之所以选择版本2.12,纯粹是因为我手头的目标软件procport v3.0 final被PeiD识别为“ASPack 2.12 -> Alexey Solodovnikov”,procport可至http://www.cnasm.com下载。我不想去修改Peid的数据库的错误(以后有可能),或去研究aspack的版本2.12, 2.10, 1.20, 1.21…之间的细微差别在哪里,这两个对我的生活都没有意义,我只想在procport程序结束时,不要弹出网页,打扰我清净的世界,我就心满意足了
Procport V3.0 Final由二个文件组成,PeID检测的结果相同。首先从主程序文件入手。文件头被Aspack加入两个节,一个代码节,名为”.aspack”,大小为0x2000,一个数据节,名为”.adata”。其它没有什么特别之处。
开始分析程序。一开始二处花指令,使得IDA的分析结果与实际执行时指令序列不相同,按着实际执行指令序列走。到此处,获得程序基址,一般为0x00400000。
.aspack:004AF014 pop ebp
.aspack:004AF015 mov ebx, 0FFFFFFEDh
.aspack:004AF01A add ebx, ebp
.aspack:004AF01C sub ebx, 0AF000h
.aspack:004AF022 cmp dword ptr [ebp+422h], 0
.aspack:004AF029 mov [ebp+422h], ebx ; 程序基址
利用GetModuleHandle()获取kernel32.dll的句柄,利用GetProcAddress()获取VirtualAlloc()和VirtualFree()两个函数的地址指针,然后进入解压缩的大循环。在解压缩循环中,调用VirtualAllo()分配指定的大小的虚拟内存。调用sub_4AF66C解压缩程序段。
.aspack:004AF0EE push dword ptr [ebp+156h] ; Param4 – 虚拟内存句柄2
.aspack:004AF0F4 push dword ptr [esi+4] ; Param3 – 虚拟内存大小
.aspack:004AF0F7 push eax ; Param2 - 虚拟内存句柄1
.aspack:004AF0F8 push ebx ; Param1 – 起址
.aspack:004AF0F9 call sub_4AF66C
下面代码段干扰分析,实际还是继续执行,因为C3是retn的机器码,call edi后立刻返回。
.aspack:004AF113 FF 37 push dword ptr [edi]
.aspack:004AF115 C6 07 C3 mov byte ptr [edi], 0C3h
.aspack:004AF118 FF D7 call edi
.aspack:004AF11A 8F 07 pop dword ptr [edi]
修补Call调用,寄存器入栈保存可看作这一功能代码段的开始,寄存器出栈恢复可看作结束,初始化ESI为起址,ECX为长度。下面的代码段是将虚拟内存中解压缩成功的程序移回程序内存映像中。
.aspack:004AF16D 8B C8 mov ecx, eax
.aspack:004AF16F 8B 3E mov edi, [esi]
.aspack:004AF171 03 BD 22 04 00 00 add edi, [ebp+422h]
.aspack:004AF177 8B B5 52 01 00 00 mov esi, [ebp+152h]
.aspack:004AF17D C1 F9 02 sar ecx, 2
.aspack:004AF180 F3 A5 rep movsd
.aspack:004AF182 8B C8 mov ecx, eax
.aspack:004AF184 83 E1 03 and ecx, 3
.aspack:004AF187 F3 A4 rep movsb
解压缩大循环结束后就是经典的装载输入函数的代码段,分析后可知,原始程序中的每一个代表DLL库的IMAGE_IMPORT_DESCRIPTOR、每一个代表DLL函数的IMAGE_IMPORT_BY_NAME都没有被破坏,可以直接使用。而且原程序的输入数据目录的指针也被硬编码进代码中。
.aspack:004AF278 BE 00 60 09 00 mov esi, 96000h ; 原程序的输入数据目录的指针
.aspack:004AF27D 8B 95 22 04 00 00 mov edx, [ebp+422h]
.aspack:004AF283 03 F2 add esi, edx
.aspack:004AF285 ImportFunc:
.aspack:004AF285 mov eax, [esi+0Ch] ; DLL文件的名字指针
.aspack:004AF288 test eax, eax ; 名字指针是否有效
.aspack:004AF28A jz AllDone
.aspack:004AF290 add eax, edx
.aspack:004AF292 mov ebx, eax
.aspack:004AF294 push eax
.aspack:004AF295 call dword ptr [ebp+0F4Dh] ; 首先尝试用GetModuleHandleA获取DLL句柄
.aspack:004AF29B test eax, eax
.aspack:004AF29D jnz short loc_4AF2A6 ; 然后尝试用LoadLibraryA获取DLL句柄
.aspack:004AF29F push ebx
.aspack:004AF2A0 call dword ptr [ebp+0F51h]
.aspack:004AF2A6 mov [ebp+545h], eax
.aspack:004AF2AC mov dword ptr [ebp+549h], 0
; 进入获取函数地址的循环
.aspack:004AF2B6 mov edx, [ebp+422h]
.aspack:004AF2BC mov eax, [esi]
.aspack:004AF2BE test eax, eax
.aspack:004AF2C0 jnz short loc_4AF2C5
.aspack:004AF2C2 mov eax, [esi+10h]
.aspack:004AF2C5 add eax, edx
.aspack:004AF2C7 add eax, [ebp+549h]
.aspack:004AF2CD mov ebx, [eax]
.aspack:004AF2CF mov edi, [esi+10h]
.aspack:004AF2D2 add edi, edx
.aspack:004AF2D4 add edi, [ebp+549h]
.aspack:004AF2DA test ebx, ebx
.aspack:004AF2DC jz loc_4AF384
.aspack:004AF2E2 test ebx, 80000000h
.aspack:004AF2E8 jnz short loc_4AF2EE ; 如果以指定Ordinal的方式调用
.aspack:004AF2EA add ebx, edx ; 如果以指定函数名的方式调用
.aspack:004AF2EC inc ebx
.aspack:004AF2ED inc ebx ; ebx是指向函数名字的指针
.aspack:004AF2EE push ebx
.aspack:004AF2EF and ebx, 7FFFFFFFh
.aspack:004AF2F5 push ebx
.aspack:004AF2F6 push dword ptr [ebp+545h]
.aspack:004AF2FC call dword ptr [ebp+0F49h] ; 调用GetProcAddress()获取函数指针
.aspack:004AF302 test eax, eax
…… 略,略,略
.aspack:004AF373 push edi
.aspack:004AF374 jmp short loc_4AF3C0
.aspack:004AF376 loc_4AF376: ; CODE XREF: start+304 j start+35D j
.aspack:004AF376 mov [edi], eax
.aspack:004AF378 add dword ptr [ebp+549h], 4
.aspack:004AF37F jmp loc_4AF2B6
.aspack:004AF384 mov [esi], eax ; 获取完毕这个DLL文件的输入函数的地址
.aspack:004AF386 mov [esi+0Ch], eax ; 将I_I_Descriptor破坏掉
.aspack:004AF389 mov [esi+10h], eax
.aspack:004AF38C add esi, 14h
.aspack:004AF38F mov edx, [ebp+422h]
.aspack:004AF395 jmp ImportFunc ; 继续循环
AllDone是在返回OEP之前的最后一段代码,分析可知,OEP被硬编码进最后一段。
.aspack:004AF39A AllDone:
.aspack:004AF39A mov eax, 9243Ch ; OEP为9243C
.aspack:004AF39F push eax
.aspack:004AF3A0 add eax, [ebp+422h]
.aspack:004AF3A6 pop ecx
.aspack:004AF3A7 or ecx, ecx
.aspack:004AF3A9 mov [ebp+3A8h], eax
.aspack:004AF3AF popa
.aspack:004AF3B0 jnz short loc_4AF3BA
.aspack:004AF3B2 mov eax, 1
.aspack:004AF3B7 retn 0Ch
.aspack:004AF3BA
.aspack:004AF3BA loc_4AF3BA: ; CODE XREF: start+3AF j
.aspack:004AF3BA push 0
.aspack:004AF3BF retn
[手工脱壳]
在装载输入函数之前转储,可以保留住原程序的输入数据目录。
修改文件头。
修改AddressOfEntryPoint/IOH :OEP被硬编码进壳的最后一段,为9243C,。
修改IMAGE_OPTIONAL_HEADER中的数据目录第二项——输入数据目录:VirtualAddress被硬编码进壳中,为96000。观察以96000为起址的节大小为3000,Size修改为3000。
完成修改后,脱壳程序现在可以运行。
[修改]
坐下来静静想想,程序结束时打开网页,最简单地一种方式就是调用ShellExecute()。在程序的输入表中发现了ShellExecuteA这一项,在程序运行后下断点,关闭程序时触发中断,来到此处。将push 1修改为jmp $+1A,机器码由6A 01改为EB 18。成功。
CODE:004921BD push 1 ; 修改这里,跨过调用ShellExecute
CODE:004921BF push 0
CODE:004921C1 push offset unk_4921D8
CODE:004921C6 push offset aHttpWww_cnasm_ ; "HTTP:\\\\WWW.CNASM.COM"
CODE:004921CB push offset aOpen ; "open"
CODE:004921D0 push 0
CODE:004921D2 call ShellExecuteA
文章到此结束。当在脱壳和分析代码的过程中,发现:如果文件改名,程序主界面无任何显示。脑袋中没有什么灵感,下一篇文章再解决吧。
跟以往一样,在程序中留点记号吧。下面图片中的窗口的标题已经被改为“TNTTOOLS到此一游”。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!