我在《(之四)》一文中大言不惭地搞了一个“通用脱壳机”,当时我甚至不知道壳还有“压缩壳”和“加密壳”之分。在网友的提示下自觉汗颜,气不敢出,忙翻书充电。找了一个ASProtect 1.31版尝尝(不敢找更高的版本,因为我现在还不知道什么叫SDK)。果然ASProtect名不虚传,无数的“暗道机关”加“地雷”,初入者简直寸步难行。我花了约一个月的(业余)时间,居然摸出了一些门道,只用OD,无需任何其它工具,实现了“完美脱壳”。现把我的方法总结如下,和网友分享。
一、认识ASProtect中的SEH
1.ASProtect 1.31版中,共有29――31个SEH异常结构,对不同的程序加密方式可以不同,所以设置的SEH结构也稍有不同。大多数SEH我们无需理它,更不必了解它在干什么,不去招惹它可以相安无事。
2.几乎所有的SEH都可以nop掉或jmp,不过一定要(从异常开始处)nop到或jmp到“pop dword ptr fs:[0]”。由于有花指令,“pop dword ptr fs:[0]”并不总是可以直接看到,但代码“67 64 8F 06 00 00”或“64 8F 05 00 00 00 00”就是pop dword ptr fs:[0]的位置。本来,pop dword ptr fs:[edi]等,只要edi=0,都是可以的,但ASProtect 1.31版中只有pop dword ptr fs:[0]一种形式。
3.所有的SEH异常位置,最后4个字节是不变的。这样就很容易识别该SEH的功能。
4.几个重要的SEH位置(只是位置,并不关心SEH干什么,加密等工作也不是SEH干的)
xxxxDC77: 将压缩程序解压还原;
xxxx578B: 加密IAT表;
(xxxx58A9,xxxx4AAA,xxxx4AED)是更利害的加密IAT过程;(供不同程序使用,与前者不共用)
xxxxEA2E: 只要知道了OEP,从这里跳出就可以dump了,后面近10个SEH都是为加密OEP设置的;
5.程序中有几个必需经过的“文件校验”(查断点,修改,反Anti)结合具体程序再说。
二、什么都不管,首先寻找OEP
打开OD,去掉“忽略内存访问”的勾,让它每次中断在SEH。一律按“shift+F9”运行,大约中断29次后,来到“xxxxF145”中断。将该处代码“01 56 00”改为“EB 08”(即jmp xxxxF14F)。按F7不放(单步跟踪),注意观察OD的地址栏,有时在一个循环中久久不能跳出,可以尝试在可能跳出的位置设断,再按F9。当地址出现“xxx8xxxx”时要小心了(OEP入口,每次运行地址都不同,但前四字节xxx8是不变的)。将内存显示地址设置为“xxx81000”并可以尝试在内存栏中搜索代码“8D 04 18 5C FF E0”。若找到了,OEP就很快就得到了。由于有花指令,即使OEP入口就在眼前,你也很可能错过。入口处代码是(每次地址是不同的):
00983B2A F4 hlt
00983B2B F4 hlt
00983B2C FB sti
00983B2D 39EB cmp ebx,ebp
00983B2F 019A 8D04185C add dword ptr ds:[edx+5C18048D],ebx
00983B35 FFE0 jmp eax
由于在如下00983B03处多次回跳,可以先搜索“0F BF C1 E9 D5 FE FF FF”,入口转跳“jmp eax”就在它的下面几行处,那怕是到了最后几跳,jmp eax前面的代码还在不断地变化!(利害!)
00983AFB 66:81DB 2B1F sbb bx,1F2B
00983B00 0FBFC1 movsx eax,cx
00983B03 ^ E9 D5FEFFFF jmp 009839DD
单步走到jmp eax,记下eax的值,这项工作就完成了。
三、查找IAT表的加密过程
重新运行OD,打开“notepade_asp.exe”(XP中的notepade.exe用ASProtect 1.31加密),18次按shift+F9来到0096578B异常,将该处代码改为“EB 00”,可以很快到达009655E4――0096572B,这就是加密IAT表的全部代码。花指令不算太多,只需将每个“EB 01”下面的一个字节改为90(nop)就可以去除了。
不必关心它是怎样加密的,只需知道它有多少种加密方式。每加密一次,它都要将加密后的代码存入IAT表,因此查找存入IAT表地址的代码是关键。原来,它是用下面方法存入加密代码的:
00965707 8B55 0C mov edx,dword ptr ss:[ebp+C]
0096570A 8B12 mov edx,dword ptr ds:[edx]
0096570C 8902 mov dword ptr ds:[edx],eax
其中,eax是加密后的代码,edx是IAT表的位置。本段代码中,供有5处相同的代码,说明它有5种加密方式,但对一个程序不是5种加密都用上的。为此,在每个mov dword ptr ds:[edx],eax处设断,按F9运行,每中断一次,释放一个断点,当程序走到下一个异常时,回头看看剩下几个断点。剩下的就是没有使用的加密方式。notepade_asp.exe只用了三种方式,它们是:
00965663 57 push edi ;API函数名
00965664 8B45 10 mov eax,dword ptr ss:[ebp+10]
00965667 50 push eax ;库函数名
00965668 56 push esi
00965669 E8 8AFDFFFF call 009653F8 ;GetProcAddress
0096566E 8BD8 mov ebx,eax
00965670 6A 00 push 0
00965672 68 48489600 push 964848
00965677 8D4D FC lea ecx,dword ptr ss:[ebp-4]
0096567A 8BD3 mov edx,ebx
0096567C 8BC6 mov eax,esi
0096567E E8 15F9FFFF call 00964F98 ;加密
00965683 8B55 0C mov edx,dword ptr ss:[ebp+C]
00965686 8B12 mov edx,dword ptr ds:[edx]
00965688 8902 mov dword ptr ds:[edx],eax ;存入IAT(加密)
0096568A E9 90000000 jmp 0096571F
009656FC 57 push edi ;API函数名
009656FD 8B45 10 mov eax,dword ptr ss:[ebp+10]
00965700 50 push eax ;库函数名
00965701 56 push esi
00965702 E8 F1FCFFFF call 009653F8 ;GetProcAddress
00965707 8B55 0C mov edx,dword ptr ss:[ebp+C]
0096570A 8B12 mov edx,dword ptr ds:[edx]
0096570C 8902 mov dword ptr ds:[edx],eax ;存入IAT(未加密)
0096570E EB 0F jmp short 0096571F
00965710 B8 08439600 mov eax,964308 ;964308干什么?
00965715 8B55 0C mov edx,dword ptr ss:[ebp+C]
00965718 8B12 mov edx,dword ptr ds:[edx]
0096571A 8902 mov dword ptr ds:[edx],eax ;存入IAT(加密)
0096571C EB 01 jmp short 0096571F
964308干什么的?它作为加密地址存入了IAT?跟踪进入964308,原来,它就是调用GetProcAddress,思路完全清楚了,且API都是明码出现,脱壳工作就显得简单多了!
四、增加notepade_asp.exe的空字节,准备写入IID和INT(函数名表)
不知什么原因,每个ASProtect加壳程序最后都有4096个0字节(但不一定在文件中),可按如下修改,并将装载尺寸改为32000,用16进制编辑器,在文件最后添加4096个0字节:
----------------------------------------------------------
节区名称 实际尺寸 内存地址 对齐尺寸 文件地址 节区属性
----------------------------------------------------------
00008000 00001000 00004000 00000400 E0000040
00002000 00009000 00000200 00004400 E0000040
.rsrc 00008000 0000B000 00000E00 00004600 E0000040
.data 0001E000 00013000 0001D400 00005400 E0000040
.adata 00001000 00031000 00001000 00022800 E0000040
五、添加脱壳代码
在文件最后1000h字节中添加如下代码:(全部的代码就这些)
00228000 00 00 00 00 B0 11 03 01 B0 01 03 01 00 00 00 00
00228010 00 00 00 00 56 50 57 39 05 00 10 03 01 74 1F A3
00228020 00 10 03 01 8B F0 E8 58 00 00 00 83 C0 10 89 38
00228030 83 C0 04 A3 08 10 03 01 FF 05 10 10 03 01 5E 56
00228040 E8 3E 00 00 00 83 EF 02 89 3D 0C 10 03 01 5F 58
00228050 5E E8 A2 43 93 FF 8B 55 0C 8B 12 A1 0C 10 03 01
00228060 89 02 83 3D 10 10 03 01 00 74 13 81 F2 00 00 00
00228070 01 A1 08 10 03 01 89 10 FF 0D 10 10 03 01 E9 9C
00228080 46 93 FF 8B 3D 04 10 03 01 57 AC 3C 00 74 03 AA
00228090 EB F8 47 47 89 3D 04 10 03 01 5F 81 F7 00 00 00
002280A0 01 A1 08 10 03 01 C3 90 00 00 00 00 00 00 00 00
228000――228014是作为寄存器使用,228004存入的是函数名表开始地址,228008存入的是IID表开始地址。程序从228014开始,反汇编是:
01031014 56 push esi
01031015 50 push eax
01031016 57 push edi
01031017 3905 00100301 cmp dword ptr ds:[1031000],eax ;将[1031000]作为新库识别标记
0103101D 74 1F je short 0103103E ;新库开始则不跳
0103101F A3 00100301 mov dword ptr ds:[1031000],eax
01031024 8BF0 mov esi,eax
01031026 E8 58000000 call 01031083 ;取库函数名字串
0103102B 83C0 10 add eax,10
0103102E 8938 mov dword ptr ds:[eax],edi ;库名地址存IID
01031030 83C0 04 add eax,4
01031033 A3 08100301 mov dword ptr ds:[1031008],eax
01031038 FF05 10100301 inc dword ptr ds:[1031010] ;新库开始标记
0103103E 5E pop esi
0103103F 56 push esi
01031040 E8 3E000000 call 01031083 ;取API函数名字串
01031045 83EF 02 sub edi,2
01031048 893D 0C100301 mov dword ptr ds:[103100C],edi ;API地址暂存
0103104E 5F pop edi
0103104F 58 pop eax
01031050 5E pop esi
01031051 E8 A24393FF call 009653F8 ;借用原程序取IAT表地址
01031056 8B55 0C mov edx,dword ptr ss:[ebp+C]
01031059 8B12 mov edx,dword ptr ds:[edx]
0103105B A1 0C100301 mov eax,dword ptr ds:[103100C]
01031060 8902 mov dword ptr ds:[edx],eax ;复原后的API地址存入IAT
01031062 833D 10100301 00 cmp dword ptr ds:[1031010],0
01031069 74 13 je short 0103107E ;若不是新库则跳
0103106B 81F2 00000001 xor edx,1000000
01031071 A1 08100301 mov eax,dword ptr ds:[1031008]
01031076 8910 mov dword ptr ds:[eax],edx ;IAT表开始地址存入IID表
01031078 FF0D 10100301 dec dword ptr ds:[1031010]
0103107E - E9 9C4693FF jmp 0096571F ;返回原程序,准备下次复原
01031083 8B3D 04100301 mov edi,dword ptr ds:[1031004] ;写INT函数名表子程序
01031089 57 push edi
0103108A AC lods byte ptr ds:[esi]
0103108B 3C 00 cmp al,0
0103108D 74 03 je short 01031092
0103108F AA stos byte ptr es:[edi]
01031090 ^ EB F8 jmp short 0103108A
01031092 47 inc edi
01031093 47 inc edi
01031094 893D 04100301 mov dword ptr ds:[1031004],edi
0103109A 5F pop edi
0103109B 81F7 00000001 xor edi,1000000
010310A1 A1 08100301 mov eax,dword ptr ds:[1031008]
010310A6 C3 retn
六、修改几个转跳
打开OD,按shift+F9,18次后来到0096578B处,将该处异常代码改为“EB 00”。剩下的工作就不多了,将00965669处的“call 009653F8”改为“jmp 01031014”,将00965702处的“call 009653F8”改为“jmp 01031014”,最后将00965710作如下改动:
00965710 BF 009A9700 mov edi,979A00 ;在该处自已键入GetProcAddress字串
00965715 57 push edi
00965716 8B45 10 mov eax,dword ptr ss:[ebp+10]
00965719 50 push eax
0096571A 56 push esi
0096571B ^ EB E5 jmp short 00965702
0096571D 90 nop
反复核对修改无误后,按F9,再按一次shift+F9来到0096EA2E,准备dump了。虽然后面设置了许多的陷阱,但我根本就不过去了,ASProtect看着我干着急^_^.
七、Dump,修改最后的PE头的函数导入表
到了0096EA2E后,修改该代码为“EB 0E”(即jmp 96EA3E),单步二次,将“add esp,4”改为“add esp,84”,继续汇编“push 100739D”(OEP地址),“ret”。然后单步存入100739D。或先修改为:
0096EA2E /EB 0E jmp short 0096EA3E
0096EA30 |BA 886DC1E7 mov edx,E7C16D88
0096EA35 |C3 retn
0096EA36 |FE ???
0096EA37 |AD lods dword ptr ds:[esi]
0096EA38 |221B and bl,byte ptr ds:[ebx]
0096EA3A |F2: prefix repne:
0096EA3B |90 nop
0096EA3C |90 nop
0096EA3D |90 nop
0096EA3E \67:64:8F06 0000 pop dword ptr fs:[0]
0096EA44 81C4 84000000 add esp,84
0096EA4A 68 9D730001 push 100739D
0096EA4F C3 retn
然后单步运行。进入100739D后。开始dump,去掉“重建输入表”的勾,dump存盘为notepad_new.exe,最后,用16进制编辑器将函数导入表地址改为:“00310B4”(B4 10 03 00),导入表长度可改可不改,或改为“000000F0”(F0 00 00 00)存盘。然后运行,怎么样,完美脱壳了吧!只不过脱壳程序中垃圾过多,显得很臃肿,可以减肥。
这次就写到这里,下次准备说说xxxx58A9,xxxx4AAA,xxxx4AED更利害的加密IAT过程。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课