导出表
exe程序中通常会使用动态链接库dll中的函数;
dll相当于一个独立的模块,dll中的代码并不会编译到exe程序中;
这就产生了一个问题:exe怎么知道dll中的代码在什么位置;
这就需要dll提供一个清单,这个清单中能清晰说明有多少个函数、它们的名字、地址;
导出表就是这样的一个清单;
exe和dll都可以提供函数给其它模块使用;
如果要这么做就必须提供一个清单,也就是导出表;
一般情况下exe都没有导出表,因为exe通常不需要给其它模块提高函数,但这不是绝对的;
1.定位导出表
数据目录项的第一个结构,就是导出表.
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
VirtualAddress 导出表的RVA
Size 导出表大小 ->size只是参考,导出表有自己的方式约定自己的大小;修改了size并不影响程序;
2、导出表结构
上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表.
如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位
必须要先将该RVA转换成FOA.
真正的导出表结构如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用 11
WORD MinorVersion; // 未使用 12
DWORD Name; // 指向该导出表文件名字符串 13
DWORD Base; // 导出函数起始序号 15
DWORD NumberOfFunctions; // 所有导出函数的个数 17
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
地址表AddressOfFunctions ->由导出函数的个数NumberOfFunctions决定;
名字表NumberOfNames ->由NumberOfNames决定,因为可能有的函数用隐藏函数名的方式导出;
序号表AddressOfNameOrdinals ->和名字表一样大,也是由NumberOfNames决定;
导出函数有两种方式:
1】名字导出;
->遍历名字表,用名字表中的地址找字符串,与目标字符串比对;
->找到字符串一样的之后,得到该处的索引;
->按照相同的索引号从序号表中找序号值;
->用序号值为索引,从地址表中找到目标函数的地址;
2】序号导出;
->用目标序号-BASE,得到一个值;
->直接用这个值为索引,从地址表中找函数的地址;
->不需要查序号表;
3、AddressOfFunctions说明
该表中元素宽度为4个字节
该表中存储所有导出函数的地址
该表中个数由NumberOfFunctions决定
该表项中的值是RVA, 加上ImageBase才是函数真正的地址
定位:IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA
4、AddressOfNames说明
该表中元素宽度为4个字节
该表中存储所有以名字导出函数的名字的RVA
该表项中的值是RVA, 指向函数真正的名称
特别说明:
1、函数的真正的名字在文件中位置是不确定的
2、但函数名称表中是按名字排序的
也就是说,A开头的函数在AddressOfNames排在最前面.
但AXXXXXX这个真正的名字,可能排在BXXXXX后面
3、如果想打印名字,要先将AddressOfNames转换为FOA
5、AddressOfNameOrdinals说明
该表中元素宽度为2个字节
该表中存储的内容 + Base = 函数的导出序号
6.总结
1)为什么要分成3张表?
函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.
比如有些函数可以用隐藏函数名的方式导出;
2)函数地址表是不是一定大于函数名称表?
未必,一个相同的函数地址,可能有多个不同的名字.
3)如何根据函数的名字获取一个函数的地址?
4)如何根据函数的导出序号获取一个函数的地址?
7.打印导出表信息
RVA转FOA:
//将内存偏移转换为文件偏移
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva){
//定义文件头
PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针
PIMAGE_NT_HEADERS ntHeader = NULL; //nt头指针
PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针
PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针
PIMAGE_SECTION_HEADER sectionHeader = NULL; //节表指针
//找到文件头
dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)pFileBuffer + dosHeader->e_lfanew);
peHeader = (PIMAGE_FILE_HEADER) ((DWORD)ntHeader + 4);
opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
sectionHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
//1.判断是哪个节
int sec = -1;
for(int i=0;i<peHeader->NumberOfSections;i++){
DWORD va = (sectionHeader+i)->VirtualAddress;
DWORD size = (sectionHeader+i)->Misc.VirtualSize;
if(dwRva >= va && dwRva<=(va+size) ){
sec = i;
printf("在第%d个节\n",sec);
break;
}
}
if(sec<0){
printf("内存偏移不在任何一个节\n");
return 0;
}
//2.转换
DWORD secOffset = dwRva - (sectionHeader + sec)->VirtualAddress;
DWORD foa = (sectionHeader + sec)->PointerToRawData + secOffset;
return foa;
}
输出导出表:
#include "stdafx.h"
#include "PeTool.h"
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
void printExportDir(){
//定义头结构指针
PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针
PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针
PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针
PIMAGE_DATA_DIRECTORY dataDir = NULL; //数据目录指针
PIMAGE_EXPORT_DIRECTORY exportDir = NULL; //导出表指针
//1.读取文件到缓冲区
LPVOID pFileBuffer = NULL;
DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
if(!fileSize){
printf("读取文件失败\n");
return;
}
//2.初始化头指针
dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
peHeader = (PIMAGE_FILE_HEADER) ((DWORD)dosHeader + dosHeader->e_lfanew + 4);
opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
dataDir = opHeader->DataDirectory;
exportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer,dataDir->VirtualAddress));
if(!exportDir){
printf("该文件没有导出表\n");
free(pFileBuffer);
return;
}
//3.输出信息
LPSTR expName = (LPSTR) ((DWORD)pFileBuffer + exportDir->Name); //注意导出表中的name保存的是:dll名的地址相对于首地址的偏移
printf("导出表文件名Name:%s\n",expName);
printf("导出函数起始序号Base:%d\n",exportDir->Base);
printf("所有导出函数的个数NumberOfFunctions:%d\n",exportDir->NumberOfFunctions);
printf("以函数名导出的函数个数NumberOfNames:%d\n",exportDir->NumberOfNames);
printf("地址表内存偏移AddressOfFunctions:%x\n",exportDir->AddressOfFunctions);
printf("名字表内存偏移AddressOfNames:%x\n",exportDir->AddressOfNames);
printf("序号表内存偏移AddressOfNameOrdinals:%x\n",exportDir->AddressOfNameOrdinals);
printf("***************地址表*********************\n");
LPDWORD pFunAddr = (LPDWORD)(exportDir->AddressOfFunctions);
for(int i=0;i<exportDir->NumberOfFunctions;i++){
DWORD funAddr = *(LPDWORD)((DWORD)pFileBuffer + (DWORD)(pFunAddr+i));
printf("函数地址[%d]-%x:%x\n", i, pFunAddr+i, funAddr);
}
printf("*************名字表****************\n");
LPDWORD pNameAddr = (LPDWORD)((DWORD)pFileBuffer + exportDir->AddressOfNames);
for(int j=0;j<exportDir->NumberOfNames;j++){
DWORD nameOffset = *(pNameAddr + j);
LPSTR funName = (LPSTR)((DWORD)pFileBuffer + nameOffset);
printf("函数名[%d]-%x:%s\n", j, nameOffset, funName);
}
printf("*********序号表***************\n");
LPWORD pOrdinals = (LPWORD) (exportDir->AddressOfNameOrdinals);
for(int l=0;l<exportDir->NumberOfNames;l++){
WORD ord = ((LPWORD)((DWORD)pFileBuffer + (DWORD)pOrdinals))[l];
printf("序号[%d]-%x:%d\n",l, pOrdinals+l,ord);
}
//释放内存
free(pFileBuffer);
}
int main(int argc, char* argv[])
{
printExportDir();
getchar();
}
结果: