引言
假设现在有一个exe程序使用了一个dll,对于这个exe程序而言,dll文件就相当于一个黑盒子,里面装的内容exe一点也不清楚。
所以什么是导出表?
导出表就是dll的一份清单,里面记录了它有多少个函数,函数的地址等相关信息
补充:exe并不是没有导出表,而是大部分exe没有导出表
另外,数据目录表有16个,其中第一个就是导出表
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
导出表结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //未使用
DWORD TimeDateStamp; //时间戳
WORD MajorVersion; //未使用
WORD MinorVersion; //未使用
DWORD Name; //指向该导出表文件名字符串
DWORD Base; //导出函数其起始序号
DWORD NumberOfFunctions; //所有导出函数的个数
DWORD NumberOfNames; //以函数名字导出的函数的个数
DWORD AddressOfFunctions; //导出函数地址表(RVA)
DWORD AddressOfNames; //导出函数名称表(RVA)
DWORD AddressOfNameOrdinals; //导出函数序号表(RVA)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
可以看出,整个导出表结构里面又包括了三张表,分别是AddressOfFunctions(导出函数地址表)、AddressOfNameOrdinals(导出函数序号表)以及AddressOfNames(导出函数名称表)
其中,AddressOfFunctions表储存了每个导出函数的地址(RVA),个数由NumberOfFunctions决定,需要加上ImageBase才是真正的函数地址,表宽为4字节;AddressOfNames表储存了每个导出函数的名称的地址(RVA),需要将这些RVA转换成FOA才能找到函数名称的真正位置,表宽4字节;AddressOfOrdinals表储存的是函数的导出序号,这个序号需要加上Base所存的值才是真正的导出序号,表宽2字节
- AddressOfFunctions通常比AddressOfNames大,因为可能存在没有以名字导出的函数。但存在一种可能,一个函数有多个名字指向同一地址,就会使AddressOfFunctions小于AddressOfNames
- AddressOfNameOrdinals的数量由AddressOfNames,他们的数量相等
- 而AddressOfFunctions的序号并不直接是AddressOfNameOrdinals中的值,而是它里面的值加上导出表结构里面的Base所存的值
- 导出函数方法分两种,一种是按名字导出,另一种是按序号导出
过程
1.名称导出
假设我们提供的函数名为Test。
先找到AddressOfNames表,将函数名Test和这个表里面存的地址指向的函数名一个一个比较,直到两者相同,此时这个函数名字的地址在AddressOfNames表中的索引为2。
再找到AddressOfOrdinals表,拿着刚刚找到的索引值去找到AddressOfOrdinals表中索引值与它相同的序号,此时这个序号为4。
最后再找到AddressOfFunctions表,将刚刚找到的序号4对应找到该表索引为4的地方所存的地址,该地址即为Test函数的地址。
2.序号导出
假设我们提供的序号是10,Base值为2。
用这个序号10减去Base值2得到一个序号8,就可以直接拿着这个序号8直接前往AddressOfFunctions找到索引为8的地方所存的地址,即为所找的函数地址。
另外,所有导出函数的个数=最后一个序号-第一个序号+1;
所以当序号表是乱序时,NumberOfFunctions的值就是错误的。