目录
什么是导出表
导出表的关键点
函数地址表
当序号不是从0开始
Base基序号
查序号太麻烦 ===>函数名称
导出表遍历
导出表 作用与总体布局
IMAGE_EXPORT_DIRECTORY 字段逐项细化
三张关键的表
AddressOfFunctions(RVA 到 EAT)函数地址表
AddressOfNames(RVA 到名字指针数组)名称表
AddressOfNameOrdinals(RVA 到序号表) 序号表
结构体

什么是导出表
-
也就是指的是DLL 函数模块等等
-
DLL导出函数
-
Dll函数调用 也就是通过__declspec(dllexport)
-
在通过LoadLiabry 加载进这个DLL模块
-
在通过GetProcAddress 拿到函数的地址
-
最后进行一个加载这样就拿到了函数地址
-
学会了导出表以后就是可以去实现 GetProcAddress 这个函数

-
提供模块导出函数
-
支持动态链接库 运行其他程序使用LoadLiabry GetProcAddress的导出函数地址
-
可以支持序号 可以支持名字
导出表的关键点
-
序号表 地址表 名称表 [ Base基序号] ====>>>> 三表的连接如何连接
-
早期只有序号表=函数地址表
-
也就是早期windows 导出表(IAT)只有2个 一个是序号 一个函数地址
-
这样就可以实现一一对应
-
函数地址表
-
但是C语言的特性我们就有Index
-
index和函数地址一一对应 这样就不需要函数表了
-
也就是inedex 就可以当序号表和函数地址表
-
当序号不是从0开始
-
那么使用index就对应不到了
-
也就是导出的函数序号不是从1开始
-
假设导出的序号是0x1001 1002 1003 这样前面0x1000就是浪费了
Base基序号
-
也就是我们可以在导出函数的时候自定义序号就不对了
-
也就是我们直接从0开始 但是这个0并不是函数的序号 也就是在导出序号不是从0开始就会出现这个情况
-
也就是我们导出的序号windows把最小值当成一个基序号比如说就是0x1001
-
也就是说base基地址=0x1001 – 0x1001 = 0 base基地址=0x1001 – 0x1002 = 1 这样的话我们就可以正常申请空间不浪费空间了。
-
最后序号表也就是 base基地址=0x1001 – 0x1001 = 拿到序号 ==> 去函数地址表找到对应序号 拿到函数地址表
-
这样的数组下表一样是从012345开始
-
也就是说我们查找函数地址的时候 GetProcAddress("1001") 的时候就不会像原来的函数地址表函数【函数地址表【1】这样进行查找了】这个时候需要加上base基地址(0x10001 = 函数地址表【1+ base基地址序号】
-
最后也就是 函数地址表【序号 – base基序号】
-
函数地址表【序1002- 1001】 = 序号1
查序号太麻烦 ===>函数名称
-
序号和地址一一对应
-
函数名称和地址是一一对应吗 ===> 答案肯定不是的
-
一个函数的地址 可能有多个函数的名称
-
函数的地址和函数的名称是一对多的关系
-
也就是一个函数地址可以有多个函数名称
-
CreateFileEx CreateFile2 也就是不同版本 但是指向地址是一样的
-
函数的名称是多个 地址是唯一的
-
hModule (DLL基址)
↓
解析 DOS Header → NT Header → Export Directory RVA
↓
三大表:
├── AddressOfNames → 名称字符串数组
├── AddressOfNameOrdinals → 名称索引 → 序号
└── AddressOfFunctions → 序号 → 函数RVA
↓
匹配名称/序号 → 取对应RVA
↓
返回:hModule + RVA(函数真实地址)
导出表遍历
-
导入表(Import Directory)告诉加载器这个模块需要哪些外部 DLL 及哪些符号;
-
导出表(Export Directory)列出模块向外暴露的函数/变量和序号(ordinal),以供其他模块链接或运行时解析
导出表 作用与总体布局
-
导出表(Export Directory)用于列出该模块对外提供的入口(函数/变量),它包含 IMAGE_EXPORT_DIRECTORY 结构及三张并行数组:
-
Export Address Table(EAT)
-
Export Name Pointer Table(指向名字字符串的指针数组)
-
Export Ordinal Table(名字对应的序号)
-
-
调用者可以通过名称或序号解析导出地址;
-
此外还有“转发导出”(forwarded export)机制:导出的条目可以指向另一个 DLL 的符号而不是实际地址。
IMAGE_EXPORT_DIRECTORY 字段逐项细化
-
Name(RVA) 模块自己的名称字符串(通常 DLL 名)。Microsoft Learn
-
Base(DWORD)
-
导出序号的基值(ordinal base)。导
-
出序号 = Base + index(index 为 EAT 的索引)。
-
这允许序号不必从 0 开始。
-
-
NumberOfFunctions / NumberOfNames(DWORD)
-
分别表示 EAT 长度(全部导出的地址,包括按序号导出但无名字的)
-
按名字导出的数量(名字数组长度)
-
二者不同步时说明有按序号导出但无名字的项。
-
=====——->
变量和函数的区分 判断地址落在哪个节区里面就可以区分出来
函数转发
也就是函数名称是add = kernel32.Message 这样就是函数转发
判断范围在不在 导出表范围内 如果在则说明转发
三张关键的表
AddressOfFunctions(RVA 到 EAT)函数地址表
-
指向 Export Address Table(每项是 RVA 指向函数代码或转发字符串)
-
调用者通过 EAT[index] 获取 RVA,然后加上映像基址得到实际函数地址。
AddressOfNames(RVA 到名字指针数组)名称表
-
指向一个数组,数组每项是指向导出名字符串的 RVA。
-
与 AddressOfNameOrdinals 并行用于基于名字的查找。
AddressOfNameOrdinals(RVA 到序号表) 序号表
-
这是一个 WORD 数组,长度等于 NumberOfNames,存储每个名字在 EAT 中的索引(index);
-
用来把名字映射到导出表中的地址。
结构体
_IMAGE_EXPORT_DIRECTORY
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 保留
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 版本
WORD MinorVersion;
DWORD Name; // DLL 名 RVA
DWORD Base; // 起始序数
DWORD NumberOfFunctions; // 函数数
DWORD NumberOfNames; // 名称数
DWORD AddressOfFunctions; // 函数地址表 RVA
DWORD AddressOfNames; // 名称表 RVA
DWORD AddressOfNameOrdinals;// 序数表 RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;




