PE加载器代码无法从导出表获取正确函数RVA的问题排查
我在虚拟机管理程序中编写了一个极简PE加载器,目标是通过解析导出表按名称定位函数,但获取的函数RVA值和预期不符。运行环境是Linux下的QEMU虚拟化。相关代码如下:
#include <utils/stdlib.h> __attribute__((section(".vmm"))) BYTE_PTR GetAssetFromExportedTable(BYTE_PTR physicalBaseAddress, PCHAR assetName) { PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)(physicalBaseAddress); if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { logDebug("Could not find the %s address. Invalid DOS header magic!!!\n", assetName); return 0; } PIMAGE_NT_HEADERS64 pNtHeader = (PIMAGE_NT_HEADERS64)((BYTE_PTR)(pDosHeader) + pDosHeader->e_lfanew); if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { logDebug("Could not find the %s address. Invalid NT header magic!!!\n", assetName); return 0; } PIMAGE_OPTIONAL_HEADER64 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER64)&(pNtHeader->OptionalHeader); if (pOptionalHeader->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { logDebug("Could not find the %s address. Invalid OPTIONAL header magic!!!\n", assetName); return 0; } // Retrieve Export Directory PIMAGE_DATA_DIRECTORY pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (pDataDirectory->VirtualAddress == 0) { return FALSE; // No export directory present } PIMAGE_EXPORT_DIRECTORY pExportedDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE_PTR)physicalBaseAddress + pDataDirectory->VirtualAddress); if (pExportedDirectory == 0 || pExportedDirectory->AddressOfNames == 0) { logDebug("Could not find the %s address. No exported names found.\n", assetName); return 0; } DWORD_PTR pNames = (DWORD_PTR)((BYTE_PTR)physicalBaseAddress + pExportedDirectory->AddressOfNames); DWORD_PTR pFunctions = (DWORD_PTR)((BYTE_PTR)physicalBaseAddress + pExportedDirectory->AddressOfFunctions); WORD_PTR pOrdinals = (WORD_PTR)((BYTE_PTR)physicalBaseAddress + pExportedDirectory->AddressOfNameOrdinals); for (DWORD i = 0; i < pExportedDirectory->NumberOfNames; i++) { PCHAR pFunctionName = (PCHAR)(physicalBaseAddress + pNames[i]); logDebug("Name: %s, Ordinal: %x, function RVA: %x\n", pFunctionName, pOrdinals[i], pFunctions[pOrdinals[i]]); if (strcmp(pFunctionName, assetName)) { DWORD functionRVA = pFunctions[pOrdinals[i]]; BYTE_PTR functionAddress = (BYTE_PTR)(physicalBaseAddress + functionRVA); logDebug("Found %s at address: %x\n", assetName, functionAddress); return functionAddress; } } logDebug("Could not find the %s address in the export table.\n", assetName); return 0; }
可能导致RVA错误的潜在问题
数组访问类型错误
AddressOfNames、AddressOfFunctions是DWORD数组的RVA,AddressOfNameOrdinals是WORD数组的RVA,但你把它们转换成了DWORD_PTR/WORD_PTR后直接用数组下标访问,这相当于把指针值当成数组起始,会读取到错误的内存内容。正确的写法应该是转换成对应类型的数组指针:DWORD* pNames = (DWORD*)(physicalBaseAddress + pExportedDirectory->AddressOfNames); DWORD* pFunctions = (DWORD*)(physicalBaseAddress + pExportedDirectory->AddressOfFunctions); WORD* pOrdinals = (WORD*)(physicalBaseAddress + pExportedDirectory->AddressOfNameOrdinals);这是最可能导致RVA不符的核心原因。
字符串比较逻辑颠倒
strcmp返回0表示字符串相等,但你的代码中if (strcmp(pFunctionName, assetName))会在字符串不相等时进入分支,导致返回错误的函数地址,或者提前终止循环返回错误结果。正确判断应为if (strcmp(pFunctionName, assetName) == 0)。虚拟地址与物理地址转换错误
导出表中的VirtualAddress是相对虚拟地址(RVA),如果PE文件没有加载到默认ImageBase,或者physicalBaseAddress不是加载基址对应的物理地址,直接用physicalBaseAddress + RVA计算会出错。VMM环境中需要先确认PE的实际加载基址,再将RVA转换为虚拟地址,最后通过页表映射得到物理地址。未处理转发函数
如果导出函数是转发函数,AddressOfFunctions中的值不是真实RVA,而是指向DLLName.FunctionName格式字符串的RVA。若该值落在导出表的地址范围内(pDataDirectory->VirtualAddress <= functionRVA < pDataDirectory->VirtualAddress + pDataDirectory->Size),则需要解析转发逻辑,否则会得到错误的RVA。PE对齐方式未适配
如果读取的是磁盘上的PE文件(而非内存加载后的镜像),PE的FileAlignment和SectionAlignment不同,不能直接用RVA加文件基址访问内容。需要通过节表将RVA转换为文件偏移:文件偏移 = 节的PointerToRawData + (RVA - 节的VirtualAddress)。
内容的提问来源于stack exchange,提问作者עמית ברוניצקי

