PE文件解析(3):导出表的解析
可选NT头的结构体中存储着ExportTable(导出表)和 Import Table(导入表)
导出表
什么是导出表?
在我们写的DLL或者EXE导出的函数,会在程序运行时,把这个API加载入程序的运行内存中。
导出表记录了我们加载的这些API函数的的地址,名称,与序号。
导出表具有的特性:
1.导出表的地址是一个RVA,通过基址+偏移可以得到导出表
2.导出表可以用序号
记录导出的函数
3.导出表也可以直接显示函数的名字
。
导出表的位置:
在IMAGE_OPTIONAL_HEADER结构体,即可选NT头的DataDirectory数组字段保存。 这个字段又叫做 数据目录表
,是PE文件的一个非常重要的东西。
导出表位于: DataDirectory[0]的位置,是一个RVA偏移,需要我们进行转换才是真正的地址
导出表的结构体解析
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics;//没用 DWORD TimeDateStamp;//时间戳 WORD MajorVersion;//没用 WORD MinorVersion;//没用 DWORD Name;//指向导出表文件名 RVA -->FOA+FileBuff=char *name; DWORD Base;//导出函数起始序号 DWORD NumberOfFunctions;//导出函数个数 DWORD NumberOfNames;//以名称导出函数个数 DWORD AddressOfFunctions;//导出函数地址表 RVA-->FOA +FileBuff= DWORD AddressOfNames; //导出函数名称表 // RVA from base of image DWORD AddressOfNameOrdinals; //导出函数序号表 // RVA from base of image } IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;
重要的几个字段:
- Name:导出表函数的名称,是一个RVA偏移。
- NumberOfFunctions:导出函数的个数
- NumberOfNames:以名称导出函数个数
AddressOfFunctions:导出函数地址表
AddressOfNames:导出函数名称表
AddressOfNameOrdinals:导出函数序号表
010editor解析:
寻找导出表的地址
- 根据数据目录表找到导出表(索引0 )的RVA:19060h
- 转到区段表:计算这个RVA在哪一个区段内,区段具有virtualaddress:区段的RVA偏移地址,virtualSize:区段的字节大小,pointofRawData:区段的FOA。
- 计算可知: 19060h > virtualAddress AND 19060h <virtualAddress +VirtualSize ,所以这个导出表的起始地址位于区段内部,所以利用公式:
数据的FOA = 数据的RVA - 区段的RVA + 区段的FOA
, 可以得到数据的FOA,也就是在文件中的偏移: 19060h - 17000h + (5C00)h = 7C60h,所以7C60就是我们的导出表的FOA,再加上基址即可得到我们的真正地址。
- 010editor根据偏移可以直接得到导出表位置,无需加上基址:这就是我们的导出表的位置: 注意:7C60不是真正的地址,7C60+imagebase才是。
代码解析导出表
void cPE::GetExportTable() { /* 得到导出表的的信息 */ // 第一个存储的就是导出表的偏移地址 IMAGE_DATA_DIRECTORY ExPortAddr = pOptionHeader->DataDirectory[0]; // 找到导出表 PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY) (RvaToFoa(ExPortAddr.VirtualAddress) + FileBuff); if (ExportTable == NULL || ExportTable->NumberOfFunctions == 0) { printf("导出表为空!\n"); return; } // 打印导出表的名称 char* DllName =(RvaToFoa(ExportTable->Name) + FileBuff); printf("DLL名称:%s\n", DllName); //获取其他函数信息 DWORD* FuncAddr = (DWORD*)(RvaToFoa(ExportTable->AddressOfFunctions) + FileBuff); WORD* FuncOrder = (WORD*)(RvaToFoa(ExportTable->AddressOfNameOrdinals) + FileBuff); DWORD* FuncName = (DWORD*)(RvaToFoa(ExportTable->AddressOfNames) + FileBuff); bool NameIsNull{ false }; for (UINT i = 0; i < ExportTable->NumberOfFunctions; i++) { printf("函数地址:%x\t", *FuncAddr); for (UINT Order = 0; Order < ExportTable->NumberOfNames; Order++) { //看看在序号表中有没有等于地址表的索引的 if (FuncOrder[Order] == i) { //如果序号表存在,则取序号表的索引 i,即取名称表的第i的元素 printf("序号:%d\t", FuncOrder[Order]); NameIsNull = false; char* Name = RvaToFoa(FuncName[Order]) + FileBuff; printf("%s\n", Name); break; } else { NameIsNull = true; } } if (NameIsNull) { printf("NoName\n"); } FuncAddr++; } }
地址,序号与名称表:
序号表Order和名称Name表的索引是一致的。
例如:我们已知函数地址表中MessageBox的地址(我们此时还不知道名称),如何通过地址找到函数的名称?
- AddressFunctions: 索引为6
- 根据索引为6,找到序号表中序号值等于6的索引,得到索引5。
- 找到名称表中索引为5的名称,即找到了MessageBoxW的名称。
注: 有关RVA和FOA 和NT头部分解析请看我前面的几篇博客!!!!!!
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209673.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix