如何按指定格式输出IMAGE_EXPORT_DIRECTORY信息并访问其数组?
如何按,,格式输出IMAGE_EXPORT_DIRECTORY信息
我来帮你一步步拆解这个问题,其实核心就是理清三个导出数组的关联关系,再把内存相对地址(RVA)转换成文件偏移就搞定了!
先搞懂三个数组的核心关系
首先明确你提到的三个数组的作用和关联:
- AddressOfNames:这个数组是按名称字典序排序的,每个元素是导出名称字符串的RVA(内存相对地址)。遍历它就能拿到所有带名称的导出项。
- AddressOfNameOrdinals:和AddressOfNames是一一对应的数组,每个元素是一个16位整数,代表「导出序数 - 导出表的Base值」。用这个值作为索引,就能从AddressOfFunctions里找到对应的导出地址。
- AddressOfFunctions:存储所有导出地址的RVA数组,索引就是「导出序数 - Base」,对应每个导出项的内存地址(可能是函数入口,也可能是转发地址)。
具体实现步骤
要输出你要的格式,需要完成这几个关键步骤:
- 定位PE文件中的IMAGE_EXPORT_DIRECTORY结构;
- 将三个数组的RVA转换成文件偏移(因为我们是从磁盘文件读取,不是内存映射的PE);
- 遍历AddressOfNames数组,逐个获取名称、序数、文件地址;
- 按指定格式输出。
代码示例(C++)
下面是一个简化的实现,假设你已经把PE文件读取到内存中(比如用CreateFile+MapViewOfFile):
首先写一个辅助函数,用来把RVA转换成文件偏移:
DWORD RvaToFileOffset(PIMAGE_NT_HEADERS pNtHeaders, DWORD rva) { PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders); for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++, pSection++) { // 检查RVA是否在当前节的虚拟地址范围内 if (rva >= pSection->VirtualAddress && rva < pSection->VirtualAddress + pSection->Misc.VirtualSize) { // 计算文件偏移:RVA - 节虚拟基址 + 节文件偏移 return rva - pSection->VirtualAddress + pSection->PointerToRawData; } } return 0; // 找不到对应节,返回0 }
然后是主逻辑:
// 假设pFileBase是映射到内存的PE文件起始地址 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBase; // 定位NT头 PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pFileBase + pDosHeader->e_lfanew); // 定位导出表 DWORD exportDirRva = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)pFileBase + RvaToFileOffset(pNtHeaders, exportDirRva)); // 读取三个数组的文件偏移并转换成指针 DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)pFileBase + RvaToFileOffset(pNtHeaders, pExportDir->AddressOfFunctions)); DWORD* pAddressOfNames = (DWORD*)((BYTE*)pFileBase + RvaToFileOffset(pNtHeaders, pExportDir->AddressOfNames)); WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)pFileBase + RvaToFileOffset(pNtHeaders, pExportDir->AddressOfNameOrdinals)); // 遍历所有带名称的导出项 for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) { // 1. 获取导出名称 DWORD nameRva = pAddressOfNames[i]; char* exportName = (char*)((BYTE*)pFileBase + RvaToFileOffset(pNtHeaders, nameRva)); // 2. 计算导出序数 WORD ordinalOffset = pAddressOfNameOrdinals[i]; DWORD exportOrdinal = pExportDir->Base + ordinalOffset; // 3. 计算导出地址的文件偏移 DWORD funcRva = pAddressOfFunctions[ordinalOffset]; DWORD fileAddress = RvaToFileOffset(pNtHeaders, funcRva); // 4. 按格式输出 printf("%s,%lu,%lu\n", exportName, exportOrdinal, fileAddress); }
几个需要注意的细节
- 如果某个导出项的
funcRva指向的是转发地址(比如RVA对应的是一个类似"kernel32.CreateFileA"的字符串),那么fileAddress会指向这个字符串在文件中的位置,如果你需要区分普通函数和转发函数,可以额外判断地址是否在节表的可执行区域内。 - 有些导出项可能只有序数没有名称(这类不会出现在AddressOfNames数组里),如果需要输出这些项,需要遍历AddressOfFunctions数组,结合Base来处理,但你要的格式里包含Name,所以通常只需要处理带名称的项。
- 确保PE文件是32位的,64位PE的结构类似,但指针类型要改成
ULONGLONG,RVA和文件偏移的计算逻辑不变。
内容的提问来源于stack exchange,提问作者user9511374
相关产品推荐
相关产品推荐

