PE文件解析(3):导出表的解析

可选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解析:

寻找导出表的地址

  1. 根据数据目录表找到导出表(索引0 )的RVA:19060h
    在这里插入图片描述
  2. 转到区段表:计算这个RVA在哪一个区段内,区段具有virtualaddress:区段的RVA偏移地址,virtualSize:区段的字节大小,pointofRawData:区段的FOA。
  3. 计算可知: 19060h > virtualAddress AND 19060h <virtualAddress +VirtualSize ,所以这个导出表的起始地址位于区段内部,所以利用公式:数据的FOA = 数据的RVA - 区段的RVA + 区段的FOA 可以得到数据的FOA,也就是在文件中的偏移: 19060h - 17000h + (5C00)h = 7C60h,所以7C60就是我们的导出表的FOA,再加上基址即可得到我们的真正地址。
    在这里插入图片描述
  4. 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的地址(我们此时还不知道名称),如何通过地址找到函数的名称?

  1. AddressFunctions: 索引为6
  2. 根据索引为6,找到序号表中序号等于6的索引,得到索引5
  3. 找到名称表中索引为5的名称,即找到了MessageBoxW的名称。
    在这里插入图片描述

注: 有关RVA和FOA 和NT头部分解析请看我前面的几篇博客!!!!!!

posted @ 2022-10-31 23:44  hugeYlh  阅读(85)  评论(0编辑  收藏  举报  来源