PE文件解析--导出表

1、定位导出表
可选pe头中的最后一个字段:数据目录项

typedef struct _IMAGE_DATA_DIRECTORY {                
    DWORD   VirtualAddress;                
    DWORD   Size;                
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;  

如何找到这个结构前面的文章已经说过。而且这个 Size 字段不一定是真实的。

上面 VirtualAddress 字段 作为 RVA, 需转换成 FOA 然后加上 我们以文件方式打开的 pFileBuffer ,才能定位到真正的 导出表。(这种 RVA 寻址前面也用到不少了,应该很熟悉了)

下文及以后在数据目录表中出现的所有地址,全都为 RVA
在这里插入图片描述
2、真正的导出表结构(滴水三期课件)
在这里插入图片描述

  • 其中 最左的 AddressOfFunctions 就是记录了前面动态库用到的 每个导出函数 的
    所在地址,但仍然是RVA,找到这个绝对地址就能调用到函数。因为是地址,所以表宽为4字节,表项数为 NumberOfFunctions
  • 最右的 AddressOfNames 表 记录了 导出函数名字的 RVA ,需要将该 RVA
    转换成绝对地址(先转FOA在加上当前FileBuffer基址)才能找到真正的函数名的字符串。即前面动态库被名称粉碎或没有粉碎的名字,就存在这个RVA 指向的地方。具体如下图:
    在这里插入图片描述
  • 要注意:函数的真正的名字在文件中如上图所示,位置是不确定的。但函数名称表中是按名字排序的,即A开头的函数在AddressOfNames 排在最前面。但AXX这个名字字符串的位置,可能排在BXX后面.
    动态库中提到 可以导出无名字函数,如果无名字那就不会存在名称表中,即不包括在 NumberOfNames 里,名称表不会存它的数据也不会有位置给它。
  • 中间的序号表 AddressOfNameOrdinals ,表项宽度为2字节;该表中存储的内容 + Base =
    函数的导出序号;Base 即是 导出表_IMAGE_EXPORT_DIRECTORY 结构体中的一个字段。记录了导出函数起始序号。

如何根据函数名获取到函数地址?(这些事都是操作系统做的事,当然我们懂了原理即可以绕开操作系统自己做)

见下图,遍历名称表内的字符串,逐一匹配,比如匹配到下标为 2 的名称,则同样取下标为 2 的序号表元素。该元素是4,则直接以4为下标,取出函数地址表中的函数地址。(注意这全部的操作都和 base 字段无关!全部都只是以这三个表记录内容来操作)
在这里插入图片描述
3、问题总结
1)为什么要分成3张表?
函数导出的个数与函数名的个数未必一样(存在不以名字导出的函数),所以要把函数地址表和函数名称表分开;
2)函数地址表是不是一定大于函数名称表?
未必,一个相同的函数地址可能有多个名字;
3)如何根据函数名字获取一个函数的地址?
定位到函数名称表 --》循环对比,并记录对应的序号 --》找到后根据第二步记录的序号,遍历序号表 --》根据序号表对应的值,取地址表中对应的函数地址;
在这里插入图片描述
4)如何根据函数的导出序号获取一个函数的地址?
已知序号减去Base,然后查询函数地址表即可获得;
在这里插入图片描述
4、代码实现:

  • 遍历导出表
void ExportTablePrint(PVOID pFileBuffer)
{
	//指针定义:pDos,pNT
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDos->e_lfanew);
	//定位导出表:
	PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(
	(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
	);//pNT->OptionalHeader.DataDirectory
	//定位地址表,名称表,序号表,每打印一个函数名,要连同他的序号和地址都打印出来
	DWORD* addrFunctions = (DWORD*)(
	(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfFunctions)
	);
	DWORD* addrNames = (DWORD*)((DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfNames));
	WORD* addrOrdinals = (WORD*)((DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfNameOrdinals));

	cout << "\n********输出函数序号表的值********" << endl;
	for (int i = 0; i < pExportDir->NumberOfFunctions; i++)
	{
		printf("\t%04x\n", addrOrdinals[i]);
	}
	cout << "\n**********输出函数名称表************\n" << endl;
	for (int i = 0; i < pExportDir->NumberOfNames; i++)
	{
		printf("\t%s\n", (DWORD)pFileBuffer + RvaToFoa(pFileBuffer, addrNames[i]));
	}
	cout << "\n**********输出函数地址表************\n" << endl;
	for (int i = 0; i < pExportDir->NumberOfFunctions; i++)
	{
		printf("\t%08x\n", addrFunctions[i]);
	}

	cout << "\n**********序号表-地址表-名称表************\n" << endl;
	DWORD i = 0;
	for (; i < pExportDir->NumberOfFunctions; i++)
	{
		DWORD j = 0;
		if (addrFunctions[i] == 0)//这里跳过了地址为0的函数
			continue;
		//i每增加1都要遍历NumberOfNames长度找到序号表中的对应的值,此时序号表的值仅仅是为了验证地址表中的序号是否在序号表中有位置
		for (; j < pExportDir->NumberOfNames; j++)//内循环从序号表中找到等于地址表第i个函数的序号的值
		{
			if (addrOrdinals[j] == i)
			{//找到了序号表中等于地址表序号的值
				printf("\t%04x\t%08x\t%s\n", i, addrFunctions[i], (DWORD)pFileBuffer + RvaToFoa(pFileBuffer, addrNames[j]));
				break;
			}
		}
	}
}

  • 根据函数名找到导出表中对应的函数地址
//**************************************************************************								
//GetFunctionAddrByName:根据名字找到导出表中的函数地址			
//参数说明:								
//pFileBuffer:FileBuffer指针						
//str: 函数名指针	
//AddressOfNamesFOA:名称表的文件偏移	
//nameFoa:名称表的值--具体函数名的RVA			
//返回值说明:								
//返回导出表中的函数地址								
//**************************************************************************	
PVOID GetFunctionAddrByName(PVOID pFileBuffer, char str[])
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDOS->e_lfanew);
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_EXPORT_DIRECTORY pExportFunctionTable = (PIMAGE_EXPORT_DIRECTORY)(
		(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
		);

	DWORD AddressOfNamesFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfNames);//导出表的FOA
	DWORD nameFoa = 0;
	char* name = NULL;
	int i = 0;
	for (; i < pExportFunctionTable->NumberOfNames; i++)
	{	//名称表里面存储的是函数名的RVA,进行轮询比较
		nameFoa = RvaToFoa(pFileBuffer, *(PDWORD)((DWORD)pFileBuffer + AddressOfNamesFOA));
		name = (char*)((DWORD)pFileBuffer + nameFoa);
		if (!strcmp(str, name))
			break;
		AddressOfNamesFOA += 4;//地址表元素宽度为4,每加4跳到下一个地址
	}
	if (i == pExportFunctionTable->NumberOfNames)
	{
		printf("找不到名字为:%s的函数!\n", str);
		return 0;
	}
	/*
	以下操作意味着已经找到了对应的函数名,其中i记录的是名称表的第几个,
	再用i的值去序号表查询,最后用序号表的值寻到函数地址
	*/
	DWORD AddressOfNameOrdinalsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfNameOrdinals);
	WORD ordinal = *(PWORD)((DWORD)pFileBuffer + AddressOfNameOrdinalsFOA + i * 2);
	printf("函数:%s 在名称表的位置:%d 在序号表的值:%d\n", str, i, ordinal);
	DWORD AddressOfFunctionsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfFunctions);
	DWORD* AddressOfFunctions = (DWORD*)((DWORD)pFileBuffer + AddressOfFunctionsFOA);
	printf("函数%s的地址(rva)是%x\n", str, AddressOfFunctions[ordinal]);
	return (PVOID)*(PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + ordinal * 4);
}
  • 通过导出序号找到函数地址
//GetFunctionAddrByOrdinals(pFileBuffer,函数名导出序号)
//**************************************************************************								
//GetFunctionAddrByOrdinals:根据序号找到导出表中的函数地址			
//参数说明:								
//pFileBuffer:FileBuffer指针						
//ord:函数序号		
//AddressOfNameOrdinalsFOA:序号表的文件偏移
// AddressOfFunctionsFOA:地址表的文件偏移
//返回值说明:								
//返回导出表中的函数地址								
//**************************************************************************	
PVOID GetFunctionAddressByOrdinals(PVOID pFileBuffer, WORD ord)
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDOS->e_lfanew);
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_EXPORT_DIRECTORY pExportFunctionTable = (PIMAGE_EXPORT_DIRECTORY)(
		(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
		);
	
	DWORD AddressOfNameOrdinalsFOA = NULL;
	DWORD AddressOfFunctionsFOA = NULL;
	

	//判断传入的序号是否在序号表的范围之内,不在则结束;在则继续往下走
	if (ord - pExportFunctionTable->Base >= pExportFunctionTable->NumberOfNames || ord - pExportFunctionTable->Base < 0)
	{	
		printf("找不到序号为%d的函数\n", ord);
		return 0;
	}
	//获得函数地址表的文件偏移
	AddressOfFunctionsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfFunctions);
	printf("序号表Base:%d\n", pExportFunctionTable->Base);
	printf("导出序号为:%d的导出函数地址为%08x\n", ord, (PVOID) * (PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + (ord - pExportFunctionTable->Base) * 4));
	return (PVOID) * (PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + (ord - pExportFunctionTable->Base) * 4);
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值