PE手工分析-导入表和导入函数地址表分析
在上篇:PE手工分析-PE头 中我们了解了PE文件头内容,在此基础上我们来分析一下导入表和导入函数地址表的内容.
还是使用上篇使用的PE文件来分析,上一篇中我们基本上已经定位出PE头的位置以及相应内容.
在PE扩展头中包含 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
通过该数据目录表我们可以进一步对导入表和导入函数地址表进行详细的分析(其数据目录表分析也雷同)
详细信息可以查看MSDN定义.
数据目录表结构定义:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]数据目录表分析如下:
索引 |
数据目录表 |
文件偏移地址 |
偏移 |
大小 |
说明 |
0 |
导出表 |
00000170h |
00 00 00 00 |
00 00 00 00 |
没有导出 |
1 |
导入表 |
00000178h |
00 E0 01 00 |
64 00 00 00 |
|
|
... |
|
|
|
|
12 |
导入地址表 |
000001d0h |
A0 E2 01 00 |
3C 02 00 00 |
|
|
… |
|
|
|
|
|
... |
|
|
|
|
|
... |
|
|
|
|
导入表RVA=01E000
IAT RVA=01E2A0
RVA的概念请参考:RVA,另外可以参考《加密解密ii》中的第二章介绍
了解了RVA之后我们就明白了在获取导入表的RVA之后需要根据节地址转换成文件相对地址FOA
所以为了获取导入表的文件相对地址需要对PE的节表进行分析
首先需要知道节表所在的位置(紧跟数据目录表结尾)
总共16个数据目录,导入地址函数表在第12(0开始)个位置
节表起始位置=1d0+8+3×8=1F0
节表结构如下:
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME];// #define IMAGE_SIZEOF_SHORT_NAME 8 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
从上面镜像头分析可以之后该PE中包含有7个节
每个节包含40个字节
总共占有空间:40×7=280(0x118)->01F0-20E
可以看到每个节对应的内容如下:
段名称 |
虚拟地址 |
虚拟大小 |
物理地址 |
物理大小 |
标志 |
PointerToRawData |
.textbss |
1000 |
0 |
10000 |
0 |
2EE0 |
|
.text |
11000 |
|
897D |
00008A00 |
60000020 |
00000400 |
.rdata |
1A000 |
|
24B5 |
2600 |
40000040 |
8E00 |
.data |
01D000 |
|
05F4 |
2000 |
C0000040 |
B400 |
.idata |
0001E000 |
|
10C8 |
1200 |
C0000040 |
B600 |
.rsrc |
00020000 |
|
0459 |
0600 |
40000040 |
C800 |
.reloc |
00021000 |
|
06DA |
0800 |
42000040 |
CE00 |
节表的分析完毕,然后我们需要针对导入表的RVA获取相应的FOA
RVA:01E000=>PointerToRawData:B600
指向导入表描述符
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // DWORD OriginalFirstThunk; // } DUMMYUNIONNAME; //0001e270=>B600+270=b870->01E4DC->_foo@4 DWORD TimeDateStamp; // 0 DWORD ForwarderChain; //0 DWORD Name; //01E4E6=>B600+4E6=BAE6->dllExport.dll DWORD FirstThunk; //01E4AC=>B600+4AC=BAAC->01E4DC->_foo@4 } IMAGE_IMPORT_DESCRIPTOR;
其文件内容如下:
OriginalFirstThunk指向地址(RVA)01E4DC->(FOA)BADC
FirstThunk指向地址(RVA)01E4AC->(FOA)BAAC对应结构为
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
相应IMAGE_THUNK_DATA指向的内容为
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //00 BYTE Name[1]; //_foo@4 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
这里对应的导入表分析基本完成了
- 相应的IAT分析如下:
RVA:01E2A0=>PointerToRawData:B600+(2A0)=B8A0在找到了IAT表的地址后让我们来看一下其中的内容
可以看到这里的BAAC正好处理B8A0的区域中.
所以很显然,FirstThunk属于IAT中的某一个地址区域
相应PE文件内容如下
Dll加载之前导入表结构
在DLL加载之后导入表中内容将被操作系统填充为函数的VA
到这里基本上已经把IAT和INT绑定起来了,而在函数调用过程中将如何实现调用IAT的函数地址呢?
程序中每个调用 API 函数的 CALL 指令所使用的地址都是相应函数登记在 IAT 表的地址
源文档 <http://blog.csdn.net/misterliwei/article/details/840983>
那么,IAT导入函数地址表,和导入表有什么联系呢??
其实,所有的DLL的IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk指向的是一片连续的内存空间,第一个DLL的IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk的值,就是IAT表的起始地址!
也就是说,导入表中的首个IMAGE_IMPORT_DESCRIPTOR的FirstThunk字段,在内存中,等同于IMAGE_NT_HEADER.OptionHeader.DataDirectory[12].VirtualAddress
数据目录表第13项,索引值为12,就是IAT了。
在RING3的API劫持中,很多人都会选择使用IAT劫持,也就是基于这个理论的。
源文档 <http://tieba.baidu.com/f?kz=726947835>
对于IAT和导入表之间的关系用如下图片作为结尾
导入表中的FirstThunk指向IAT表中的某一项
之前的理解有问题所以后面加以修改