You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按指定格式输出IMAGE_EXPORT_DIRECTORY信息并访问其数组?

如何按,,格式输出IMAGE_EXPORT_DIRECTORY信息

我来帮你一步步拆解这个问题,其实核心就是理清三个导出数组的关联关系,再把内存相对地址(RVA)转换成文件偏移就搞定了!

先搞懂三个数组的核心关系

首先明确你提到的三个数组的作用和关联:

  • AddressOfNames:这个数组是按名称字典序排序的,每个元素是导出名称字符串的RVA(内存相对地址)。遍历它就能拿到所有带名称的导出项。
  • AddressOfNameOrdinals:和AddressOfNames是一一对应的数组,每个元素是一个16位整数,代表「导出序数 - 导出表的Base值」。用这个值作为索引,就能从AddressOfFunctions里找到对应的导出地址。
  • AddressOfFunctions:存储所有导出地址的RVA数组,索引就是「导出序数 - Base」,对应每个导出项的内存地址(可能是函数入口,也可能是转发地址)。

具体实现步骤

要输出你要的格式,需要完成这几个关键步骤:

  1. 定位PE文件中的IMAGE_EXPORT_DIRECTORY结构;
  2. 将三个数组的RVA转换成文件偏移(因为我们是从磁盘文件读取,不是内存映射的PE);
  3. 遍历AddressOfNames数组,逐个获取名称、序数、文件地址;
  4. 按指定格式输出。

代码示例(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:47:55