2.6 PE结构:导出表详细解析
导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
导出表中包含了三种信息:
- 函数名称:记录了可执行文件中导出函数的名称,在其他程序中调用时需要用到这个名称。
- 函数地址:记录了可执行文件中导出函数的地址,使用时需要调用该函数的地址。
- 函数序号:记录了每个导出函数的序号,可以通过序号直接调用函数。
导出函数的DLL文件中,导出信息被保存在导出表,导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows装载器能够通过这些信息来完成动态链接的整个过程。
导出函数存储在PE文件的导出表里,导出表的位置存放在PE文件头中的数据目录表中,与导出表对应的项目是数据目录中的首个IMAGE_DATA_DIRECTORY结构,从这个结构的VirtualAddress字段得到的就是导出表的RVA值,导出表同样可以使用函数名或序号这两种方法导出函数。
导出表的起始位置有一个IMAGE_EXPORT_DIRECTORY结构与导入表中有多个IMAGE_IMPORT_DESCRIPTOR结构不同,导出表只有一个IMAGE_EXPORT_DIRECTORY结构,该结构定义如下:
typedef struct _IMAGE_EXPORT_DIRECTORY
{
DWORD Characteristics; // 保留,恒为0x00000000
DWORD TimeDateStamp; // 文件的产生时间戳
WORD MajorVersion; // 主版本号
WORD MinorVersion; // 次版本号
DWORD Name; // 指向文件名的RVA
DWORD Base; // 导出函数的起始序号
DWORD NumberOfFunctions; // 导出函数总数
DWORD NumberOfNames; // 以名称导出函数的总数
DWORD AddressOfFunctions; // 导出函数地址表的RVA
DWORD AddressOfNames; // 函数名称地址表的RVA
DWORD AddressOfNameOrdinals; // 函数名序号表的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
上面的_IMAGE_EXPORT_DIRECTORY 结构如果总结成一张图,如下所示:

在上图中最左侧AddressOfNames结构成员指向了一个数组,数组里保存着一组RVA,每个RVA指向一个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的结构成员,该对应项存储的正是函数的唯一编号并与AddressOfFunctions结构成员相关联,形成了一个导出链式结构体。
获取导出函数地址时,先在AddressOfNames中找到对应的名字MyFunc1,该函数在AddressOfNames中是第1项,然后从AddressOfNameOrdinals中取出第1项的值这里是1,然后就可以通过导出函数的序号AddressOfFunctions[1]取出函数的入口RVA,然后通过RVA加上模块基址便是第一个导出函数的地址,向后每次相加导出函数偏移即可依次遍历出所有的导出函数地址,代码如下所示:
int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/lyshark.dll"), 0);
if (PE == TRUE)
{
// 0. 获取到ImageBase镜像基地址
DWORD ImageBase = NtHeader->OptionalHeader.ImageBase;
// 1. 从数据目录表的下标为 0 的项找到rva
DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
// 2. 找到导入表结构体
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + GlobalFileBase);
// 3. 获取有名字的个数和函数总个数
DWORD NameCount = ExportTable->NumberOfNames;
DWORD FunctionCount = ExportTable->NumberOfFunctions;
// 4. 获取三张表,分别是 地址表,名称表,序号表,其中序号表是WORD
DWORD* Addr_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + GlobalFileBase);
DWORD* Name_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + GlobalFileBase);
WORD* Id_Table = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + GlobalFileBase);
printf("序号 \t 导出RVA地址 \t 导出VA地址 \t 导出FOA地址 \t 导出函数 \t \n");
// 5. 遍历地址表
for (DWORD i = 0; i < FunctionCount; ++i)
{
bool HaveName = FALSE;
// 6. 判断是否有名字,有名字的话,下标会存在序号表中
for (DWORD j = 0; j < NameCount; ++j)
{
// 如果有名字则执行此处
if (i == Id_Table[j])
{
HaveName = TRUE;
// 对应序号表下标的名称表内保存的是名字
CHAR* Name = (CHAR*)(RVAtoFOA(Name_Table[j]) + GlobalFileBase);
printf("%5d \t %10p \t 0x%08X \t 0x%08X \t %-35s \n",
i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i]), Name);
break;
}
}
// 如果全部找完还没有名字
if (HaveName == FALSE)
{
printf("%5d \t %10p \t 0x%08X \t 0x%08X \t None \n",
i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i]));
}
}
}
else
{
printf("非标准程序 \n");
}
system("pause");
return 0;
}
运行如上程序片段,则会输出lyshark.dll动态链接库里面所有的导出函数,其输出效果如下图所示;

2.6 PE结构:导出表详细解析的更多相关文章
- [PE结构]导出表结构浅析
导出函数的总数-->以导出函数序号最大的减最小的+1,但导出函数序号是可自定义的,所以NumbersOfFunctions是不准确的 1.根据函数名称找,函数名称表->对应索引函数序号表中 ...
- 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)
0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...
- 手写PE结构解析工具
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如 ...
- Win32汇编-编写PE结构解析工具
汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地 ...
- 【转】pe结构详解
(一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等, 事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是 ...
- 修改记事本PE结构弹计算器Shellcode
目录 修改记事本PE结构弹计算器Shellcode 0x00 前言 0x01 添加新节 修改节数量 节表位置 添加新节表信息 0x02 添加弹计算器Shellcode 修改代码 0x03 修改入口点 ...
- PE知识复习之PE的导出表
PE知识复习之PE的导出表 一丶简介 在说明PE导出表之前.我们要理解.一个PE可执行程序.是由一个文件组成的吗. 答案: 不是.是由很多PE文件组成.DLL也是PE文件.如果我们PE文件运行.那么就 ...
- PE节表详细分析
目录 PE节表详细分析 0x00 前言 0x01 PE节表分析 节表结构 节表数量 节表名字 节表大小 节位置 节表属性 0x02 代码编写 PE节表详细分析 0x00 前言 上一篇文章我们学习了PE ...
- java类生命周期详细解析
(一)详解java类的生命周期 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前 ...
- 单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式
单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式 一 表结构如下: 万行 CREATE TABLE t_audit_operate_log ( Fid b ...
随机推荐
- 手把手教你搭建深度学习开发环境(Tensorflow)
前段时间在阿里云买了一台服务器,准备部署网站,近期想玩一些深度学习项目,正好拿来用.TensorFlow官网的安装仅提及Ubuntu,但我的ECS操作系统是 CentOS 7.6 64位,搭建Pyth ...
- @Scheduled cron 定时任务表达式含义,及* ?的区别
好多网友对@Scheduled cron表达式含义做了阐述,个人认为很多对于 * ?的说明不够具体也不算准确,借此本文特别对 * ?做一下说明. cron格式:[秒数][分钟][小时][日期][月份] ...
- 如何用 7 分钟击破 Serverless 落地难点?
当前,Serverless 覆盖的技术场景正在不断变广.Serverless 已在微服务.在线应用.事件驱动.任务处理等众多场景被验证且广泛应用 .当你想要部署一个网站时,需要自己购买服务器并花费时间 ...
- vue-echarts之折线图以及双Y轴折线,柱状混合图,部分属性记录
https://blog.csdn.net/qq_41139348/article/details/106870005 https://segmentfault.com/a/1190000021898 ...
- C++跨DLL内存所有权问题探幽(一)DLL提供的全局单例模式
最近在开发的时候,特别是遇到关于跨DLL申请对象.指针.内存等问题的时候遇到了这么一个问题. 问题 跨DLL能不能调用到DLL中提供的单例? 问题比较简单,就是我现在有一个进程A,有DLL B DLL ...
- spring--AOP通知类型有哪些
Spring AOP(Aspect-Oriented Programming,面向切面编程)提供了五种类型的通知(advice),这些通知定义了切面(aspect)是在目标对象的方法执行的哪个点被应用 ...
- 【荐】开源Winform控件库:花木兰控件库
微信好友推荐,挺好看的Winfrom控件库,下面来看看. 介绍 基于 C#(语言) 4.0 . VS2019 . Net Framework 4.0(不包括Net Framework 4.0 Clie ...
- JMS微服务开发示例(七)使用 Serilog 作为日志提供者
nuget 引入: Serilog.Extensions.LoggingSerilog.Settings.ConfigurationSerilog.Sinks.ConsoleSerilog.Sinks ...
- [转帖]nginx源码层面探究request_time、upstream_response_time、upstream_connect_time与upstream_header_time指标具体含义与区别
https://www.cnblogs.com/AcAc-t/p/nginx_request_time_upstream_respone_time_analysis.html 背景概述 最近计划着重分 ...
- [转帖]Linux如何查看网关地址
转至:https://baijiahao.baidu.com/s?id=1733537078943023051&wfr=spider&for=pc 服务器之间的通信是通过ip地址来 ...