导入表
在数据目录中一共有四种类型的数据与导入表数据有关。这四种数据依次为:
1.导入表
2.导入函数地址表
3.绑定导入表
4.延迟加载导入表
2.1 导入表
当程序调用了动态链接库的相关函数,在进行编译和链接的时候,编译程序和链接 程序就会将调用的相关信息写入最终生成的PE文件中,以告诉操作系统这些函数的执行 指令字节码从哪里能够获取。这些信息就是导入表所要描述的内容。
2.2 导入函数
程序开发者在基于汇编语言的源程序中,通过invoke指令调用用户自定义的函数,或者从其他动态链接库中导入的函数。如果没有导入函数的调用机制,程序开发者必须面对自行 开发大量基础源码的尴尬,这将直接导致开发工作无法进行下去。
2.2.1 invoke指令分解
在汇编语言中,**程序一旦被编译,编译器会对invoke指令进行适当分解。**分解后的指令 中将会包含指向导入函数的地址的操作数。当PE文件被装载到内存中时,该操作数就会变成导入函数所在虚拟地址空间真实的VA。
2.2.2 导入函数地址
导入函数是从动态链接库引入的函数,所以,导入函数的地址位于被加载的进程地址空 间中的相应的动态链接库模块内。系统在执行用户程序对导入函数的调用语句时,会跳转到该地址处执行导入函数代码。
2.2.3 导入函数宿主
操作系统会在加载时根据导入表的描述将调用的函数指令字节码复制到进程地址空间中。
为了节约内存资源,操作系统只保证有一份代码存在于物理内存中,大家看到的在毎个进程中加载的不同地址的相同动态链接库,其实只是在页面存取机制下的一个映射而已。
编译程序在编译汇编语言源文件时,会把程序中的invoke语句分解成三部分:
1.将参数压栈部分
2.call指令部分
3.jmp指令部分
call的操作数是jmp指令所在的地址;而jmp指令的操作数则是该导入函数在导入表的地址。在程序中所有的导入函数地址被排列在一起,组成IAT,通过这样的分解操作配合导入表实现对外部函数的调用
导入函数机制
2.3 PE中的导入表
2.3.1 导入表定位
导入表是数据目录中注册的数据类型之一,其描述信息位于数据目录的第2个目录项 IAT也是数据目录中注册的数据类型之一,其描述信息位于数据目录的第13个目录项。
IAT实际是导人表数据组织中的一个重要的组成部分
2.3.2 导入表描述符 IMAGEJMPORT_DESCRIPTOR
导入表数据的起始是一组导入表描述符结构。毎组为20个字节,实例中60个字节的导入表数据被分成三个组。前两组均代表两个动态链接库,后一组为全0结构.我示导入表描述 已经结束。可以通过导入表起始地址和这个空结构计算出导人表中引用的动态链接库的个数。
其实,Windows在査找导入表的时候并不一定要求最后一组的20个字节都为0,只要其中的字段Namel是0就已经满足结束条件了。导人表的每一组都是一个结构,称为导入表描述符IMAGE^IMPORT DESCRIPTOR,该结构的具体定义如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics //dd
OriginalFirstThunk //dd 0000h -桥1
ends
TimeDateStamp //dd 0004h •时间戮
Forwarderchain //dd 0008h -链表的前一个结构
Namel //dd 000ch -指向链枯库名字的指针
FirstThunk //dd 0000h •桥2
IMAGE_IM PORT_DESCRIPTOR ENDS
54.IMAGE_IMPORT_DESCRIPTOR. OriginalFirstThunk
+0000h,双字。因为它是指向另外数据结构的通路.因此简称为桥1。该字段指向一个包 含了一系列结构的数组。
指向的数组中的毎个结构定义了一个导入函数的信息,最后以一个内容为全0的结构作为结束。指向的数组中毎一项为一个结构,此结构名称是IMAGE_THUNK_DATA。该结构实 际上只是一个双字,但在不同的时刻却拥有不同的解释。该字段有两种解释:
1.双字最髙位为0,表示导入符号是一个数值,该数值是一个RVA。
2.双字最髙位为1,表示导入符号是一个名称。
55.IMAGE_IMPORT_DESCRIPTOR. TimeDateStamp
+0004h,双字。时间戳,一般不用,多为0。如果该导入表项被绑定,那么绑定后的这个 时间戳就被设莨为对应DLL文件的时间戳。操作系统在加载时。可以通过这个时间戳来判断绑定的信息是否过时。
56.IMAGE_IMPORT_DESCRIPTOR. ForwarderChain
+0008h.双字。链表的前一个结构。
57.IMAGE_IMPORT_DESCRIPTOR. Namel
+000ch,双字。这个字段的含义和名称并不一致,这里的Namel是一个RVA,它指向该结构所对应的DLL文件的名称,而这个名称是以“\0”结尾的Ansi字符串。
58.IMAGE_lMPORT_DESCRIPTOR. FirstThunk
+001 Oh,双字。与OriginalFirstThunk相同,它指向的链表定义了针对Namel这个动态链 接库引人的所有导入函数,简称桥2。
2.3.3 导入表的双桥结构
桥1和桥2最终通向了一个目的地,都指向了引入函数的“编号—名称”(Hint/Name) 描述部分。而从桥2到目的地的过程中,还经过了另外一个很重要的结构IAT。
毎一个双字都是结构IMAGE_THUNK_DATA。该结构的详细定义如下:
IMAGE_THUNK_DATA STRUCT
union ul Forwarderstring //dd ?
Function //dd
Ordinal //dd
AddressOfData //dd
ends IMAGE_THUNK DATA ENDS
IMAGE_IMPORT_BY_NAME的详细描述如下:
IMAGE_IMPORT_BY_NAME STRUCT Hint //dw OOOOh -函数编号
Namel //db 0004h -表示函数名的字符串
IMAGE_IMPORT_BY_NAME ENDS
59.IMAGE_IMPORT_BY_NAME.Hint
+0000h,双字。函数的编号,在DLL中对毎个函数都进行了编号,访问函数时可以通过名称访问,也可以通过编号访问。
60.IMAGE_IMPORT_BY_NAME.Name1
+0004h,大小不确定。函数名字字符串的具体内容,以“\0”作为字符串结束标志。
文件中尽管通过桥2和桥1指向的数据值相同,但其存储的位置却是不同的。 桥1指向的INT与桥2指向的IAT内容完全一样,但INT和IAT却存储在文件的不同位置。
每一个结构IMAGE_IMPORT_DESCRIPTOR都对应一个唯一的动态链接库文件,以及引用了该动态链接库的多个函数,每个函数的最终“值-名称“描述均可以沿着桥1或者桥2找到,这种导入表结构被称为双桥结构。
**双桥结构的导入表在文件中存在两份内容完全相同的地址列表。一般情况下,桥2指向的地址列表被定义为IAT,而桥1指向的地址列表则被定义为INT (Import Name Table)。**有的链接程序只为导入表存储一个桥,如某些只保留桥2,这样的导人表我们称之为单桥结构的导入表
2.3.4 导入表函数地址表
PE文件中所有导人函数jmp指令操作数的集合,组成了另外一个数据结构,这个结构就是导入函数地址表 (Import Address Table, IAT)。该地址表是数据目录的第13个数据目录项。
导入函数地址表是一个双字的数组,毎个双字代表的是一个导入函数的VA,该地址称为导入函数地址 (Import Address, IA)。用户程序通过无条件跳转指令跳转到VA指定处,便可以运行引入函数的指令。由于IAT中定义了不止一个链接库的函数,为了区分这些从不同链接库引入的函数,规定所有引入函数按照链接库分类;相 同链接库的函数地址排列在一起,最后以一个双字的0结 束。
导入表和IAT是有紧密联系的,通过桥2即可定位到IAT。在内存中,桥1是以让你找到调用的函数名称或函数的索引编号,桥2却可以帮助找到该函数指令代码在内存空间的地址。
当PE被加载进虚拟地址空间以后,IAT的内容会被操作系统更改为函数的VA。这个修改最终会导致通向“值-名称”描述的桥2发生断裂
当桥2发生断裂以后,如果没有桥1作为参照(因为桥1和桥2维护了两个——对 应的函数RVA),我们就无法重新找到该地址到底是调用了哪个函数。
2.3.5 调用同一个DLL文件的多个函数的导入表
从现在掌握的对导入表的知识来看,定位导入函数地址表的方法有两种:
1.从导入表的最后一个导入表项IMAGE_IMPORT_DESCRIPTOR结构中的字段IMAGE_IMPORT_DESCRIPTOR.FirstThunk 来定位 IAT。
2.通过数据目录第13个数据项的描述直接定位IAT。
每个动态链接库都维护了自己的IAT内容,而且不同链接库维护的这些内容可以是不连续的
当程序加载到内存以后,导入表部分发生变化的值正是IMAGE_IMPORT_DESCR1PTOR 结构中的FirstThunk字段指向的函数指针表内容。
这些内容已经不是指向函数名的指针了, 而是指向了虚拟内存中该函数的可执行代码的地址!
所以其含义也由原来的函数指针更改为函数的入口地址。现在看来,所有的这些值最终都指向了同一片连续的区域,从而形成了我 们常说的IAT。
狭义上的导入表却只是几个IMAGE_IMPORT_DESCRIPTOR结构而已,因为在数据目录中记录的导入表长度只计算了这些结构的大小,其 他的数据并没有包含在内
2.4 导入表编程
2.4.1 导入表遍历的思路
遍历导入表的大致步骤如下:
步骤1:将导入表的第一个IMAGE_ IMPORT_DESCRIPTOR的起始地址给edi。
步骤2:获取导入表所处的节的名称,并显示。
步骤3:构造循环条件,当IMAGE_ IMPORT_DESCRIPTOR结构中所有的字段均不为0时作为执行条件。
步骤4:获取IMAGE_ IMPORT_DESCRIPTOR结构所有字段,并显示该结构对应的动态 链接库名称。
步骤5:显示该动态链接库下调用的所有函数的编号和名称。
2.5 绑定导入
2.5.1 绑定导入机制
双桥结构的导入表中,桥2是指向IAT的,Windows加载程序负责IAT中地址的修正工作。 如果一个PE中导入的函数比较多,那么这部分工作就会占用一些时间,PE加载速度就会变慢。绑定导入的目的就是把由Windows加载程序负责的IAT地址修正工作提前到加载前进行,要 么由用户手工完成,要么由专门的程序来完成:然后,通过在PE文件中声明绑定导入数据,以便告诉操作系统加载器说这部分工作不需要你做了。
不同的操作系统中,动态链接库的基地址通常是不一样的。
2.5.2 绑定导入数据定位
绑定数据是数据目录中注册的一种类型,位于数据目录的第12项。
注意系统文件的绑定导入数据在文件中的存放位置:大部分情况下,该数据被存储在PE文件头部,紧跟在节表后。
2.5.3 绑定导入数据结构
绑定导人数据由一系列的绑定导入描述符IMAGE_BOUND_IMPORT_DESCRIPTOR的 结构组成。毎一个结构对应一个导入的动态链接库,它描述了导入动态链接库的版本信息 (通过时间戳来定义),该结构的详细定义如下:
IMAGE_BOUND_IMPORT_DESCRIPTOR STRUCT
TimeDateStamp //dword OOOOh -时间戮
OffsetModuleName //word 0004h - 指向 DLL 名称
NumberOfModuleForwarderRefs //word 0006h - ModuleForwarderRef 数目
IMAGE_BOUND_IMPORT_DESCRIPTOR ENDS
61.IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp
+0000h,双字。该字段的值必须与要引用的DLL的文件头IMAGE_FILE_HEADER.TimeDateStamp字段值相吻合,否则就会促使加载器去重新计算新IAT,这种情况一般发生在DLL版本不同时或者DLL映像被重定位时。
62.IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName
+0004h,单字。该字段包含了以第一个IMAGE_BOUND_IMPORT_DESCRIPTOR作为基址,DLL名称字符串(ASCII且以“\0”结束)的偏移。
注意:该偏移地址是一个特殊地址,它即不是RVA,也不是FOA。
63.IMAGE_BOUNDJMPORT_DESCRIPTOR. NumberOfModuleForwarderRefs
+0006h,单字。该字段描述f紧接在IMAGE_BOUND_IMPORT_DESCRIPTOR结构后的另一个结构IMAGE_BOUND_FORWARDER_REF数组的元素个数。
以下是结构IMAGE_BOUND_FORWARDER_REF的定义:
IMAGE_BOUND_FORWARDER_REF STRUCT TimeDateStamp //dword OOOOh - 时间戳
OffsetModuleName //word 0004h - 指向DLL名称
Reserved //word 0006h - 预留
IMAGE_BOUND_FORWARDER_REF ENDS
一个导入函数将涉及对多 动态链接库函数的调用,数据结构IMAGE_BOUND_FORWARDER_REF就是在这样一 个背景下产生的,它将引入函数涉及的所有动态链接库都列举出来。
绑定导入数据的组织方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VF6chZVT-1639890748100)(C:\Users\megaparsec\AppData\Roaming\Typora\typora-user-images\image-20211106170205088.png)]
2.6 重组导入表
2.6.1 常用注册表API
1.函数 RegCreateKey
https://www.cnblogs.com/zhuzhuxia480/p/4138290.html
2.函数 RegCreateKeyEx
https://blog.csdn.net/ke_yi_/article/details/80928693
3.函数 RegOpenKeyEx
https://blog.csdn.net/ke_yi_/article/details/80930685
4.函数 RegSetValueEx
https://blog.csdn.net/ke_yi_/article/details/80930423
5.函数 RegCloseKey
https://blog.csdn.net/ke_yi_/article/details/80928198
2.6.2 手工重组
https://blog.csdn.net/lagon_/article/details/23002225
2.6.3 IAT的连贯性
IAT不一定必须连续
2.6.4 导入表位置
net/ke_yi_/article/details/80930423
5.函数 RegCloseKey
https://blog.csdn.net/ke_yi_/article/details/80928198
2.6.2 手工重组
https://blog.csdn.net/lagon_/article/details/23002225
2.6.3 IAT的连贯性
IAT不一定必须连续
2.6.4 导入表位置
导入表通常在常量节里,也可在代码节,或其他任何可读属性的节里