可选NT头的结构体中存储着ExportTable(导出表)和 Import Table(导入表)
导出表
什么是导出表?
在我们写的DLL或者EXE导出的函数,会在程序运行时,把这个API加载入程序的运行内存中。
导出表记录了我们加载的这些API函数的的地址,名称,与序号。
导出表具有的特性:
1.导出表的地址是一个RVA,通过基址+偏移可以得到导出表
2.导出表可以用序号
记录导出的函数
3.导出表也可以直接显示函数的名字
。
导出表的位置:
在IMAGE_OPTIONAL_HEADER结构体,即可选NT头的DataDirectory数组字段保存。 这个字段又叫做 数据目录表
,是PE文件的一个非常重要的东西。
导出表位于: DataDirectory[0]的位置,是一个RVA偏移,需要我们进行转换才是真正的地址
导出表的结构体解析
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;//没用
DWORD TimeDateStamp;//时间戳
WORD MajorVersion;//没用
WORD MinorVersion;//没用
DWORD Name;//指向导出表文件名 RVA -->FOA+FileBuff=char *name;
DWORD Base;//导出函数起始序号
DWORD NumberOfFunctions;//导出函数个数
DWORD NumberOfNames;//以名称导出函数个数
DWORD AddressOfFunctions;//导出函数地址表 RVA-->FOA +FileBuff=
DWORD AddressOfNames; //导出函数名称表 // RVA from base of image
DWORD AddressOfNameOrdinals; //导出函数序号表 // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;
重要的几个字段:
- Name:导出表函数的名称,是一个RVA偏移。
- NumberOfFunctions:导出函数的个数
- NumberOfNames:以名称导出函数个数
AddressOfFunctions:导出函数地址表
AddressOfNames:导出函数名称表
AddressOfNameOrdinals:导出函数序号表
010editor解析:
寻找导出表的地址
- 根据数据目录表找到导出表(索引0 )的RVA:19060h
- 转到区段表:计算这个RVA在哪一个区段内,区段具有virtualaddress:区段的RVA偏移地址,virtualSize:区段的字节大小,pointofRawData:区段的FOA。
- 计算可知: 19060h > virtualAddress AND 19060h <virtualAddress +VirtualSize ,所以这个导出表的起始地址位于区段内部,所以利用公式:
数据的FOA = 数据的RVA - 区段的RVA + 区段的FOA
, 可以得到数据的FOA,也就是在文件中的偏移: 19060h - 17000h + (5C00)h = 7C60h,所以7C60就是我们的导出表的FOA,再加上基址即可得到我们的真正地址。
- 010editor根据偏移可以直接得到导出表位置,无需加上基址:这就是我们的导出表的位置: 注意:7C60不是真正的地址,7C60+imagebase才是。
代码解析导出表
void cPE::GetExportTable()
{
/*
得到导出表的的信息
*/
// 第一个存储的就是导出表的偏移地址
IMAGE_DATA_DIRECTORY ExPortAddr = pOptionHeader->DataDirectory[0];
// 找到导出表
PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)
(RvaToFoa(ExPortAddr.VirtualAddress) + FileBuff);
if (ExportTable == NULL || ExportTable->NumberOfFunctions == 0)
{
printf("导出表为空!\n");
return;
}
// 打印导出表的名称
char* DllName =(RvaToFoa(ExportTable->Name) + FileBuff);
printf("DLL名称:%s\n", DllName);
//获取其他函数信息
DWORD* FuncAddr = (DWORD*)(RvaToFoa(ExportTable->AddressOfFunctions) + FileBuff);
WORD* FuncOrder = (WORD*)(RvaToFoa(ExportTable->AddressOfNameOrdinals) + FileBuff);
DWORD* FuncName = (DWORD*)(RvaToFoa(ExportTable->AddressOfNames) + FileBuff);
bool NameIsNull{ false };
for (UINT i = 0; i < ExportTable->NumberOfFunctions; i++)
{
printf("函数地址:%x\t", *FuncAddr);
for (UINT Order = 0; Order < ExportTable->NumberOfNames; Order++)
{
//看看在序号表中有没有等于地址表的索引的
if (FuncOrder[Order] == i)
{
//如果序号表存在,则取序号表的索引 i,即取名称表的第i的元素
printf("序号:%d\t", FuncOrder[Order]);
NameIsNull = false;
char* Name = RvaToFoa(FuncName[Order]) + FileBuff;
printf("%s\n", Name);
break;
}
else
{
NameIsNull = true;
}
}
if (NameIsNull)
{
printf("NoName\n");
}
FuncAddr++;
}
}
地址,序号与名称表:
序号表Order和名称Name表的索引是一致的。
例如:我们已知函数地址表中MessageBox的地址(我们此时还不知道名称),如何通过地址找到函数的名称?
- AddressFunctions: 索引为6
- 根据索引为6,找到序号表中序号值等于6的索引,得到索引5。
- 找到名称表中索引为5的名称,即找到了MessageBoxW的名称。
注: 有关RVA和FOA 和NT头部分解析请看我前面的几篇博客!!!!!!