延迟载入一个DLL是一种混合方式,通过LoadLibrary和GetProcAddress,然后我们可以在程序里直接调用它。
延迟加载表本质上跟绑定导入表的目的是一样的,都是为了加快程序加载文件的速度,只不过方法不一样,绑定导入表是事先预测DLL的加载位置,从而减少指令的执行。
延迟加载是指在调用某个DLL时才去加载,目的是为了避免在程序启动之初就加载了不必要的DLL而浪费了时间。
延迟载入并不是操作系统的特征!!!,所以在WinNT.h中无法找到对它的描述。这项特性是通过向链接器和运行库加入额外的代码来实现的。
在目录表中可以寻找到。
这是一个指向ImgDelayDescr数组的一个结构,其中每一个延迟载入的DLL都对应于一个ImgDelayDescr的结构。ImgDelayDescr的格式上与IAT和INT是相同的,唯一区别是ImgDelayDescr是由运行库代码而不是操作系统来写入和读取的。。当第一次从一个延迟载入的DLL中调用一个API函数时,运行库会先调用LoadLibrary,再调用GetProcAddress,得到的地址被保存在延迟载入的IAT中。这样的话,以后每次调用这个API都会来这个地方。
typedef struct ImgDelayDescr {
DWORD grAttrs; //结构的属性,目前唯一的属性是dlattrRVA,表明这个结构中的字段应是RVA,而不是虚拟地址。这个值被设置为1,结构成员代表的是RVA值。如果这个位关掉,则RVA代表的是虚拟地址
RVA rvaDLLName; //这个DLL名字的RVA
RVA rvaHmod; //指向该DLL被载入内存时,其模块句柄的值,实际上就是DLL在内存中的位置
RVA rvaIAT; //IAT的RVA,与常规IAT的格式相同
RVA rvaINT; //INT的RVA,与常规的INT格式相同
RVA rvaBoundIAT; //绑定导入的IAT的RVA,尽管并不是实际绑定的,但是特征可能会在未来版本出现。
RVA rvaUnloadIAT; //总是设为0
DWORD dwTimeStamp; //时间戳
} ImgDelayDescr, * PImgDelayDescr;
下面实战下,从数据目录表里得到了延迟载入的RVA,跳到那里。
我们在这里只看关键的部分,grAttr被设置为0,说明后面的RVA声明的成员的值是虚拟地址。rvaDllName的值是0x401050,根据它的加载基址,我们可以算出它在内存中的RVA是0x1050,换算成文件偏移就是0x250。正是user32.dll
其在内存中的HMODULE是0x40141c,IAT是0x401038,换算成文件偏移是0x238, INT是0x4013d0,换算成文件偏移是0x5d0,我们用winhex看下。
第一个是0x4013EA,第二个是0x4013DC,换算成文件偏移也就是0x5ea,0x5dc,在winhex中查找
其中第一项是hint,第二项是ascii字符串。IAT我们就不看了。
再看下第二项的ImgDelaydesc
grAttr旗标仍然是0,所以后面跟着的仍然是虚拟地址,第二项延迟加载的DLL的VA是0x401060,换算成RVA也就是0x260,跳到那里看下。
这次加载的是advapi32.dll。后面的就不分析了。
重点记住延迟载入不是操作系统的特征,而是运行库代码所实现的!!!