我们前面在学习PE文件结构的时候,在可选PE头里跳过了最后一项,为一个有16个元素结构体数组,我们称它为数据目录。
今天我们来学习数据目录的第一个结构:导出表。
一.如何定位导出表
我们先来看看数据目录的结构:
typedef struct _IMAGE_DATA_DIRECTORY{ DWORD VirtualAddress;//数据的RVA DWORD Size;//数据的大小 }
这里的VirtualAddress指向导出表
需要注意的是,这里的VirtulaAddress为RVA,也就是ImageBuffer中的偏移,如果我们要在ImageBuffer里找到导出表,我们需要将RVA转换为FOA之后在ImageBuffer中通过偏移找到导出表。
这里的Size表示数据的大小,也就是导出表的大小
二.导出表解析
我们来看看导出表的结构:
typedef struct _IMAGE_EXPORT_DIRECTORY{ DWORD Characteristics;//未使用 DWORD TimeDateStamp;//时间戳,表示输出表的创建时间 WORD MajorVersion;//输出表的主版本号,未使用,设置为0 WORD MinorVersion;//输出表的次版本号,未使用,设置为0 DWORD Name;//指向一个与输出函数关联的文件名的RVA DWORD Base;//导出函数的其实序号 DWORD NumberOfFunctions;//导出函数的总数 DWORD NumberOfNames;//以函数名导出的函数的个数 DWORD AddressOfFunctions;//指向导出函数地址表的RVA DWORD AddressOfNames;//指向导出函数名称表的RVA DWORD AddressOfOrdinals;//指向函数序号表的RVA }IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY
在导出表中我们要特别注意最后三个元素:它们分别指向导出函数地址表,导出函数名称表,导出函数序号表,我们需要将三个RVA转换为FOA来找到三张表。
我们来分别讲解一下这三张表:
1.函数地址表
如何找到函数地址表:
通过导出表的AddressOfFunction字段,若要在FIleBuffer中找到函数地址表,还需将其转换为FAO加上FileBuffer基址。
我们来看看函数地址表:
这里我们可以通过下标来寻找函数地址,下标标记方式为函数导出序号-Base
。
比如说,我们已知函数导出序号为5,Base为2,那么该函数的函数地址为:函数地址表的第5-2=3
个。
2.函数导出序号表
如何找到函数导出序号表:
通过导出表的AddressOfOrdinals字段,同样,若要在FileBuffer中找到函数导出序号表,需要将其转换为FOA加上FileBuffer基址。
我们来看看函数导出序号表:
需要注意的是,函数导出序号表中存储的函数导出序号为:函数真正导出序号-Base
我们可以发现:函数导出序号表中的元素并不是按照顺序排列的,那么他是如何排列的呢?
其实这里的函数导出序号是和函数名称表对应的,我们假设有两个函数,其函数名称为Abcde(函数真正导出序号为3)和Bcdef(函数真正导出序号为2),那么Base就为2,在函数名称表中,以A开头的函数名称存储在最前面,以B开头的函数名称存储在后面,在我们举例的两个函数中,函数名称表中Abcde存储在Bcdef前面,所以Abcde的导出序号就存储在Bcdef后面,也就是在函数导出序号表中,存储1和0,而且1在0前面。
我们给出图来加深理解: