解析PE文件时获取PIMAGE_EXPORT_DIRECTORY及偏移失败,求排查
排查PE导出表获取失败的问题
咱们来一起解决这个问题——你当前代码的核心问题是混淆了虚拟地址(RVA)和磁盘文件偏移:直接把导出表的RVA加到文件加载的基地址pSrcData上是错误的,因为PE文件在磁盘上的节偏移和内存中的虚拟地址是完全不同的映射关系。下面一步步分析排查,同时给出修复后的代码:
一、核心问题拆解
PE文件的IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress是导出表的虚拟内存地址(RVA),而你加载到内存的是磁盘上的原始文件数据。要找到导出表在磁盘文件中的位置,必须先把RVA转换成对应的文件偏移,不能直接相加。
二、分步修复与排查
1. 先给代码补全必要的有效性检查
很多时候获取失败是因为没做基础校验,比如文件是否有效、头信息是否合法。咱们先加上这些检查,避免无效指针访问:
- 验证DOS头的签名
e_magic是否为IMAGE_DOS_SIGNATURE(0x5A4D) - 验证NT头的签名
Signature是否为IMAGE_NT_SIGNATURE(0x00004550) - 检查导出表的RVA和Size是否非零(即使你说DLL有导出,也可能因为读取错误导致值为0)
2. 实现RVA转文件偏移的工具函数
要把RVA转换成磁盘文件中的实际偏移,需要遍历PE的节表,找到包含该RVA的节,然后用公式计算:
DWORD RvaToFileOffset(BYTE* pSrcData, DWORD rva) { IMAGE_DOS_HEADER* pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(pSrcData); IMAGE_NT_HEADERS* pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS*>(pSrcData + pDosHeader->e_lfanew); IMAGE_SECTION_HEADER* pSectionHeaders = IMAGE_FIRST_SECTION(pNtHeaders); for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; ++i) { IMAGE_SECTION_HEADER* pSection = &pSectionHeaders[i]; // 检查当前RVA是否落在该节的虚拟地址范围内 if (rva >= pSection->VirtualAddress && rva < pSection->VirtualAddress + pSection->SizeOfRawData) { // 计算文件偏移:RVA - 节虚拟地址 + 节文件偏移 return rva - pSection->VirtualAddress + pSection->PointerToRawData; } } return 0; // 未找到对应节,返回0表示错误 }
3. 修改原代码,正确获取导出表
把原代码中直接转换的逻辑替换成RVA转文件偏移的逻辑,同时加上错误检查和内存释放(原代码有内存泄漏问题):
bool InfoPE(const char* dllFile) { IMAGE_NT_HEADERS* pNtHeaders; IMAGE_OPTIONAL_HEADER* pOptionalHeader; IMAGE_FILE_HEADER* pFileHeader; std::ifstream File(dllFile, std::ios::binary | std::ios::ate); // 检查文件是否打开成功 if (!File.is_open()) { return false; } auto FileSize = File.tellg(); if (FileSize <= 0) { File.close(); return false; } std::allocator<BYTE> alloc; BYTE* pSrcData = alloc.allocate(static_cast<UINT_PTR>(FileSize)); if (!pSrcData) { File.close(); return false; } File.seekg(0, std::ios::beg); File.read(reinterpret_cast<char*>(pSrcData), FileSize); File.close(); // 验证DOS头有效性 IMAGE_DOS_HEADER* pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(pSrcData); if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { alloc.deallocate(pSrcData, static_cast<UINT_PTR>(FileSize)); return false; } pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS*>(pSrcData + pDosHeader->e_lfanew); // 验证NT头有效性 if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) { alloc.deallocate(pSrcData, static_cast<UINT_PTR>(FileSize)); return false; } pOptionalHeader = &pNtHeaders->OptionalHeader; pFileHeader = &pNtHeaders->FileHeader; IMAGE_DATA_DIRECTORY* dataDirectory = pOptionalHeader->DataDirectory; DWORD exportDirRVA = dataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; DWORD exportDirSize = dataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; // 检查导出表RVA是否有效 if (exportDirRVA == 0 || exportDirSize == 0) { alloc.deallocate(pSrcData, static_cast<UINT_PTR>(FileSize)); return false; } // 转换RVA到文件偏移 DWORD exportFileOffset = RvaToFileOffset(pSrcData, exportDirRVA); if (exportFileOffset == 0 || exportFileOffset + exportDirSize > static_cast<DWORD>(FileSize)) { alloc.deallocate(pSrcData, static_cast<UINT_PTR>(FileSize)); return false; } // 现在可以正确获取导出表了 PIMAGE_EXPORT_DIRECTORY pimage_export_directory = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(pSrcData + exportFileOffset); // 打印你需要的偏移与值 printf("导出表文件偏移: 0x%X\n", exportFileOffset); printf("导出表RVA: 0x%X\n", exportDirRVA); printf("导出函数总数: %u\n", pimage_export_directory->NumberOfFunctions); printf("已命名导出函数数: %u\n", pimage_export_directory->NumberOfNames); // 别忘了释放内存,避免泄漏 alloc.deallocate(pSrcData, static_cast<UINT_PTR>(FileSize)); return true; }
三、额外提醒
- 如果需要处理64位PE文件,要替换成
IMAGE_NT_HEADERS64结构体,对应的成员也需要调整 - 原代码中的
allocator分配内存后没有释放,修复后的代码已经补上了这部分逻辑 - 可以在转换前后打印关键值(比如RVA、文件偏移),方便排查定位问题
内容的提问来源于stack exchange,提问作者jubibanna
相关产品推荐
相关产品推荐

