起初有点奇怪:如果这么顺利就脱壳,那还是加密壳么?!
自己单步了一下,起初也没啥感觉,不就几个SEH和IsDebuggerPresent,以及
int 68这样的反调试手段么?的确比较容易地跑到了OEP:00452384
然而,对于加密壳的手脱来说,“找入口点只是第一步”,很多情况下,能不能修复文件,特别是修复IAT,才是重点。
而在这后面的步骤中,我遇到问题,回头再重新单步跟踪,才发现了一些关键。
其实,如果楼主把脱壳的步骤做完,你就会发现,有很多问题你之前根本就没有看到,而这些将可能使你前面的工作功亏一篑。
下面就来谈一谈这几个需要注意的问题,按楼主这种跑法是没有办法直接解决的。
1. 镜像大小错误——LordPE与ImportREC等工具读取错误
当你要用LordPE将文件完整转存的时候,你就看到该文件的模块属性显示:
镜像基址:00400000,镜像大小:00001000
这样dump下来的文件,也只有4KB(0x1000字节)这么大。
当用ImportREC修复IAT,IATAutoSearch之后,Get Imports,也会出现内存不可读取的错误提示
实际上,镜像文件真正大小是00069000,是壳程序从PEB结构顺藤摸瓜,改变了这个值。
相关代码:
00467286 64:FF35 3000000>
push dword ptr fs:[30]
; PTR _PEB
0046728D 58
pop eax
0046728E 85C0
test eax,
eax
00467290 78 0F
js short 004672A1
00467292 8B40 0C
mov eax,
dword ptr [
eax+C]
; LDR_DATA
00467295 8B40 0C
mov eax,
dword ptr [
eax+C]
00467298 C740 20 0010000>
mov dword ptr [
eax+20], 1000
; 把模块大小改为0x1000
这就是壳程序做的事情,故意将模块大小改变,使得LordPE和ImportREC等工具在dump的时候出错了。
解决方法:
当然可以将00467298处
nop掉。不过后面有类似自校验的东西(虽然在脱壳完之前,似乎校验得不是很严密),以防万一,在这一句时,数据窗口跟随内存地址,我这里是00251F00,未改之前的
DWORD值是00069000。单步过这一句后,数据窗口可以看到此处已被改了。这时就应该顺手把它改回来。
2. HOOK-API方式对IAT加密——ImportREC修复IAT比较麻烦
解决了上面的问题之后,再跑到OEP,LordPE能够正确dump镜像文件,ImportREC能够IAT AutoSearch了,但是Get Imports后,你会看到一大堆不可用的IAT项目,几乎只有kernel32.dll和comctl32.dll的项目可用。
显然,IAT被壳程序用HOOK-API方式加密了,这是加密壳的典型特点。
解决方法:
可以试着用ImportREC的Trace功能,Level1到Level3都试一试。
不过我觉得这种方法比较碰运气,如果加密得深了,Trace的时候会CPU占用高(我的本本很破受不了),还不一定Trace得到。
对付HOOK-API比较好的方法,是找到壳填充IAT后进行加密的代码,设法跳过这些代码,使得IAT不被加密,从而使ImportREC能够直接得到原始的IAT,直接修复。
相关代码位置:
edx为IAT中的地址,
eax为相应API函数地址,在此之前已经被填入
edx指向的位置中;
ebx为相应dll的基址
0046763A F785 AF2F4000 2>
test dword ptr [
ebp+402FAF], 20
00467644 74 45
je short 0046768B
00467646 83BD BB2F4000 0>
cmp dword ptr [
ebp+402FBB], 0
0046764D 74 14
je short 00467663
0046764F 81FB 00000070
cmp ebx, 70000000
00467655 72 08
jb short 0046765F
00467657 81FB FFFFFF77
cmp ebx, 77FFFFFF
0046765D 76 0E
jbe short 0046766D
; 跳入进行HOOK-API
0046765F EB 2A
jmp short 0046768B
00467661 EB 0A
jmp short 0046766D
00467663 81FB 00000080
cmp ebx, 80000000
00467669 73 02
jnb short 0046766D
0046766B EB 1E
jmp short 0046768B
0046766D 57
push edi
0046766E 56
push esi
0046766F 8DBD E5344000
lea edi,
dword ptr [
ebp+4034E5]
00467675 8B77 04
mov esi,
dword ptr [
edi+4]
00467678 8932
mov dword ptr [
edx],
esi
0046767A 2BC6
sub eax,
esi
0046767C 83E8 05
sub eax, 5
0046767F C606 E9
mov byte ptr [
esi], 0E9
00467682 8946 01
mov dword ptr [
esi+1],
eax
00467685 8347 04 05
add dword ptr [
edi+4], 5
00467689 5E
pop esi
0046768A 5F
pop edi
0046768B 83C1 04
add ecx, 4
0046768E 83C2 04
add edx, 4
00467691 8339 00
cmp dword ptr [
ecx], 0
00467694 ^ 0F85 34FFFFFF
jnz 004675CE
; 取下一个API
0046769A 83C6 0C
add esi, 0C
0046769D 837E 04 00
cmp dword ptr [
esi+4], 0
004676A1 ^ 0F85 B4FEFFFF
jnz 0046755B
; 取下一个DLL
004676A7 33C0
xor eax,
eax
从以下判断代码:
0046764F 81FB 00000070
cmp ebx, 70000000
00467655 72 08
jb short 0046765F
00467657 81FB FFFFFF77
cmp ebx, 77FFFFFF
0046765D 76 0E
jbe short 0046766D
; 跳入进行HOOK-API
对于所有基址在0x70000000-0x77FFFFFF的dll,壳将与之相关的IAT项全部用HOOK-API方式加密,加密方法是生成一条
jmp代码,
jmp的目标是真正的API函数地址,而将原IAT中的API函数地址替换为
jmp代码的地址。
只要设法改变这个判断流程,使之不跳进0046766D,而直接跳到0046768B,就可以使IAT不被加密,保留原始的IAT。
这里有好几个判断,所以改的方法应该就不止一种。
比如将以下代码:
00467644 74 45
je short 0046768B
改为绝对跳转:
00467644 /EB 45
jmp short 0046768B
之后在004676A7处F4,就跑完了填充IAT的代码了,而且得到的是原始未加密的IAT。
3. 最佳时机
脱壳不一定非要跑到OEP才可以dump。完成了上面这个部分,这时应该就已经是脱壳的最佳时机了。因为我们已经在之前的几次“不成功的脱壳”中知道OEP的位置了,而这时程序的区段已经解密完成,原始完整的IAT又已经得到。
如果不在这里dump,而是坚持跑到OEP,后面又有检测IsDebuggerPresent,SEH结构化异常处理,以及类似自校验和SMC的东西,实际上只是一系列反调试的拦路虎,对脱壳结果并没有什么影响。
所以直接在这里用LordPE完整转存,再用ImportREC修复IAT,填入OEP偏移00052384,IAT AutoSearch,再Get Imports。这时的IAT就完好无损了,直接fix dump,完成。
附件中Project2_u1.exe为LordPE转存下来的文件,Project2_u1_.exe则为ImportREC修复后的程序。