手动加载PE导入表时解析PIMAGE_IMPORT_DESCRIPTOR出现数值错误
问题根因
- 核心错误是把PE结构里的相对虚拟地址(RVA)直接当成文件偏移使用。
PIMAGE_DATA_DIRECTORY中存储的VirtualAddress是PE被系统加载器映射到内存后,相对于模块基址的偏移量,不是PE文件在磁盘上存储时的文件偏移。你当前是把DLL完整文件以字节数组形式读入内存,没有做节区对齐展开,直接拿RVA加文件缓冲区基址寻址,必然会读到错误的内存位置。- 你打印出的错误值
ForwarderChain: 74737973按小端序转ASCII是syst,明显是导入表中的DLL名字符串内容,刚好可以佐证寻址位置完全偏离了IMAGE_IMPORT_DESCRIPTOR结构的实际存储位置。 - 另外你的代码存在笔误:定义的NT头变量是小写开头的
ntHeader,后续取数据目录时写成了大写开头的NtHeader,会直接触发编译错误,需要先修正。
修复方案
- 首先实现RVA到文件偏移的转换函数,逻辑是遍历节表找到目标RVA所属的节区,通过节区的虚拟地址和原始数据偏移计算出对应的文件偏移:
DWORD RvaToFileOffset(PIMAGE_NT_HEADERS32 ntHdr, DWORD rva) { PIMAGE_SECTION_HEADER secHeader = IMAGE_FIRST_SECTION(ntHdr); WORD sectionCount = ntHdr->FileHeader.NumberOfSections; for (WORD i = 0; i < sectionCount; i++) { DWORD secVaStart = secHeader[i].VirtualAddress; DWORD secVaEnd = secVaStart + max(secHeader[i].Misc.VirtualSize, secHeader[i].SizeOfRawData); if (rva >= secVaStart && rva < secVaEnd) { return rva - secVaStart + secHeader[i].PointerToRawData; } } // PE头区域的RVA和文件偏移一致,直接返回 return rva; }
- 修正原有导入表解析逻辑,所有从PE结构中读取到的RVA,都要先转成文件偏移再和缓冲区基址相加寻址:
PIMAGE_NT_HEADERS32 ntHeader = (PIMAGE_NT_HEADERS32)((DWORD)dllFile32 + ((PIMAGE_DOS_HEADER)dllFile32)->e_lfanew); PIMAGE_DATA_DIRECTORY importDir = &ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (importDir->VirtualAddress > 0 && importDir->Size > sizeof(IMAGE_IMPORT_DESCRIPTOR)) { DWORD importTableOffset = RvaToFileOffset(ntHeader, importDir->VirtualAddress); PIMAGE_IMPORT_DESCRIPTOR iid = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)dllFile32 + importTableOffset); // 后续遍历IID时,OriginalFirstThunk、Name、FirstThunk字段都是RVA,使用前都需要做同样的偏移转换 printf(" VirtualAddress: %08X\n", importDir->VirtualAddress); printf(" Size: %08X\n", importDir->Size); printf("OriginalFirstThunk: %08X\n", iid->OriginalFirstThunk); printf(" TimeDateStamp: %08X\n", iid->TimeDateStamp); printf(" ForwarderChain: %08X\n", iid->ForwarderChain); }
注意事项
- 只有当PE被系统加载器完整映射到内存、所有节区按内存对齐要求展开后,RVA才能直接加模块基址寻址。手动读取磁盘文件做解析时,所有RVA都必须先转成文件偏移才能正确定位数据。
- 遍历导入表时,终止条件是读到
OriginalFirstThunk和FirstThunk都为0的IID结构,不能直接用数据目录的Size硬编码遍历长度。
内容的提问来源于stack exchange,提问作者Travis
相关产品推荐
相关产品推荐

