PE体系
PE结构&整体叙述
PE结构&导入表
PE结构&导出表
PE结构&基址重定位表
PE结构&绑定导入实现
PE结构&延迟加载导入表
简介
延迟加载导入表是PE中引入的专门用来描述与动态链接库延迟加载相关的数据,因为这些数据所起的作用和导入表数据基本一致,所以称为延迟加载导入表。
延迟加载导入表和导入表是相互分离的,一个PE文件中可以同时存在这两种数据,也可以单独存在一种。延迟加载导入表是一种特殊类型的导入表,同导入表一样,它记录了应用程序要导入的部分或全部动态链接库及相关的函数信息。与导入表不同的是,它所记录的这些动态链接库并不会被操作系统的PE加载器加载,只有等到由其登记的相关函数被应用程序调用时,PE中注册的延迟加载函数才会根据延迟加载导入表中对该函数的描述,动态加载链接库并修正函数的VA地址,实现对函数的调用。
延迟加载导入表的概念及作用
延迟加载导入是一种合理利用进程加载机制提高进程加载效率的技术,使用延迟加载导入表能跳过加载前对引入函数的检测及加载后对IAT的修正(只有等到由其登记的相关函数被应用程序调用时,PE中注册的延迟加载函数才会根据延迟加载导入表中对该函数的描述,动态加载链接库并修正函数的VA地址)。
延迟加载导入的概念:系统开始运行程序时指定的延迟加载的DLL是不被载入的,只有等到程序调用了该动态链接库的函数时,系统才将该链接库载入内存空间,并执行相关函数代码。
作用:
- 提高应用程序加载速度
- 提高应用程序兼容性
- 提高应用程序可整合性
提高应用程序加载速度
如果一个应用程序使用了很多的DLL,PE加载器在将程序映像加载的虚拟地址空间的时候,同时也会把所有的DLL一起提前加载到进程空间,而且在加载每个DLL时还会调用DLL 的入口函数,对DLL进行初始化,尽管这时候程序并没有开始调用这些引入的动态链接库的函数。这些操作的存在使得进程加载时会耗费一些时间,可能会使程序加载速度收到影响,而延迟加载则可以完全避开这点。这就好像安排一项多人要完成的工作,只有当需要某人的时候才正式通知他
提高应用程序兼容性
同一个DLL在不同时期会有不同的版本,一般情况下,新的DLL除了对原有函数的继承和优化外,通常还会增加一些新的函数。如果我们再应用程序中调用了一个DLL的新函数,运行时所在环境提供的却是老的DLL,那么加载时系统就会提示错误,然后拒绝指向应用程序。如果我们再代码中先对运行的环境进行检测,发现存在老的DLL,则不再调用这个不存在的函数,要么友好提示,要么通过其它方式实现该函数的功能。这样就可以保证在没有新DLL的环境中,程序依旧可以被PE加载器加载并运行。
假设现在有DLL的两个不同版本,旧的MyDLL.dll和新的MyDll.dll,其中在新的MyDll.dll中又增加了一个函数_getImportDescriptor()
应用程序代码如下:
把这段代码按照正常的编码方式放到一个源文件,然后编译,链接,链接的时候一定会出现错误。即使让你链接通过了,运行时也会出现错误。如果我们再链接时告诉链接器新的MyDll.dll使用了加载延迟加载导入的方法,链接器就会为我们单独处理这个函数的调用,从而避免错误的出现。这种提高应用程序适应不同环境的能力。
提高应用程序可整合性
受早期MS_DOS下应用程序的习惯影响,有的程序员并不太喜欢目前Windows下应用程序的安装方式。在Windows系统下,程序运行需要安装,不需要的时候还要通过控制面板卸载,与程序有关的 数据并不是仅仅存储在一个独立的目录下,而是遍及整个磁盘,如运行是库所在目录,注册表,系统目录,系统的配置管理器目录等。这把一个完整的程序变得四分五裂,在程序的后期管理维护和移植上制造了很多麻烦。为了使软件易于安装,于是软件就有了绿色的概念,将所有的东西全部存储在一个文件里。想拷走的时候仅仅复制一个文件,与文件有关的配置信息,数据库,链接库都在一个文件里。这里指的可整合就是这种情况。
PE中的延迟加载导入表
在Windows XP SP3的大部分系统PE文件中,都存在延迟加载导入表数据,延迟加载导入表数据的整体组织与导入表类似,也存在INT和IAT双桥结构
延迟加载导入表数据定位
延迟加载导入数据为数据目录注册的数据类型之一,其描述数据处于数据目录的第14个目录项中。
延迟加载导入数据所造的地址RVA=0x00000203c
延迟加载导入数据大小=0x00000040h
转换成文件偏移后,0x83C
延迟加载导入描述符 IMAGE_DELAY_IMPORT_DESCRIPTOR
IMAGE_DELAY_IMPORT_DESCRIPTOR STRUCT
Attributes dword ? ;0000h -属性,必须为0
Name dword ? ;0004h -指向DLL名称的RVA
ModuleHandle dword ? ;0008h -DLL模块句柄的RVA
DelayIAT dword ? ;000ch -延迟加载导入IAT的RVA
DelayINT dword ? ;0010h -延迟加载导入INT的RVA
BoundDelayIT dword ? ;0014h -绑定延迟加载导入表的RVA
UnloadDelayIT dword ? ;0018h -卸载延迟加载导入地址表的RVA
TimeStamp dword ? ;001ch -此映像绑定到DLL的时间戳
IMAGE_DELAY_IMPORT_DESCRIPTOR ENDS
IMAGE_DELAY_IMPORT_DESCRIPTOR.Attributes
+0000h,双字,属性,暂时未用,链接器在生成映像文件时将此字段设置为0.用户可以在将来扩展这个结构时用它来指明添加了新字段,或者用它来指明延迟加载导入或卸载辅助函数的行为
IMAGE_DELAY_IMPORT_DESCRIPTOR.Name
+0004h,双字,指向延迟加载导入的动态链接库的名字字符串的地址,该地址是一个RVA。
IMAGE_DELAY_IMPORT_DESCRIPTOR.ModuleHandle
+0008h,双字。被延迟加载导入的DLL模块句柄的RVA。该RVA位于PE映像的数据节中,延迟加载辅助函数使用这个位置存储要被延迟加载的DLL的模块句柄
IMAGE_DELAY_IMPORT_DESCRIPTOR.DelayIAT
+000Ch,双字。延迟加载导入地址表的RVA。延迟加载辅助函数用导入符号的实际地址来更新这些指针,以便起转换作用的这部分代码不会陷入循环调用之中
IMAGE_DELAY_IMPORT_DESCRIPTOR.DelayINT
+0010h,双字。延迟加载导入名称表(INT)包含了可能需要被加载的导入符号的名称。它们的排列方式与IAT的函数指针一样,它们的结构与标准的INT一样。结构的详细信息可以参照导入表部分。
IMAGE_DELAY_IMPORT_DESCRIPTOR.BoundDelayIT
+0014h,双字。延迟绑定导入地址表(BIAT)是由IMAGE_THUNK_DATA结构组成的数组,它是可选的。它与延迟加载目录表中的TimeStamp字段一起被用于后处理绑定阶段。
IMAGE_DELAY_IMPORT_DESCRIPTOR.UnloadDelayIT
+0018h,双字。延迟加载导入地址表(UIAT)是由IMAGE_THUNK_DATA结构组成的数组,它是可选的。卸载代码用它来处理明确的卸载请求。它由只读节中已初始化的数据组成,这些数据是原始IAT 的精确副本。在处理卸载请求时,可以释放这个DLL,同时将IMAGE_DELAY_IMPORT_DESCRIPTOR.ModuleHandle清零,并用UIAT覆盖IAT,以便将一切还原到预加载时的状态。
IMAGE_DELAY_IMPORT_DESCRIPTOR.TimeStamp
+001Ch,双字。表示应用程序绑定到DLL的时间戳
延迟加载导入表实例分析
0x83C
00 00 00 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.Attributes,属性值为0
30 20 40 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.Name,0x402030
转换为文件偏移后0x830
开始的字符串“MyDll.dll
”
1c 30 40 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.ModuleHandle。指向.data段,文件偏移0x0000a1C处,此处用来存放MyDll.dll的模块句柄
14 30 40 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.DelayIAT。指向了延迟加载导入的IAT,位于文件偏移的0x00000a14位置处。该位置位于.data段,
7c 20 40 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.DelayINT.指向了延迟加载导入的INT,位于文件偏移的0x0000087C位置处,该位置位于.rdata段。从该位置取出的值为0x00402084,它指向了函数sayHello的hint/name描述:00 00/ 73 61 79 48 65 6c 6c 6F 00 00
90 20 40 00
IMAGE_DELAY_IMPORT_DESCRIPTOR.BoundDelayIT。指向了绑定延迟加载导入表,位于文件偏移0x00000890位置处,该位置位于.rdata段,从该位置取出的值为0x00000000,表示该映像文件无绑定延迟加载导入定义