PE文件结构导入表相关疑问:冗余性、ILT作用及IAT存储位置
问题1:PE文件中数据描述和节表都有指向.idata节的RVA,是否属于冗余设计?
这不是冗余设计,两者的作用完全不同:
- 数据描述里的导入表RVA,指向的是导入目录表数组的起始位置,是PE加载器定位导入信息的核心入口,用来遍历每个导入的DLL及其对应的函数导入信息;
- 节表中的.idata节RVA,指向的是整个.idata节的内存起始地址,主要用来描述这个节的属性(如读写权限、内存对齐规则)、文件偏移与内存映射的对应关系。
加载器需要先通过节表找到.idata节在内存中的实际位置,再通过数据描述里的导入表RVA定位到具体的导入目录表内容,两者是协作完成导入流程的必要环节,不存在冗余。
问题2:为何ILT不直接存储函数名称,而是存储指向提示/名称表的RVA?导入目录表为何用ILT包装而非直接引用提示/名称表?
核心原因是ILT要兼容按序号调用和按名称调用两种导入方式:
ILT的每个条目是一个联合结构:如果是按序号导入函数,条目存储的是带最高位标记的序号值;如果是按名称导入,条目才会存储指向提示/名称表的RVA。这种设计让同一张ILT可以混合两种导入类型,适配不同的导入需求。
另外,导入目录表通过ILT关联函数信息,是因为ILT和IAT的结构完全一致——加载前,ILT存储的是导入信息的定位标记(序号或提示/名称表RVA);加载完成后,IAT会被替换成函数的实际内存地址。这种结构复用可以大幅简化PE加载器的处理逻辑,不需要为两种表设计完全不同的解析流程。如果直接让导入目录表引用提示/名称表,就无法支持按序号导入的场景,同时也破坏了ILT与IAT的结构一致性,增加加载器的复杂度。
问题3:设为只读的IAT属于.idata节还是.rdata节?
从PE规范的设计逻辑来说,.idata节默认是可读写的,因为加载器需要修改IAT写入函数的实际内存地址;但如果要将IAT设为只读,通常会把它放到.rdata节(只读数据节)中——不过正如你提到的,节名称本身只是一个标识,只要节的属性标记为只读(IMAGE_SCN_MEM_READ),不管节名是什么,加载后都会被映射为只读内存区域。
实际场景中,有些程序会把IAT合并到.rdata节来做防篡改保护,此时IAT的属性就和.rdata节保持一致为只读,只要PE加载器能正确定位到IAT的位置,就不会影响程序的正常运行。
内容的提问来源于stack exchange,提问作者Pieter Jansen

