• 标 题:ASProtect v0.95保护
  • 作 者:kanxue
  • 时 间:2000-5-30
  • 链 接:http://bbs.pediy.com

ASProtect v0.95保护

教程写作: 看雪
技术指导:D.boy 和RuFeng
写作日期:2000年5月30日
目标程序:ShowDep 4.0 beta 1
程序大小:Showdep.exe 为177K
程序下载:ShowDep 4.0
使用工具:Softice 4.05; ProcDump 1.6.2 Final; FrogsICE v0.43;Icedump 6.016

  首先忠心感谢D.boy 和RuFeng的热心帮助,是在他们的帮助下,我才对PE文件有一定程度的了解。我把从他们那里吸取的经验总结了一篇文章,希望对大家有所帮助。为了使初学者能更好理解这文章,本人尽量写的仔细点。

一、Import表的一些理论知识

在你看这篇文章之前,你应对PE文件的结构有一定了解,否则请先看看PE介绍.

1、现在不少软件脱壳后Import表被损坏或地址发生改变,需要我们手动找到正确的Import表来替换被破坏的Import表,并修正Import表的地址,程序才能正确执行。

程序装载时,需要装载很多函数和DLL文件,这时程序需要判定目标函数的地址并将该函数插补到该执行文件的映像中,所需要的信息都是放在PE文件的Import表,PE文件中的每一个输入函数都明确的列于该表中。

一般来说Import表是存放在程序的.idata块,它一般包含其他外来DLL的函数及数据信息。但也有不少程序的Import表不存在idata块中,给我们判断Import表的地址造成困难,但不要着急,只要了解Import表的结构你就能迅速定位Import表的地址。

2、Import表以一个IMAGE_IMPORT_DESCRIPTOR数组开始。每一个被PE文件隐式连结进来的DLL都有一个IMAGE_IMPORT_DESCRIPTOR。在这个数组中,没有字段指出该结构数组的项数,但它的最后一个单元是NULL,可以由此计数算出该数组的项数。IMAGE_IMPORT_DESCRIPTOR的格式如下:

image_import_descriptors结构:

IMAGE_IMPORT_DESCRIPTOR struct
 riginalFirstThunkdd 0该字段是一个指针数组的RVA偏移。其中每一个指针都指向一IMAGE_IMPORT_BY_NAME结构
 TimeDateStampdd 0时间及日期标志,在这可以忽略
 ForwarderChaindd 0正向链结索引,在这可以忽略
 Namedd 0以NULL结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名,比如"Kernel32.dll" 或"USER32.DLL" (关键!,我们定位Import表的依据)
 FirstThunkdd 0该字段是在image_thunk_data联合结构中的RVA偏移。大多数情况下,image_thunk_data是指IMAGE_IMPORT_BY_NAME结构的指针。如果不是一个指针的话,那它就是该功能在DLL中的序号。
IMAGE_IMPORT_DESCRIPTOR ends

3、为了使大家更好理解,我们以ShowDep 4.0 beta的Import表为例,ShowDep 4.0用 ASProtect 加壳,用prodump 分析,发现没有idata段。 脱壳大师D.boy很成功分析出该软件的Import表:import rav 0042D104,size 00001470 ;image Base(基址)=00400000。

该软件的Import表的image_import_descriptors结构如下:

-----SHOWDEP!.rdata+1104--------------------------dword-------------PROT---(0)--
0030:0042D104 ①0002D23C ②00000000 ③00000000 ④0002DA8A <............... ^
0030:0042D114 ⑤0002C070 ⑥0002D43C ⑦00000000 ⑦00000000 p...<........... v
(图一)

为了解释方便,我在每个数据前加了序号。上图就是一个典型的Import表开始处的image_import_descriptors结构,Import表就是以这个数组开始的一段连续内存空间,在这里大小是1470的连续空间。各项数据含义如下:

IMAGE_IMPORT_DESCRIPTOR struct
 riginalFirstThunkdd 0①0002D23C
 TimeDateStampdd 0②00000000
 ForwarderChaindd 0③00000000
 Namedd 0④0002DA8A(关键!,我们定位Import表的依据)
 FirstThunkdd 0⑤0002C070
IMAGE_IMPORT_DESCRIPTOR ends

现在我们将image_import_descriptors结构中每项的地址均显示分析一下:


Name选项(我们定位Import表地址就是以此为依据的)

在这例Name项值为:0002DA8A

下命令DD 42DA8A (显示内存地址42DA8A的数据,其中42DA8A=0002DA8Aimage Base(基址)

-----SHOWDEP!.rdata+1A8A--------------------------dword-------------PROT---(0)--
0030:0042DA8A 4E52454B 32334C45 4C4C442E 019A0000 KERNEL32.DLL.... ^
0030:0042DA9A 64616F4C 73727543 0041726F 6F4C01AB LoadCursorA...Lo v
(图二)

想必大家己睁大眼睛了,发现什么有价值的东西?对,就是KERNEL32.DLL!就是我们的突破口。

Import表装载的基本原理是:根据Import表的指示找到外部模块的文件名,再使用Win32 API函数GetModuleHandleA获得该模块在内存中的句柄。如果没在内存中就使用LoadLibraryA API调用装入该模块。随后使用获得的模块句柄调用Win32 API函数GetProcAddress 获得该模块中Import表指定功能的实际地址,加上装入基址,并且填入Import表的FirstThunk所指的IMAGE_IMPORT_BY_NAME结构指针数组中,完成该模块的一个功能的人工装入填写。循环调用函数GetProcAddress以获得其他功能调用的地址,加上装入基址,并填入之,以完成一个外部模块的装入。再循环上述过程对其他模块进行装入。

因此我们可以从函数LoadLibraryA入手,该函数会装入外部模块,我们监视这函数的入口参数是否为KERNEL32.DLL,以此来确定Import表的状况。即确定 image_import_descriptors结构中的name选项。

函数LoadLibrary:

HINSTANCE LoadLibrary(
 LPCTSTR lpLibFileName // 执行模块的文件名和地址
);

只要函数LoadLibrary参数的模块名为KERNEL32.DLL,就会出现图二的情况,这时KERNEL32.DLL地址为0042DA8A。因此image_import_descriptors结构中name为0042DA8A—400000=2DA8A,这时利用S命令在内存中查找字条串002DA8A,就可确定import表的地址。

(到这里我们就能确定Import表的地址了,下面几项可帮助我们大家更好理解Import表)

riginalFirstThunk

在这里riginalFirstThunk项值为:0002D23C

下命令DD 42D23C (显示内存地址42D23C的数据,其中42DA8A=0002D23C+image Base(基址)

-----SHOWDEP!.rdata+123C--------------------------dword-------------PROT---(0)--
0030:0042D23C 0002D798 0002D7A8 0002D7BE 0002D782 ................ ^
0030:0042D24C 0002D7CC 0002D7E0 0002D7F6 0002D80A ................ ^
0030:0042D25C 0002D81C 0002D830 0002D840 0002D854 ....0...@...T... v
0030:0042D26C 0002D868 0002D87C 0002D890 0002D8A0 h...|........... v
(图三)

图三是一个指针数组,其中每一个指针都指向一IMAGE_IMPORT_BY_NAME结构。

我们以第一个指针0002D798为例,显示它所指的IMAGE_IMPORT_BY_NAME结构.
下命令:dd 42d798(注意:0002D798+image Base(基址)

-----SHOWDEP!.rdata+1798--------------------------dword-------------PROT---(0)--
0030:0042D798 6547011B 636F4C74 69546C61 0000656D ..GetLocalTime.. ^
0030:0042D7A8 6F430025 6E69746E 65446575 45677562 %.ContinueDebugE ^
0030:0042D7B8 746E6576 022B0000 65736552 65764574 vent..+.ResetEve v
0030:0042D7C8 0000746E 615702CB 6F467469 62654472 nt....WaitForDeb v
(图四)

FirstThunk

在这例,FirstThunk项的值为:0002C070

下命令dd 42c070 (注意:0002c070+image Base(基址)):

-----SHOWDEP!.rdata+0070--------------------------dword-------------PROT---(0)--
0030:0042C070 0002D798 0002D7A8 0002D7BE 0002D782 ................ ^
0030:0042C080 0002D7CC 0002D7E0 0002D7F6 0002D80A ................ ^
0030:0042C090 0002D81C 0002D830 0002D840 0002D854 ....0...@...T... v
0030:0042C0A0 0002D868 0002D87C 0002D890 0002D8A0 h...|........... v
(图五)

图五是一个指针数组(image_thunk_data联合结构),大多数情况下,image_thunk_data是指IMAGE_IMPORT_BY_NAME结构的指针。如果不是一个指针的话,那它就是该功能在DLL中的序号。不知你发现没有,图五的数据和图三完全相同,原因就是这个。

二、确定Import表的地址和大小并修正Import表

通过上面的讲解,想必大家对Import表己比较熟悉了吧,现在以脱ShowDep 4.0 beta 1的壳为例,讲解定位Import表的位置和大小的几种方法。

方法一:通过idata来确定Import表的位置

大部分加壳程序的块表中都有.idata这一项,.idata包含其他外来DLL的函数及数据信息,也就是说Import表的起始地址就是.idata的地址。如是这种情况,脱壳就简单多了。

先dump取正确的Import表(假设取名为idata.bin),然后在解压入口点full dump取整个程序(假设取名为dump.exe),用 Procdump打开dump.exe文件, 察看.idata Section. 记下Raw Size和 Raw Offset的值。用HexWorkshop打开dump.exe, 将idata.bin拷贝/粘贴到 dump.exe(粘贴位置为Raw Offset, 大小为Raw Size). 再修正Entry Point和Import表的地址(此值就是.idata的RVA)

具体操作参考后面几节的脱壳教学实例。

方法二:没.idata一项,利用bpx loadlibrarya来判断Import表的位置

由于ShowDep 4.0 beta 1没.idata一项,因此要确定Import表的位置和大小较困难,步骤如下:

①分析ShowDep 4.0 beta 1的文件PE头

运行 Procdump,点击pe-editor按钮,选中ShowDep.exe文件:

Size of image : 000A0000 ; 这个PE文件执行时分配的内存空间。
image Base : 00400000 ; 基址

②确定Import表的地址

a.先装载Icedump
在这用Icedump 6.016版本,其命令操作形式完全和以前的版本不同。先在Icedump目录里运行相应SOFTICE版本的icedump.exe(我用的SOFTICE是4.05版,因此在win9x/405目录下运行icedump.exe),如Icedump装载成功,Icedump会返回如下信息:

icedump v6.0.1.6 for winice v4.05 loader
icedump unloaded
icedump loaded
←出现这句话表示Icedump装载成功

C:>
(图六)

b.再装载FrogsICE
由于ShowDep能检测到SOFTICE的存在,因此装载Frogsice就可躲过。在我机子里:FrogsICE 1.00 Final和Icedump不能很好兼容工作,因此我将FrogsICE换成版本v0.43,hehe..它们配合的很好。双击FPloader.exe文件即可装载成功。
OK,到此你的SOFTICE的功能己大大加强里。
这时试试运行ShowDep,这时屏幕将蓝屏给你一菜单选项,告知ShowDep发现了SOFTICE,是否欺骗它,这时你按ESC按钮,程序即可正常运行。(注:我的SOFTICE用icepath打过补丁)

c、拦截函数loadlibararya

下命令:bpx loadlibrarya do "dd esp->4"
(注:在TRW2000下实现同样功能的命令是: bpx loadlibrarya do "dd *(esp+4)"

这个命令就是当拦截loadlibararya函数时,显示其入口参数的在内存的值,如:

0137:00710242 PUSH EAX
0137:00710243 CALL [loadlibarary] ;当调用此函数将中断,并显示push参数的值,在这里即:d eax

ok,断点设置好后,运行ShowDep程序,将中断,此时按F5一直到数据窗口显示为:KERNEL32.DLL字符。在我win97系统下,只要按两下F5,即可看到如下情况:

-----SHOWDEP!.rdata+1A8A--------------------------dword-------------PROT---(0)--
0030:0042DA8A 4E52454B 32334C45 4C4C442E 019A0000 KERNEL32.DLL.... ^
0030:0042DA9A 64616F4C 73727543 0041726F 6F4C01AB LoadCursorA...Lo v
(图六)

hehe...看看图六和图三是不是一样啊!其中前面的地址0042DA8A就是关键。
0042DA8A就是image_import_descriptors结构中的name项的值,因为该结构如下:

0030:0042D104 0002D23C 00000000 00000000 0002DA8A <............... ^
0030:0042D114 0002C070 0002D43C 00000000 00000000 p...<........... v

因此这时我要在数据窗口向前查找字符串0002DA8A0002DA8A0042DA8A-基址):

下命令:S DS:400000 L FFFFFFFF 8A DA 02 00
(注:在TRW2000下实现同样功能的命令是: S 30:0 L FFFFFFFF 8A DA 02 00)(用上面的好象不行,但愿新版能改进)
结果如下:

-----SHOWDEP!.rdata+1100--------------------------dword-------------PROT---(0)--
013F:0042D100 0042B385 0002D23C 00000000 00000000 ..B.<........... ^
013F:0042D110 0002DA8A 0002C070 0002D43C 00000000 ....p...<....... v
(图七)

仔细比较图七和图一,发现这就是Import表的IMAGE_IMPORT_DESCRIPTOR数组。
如你看不习惯,可再下命令: D 42D110-C 这样就可显示和图一一样的画面了。

这样就可确定Import表的地址是2D104=0042D104—400000(基址)

③确定Import表的大小

现己将Import表的起始地址确定了:RVA=42D104。只要确定Import表的尾部就可计算出其大小,Import表在内存里是连续存放的一段数据,其一般结尾处一段内存空间都是0,在此例,你开始先定位来到Import表的起始处,按ALT+↓向下翻页(或ALT+PageDown),直到看到如下情况:

013F:0042E54A 65530262 766E4574 6E6F7269 746E656D b.SetEnvironment
013F:0042E55A 69726156 656C6261 011D0041 4C746547 VariableA...GetL
013F:0042E56A 6C61636F 666E4965 0000576F 00000000 ocaleInfoW......
013F:0042E57A 00000000 00000000 00000000 00000000 ................ v
013F:0042E58A 00000000 00000000 00000000 00000000 ................ v
(图八)

字符串0000576F就是Import表的最后一项,其后面一位000000的地址为:42e574(其边界就是上面红色的W,如你在SOFTICE不能确定可DUMP后在十六进制工具Hexworkshop很方便知边界地址)

因此Import表的大小=42E574-42D104=1470

④、找入口点

在SOFTICE你会来到如下:
0137:00710B35 MOV EDX,[EAX] 
0137:00710B37 MOV EAX,[EBP+08] 
0137:00710B3A ADD EDX,[EAX+18]
0137:00710B3D MOV EAX,[EBP+08]
0137:00710B40 MOV EAX,[EAX+1C]
0137:00710B43 CALL 007104C8 ←按F8进去
0137:00710B48 POP EDI
0137:00710B49 POP ESI
0137:00710B4A POP EBX
0137:00710B4B POP ECX

按F8进去来到:

0137:007104C6 8BC0 MOV EAX,EAX
0137:007104C8 89C4 MOV ESP,EAX
0137:007104CA 89D0 MOV EAX,EDX
0137:007104CC 8B1D34567100 MOV EBX,[00715634]
137:007104D2 89041C MOV [EBX+ESP],EAX
0137:007104D5 61 POPAD
0137:007104D6 50 PUSH EAX ←此处EAX=422c3a即入口点的值
0137:007104D7 C3 RET ←返回到入口点
(图九)

此时程序己完全解压准备运行了。记下程序入口点:00422c3a 在dump前,清除所有的断点:bc *. 因此你可在SOFTICE下用命令:

:/dump 400000 A0000 c:\temp\dump.exe
(如你是用Icedump 6.016以前版本用此命令:pagein d 400000 A0000 c:\temp\dump.exe)

修正PE文件头

用 Procdump打开刚建好的 dump.exe文件,点击pe-editor按钮,然后再点击SECTIONS按钮,在每个section点击右键,选中Edit section,把所有的 section 的PSize == VSize offset == RVA (即让物理地址和大小等于虚拟地址和大小)。如你是用Procdump脱的壳,可省去这一步。

在改完所有的sections后,按OK,存盘后,你在资源管理器中刷新一下,就会发现dumped.exe的图标回来了,但还不能运行,你还要修正入口点和import表

将入口点(Entry Point)改为:00422c3a(记着:00422c3a-imagebase=22c3a

再点击Directory按钮,将Import Table改为 RVA (2D104 );而其选项Size只要比0大就可;(这程序DUMP后improt并没被破坏,只要把import rav/size 填上就OK了!) 在这例中,Import表的尺寸没用上,但方法要掌握,如碰到Import表损坏的程序,就要替换Import表程了,这时需要Import表的大小了。

然后点击OK,退出Procdump,再运行 dumped.exe ,程序成功运行!

方法三:利用S命令查找字串KERNEL32.DLL来确定Import表的位置

如果软件没.idata项,用方法二bpx loadlibrarya do "dd esp->4"也不能看到KERNEL32.DLL,这种情况就要用S命令来协助了。

bpx loadlibrarya do "dd esp->4"

中断后,凭经验来判断Import表完全触压时机,一般中断在第一次或第二次(某些情况要几次)Import表就基本解压结束了。这时下命令:S DS:400000 l FFFFFFFF 'KERNEL32.DLL'

会在数据区找到KERNEL32.DLL ,但这不一定是image_import_descriptors结构中对应的KERNEL32.DLL项,这要跟据具体情况来分析了,一般我们要找到的KERNEL32.DLL是在xxx:4xxxxxx的地址形式处。

如不能确定何处是关键的KERNEL32.DLL,只好dump后,用十六进制工具来分析了,这样较直观,打开后查找image_import_descriptors类似结构

当然Import表确定方法多种,这里将本人常用的几种方法列出供参考!也欢迎你将自己的经验告诉大家,互相提高。