滴水逆向三期实践11:导出表

前面提到头OPTIONAL_HEADER 的最后一项,是一个16个元素的结构体数组,为数据目录

而数据目录项的第一个结构,就是动态链接库中经常提到的 导出表.

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

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

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

下文及以后在数据目录表中出现的所有地址,全都为 RVA 

真正的导出表结构如下:(来自三期课件)

 其中 最左的 AddressOfFunctions 就是记录了前面动态库用到的 每个导出函数 的 所在地址,但仍然是RVA,找到这个绝对地址就能调用到函数。因为是地址,所以表宽为4字节,表项数为 NumberOfFunctions

最右的 AddressOfNames 表 记录了 导出函数名字的 RVA ,需要将该 RVA 转换成绝对地址(先转FOA在加上当前FileBuffer基址)才能找到真正的函数名的字符串。即前面动态库被名称粉碎或没有粉碎的名字,就存在这个 RVA 指向的地方。具体如下图:

 要注意:函数的真正的名字在文件中如上图所示,位置是不确定的。但函数名称表中是按名字排序的,即A开头的函数在 AddressOfNames 排在最前面。但AXX这个名字字符串的位置,可能排在BXX后面. 

注意表项数为 NumberOfNames 和 前面的函数地址表 不一样了。

动态库中提到 可以导出无名字函数,如果无名字那就不会存在名称表中,即不包括在 NumberOfNames 里,名称表不会存它的数据也不会有位置给它。

中间的序号表 AddressOfNameOrdinals  ,表项宽度为2字节

该表中存储的内容 + Base = 函数的导出序号

Base 即是 导出表_IMAGE_EXPORT_DIRECTORY 结构体中的一个字段。记录了导出函数起始序号。

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

见下图,遍历名称表内的字符串,逐一匹配,比如匹配到下标为 2 的名称,则同样取下标为 2 的序号表元素。该元素是4,则直接以4为下标,取出函数地址表中的函数地址。(注意这全部的操作都和 base 字段无关!全部都只是以这三个表记录内容来操作)

那么如何根据函数的导出序号获取一个函数的地址?

为了再巩固一下,这里再举一个真实的例子,根据上面的规则自己走一遍。

dll 导出函数由以下def 文件导出,可见序号为 2 3 5 6

EXPORTS	
	
Plus   	@2
Sub	    @5 NONAME
Mul    	@3
Div    	@6

由上述 def 生成.dll ,查看导出表的结果是:

1、为什么要分成3张表?                
函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.

                 
2、函数地址表是不是一定大于函数名称表?                                    
未必,一个相同的函数地址,可能有多个不同的名字.                    

练习:

1、编写程序打印所有的导出表信息

2、编写函数 GetFunctionAddrByName ( FileBuffer指针,函数名指针)

3、编写函数 GetFunctionAddrByOrdinals ( FileBuffer指针,函数名导出序号)


#include "Currency.h"
#include "windows.h"
#include "stdio.h"

VOID h324()		//输出导出表
{
	char FilePath[] = "Dll1.dll";	//CRACKME.EXE        CrackHead.exe
	LPVOID pFileBuffer = NULL;				//会被函数改变的 函数输出之一
	LPVOID* ppFileBuffer = &pFileBuffer;	//传进函数的形参
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
	PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
	DWORD nameFOA = NULL;
	DWORD AddressOfNamesFOA = NULL;
	DWORD AddressOfNameOrdinalsFOA = NULL;
	DWORD AddressOfFunctionsFOA = NULL;
	DWORD AddressOfFunctions = NULL;
	WORD Ordinal = NULL;
	char * name = NULL;

	DWORD result = NULL;
	typedef int(*lpPlus)(int, int);	//测试查找出的函数用
	lpPlus myPlus;

	if (!ReadPEFile(FilePath, ppFileBuffer))
	{
		printf("文件读取失败\n");
		return;
	}
	//Dos头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;	// 强转 DOS_HEADER 结构体指针
	//可选PE头	  简化后的处理
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileBuffer + pDosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
	//导出表
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + RVA2FOA(pFileBuffer, pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
	printf("DIRECTORY_ENTRY_EXPORT VirtualAddress:%x\n", pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	printf("FOA:%x\n", RVA2FOA(pFileBuffer, pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
	printf("导出表文件名字符串Name:%x\n", pExportDirectory->Name);
	printf("导出函数起始序号Base:%d\n", pExportDirectory->Base);
	printf("导出函数的个数:%d\n", pExportDirectory->NumberOfFunctions);
	printf("以函数名字导出的函数个数NumberOfNames:%d\n", pExportDirectory->NumberOfNames);
	
	printf("*******函数地址表*******\n");
	AddressOfFunctionsFOA = RVA2FOA(pFileBuffer, pExportDirectory->AddressOfFunctions);
	for (int i = 0; i < pExportDirectory->NumberOfFunctions; i++)
	{	//因Address表元素为4字节,绝对地址加上i*4直接取第i个元素
		AddressOfFunctions = *(PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + i * 4);
		printf("下标:%d,函数地址:%x\n", i, AddressOfFunctions);
	}
	printf("*******函数名称表*******\n");
	//导出表中的AddressOfNames为Rva,将其转换为FOA得到AddressOfNamesFOA
	AddressOfNamesFOA = RVA2FOA(pFileBuffer, pExportDirectory->AddressOfNames);
	for (int i = 0; i < pExportDirectory->NumberOfNames; i++)
	{	//AddressOfNamesFOA只是Names表的FOA地址,需加上pFileBuffer构成的绝对地址才能取出其中的值。
		//取出的值即Names地址表第i个name的Rva地址,转成FOA得到name的FOA地址
		nameFOA = RVA2FOA(pFileBuffer, *(PDWORD)((DWORD)pFileBuffer + AddressOfNamesFOA));
		name = (char *)(nameFOA + (DWORD)pFileBuffer);//name的FOA加上pFileBuffer构成绝对地址,该地址才真正指向字符串
		printf("下标:%d,函数名:%s\n", i, name);
		AddressOfNamesFOA += 4;	//往前走4字节,指向Names地址表下一个元素,即下一个name地址
	}
	printf("*******函数序号表*******\n");
	AddressOfNameOrdinalsFOA = RVA2FOA(pFileBuffer, pExportDirectory->AddressOfNameOrdinals);	//同Names表找法
	for (int i = 0; i < pExportDirectory->NumberOfNames; i++)
	{
		Ordinal = *(PWORD)((DWORD)pFileBuffer + AddressOfNameOrdinalsFOA + i * 2);	//因为Ordinal表元素为2字节,绝对地址加上i*2直接取第i个元素
		printf("下标:%d,Ordinal序号:%d\n", i, Ordinal);
	}
	result = (DWORD)GetFunctionAddrByName(pFileBuffer, "Plus");	//得到的是函数Rva地址
	printf("result:%x\n", result);
	

	result = (DWORD)GetFunctionAddrByOrdinals(pFileBuffer, 2);
	printf("result:%x\n", result);
}

其中 GetFunctionAddrByName 和 GetFunctionAddrByOrdinals 函数的实现都在 Currency.cpp 中,详见 滴水逆向三期实践附:前后使用到的PE相关函数合集 Currency.h_Rivival_S 的博客-CSDN博客

结果如下:(就是上面画的图表)

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值