高数考完了,终于可以轻松的写代码了。哈哈
终于开始写输入表了。输入表是我们项目的重头戏。首先还是先介绍下输入表的基本知识。
自己写的话有点麻烦,还是copy一下小甲鱼辛辛苦苦打出来的课件吧
PE文件头的 IMAGE_OPTIONAL_HEADER 结构中的 DataDirectory(数据目录表) 的第二个成员就是指向输入表的。而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组开始。每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构。在这个 IID数组中,并没有指出有多少个项(就是没有明确指明有多少个链接文件),但它最后是以一个全为NULL(0) 的 IID 作为结束的标志。
我们首先来找到这个输入表的地址:还是首先附上上次的那张图片:
如果看不清楚的话,这里给个链接,是我自己的博客里面的,放大应该就看的清楚了
http://miibotree.com/?p=226
在PE头那里我们可以看到数据目录表,(用红框框圈起来的那里) IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
接着红色的箭头指向了我们的数据目录表的数据结构
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory (输入表,我们所需要的) #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #defi ne IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //输入表的RVA DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
得到RVA只是第一步,我们接下来要判断输入表在哪个区块里面。
当然这个时候我们必须要做的就是读取区块的信息了。区块的数据结构如下(可以在图里面找到哦)
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; //区块的RVA DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;通过输入表的RVA与区块的RVA比较,我们就能知道输入表在哪个区块里面。
好,第二步已经完成了。
现在是最关键的第三步:
这里必须明确掌握RVA, VA, offset。因为这个是PE文件的最重点。下面再引用一下小甲鱼的课件:
在前边我们探讨过RVA 这个词,但对于初次接触PE 文件的朋友来说,显得尤其陌生和无奈。中国人不喜欢老外的缩写,但总要**着接受……不过,在有了前边知识的铺垫之后,现在来谈这个概念大家伙应该能够得心应手了。起码不用显得那么的费解和无奈~
RVA 是相对虚拟地址(Relative Virtual Address)的缩写,顾名思义,它是一个“相对地址”。PE 文件中的各种数据结构中涉及地址的字段大部分都是以 RVA 表示的,有木有??
更为准确的说,RVA 是当PE 文件被装载到内存中后,某个数据位置相对于文件头的偏移量。举个例子,如果 Windows 装载器将一个PE 文件装入到 00400000h 处的内存中,而某个区块中的某个数据被装入 0040**xh 处,那么这个数据的 RVA 就是(0040**xh - 00400000h )= **xh,反过来说,将 RVA 的值加上文件被装载的基地址,就可以找到数据在内存中的实际地址。
很明显,我们发现,DOS 文件头、PE 文件头和区块表的偏移位置与大小均没有变化。而各个区块映射到内存后,其偏移位置就发生了变化。
RVA 使得文件装入内存后的数据定位变得方便,然而却给我们要定位位于磁盘上的静态PE 文件带来了麻烦。举个例子说话:……由于例子在视频中,这里争取时间我就不写啦,大伙看参考视频演示吧。
当处理PE 文件时候,任何的 RVA 必须经过到文件偏移的换算,才能用来定位并访问文件中的数据,但换算却无法用一个简单的公式来完成,事实上,唯一可用的方法就是最土最笨的方法:
步骤一:循环扫描区块表得出每个区块在内存中的起始 RVA(根据IMAGE_SECTION_HEADER 中的VirtualAddress 字段),并根据区块的大小(根据IMAGE_SECTION_HEADER 中的SizeOfRawData 字段)算出区块的结束 RVA(两者相加即可),最后判断目标 RVA 是否落在该区块内。
步骤二:通过步骤一定位了目标 RVA 处于具体的某个区块中后,那么用目标 RVA 减去该区块的起始 RVA ,这样就能得到目标 RVA 相对于起始地址的偏移量 RVA2.
步骤三:在区块表中获取该区块在文件中所处的偏移地址(根据IMAGE_SECTION_HEADER 中的PointerToRawData 字段这个字段是物理偏移地址 将这个偏移值加上步骤二得到的 RVA2 值,就得到了真正的文件偏移地址。
当然这里理解起来可能比较困难,大家可以看看小甲鱼的视频然后看看加密与解密的那本书最后动手实践来慢慢理解的。
将这个 真正的文件偏移地址加上基地址就是输入表的地址了。
如此一来,我们千里迢迢终于追踪到了输入表的地址了。