PE-延迟导入表

延迟导入表

延迟导入的加载只发生在函数第一次被调用的时候,之后IAT就填充为正确函数地址,不会再走_delayLoadHelper了。

延迟导入一次只会导入一个函数,而不是一次导入整个模块的所有函数。

延迟加载导入表和导入表是相互分离的。一个PE文件中可以同时存在这两种数据,也可 以单独存在一种。延迟加载导入表是一种特殊类型的导入表,同导入表一样,它记录了应用程序要导入的部分或全部动态链接库及相关的函数信息。与导入表不同的是,它所记录的这 些动态链接库并不会被操作系统的PE加载器加载,只有等到由其登记的相关函数被应用程序 调用时,PE中注册的延迟加载函数才会根据延迟加载导入表中对该函数的描述,动态加载相 关链接库并修正函数的VA地址,实现对函数的调用。

6.1 延迟加载导入的概念及其作用

延迟加载导入是一种合理利用进程加载机制提髙进程加载效率的技术,使用延迟加载导入能跳过加载前对引入函数的检测及加载后对IAT的修正,从而避免出现诸如“无法找到组 件”的错误提示,提高程序的适应性。

一个应用程序要调用动态链接库的某个函数,需要先 在程序中静态引人该动态链接库,编译器在编译时会分解调用该引入函数的invoke指令,并将其调用最终指向IAT。PE加载器要完成的任务就是根据导入表的描述,将IAT中的地址修 正为函数在进程地址空间的真实地址VA,这样就能保证该函数被正确调用。

程序要正确运行,必须保证该动态链接库能够在进程环境变量指定的PATH中找到,如果程序已经开始运行,无论指令指针寄存器eip是否指向调用引入函数的指令,如果此时相应的DLL文件还未出现在路径中,就会导致错误出现

延迟加载导入的槪念:系统开始运行程序时被指定的延迟加载的DLL是不被载入的,只有等到程序调用了该动态链接库的函数时,系统才将该链接库载入内存空间,并执行相关函数代码。
延迟加载导入技术在很多场合是非常有用的,这些情况包括但不限于以下三种情况:

1.提高应用程序加载速度

如果一个应用程序使用了很多的DLL,PE加载器在将程序映像加载到虚拟地址空间的 时候,同时也会把所有的DLL —起提前加载到进程空间,而且在加载毎个DLL时还会调用 DLL的人口函数,对DLL进行初始化,尽管这时候程序并没有开始调用这些引入的动态链接 库的函数。这些操作的存在使得进程加载时会耗费一些时间,可能会使程序加载速度受到影 响,而延迟加载则可以完全避开这一点。

2.提高应用程序兼容性

同一个DLL在不同时期会有不同的版本。一般情况下,新的DLL除了对原有函数的继 承和优化外,通常还会增加一些新的函数。如果我们在应用程序中调用了一个DLL的新函数, 运行时所在环境提供的却是老的DLL,那么加载时系统就会提示错误,然后拒绝执行应用程 序。如果我们在代码中先对运行的环境进行检测,发现存在老的DLL,则不再调用这个不存在的函数,要么友好提示,要么通过其他方式实现该函数的功能。这样就可以保证在没有新 DLL的环境中,程序依旧可以被PE加载器加载并运行。

3.提高应用程序可整合性

受早期MS_DOS下应用程序的习惯影响,有的程序员并不太喜欢目前Windows下应用程序的安装方式。在Windows系统下,程序运行需要安装,不需要的时候还要通过控制面板卸载,与程序有关的 数据并不是仅仅存储在一个独立的目录下,而是遍及整个磁盘,如运行是库所在目录,注册表,系统目录,系统的配置管理器目录等。这把一个完整的程序变得四分五裂,在程序的后期管理维护和移植上制造了很多麻烦。为了使软件易于安装,于是软件就有了绿色的概念,将所有的东西全部存储在一个文件里。想拷走的时候仅仅复制一个文件,与文件有关的配置信息,数据库,链接库都在一个文件里。这里指的可整合就是这种情况。

当应用程序运行时,对延迟加载函数的调用实际上是对函数.delayLoadHelper的调用。 该函数知道链接器创建的与MyDll.dll有关的导入信息,并且还能自己通过函数LoadLibrary 动态加载DLL文件,然后调用函数GetProcAddress获取引人函数的地址信息。一且获得延迟 *加载函数的地址,函数delayLoadHelper的使命就终止了。下一次该函数再被调用时,就会直接跳转到函数的VA处执行,而不再像第一次执行函数delayLoadHelper那样了。

6.2 延迟加载导入描述符 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

6.3 关于延迟加载导入的两个问题

6.3.1 异常处理

通常情况下,当操作系统的加载程序加载可执行模块时,它都会设法加载必要的DLL。 如果一个DLL无法加载(比如不存在该DLL文件),那么加载程序就会显示一条错误消息。 如果该DLL是通过延迟加载的DLL,在进行初始化时操作系统并不负责检查是否存在该 DLLO如果调用延迟加载的函数时无法找到该DLL,函数delayLoadHelper就会引发一个软 件异常。该异常可以使用结构化异常处理(SEH)方法捕获。如果不跟踪该异常,你的进程就会被终止运行。
当函数delayLoadHelper确实找到了你的DLL,但是要调用的函数却不在该DLL中时, 将会出现另一个问题。比如前面提到的,如果加载程序找到一个老的DLL版本,就会发生这种情况。在这种情况下,函数.delayLoadHelper也会引发一个软件异常,针对这个软件异常 的处理方法与上面相同。

6.3.2 DLL的卸载

如果程序中调用完通过.delayLoadHelper加栽的DLL文件的函数后,很长一段时间不再 需要该动态链接库,那么就可以释放该DLL
手动卸载dll时,需要通过__FUnloadDelayLoadedDLL2(“xxx.dll”)卸载,不允许使用FreeLibray函数卸载。

延迟导入目的:
1.提高程序的加载速度。
2.提高兼容性:有的函数在老的Windows版本中并不存在,但是在新的Windows版本中存在,而我们想要写一个Dll来实现一个动态的判断,即可以根据操作系统的版本来决定动态库是否要被加载,则可以使用延时导入技术。

延时导入的局限性:
1.一个导出了全局变量的的Dll是无法被延时加载的。
2.Kernel32.dll是不能被延时加载的,因为要加载好该模块才能调用LoadLibrary和GetProcAddress。
3.不应该在DllMain的入口函数中调用一个延时载入的函数,因为这样可能引起程序奔溃(可能此时的Dll并没有被加载上)。

猜你喜欢

转载自blog.csdn.net/weixin_51732593/article/details/122023372