目录
导入函数地址表:(IAT)
PE 文件中所有导入函数 jmp 指令操作数的集合,组成了另外一个数据结构,这个结构就是导入函数地址表(Import Address Table,IAT)。该地址表是数据目录的第13 个数据目录项。
导入函数地址表是一个双字的数组,每个双字代表的是一个导入函数的VA,该地址称为导入函数地址(Import Address,IA)。用户程序通过无条件跳转指令JMP跳转到 VA 指定处.
由于 IAT中定义了不止一个链接库的函数,为了区分这些从不同链接库引入的函数,规定所有引入函数按照链接库分类 ; 相同链接库的函数地址排列在一起,最后以一个双字的0结束。
导入表和 IAT 是有紧密联系的,通过桥 2即可定位到 IAT。在内存中,桥 1 可以让你找到调用的函数名称或函数的索引编号(“值 - 名称”),桥 2 却可以帮助你找到该函数指令代码在内存空间的地址。
当 PE 被加载进虚拟地址空间(内存)以后,IAT 的内容会被操作系统更改为函数的 VA。这个修改最终会导致通向“值 - 名称”描述的桥 2 发生断裂。
当桥 2 发生断裂以后,如果没有桥 1 作为参照〈因为桥 1 和桥 2 维护了两个一 一对应的函数 RVA,就是“值-名称”对应函数地址),我们就无法重新找到该地址到底是调用了哪个函数。这就是为什么会在导入表数据结构中存在两个桥的原因,也是为什么单桥导人表结构中无法实施绑定的原因 。
导入表双桥结构应用举例:
构造调用同一个 DLL 文件的多个函数的导入表:
代码很简单,首先调用函数 FindWindow 获取任务栏的句柄 〈行24) 存储到 hTray 变量中。然后调用 ShowWindow (行 26) 将任务栏窗口隐藏起来,最后通过函数 EnableWindow 将任务栏设置为禁止操作。编译链接生成最终要分析的 PE 文件 LockTray.exe。
LockTray 的导入表分析:
use32.dll动态链接库的导入函数分析
程序中一共使用了 user32.dll 的三个API 函数,依次为 : EnableWindow、FindWindow和 ShowWindow,下面具体分析导入表构造:
>> 5C 20 00 00
桥 1(导入表的第一个双字),最高位为 0,表示这是一个RVA,表示函数是以字符串类型的函数名导入的。从文件偏移 0x0000065C 处取出的值应该是一组 IMAGE_THUNK_DATA,一直找到双字 0为止。取出的这些 IMAGE_THUNK_DATA 结构依次是:
前三个 RVA 分别指向了 IMAGE_IMPORT_BY_NAME 结构,代表调用的三个函数字符串文件偏移地址:
下面分析的是桥2,上面分析的是桥1,但是桥2分析方法同桥1,桥2在618+10 =628h处:
>> 08 20 00 00
桥 2(导入表的第五个双字),最高位为 0,表示这是一个RVA,表示函数是以字符串类型的函数名导入的。从文件偏移 0x00000608h 处取出的值应该是一组 IMAGE_THUNK_DATA,一直找到双字 0为止。取出的这些 IMAGE_THUNK_DATA 结构依次是:
注意:
桥2有比桥1明确的IAT地址范围,IAT地址在600h~618h处,所以桥2的 IMAGE_THUNK_DATA 就在这个IAT范围里找。
>> 8A 20 00 00
>> 7C 20 00 00
>> 6C 20 00 00
>> 00 00 00 00
前三个 RVA 分别指向了 IMAGE_IMPORT_BY_NAME 结构,代表调用的三个函数字符串文件偏移地址:
kernel32.dll动态链接库的导入函数分析
找出导入表范围内(618h~ 654h)kernel32.dll的桥1,桥2。其它分析方法同user32.dll:
用一个图来表示 LockTray.exe 的导入表结构:
文件中的 LockTray 导入表,在文件中时桥2和桥1指向的都一样都是指向函数名,所以这里没有画出来桥2的函数指针表:
装入内存后的 LockTray.exe 的导入表,这里桥2指向的函数入口地址暂未有具体分析:
从现在掌握的对导入表的知识来看,定位导入函数地址表的方法有两种:
1:第一种方法是从导入表的最后一个导入表项 IMAGE_IMPORT_DESCRIPTOR 结构中的字段 IMAGE_IMPORT_DESCRIPTOR.FirstThunk 的桥2定位 IAT。
2:第二种方法是通过PE头数据结构的数据目录项IMAGE_DATA_DIRECTORY的第 13 个数据项的描述直接定位 IAT。(具体可看导入表定位目录)
3:诚如结构里所描述的那样,每个动态链接库都维护了自己的 IAT 内容,而且不同链接库维护的这些内容可以是不连续的,这在后面还会讲到。
4:当程序加载到内存以后,导入表部分发生变化的值正是 IMAGE_IMPORT_DESCRIPTOR结构中的 FirstThunk(桥2) 字段指向的函数指针表内容。这些内容已经不是指向函数名的指针了,而是指向了虚拟内存中该函数的可执行代码的地址! 所以其含义也由原来的函数指针更改为函数的入口地址。现在看来,所有的这些值最终都指向了同一片连续的区域,从而形成了我们常说的 IAT。
从另一个侧面分析了导入表在内存中的数据组织:(2000h是IAT,2010h是导入表)
如图所示:(IAT地址在内存中变成函数入口地址了)
(1) 是user32.dll 动态链接库。
(2) 是 kernel32.dll 动态链接库。
①是指向引入 user32 函数名的指针数组,由导入表范围内红框中的两个动态链接库对应的桥1指向。
②是指向引入 kernel32 函数名的指针数组,由与桥1相隔5个字节的蓝框中的桥2指向。
① '是指向引入 user32 国数入口地址的指针数组。
② '是指向引入 kernel32 函数入口地址的指针数组 。
①'+②'组成了标准的IAT。如果在内存中,这里的地址会被修改成相关函数的绝对地址,程序只要跳转到这里就可以执行相关函数了。
图中涉及与导入表有关的所有数据,但是狭义上的导入表却只是几个 IMAGE_IMPORTDESCRIPTOR 结构而已,因为在数据目录中记录的导入表长度只计算了这些结构的大小,其他的数据并没有包含在内! 这一点一定要清楚。上图的导人表长度只有 3ch 个字节,但与导入表相关的数据却长达 92h 个字节。