ximo 发表于 2009-9-10 19:23

FSG的分析以及输入表的手动处理

本帖最后由 ximo 于 2009-9-10 20:54 编辑

好久不脱壳了,都快生疏了,于是拣个软柿子捏捏,顺便复习下PE格式。
壳很简单,FSG,大部分人接触脱壳破解几个星期估计都会脱。
做个详细的分析,并纯手动修复输入表。高手就飘过吧。。。
哎,高手都玩TMD,NP了,我这种层次只能捏压缩壳玩,实在惭愧。

一、压入壳的参数

01000154 >8725 58050201      xchg dword ptr ds:,esp
0100015A    61                   popad
0100015B    94                   xchg eax,esp

开头的3句式把的内容压入了堆栈,等效于:
01000154 > /E9 A70B0000          jmp np_fsg2.01000D00
01000159   |90                   nop

01000D00    B8 5C050201          mov eax,np_fsg2.0102055C
01000D05    B9 007D0000          mov ecx,7D00
01000D0A    BA 80000000          mov edx,80
01000D0F    BB 5C050201          mov ebx,np_fsg2.0102055C
01000D14    BC C4FF0600          mov esp,6FFC4
01000D19    BD 04760001          mov ebp,np_fsg2.01007604
01000D1E    BE ECBD0101          mov esi,np_fsg2.0101BDEC
01000D23    BF 00100001          mov edi,np_fsg2.01001000
01000D28^ E9 2FF4FFFF          jmp np_fsg2.0100015C
01000D2D    90                   nop

B8 5C 05 02 01 B9 00 7D 00 00 BA 80 00 00 00 BB 5C 05 02 01 BC C4 FF 06 00 BD 04 76 00 01 BE EC
BD 01 01 BF 00 10 00 01 E9 2F F4 FF FF 90

不过FSG用了xchg语句,更为巧妙和简捷。
观察下里的内容:
0102053C   01001000np_fsg2.01001000-----------EDI------------------------代码段的起始RVA
01020540   0101BDECnp_fsg2.0101BDEC-----------ESI------------------------保存加密数据的起始地址
01020544   01007604np_fsg2.01007604-----------EBP------------------------输入表RVA
01020548   00000000-----------------------------ESP------------------------0102053C+4*8=0102055C
0102054C   0102055Cnp_fsg2.0102055C-----------EBX------------------------FSG自己的结构
01020550   00000080-----------------------------EDX------------------------dl=80h
01020554   00007D00-----------------------------ECX------------------------加密数据的长度(大小)
01020558   0006FFC4-----------------------------EAX------------------------保存原始的ESP      
FSG自身结构:

0102055C   010001E8np_fsg2.010001E8--------------------------------------解密函数(aplib)
01020560   010001DCnp_fsg2.010001DC--------------------------------------解密函数(aplib)
01020564   010001DEnp_fsg2.010001DE--------------------------------------解密函数(aplib)
01020568   0100739Dnp_fsg2.0100739D--------------------------------------未加壳程序的入口地址(OEP)
0102056C > 7C801D77kernel32.LoadLibraryA
01020570 > 7C80ADA0kernel32.GetProcAddress

其中所代表的各个值有后面的调试可以知道。

二、解压缩
0100015C    55                   push ebp
0100015D    A4                   movs byte ptr es:,byte ptr ds:
0100015E    B6 80                mov dh,80
01000160    FF13               call dword ptr ds:
01000162^ 73 F9                jnb short np_fsg2.0100015D
01000164    33C9               xor ecx,ecx
01000166    FF13               call dword ptr ds:
01000168    73 16                jnb short np_fsg2.01000180
0100016A    33C0               xor eax,eax
0100016C    FF13               call dword ptr ds:
0100016E    73 1F                jnb short np_fsg2.0100018F
01000170    B6 80                mov dh,80
01000172    41                   inc ecx
01000173    B0 10                mov al,10
01000175    FF13               call dword ptr ds:
01000177    12C0               adc al,al
01000179^ 73 FA                jnb short np_fsg2.01000175
0100017B    75 3A                jnz short np_fsg2.010001B7
0100017D    AA                   stos byte ptr es:
0100017E^ EB E0                jmp short np_fsg2.01000160
01000180    FF53 08            call dword ptr ds:
01000183    02F6               add dh,dh
01000185    83D9 01            sbb ecx,1
01000188    75 0E                jnz short np_fsg2.01000198
0100018A    FF53 04            call dword ptr ds:
0100018D    EB 24                jmp short np_fsg2.010001B3
0100018F    AC                   lods byte ptr ds:
01000190    D1E8               shr eax,1
01000192    74 2D                je short np_fsg2.010001C1
01000194    13C9               adc ecx,ecx
01000196    EB 18                jmp short np_fsg2.010001B0
01000198    91                   xchg eax,ecx
01000199    48                   dec eax
0100019A    C1E0 08            shl eax,8
0100019D    AC                   lods byte ptr ds:
0100019E    FF53 04            call dword ptr ds:
010001A1    3B43 F8            cmp eax,dword ptr ds:
010001A4    73 0A                jnb short np_fsg2.010001B0
010001A6    80FC 05            cmp ah,5
010001A9    73 06                jnb short np_fsg2.010001B1
010001AB    83F8 7F            cmp eax,7F
010001AE    77 02                ja short np_fsg2.010001B2
010001B0    41                   inc ecx
010001B1    41                   inc ecx
010001B2    95                   xchg eax,ebp
010001B3    8BC5               mov eax,ebp
010001B5    B6 00                mov dh,0
010001B7    56                   push esi
010001B8    8BF7               mov esi,edi
010001BA    2BF0               sub esi,eax
010001BC    F3:A4                rep movs byte ptr es:,byte ptr ds:
010001BE    5E                   pop esi
010001BF^ EB 9F                jmp short np_fsg2.01000160                  


很明显这是个解压缩函数,所使用的压缩引擎为apLib。
这段是原来的.code段,也就是01001000处,长度为7D00的数据给解压缩了。
而压缩后的数据保存的地址为0101BDEC中。

0100015D    A4                   movs byte ptr es:,byte ptr ds:

ds:==A3
es:==00

由此可知:
0102053C   01001000np_fsg2.01001000-----------EDI------------------------代码段的起始RVA
01020540   0101BDECnp_fsg2.0101BDEC-----------ESI------------------------保存加密数据的起始地址
这2句结构的意思。

aplib压缩引擎是开源的,就不具体深究了。有兴趣的可以去参考它的源码。

三、输入表的处理:
010001C1    5E                   pop esi                                    ;解压数据完毕,并把输入表的RVA传给esi
010001C2    AD                   lods dword ptr ds:                      ;取出IID.FirstThunk
010001C3    97                   xchg eax,edi
010001C4    AD                   lods dword ptr ds:                      ;取出dll的name放eax
010001C5    50                   push eax                                     ;把DLL的name压栈
010001C6    FF53 10            call dword ptr ds:                   ;eax=hDll=LoadLibraryA("comdlg32.dll"),以第一个DLL为例
010001C9    95                   xchg eax,ebp
010001CA    8B07               mov eax,dword ptr ds:
010001CC    40                   inc eax                                    ;IAT存放在了-1的地方,手动修复的时候要用
010001CD^ 78 F3                js short np_fsg2.010001C2                  ;一个dll内的输入表处理完就跳,去进行下一个DLL处理
010001CF    75 03                jnz short np_fsg2.010001D4                   ;处理完所有的,则不跳
010001D1    FF63 0C            jmp dword ptr ds:                     ;跳向OEP
010001D4    50                   push eax                                     ;压入hDll
010001D5    55                   push ebp                                     ;压入要处理的API名称
010001D6    FF53 14            call dword ptr ds:                   ;eax=GetProcAddress(hDll,"PageSetupDlgW")以第一个函数为例
010001D9    AB                   stos dword ptr es:                      ;填充IAT
010001DA^ EB EE                jmp short np_fsg2.010001CA                   ;循环处理


填充IAT的过程很简单,详细见上面的注释。
处理完所有的IAT,就用010001D1    FF63 0C            jmp dword ptr ds:跳向了OEP。
由于ebx中保存的是FSG自身的结构,查上面的结构可知,指向的值即为OEP。

至此,整个流程就基本分析清楚了。

由上面的分析,可以得到2种快捷的脱壳方法:

法1.查找跳向OEP的特征代码jmp dword ptr ds:,也就是一般脚本里的FF 63 0C,在这里下好断点,中断后F7,就到OEP了。此时dump,IR修复OK。

法2.由于
01000154 >8725 58050201      xchg dword ptr ds:,esp
0100015A    61                   popad
执行完这2条语句后,堆栈中的内容为FSG自己的结构,而其自身结构的第四个值即为原始的OEP值,因此直接在OEP处下硬件断点,断下后即可脱壳了
故一般所谓的快速方法为:
1.载如OD,F8两次,然后再堆栈处,从上往下数4行,或者找LoadLibraryA前1行,在这个地址处下硬件断点,F9,中断后,即可。

四、纯手动修复IAT
首先在010001C1    5E                   pop esi    处dump程序,因为此时数据已经全部解压完毕,而IAT还没有填充。
下面就开始手动处理:
1.新建IID表:
找个空白的位置,用于新建IID表,我以9860为例。
由于用于修复的文件是从内存中dump出来的,因此不存在RVA和offset的区别,任何1个地址都是相同的。
先来看下IID的结构:
IID的结构如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
      DWORD   Characteristics;            // 0 for terminating null import descriptor
      DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                          // -1 if bound, and real date\time stamp
                                          //   in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                          // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;               // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
由5个DWORD组成

可知,他有5个DWORD值组成,具体如下:
1.OriginalFirstThunk
2.TimeDateStamp
3.ForwarderChain
4.Name
5.FirstThunk

下面就来开始填充。以第一个DLL为例:
由FSG结构中输入表的RVA得,下面的结构:
01007604010012C4np_fsg2.010012C4-------------------------------------IID->FirstThunk
0100760801007AACASCII "comdlg32.dll"---------------------------------IID->Name
0100760C01001174np_fsg2.01001174
0100761001007AFAASCII "SHELL32.dll"
01007614010012B4np_fsg2.010012B4
0100761801007B3AASCII "WINSPOOL.DRV"
0100761C01001020np_fsg2.01001020
0100762001007B5EASCII "COMCTL32.dll"
01007624010012ECnp_fsg2.010012EC
0100762801007C76ASCII "msvcrt.dll"
0100762C01001000np_fsg2.01001000
0100763001007D08ASCII "ADVAPI32.dll"
010076340100108Cnp_fsg2.0100108C
01007638010080ECASCII "KERNEL32.dll"
0100763C01001028np_fsg2.01001028
010076400100825EASCII "GDI32.dll"
0100764401001188np_fsg2.01001188
010076480100873CASCII "USER32.dll"

上述结构中,各个分支都存放着2个值:
1.dll的名称
2.起始FirstThunk的值

填充如下:
1.OriginalFirstThunk的值可以填充为00000000,我这里填充的是FirstThunk的值
2.TimeDateStamp的值可以填充为00000000,我这里填充的是FFFFFFFF
3.ForwarderChain的值可以填充为00000000,我这里填充的是FFFFFFFF
4.关键数据,这里的值是指向dll的name的,从上面的表可知,第一个dll是comdlg32.dll,而其地址为01007AAC,减去基址得00007AAC,因此这里填AC7A 0000
5.关键数据,这里的值是FirstThunk,由上表得,地址为010012C4,减去基址整理得:C412 0000
这里你可以自己找个空白的地址,然后自己填充IAT表,然后把修改FirstThunk的值,这里我直接使用的原始的输入表信息

下面的几个按照上面的方法进行填充
完全填充后如下:

2.填充IAT
以第一个DLL:comdlg32.dll的第一个函数为例子:
先来看下OD中的数据
OD中的数据:

010012C401007A7Bnp_fsg2.01007A7B
010012C801007A5Fnp_fsg2.01007A5F
010012CC01007A9Fnp_fsg2.01007A9F
010012D001007A51np_fsg2.01007A51
010012D401007A41np_fsg2.01007A41
010012D801007A8Bnp_fsg2.01007A8B
010012DC01007A6Bnp_fsg2.01007A6B
010012E001007A15np_fsg2.01007A15
010012E401007A2Dnp_fsg2.01007A2D


分析下:
IID->FirstThunk=12C4,
看下12C4的值:
=01007A7B
翻到7A7B看下数据:
01007A7B00 50 61 67 65 53 65 74 75 70 44 6C 67 57 00 00.PageSetupDlgW..
不过7A7B并没有直接指向函数的头部,而是在前1个字节,这也是上面壳在填充输入表的时候,要inc eax的原因。
很明显是指向API函数的
不过由于windows在自己装载PE的时候,是允许按序号转载的,所以在每个函数前,都会预留2个字节,给存放序号用。
故我们的在填充FirstThunk的时候,需要把原始的数据再减去1(因为本来就已经向前1个字节了)
因此,修改后的数据为:01007A7B
再减去基址01000000,整理后得:7A7A 0000

当FirstThunk所指向的值为000000000的时候,改dll填充完毕。
因此我们要把遇到的FFFFFF7F都修改为00000000

后面的都是按照这个原则进行修改,就是把原来的值-01000001
完全修改后:

这样输入表就处理好了。

最后的工作就是修复输入表的RVA和 size,以及改写OEP的值了

OEP的值=739D
输入表的RVA=9860
size=(01001340-01001000)/4-8=C8,当然这个值可以改写的稍大点。

全处理完后,保存,OK了。
文件正常运行。脱壳到此结束。

整理一下:

1.运行到010001C1,dump文件
2.按上面的过程手动处理输入表
3.修改输入表RVA,size,以及OEP的值
4.保存,完工

基本上把这个压缩壳剖析完了,可能讲的又点啰嗦了。不过觉得该讲的也都讲述清楚了。
希望新手朋友看完有所收获吧。

作者:ximo

西氏 发表于 2009-9-10 19:26

学习了。原来时分析过程。

zapline 发表于 2009-9-10 19:50

跟着超人看系列脱壳教程

zzage 发表于 2009-9-10 20:02

我是来膜拜的!!!!!

wgz001 发表于 2009-9-10 20:04

先膜拜后学习   

小糊涂虫 发表于 2009-9-10 21:23

跟着超人看系列脱壳教程
zapline 发表于 2009-9-10 19:50 http://bbs.52pojie.cn/images/common/back.gif
我也是一样看系列教程的~~~~~~`:lol

8568309 发表于 2009-9-10 22:08

跟随前人的脚步学习

CHHSun 发表于 2009-9-12 16:02

专门来膜拜的{:299_843:}

255411 发表于 2009-9-13 07:53

老大 好久没脱壳依然还是那么强悍。肯定是在偷着研究高科技

Hmily 发表于 2009-9-16 16:30

徐教授发帖了~强势插入惨无人道的围观~
页: [1] 2
查看完整版本: FSG的分析以及输入表的手动处理