延迟导入表从它的名字上,我们可以知道他是一种加载比较延迟的导入表,不是在一开始就被加载的,而是等到要被使用的时候,才会被延迟加载(动态加载相关链接库并修正函数的虚拟地址,实现对函数的调用)。
目的:
1.提高程序的加载速度。
2.提高兼容性:有的函数在老的Windows版本中并不存在,但是在新的Windows版本中存在,而我们想要写一个Dll来实现一个动态的判断,即可以根据操作系统的版本来决定动态库是否要被加载,则可以使用延时导入技术。
延时导入的局限性:
1.一个导出了全局变量的的Dll是无法被延时加载的。
2.Kernel32.dll是不能被延时加载的,因为要加载好该模块才能调用LoadLibrary和GetProcAddress。
3.不应该在DllMain的入口函数中调用一个延时载入的函数,因为这样可能引起程序奔溃(可能此时的Dll并没有被加载上)。
数据目录的第十四个成员。
typedef struct ImgDelayDescr {
DWORD grAttrs; // attributes
RVA rvaDLLName; // RVA to dll name
RVA rvaHmod; // RVA of module handle
RVA rvaIAT; // RVA of the IAT
RVA rvaINT; // RVA of the INT
RVA rvaBoundIAT; // RVA of the optional bound IAT
RVA rvaUnloadIAT; // RVA of optional copy of original IAT
DWORD dwTimeStamp; // 0 if not bound,
// O.W. date/time stamp of DLL bound to (Old BIND)
} ImgDelayDescr, * PImgDelayDescr;
typedef const ImgDelayDescr * PCImgDelayDescr;
grAttrs: 用来区分版本,1是新版本,0是旧版本,旧版本中后续的rvaxxxxxx域使用的都是指针,而新版本中都用RVA,我们只讨论新版本。
rvaDLLName:一个RVA,指向导入DLL的名字。
rvaHmod: 一个RVA,指向导入DLL的模块基地址,这个基地址在DLL真正被导入前是NULL,导入后才是实际的基地址。
rvaIAT: 一个RVA,表示导入函数表,实际上指向IAT,在DLL加载前,IAT里存放的是一小段代码的地址,加载后才是真正的导入函数地址。
rvaINT: 一个RVA,指向导入函数的名字表。 rvaUnloadIAT:延迟导入函数卸载表。
dwTimeStamp:延迟导入DLL的时间戳
延迟导入表的设置是通过VS设置来实现的,比较简单。
【DLL的延迟加载】:
1.需要DLL,MyLib.lib导入库及MyLib.h 进行【隐式加载】的全步骤
2.属性->配置属性->链接器->输入->延迟加载的Dll-> 添加MyDll.dll
(注意/DelayLoad:MyDll.dll这个开关不能用#pragma comment(linker, “/DelayLoad:MyDll.dll”)来设置。
3.属性->配置属性->链接器->输入->附加依赖项-> 添加DelayImp.lib
//也可以用#include 和#pragma comment(lib, “Delayimp.lib”)
//这个开关告诉链接器将delayimp中的__delayLoadHelper2函数嵌入到我们的可执行文件中。
4.如果需要自动卸载Dll,则需在可选 属性->配置属性->链接器->高级->卸载延迟加载的DLL->是 (/DELAY:UNLOAD);
//此时只能调用__FUnloadDelayLoadedDll2(PCSTR szDll)函数,而不能调用FreeLibrary
//并且传入的参数不包含路径,且名称与延迟加载的Dll中配置的参数必须保持一致,如果不打算卸载,就可以不指定/DELAY:UNLOAD
装载延迟导入表:实际使用的函数是实际上使用的函数是:__delayLoadHelper2
**卸载延迟导入表:**实际使用的函数是FUnloadDelayLoadedDLL()。
注意:
延迟导入的加载只发生在函数第一次被调用的时候,之后IAT就填充为正确函数地址,不会再走__delayLoadHelper了。
延迟导入一次只会导入一个函数,而不是一次导入整个模块的所有函数。