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 | ||
riginalFirstThunk | dd 0 | 该字段是一个指针数组的RVA偏移。其中每一个指针都指向一IMAGE_IMPORT_BY_NAME结构 |
TimeDateStamp | dd 0 | 时间及日期标志,在这可以忽略 |
ForwarderChain | dd 0 | 正向链结索引,在这可以忽略 |
Name | dd 0 | 以NULL结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名,比如"Kernel32.dll" 或"USER32.DLL" (关键!,我们定位Import表的依据) |
FirstThunk | dd 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+110-8-------------------------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 | ||
riginalFirstThunk | dd 0 | ①0002D23C |
TimeDateStamp | dd 0 | ②00000000 |
ForwarderChain | dd 0 | ③00000000 |
Name | dd 0 | ④0002DA8A(关键!,我们定位Import表的依据) |
FirstThunk | dd 0 | ⑤0002C070 |
IMAGE_IMPORT_DESCRIPTOR ends |
现在我们将image_import_descriptors结构中每项的地址均显示分析一下:
① Name选项(我们定位Import表地址就是以此为依据的)
在这例Name项值为:④0002DA8A
下命令DD 42DA8A (显示内存地址42DA8A的数据,其中42DA8A=④0002DA8A+image
Base(基址))
-----SHOWDEP!.rdata+1A8- -------------------------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+120-
声明:该文观点仅代表作者本人,转载请注明来自看雪
|