杂
数据目录表
位置:ImageNtHeaders->OptionalHeader->DataDirectory
描述:由NumberOfRvaAndSizes
个IMAGE_DATA_DIRECTORY
结构体组成的数组
包含有:输入表、输出表、资源表、重定位表等数据目录项的RVA和大小
输入表、输出表又称导入表、导出表
输入表
输入函数
输入函数就是在程序中被调用,但是其代码不在程序中的函数,这些函数的代码位于DLL中。在调用输入函数的程序中,只保留了如函数名及其所在的DLL名等相关信息。对于磁盘上的PE文件来说,输入函数在内存中的地址是多少,这是无法知道的,只有PE文件被装载到内存之后,Windows装载器才将相关的DLL装入,并将调用了输入函数的指令和此函数的地址联系起来。
数据结构
输入表的数据结构是一个IMAGE_IMPORT_DESCRIPTOR
结构体的数组,每个被链接到此PE文件的DLL都分别对应于一个IID结构体,整个数组以一个NULL的IID作为结束标志。
IID结构体的定义如下:
OriginalFirstThunk和FirstThunk
OriginalFirstThunk
是一个DWORD
类型(4B)的变量,实质是一个RVA值,指向一个名为INT
(ImportNameTable,导入名称表)的结构。
FirstThunk
同理,指向IAT
(ImportAddressTable,导入地址表)结构。
IAT
和INT
都是IMAGE_THUNK_DATA
结构体的数组,每一个IMAGE_THUNK_DATA
结构体都定义了一个导入函数的信息,以一个空结构体作为数组结束标志。
IMAGE_THUNK_DATA
定义如下:
它是一个union结构,共占用4B
当IMAGE_THUNK_DATA
最高位为1时,表示函数是以序号方式输入的,此时,低31位被看作是函数序号。
当IMAGE_THUNK_DATA
最高位为0时,表示函数以字符串型的函数名方式输入,此时,整个DWORD的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME
结构。
IMAGE_IMPORT_BY_NAME
结构体定义如下:
此结构体存放着输入函数的相关信息,Hint
表示函数序号,Name
字段定义了导入函数的名称字符串。
小总结
名称 | 意义 |
---|---|
IMAGE_DATA_DIRECTORY | 存放数据目录项(如导入表)的RVA和Size等信息的结构体 |
IMAGE_IMPORT_DESCRIPTOR | 每个此结构体都对应于一个被链接到此PE文件的DLL |
OriginalFirstThunk | DWORD型变量,实质是一个指针,指向导入名称表INT |
FirstThunk | DWORD型变量,实质是一个指针,指向导入地址表IAT |
IMAGE_THUNK_DATA | INT和IAT使用的结构体,每个此结构体都直接描述或间接指向了来源于DLL的一个导入函数 |
IMAGE_IMPORT_BY_NAME | 函数以字符串类型的函数名方式输入时,其IMAGE_THUNK_DATA指向一个此结构体,此结构体内存着函数序号和导入函数的名称字符串 |
导入地址表IAT
由上所述,有两个并行的指针数组同时指向IMAGE_IMPORT_BY_NAME
结构,第一个是OriginalFirstThunk
指向的导入名称表INT,第二个是FirstThunk
指向的导入地址表IAT。
之所以有两个,其实是因为在PELoader装载PE文件到内存后,IAT中原本的IMAGE_THUNK_DATA
将会被重写为导入函数的地址,如图:
装载过程简单叙述为:
PELoader首先搜索OriginalFirstThunk
,然后迭代搜索其指向的INT中所有的IMAGE_THUNK_DATA
,进而找到每个IMAGE_IMPORT_BY_NAME
结构所指向的输入函数的名称或序号,然后获得输入函数的RVA,再用所有的函数RVA覆盖原来IAT中的IMAGE_THUNK_DATA
。覆盖完成后,输入表中其余部分就不重要了,有IAT足以让程序正常运作。
实例演示
文件
#include <windows.h>
int main(){
MessageBox(NULL, "hello", "world", MB_OK);
}
注意以32位方式编译
用LordPE打开
看到输入表的RVA是00006000H
查看节表,发现此RVA在.idata节
输入表的RVA相对于.idata节起始位置的偏移量为0,则此节的FileOffset00002400H
即为输入表的FileOffset。
换成C32ASM打开PE文件,Ctrl+G跳到此位置,即为输入表的起始位置。
一个IID结构体是五个DWORD,即20B
此PE文件共有三个IID结构体和一个结束标志,如下
50 60 00 00 00 00 00 00 00 00 00 00 FC 64 00 00 18 61 00 00
A0 60 00 00 00 00 00 00 00 00 00 00 78 65 00 00 68 61 00 00
10 61 00 00 00 00 00 00 00 00 00 00 88 65 00 00 D8 61 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
以第一个IID为例
再来回顾一下其结构
找到其OriginalFirstThunk
值为00006050H
,FirstThunk
值为00006118H
注意是小端方式存储。
将RVA转FileOffset知
INT的FO:2450H
IAT的FO:2518H
先跟踪一下INT
INT的第一个IMAGE_THUNK_DATA
如图为000061E0H
当IMAGE_THUNK_DATA
最高位为0时,表示函数以字符串型的函数名方式输入,此时,整个DWORD的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME
结构。
将此RVA转FileOffset得25E0H
,跟踪下去
此结构体内,前一个WORD为函数序号,图中为00D4H
。然后是函数名,直到遇到一个0x00截止符结束,图中是44 65 6C 65 74 65 43 72 69 74 69 63 61 6C 53 65 63 74 69 6F 6E 00
。
然后看看IAT,其第一个IMAGE_THUNK_DATA
也是61E0H
,顺理成章,静态的文件中,INT和IAT指向的是同一个IMAGE_IMPORT_BY_NAME
结构体数组
镜像
这个版本的LordPE在win10下无法显示进程,所以放到xp下做了
先运行此exe
然后打开LordPE,dump一下,实际相当于保存此exe被加载到内存后的状态
使用LordPE打开dump后的dumped_delete.exe,输入表RVA:6000H。
转FileOffset还是6000H
C32ASM打开,跳到此处
第一个IID的FirstThunk
为6118H
,则IAT的FO:6118H
,跟进,这部分就是IAT了
与文件中的IAT相比
其内容不再是IMAGE_THUNK_DATA
结构体数组,而是被覆盖为导入函数地址了。
用LordPE验证一下,完全符合