- UID
- 65588
注册时间2010-2-17
阅读权限40
最后登录1970-1-1
独步武林
TA的每日心情 | 开心 2018-9-27 19:17 |
---|
签到天数: 31 天 [LV.5]常住居民I
|
发表于 2015-4-12 23:48:53
|
显示全部楼层
本帖最后由 lucky_789 于 2015-4-12 23:50 编辑
几年前分析过一个FSG2.0的壳,碰巧笔记还在,给你参考一下吧,1.1应该不会比2.0复杂。
【软件】加了FSG 2.0 -> bart/xt壳的win98记事本程序,由天草老师教程中提供。
【工具】OD、PEiD
【声明】未经本人同意,本文谢绝转载。
PEiD查看,是FSG 2.0 -> bart/xt,用OD载入。在开始分析之前,先了解一下该壳的概貌,在OD的数据窗口中看一下头部信息:
00400000 4D 5A 00 00 | 00 00 00 00 | 00 00 00 00 | 50 45 00 00 MZ..........PE..
00400010 4C 01 02 00 | 46 53 47 21 | 00 00 00 00 | 00 00 00 00 L.FSG!........
00400020 E0 00 0F 01 | 0B 01 00 00 | 00 40 00 00 | 00 70 00 00 ?[email protected]..
00400030 00 00 00 00 | 54 01 00 00 | 00 10 00 00 | 0C 00 00 00 ....T.........
00400040 00 00 40 00 | 00 10 00 00 | 00 02 00 00 | 04 00 00 00 ..@..........
00400050 00 00 00 00 | 04 00 00 00 | 00 00 00 00 | 00 50 01 00 ............P.
00400060 00 02 00 00 | 00 00 00 00 | 02 00 00 00 | 00 00 10 00 .............
00400070 00 10 00 00 | 00 00 10 00 | 00 10 00 00 | 00 00 00 00 .............
00400080 10 00 00 00 | 00 00 00 00 | 00 00 00 00 | A8 44 01 00 ...........―.
00400090 84 00 00 00 | 00 E0 00 00 | 90 33 00 00 | 00 00 00 00 ?...?.?......
。。。
我们可以看到:
1、这个压缩壳真是“压缩到了牙齿”:
其一,DOS头部被挤占,只剩下e_magic等前6个成员以及e_lfanew共16个字节。
其二,PE头被移到了0040000C处。(这样,PE头的BaseOfData成员--数据区段起始RVA--就与e_lfanew相重叠了,有影响吗?我们看到区段表表明只有2个区段,名称都被抹去了,再看看内存窗口,原来是代码段和资源(输入表)两个区段,并没有数据段,所以不会影响。)
其三,DOS头、PE头、区段表总大小被压缩为200h(512)字节,而程序入口点RVA为0000154,即壳的引导部分被插入到了文件头部区段表末尾的空白区,几乎没浪费一个字节,呵呵。
2、PE头的文件创建日期和时间字段被填充了壳标志“FSG!”。
3、输入表首地址RVA=000144A8,去004144A8看看,发现输入表只有一个IID,OriginalFirstThunk和FirstThunk都指向00414500,只有两个函数,即kernel32.LoadLibraryA和kernel32.GetProcAddress,紧随其后的是这两个函数的名称。
好了,大致了解了这些,正式开始分析。
00400154 F> 8725 EC444100 xchg dword ptr [4144EC],esp ; ds:[004144EC]处是加壳时存放的是指向8个预设值的指针
0040015A 61 popad ; 用pop方式给8个寄存器传递预设值(但esp是不受预设值的影响)
0040015B 94 xchg eax,esp ; 恢复堆栈。eax中是正常程序入口时esp的值
下面开始解压代码:
0040015C 55 push ebp
0040015D A4 movs byte ptr es:[edi],byte ptr [esi] ; ds:[esi]=[00411390]=00,es:[edi]=[00401000]=00
0040015E B6 80 mov dh,80
00400160 FF13 call dword ptr [ebx]
00400162 ^ 73 F9 jnb short FSG_2_0.0040015D
00400164 33C9 xor ecx,ecx
00400166 FF13 call dword ptr [ebx]
00400168 73 16 jnb short FSG_2_0.00400180
0040016A 33C0 xor eax,eax
0040016C FF13 call dword ptr [ebx]
0040016E 73 1F jnb short FSG_2_0.0040018F
00400170 B6 80 mov dh,80
00400172 41 inc ecx
00400173 B0 10 mov al,10
00400175 FF13 call dword ptr [ebx]
00400177 12C0 adc al,al
00400179 ^ 73 FA jnb short FSG_2_0.00400175
0040017B 75 3A jnz short FSG_2_0.004001B7
0040017D AA stos byte ptr es:[edi]
0040017E ^ EB E0 jmp short FSG_2_0.00400160
00400180 FF53 08 call dword ptr [ebx+8]
00400183 02F6 add dh,dh
00400185 83D9 01 sbb ecx,1
00400188 75 0E jnz short FSG_2_0.00400198
0040018A FF53 04 call dword ptr [ebx+4]
0040018D EB 24 jmp short FSG_2_0.004001B3
0040018F AC lods byte ptr [esi]
00400190 D1E8 shr eax,1
00400192 74 2D je short FSG_2_0.004001C1
00400194 13C9 adc ecx,ecx
00400196 EB 18 jmp short FSG_2_0.004001B0
00400198 91 xchg eax,ecx
00400199 48 dec eax
0040019A C1E0 08 shl eax,8
0040019D AC lods byte ptr [esi]
0040019E FF53 04 call dword ptr [ebx+4]
004001A1 3B43 F8 cmp eax,dword ptr [ebx-8]
004001A4 73 0A jnb short FSG_2_0.004001B0
004001A6 80FC 05 cmp ah,5
004001A9 73 06 jnb short FSG_2_0.004001B1
004001AB 83F8 7F cmp eax,7F
004001AE 77 02 ja short FSG_2_0.004001B2
004001B0 41 inc ecx
004001B1 41 inc ecx
004001B2 95 xchg eax,ebp
004001B3 8BC5 mov eax,ebp
004001B5 B6 00 mov dh,0
004001B7 56 push esi
004001B8 8BF7 mov esi,edi
004001BA 2BF0 sub esi,eax
004001BC F3:A4 rep movs byte ptr es:[edi],byte ptr [esi]
004001BE 5E pop esi
004001BF ^ EB 9F jmp short FSG_2_0.00400160 ; 循环
至此解压完毕。
下面开始填充IAT:
004001C1 5E pop esi ; 堆栈 [0012FFC0]=00406000,这里是加壳时保存的一张表,内容是输入表涉及的所有dll库名称及各dll对应的IAT指针。
004001C2 / AD lods dword ptr [esi] ; 取IAT首地址
004001C3 | 97 xchg eax,edi ; IAT首地址->edi
004001C4 | AD lods dword ptr [esi] ; 取dll库名称
004001C5 | 50 push eax ; /dll库名称(如ASCII "SHELL32.dll")
004001C6 | FF53 10 call dword ptr [ebx+10] ; \kernel32.LoadLibraryA
004001C9 | 95 xchg eax,ebp ; dll库句柄->ebp
004001CA |/8B07 mov eax,dword ptr [edi] ; /取函数名地址
004001CC ||40 inc eax ; |函数名地址(如ASCII "DragFinish")
004001CD \|78 F3 js short FSG_2_0.004001C2 ; |FSG壳在这里做了小动作,正常情况下不同dll的IAT之间是用00000000分隔,而我们从数据窗口可以看到,[edi]处是用7FFFFFFF来分隔的,最后一个dll的IAT末尾是FFFFFFFF,许多朋友脱壳后不能运行,原因就在这里!当[edi]处是7FFFFFFF时表示还有dll的函数没处理完,在这里跳去处理下一个dll的函数。
004001CF |75 03 jnz short FSG_2_0.004001D4 ; |是结束标志(FFFFFFFF+1=0)则跳出循环,否则就是函数名地址,需要继续填充。
004001D1 |FF63 0C jmp dword ptr [ebx+C] ; |全部填充完毕,则到这里跳往OEP
004001D4 |50 push eax ; |/参数2:函数名。ASCII "DragFinish"
004001D5 |55 push ebp ; ||参数1:动态库句柄
004001D6 |FF53 14 call dword ptr [ebx+14] ; |\kernel32.GetProcAddress
004001D9 |AB stos dword ptr es:[edi] ; |填充IAT
004001DA ^\EB EE jmp short FSG_2_0.004001CA ; \循环处理下一个函数
004001DC 33C9 xor ecx,ecx ; 这里是前面解压子函数[ebx+4]的入口。不用管
004001DE 41 inc ecx ; 这里是前面解压子函数[ebx+8]的入口。不用管
004001DF FF13 call dword ptr [ebx]
004001E1 13C9 adc ecx,ecx
004001E3 FF13 call dword ptr [ebx]
004001E5 ^ 72 F8 jb short FSG_2_0.004001DF
004001E7 C3 retn
004010CC 55 push ebp ; 这里就是OEP
004010CD 8BEC mov ebp,esp
004010CF 83EC 44 sub esp,44
004010D2 56 push esi
004010D3 FF15 E4634000 call dword ptr [4063E4] ; kernel32.GetCommandLineA
004010D9 8BF0 mov esi,eax
下面就是脱壳了。直接用OD的OllyDump插件脱壳,运行会报错,原因很简单,就是上面分析中提到的IAT分隔标志问题。解决办法也很简单,在到达OEP后,在反汇编窗口中找一个API函数,如“004010D3 call dword ptr [4063E4] ; kernel32.GetCommandLineA”,在这行右键-在数据窗口中跟随-内存地址,数据窗口中上下看看,将IAT中的所有7FFFFFFF和FFFFFFFF修改成00000000,再dump即可运行,无需ImportREC修复。
小结:
FSG 2.0 -> bart/xt是一个早期的压缩壳,简单的作了一点保护措施。
一、本壳的压缩方面
1、该壳删除了文件的DOS头,只保留了必要的16个字节,壳自解压代码全部存放文件头中。
2、该壳将文件包含代码段在内的全部区段合并在一起,将压缩的数据放入一个新增加的段中,同时将原文件的代码、数据、资源等所有内容抽掉,连原各段的名称也在文件头中抹掉。
3、壳代码在运行时,直接将整个压缩的数据解压到代码段中,然后填充IAT,再跳往OEP。
二、输入表处理方面
1、将输入表指向了一个壳中的伪输入表,伪输入表中只保留了1个IID且除NAME和FIRSTTHUNK外的其他3个成员为空,而dll名称(KERNEL32.dll)则附在了文件头中壳代码结束处,IAT只有两个函数,即kernel32.LoadLibraryA和kernel32.GetProcAddress,供壳使用。
2、加壳时破坏了原输入表,取而代之的是一张信息表,保存的信息是IAT首地址和它对应的dll库名称指针。IAT中填充的是处理过的函数名地址,壳在运行时,先根据信息表中的指针取出dll库名称,再LoadLibraryA调入该库,获得句柄;然后根据信息表中对应的IAT首地址,取出一个地址,该地址+1后即得到函数名,然后GetProcAddress获取函数地址,最后覆盖被篡改的IAT。
3、该壳在给文件加壳时,就对IAT除篡改函数地址外,还将不同dll的IAT数组之间的结束标志篡改,最后一个dll的IAT末尾由FFFFFFFF替代00000000,而前面的dll的IAT之间用的是7FFFFFFF。壳在装载函数时,根据这两个数+1的结果(而不是直接根据信息表)来判断是继续填充下一IAT数组还是结束填充,如果是继续填充,则从信息表中取出dll库名称地址和IAT首地址,循环处理。为何要搞的这么麻烦?相信你已经明白了--那就是反脱壳。许多新手朋友在顺利脱掉这个壳后,程序却不能运行,软件作者要的就是这个结果,呵呵。我们的对策也很简单,上面已作了说明,就是在填充完IAT后,将那些7FFFFFFF和FFFFFFFF恢复成00000000,程序就跑起来了。
4、该壳没有采取反调试措施,只是一个小技巧将跳往OEP的指令夹在处理IAT的循环代码中间,信奉“不往回跳”的新手朋友,往往就容易跑飞了。但壳代码除解压代码外,总共才十几行指令,也隐藏不了什么秘密,毕竟只是压缩壳。
|
|