PE结构(4)导出表
Last updated on April 30, 2024 pm
导出表概览
所在位置
首先我们观察PE结构的OP头结构:
1 |
|
在OP头结构的最后一个结构体数组中,存放所有数据目录页。IMAGE_NUMBERSOF_DIRECTORY_ENTRY的值固定为15。
数据目录页结构如下:
1 |
|
其中,VirtualAddress是一个RVA, Size成员则基本没有意义。
RVA,即在虚拟内存中的相对地址,或者说是基于Imagebase的内存偏移量。
与之对应的概念是FOA,即基于文件起始地址的偏移量。
RVA与FOA之间存在转换关系。
PE结构(番外)RVA与FOA
通过VirtualAddress我们可以找到指定的数据目录页所指向的数据表位于文件或内存中的具体位置。
导出表的数据目录页,即数组DataDirectory的第一个元素,即*DataDirectory[0]*。
将该数据目录页的VirtualAddress成员读取,转换为FOA,则可以定位导出表在文件中的位置。
结构
导出表的数据结构如下(内存1字节对齐):
1 |
|
其中较为重要的当属:
- AddressOfFunctions : 函数表,存储导出函数地址(RVA),即四字节整数数组:DWORD[]
- AddressOfNames : 名称表,存储导出函数名字的地址(RVA),即四字节整数数组:DWORD[]
- AddressOfNameOrdinals : 序号表,存储导出函数序号(RVA),即两字节整数数组:WORD[]
需要注意的是,序号表中存储的并不是真正的导出序号。
而是 导出序号 - Base 的值。即每个函数的序号,相对于最小序号的偏移量。
我理解这是一种防溢出的手段,毕竟序号表是两字节整形数组,能表达的序号长度有限。
而实际导出函数,也不太可能一个文件导出几十万个函数来吧,所以存储偏移量就足够了。
获取导出函数的逻辑
已知函数的名称
只有以名称导出的函数,才会存在于AddressOfNames名称表中。
首先遍历名称表,获取指向名称的指针,而后比对名称是否一致。若一致,则从序号表中获取其序号,然后通过函数表获取函数地址。
伪代码逻辑:
1 |
|
已知函数序号
若已知函数序号,则可以直接从函数表中获取函数。
直接将序号减去Base的值,然后从函数表中获取即可。
伪代码逻辑:
1 |
|
编程实现
主函数入口:
1 |
|
打印导出表函数的实现:
1 |
|