补:PE文件遍历导出表——有人为你哭,说明你还是个东西

之前一直要写的遍历导出表,一直没写,最近回去复习了一下PE结构关于IAT和EAT的内容,写了个遍历导出表的程序,也算是结了之前文章里说的话吧。
源码:

// ExportTable.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<windows.h>
using namespace std;
DWORD RVA_RAW(DWORD RVA, unsigned char * Base)
{
	//定位DOS头;
	PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)Base;
	//定位NT头;
	PIMAGE_NT_HEADERS Nt_Header = (PIMAGE_NT_HEADERS)(Base + DosHeader->e_lfanew);
	//获取节区数目
	WORD SectionNum = Nt_Header->FileHeader.NumberOfSections;
	//获取节区头的地址
	PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)(Base+ DosHeader->e_lfanew+ 0x18 + Nt_Header->FileHeader.SizeOfOptionalHeader);
	//定位目标RVA所在节区
	DWORD RAW = 0;
	for (int i = 0; i < SectionNum; i++)
	{
		DWORD SectionStart = SectionHeader[i].VirtualAddress;
		DWORD SectionEnd = SectionStart+SectionHeader[i].Misc.VirtualSize;
		if (RVA >= SectionStart && RVA <= SectionEnd)
		{
			RAW = RVA - SectionStart + SectionHeader[i].PointerToRawData;
			break;
		}
	}
	return RAW;
}
int main()
{
	//HMODULE hFile = LoadLibraryA("user32.dll");
	HANDLE hFile = CreateFileA(
		"你的目标DLL的绝对路径(注意要打双斜杠)",
		GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
		NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL
		, NULL
	);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		int a=GetLastError();
		cout << "文件打开失败" << "失败原因"<<a<<endl;
		return 1;
	}
	DWORD FileSize = GetFileSize(hFile, NULL);
	LPDWORD SizeToRead = 0;
	unsigned char * pBuf = new unsigned char[FileSize];
	ZeroMemory(pBuf,FileSize);
	int i = ReadFile(hFile, pBuf, FileSize, SizeToRead, NULL);
	if (i == 0)
	{
		cout << "文件读取失败" << endl;
	}
	cout << "文件基址是" << pBuf << endl;
	//定位DOS头;
	PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)pBuf;
	//定位NT头;
	PIMAGE_NT_HEADERS Nt_Header = (PIMAGE_NT_HEADERS)(pBuf + DosHeader->e_lfanew);
	//定位数据目录表
	PIMAGE_OPTIONAL_HEADER32 OptionHeader = &(Nt_Header->OptionalHeader);
	//定位导出表结构,获取导出表的RAW
	DWORD ExportRAW = RVA_RAW(OptionHeader->DataDirectory[0].VirtualAddress, pBuf);
	//定位导出表数据结构
	PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ExportRAW + pBuf);
	//获取目标DLL的名称
	char * DllName =(char *)(RVA_RAW(ExportTable->Name,pBuf)+pBuf);
	printf("目标DLL的名称是%s\n",DllName);
	//获取导出函数的起始序号Base
	WORD Base = ExportTable->Base;
	//获取导出函数的总数
	DWORD NumOfFunc = ExportTable->NumberOfFunctions;
	//获取以名字导出的函数的总数
	DWORD NumOfName = ExportTable->NumberOfNames;
	//获取导出函数地址表的RVA,并且将至转换成为RAW
	PDWORD AddressOfFun =(PDWORD)(RVA_RAW(ExportTable->AddressOfFunctions, pBuf)+pBuf);
	//获取函数名称地址表
	PDWORD AddressOfName =(PDWORD)(RVA_RAW(ExportTable->AddressOfNames, pBuf)+pBuf);
	//获取函数序号地址表
	PWORD AddressOfNameOrdinals =(PWORD )(RVA_RAW(ExportTable->AddressOfNameOrdinals, pBuf)+pBuf);
	//接下来开始遍历导出表
	for (int i = 0; i < NumOfFunc; i++)
	{
		if (AddressOfFun[i] == 0)
			continue;
		int j = 0;
		for (;j < NumOfName; j++)
		{
			if (AddressOfNameOrdinals[j] == i)
			{
				char * FuncName = (char *)((RVA_RAW(AddressOfName[j], pBuf)) + pBuf);
				printf("函数序号为%x  函数地址为%x  函数名称为%s \n ",Base+j,AddressOfFun[i],FuncName);
				break;
			}
		}
		if (j == NumOfName)
		{
			printf("函数序号为%x  函数地址为%x  函数名称为NULL\n", Base +i, AddressOfFun[i]);
		}
	}
}

遍历导出表其实是一个比较细致的活(对于我自己来说),因为中间涉及到较多的RVA到RAW的数据转换的问题,而且关键是哪些数据需要转换,哪些不需要转换,说实话整的有点烦.
下边是写的过程中的一点总结
1:之前也看过网上别人写的代码中有使用CreateFileA也有使用LoadLibrary的,那么使用这两者便利的时候有什么区别呢?
本质上是没有什么区别的,所谓的区别体现在代码中就是需不需要进行地址转换的问题,在使用Load Library的时候,不需要进行RVA到RAW的转换,因为使用这个二API相当于将这个DLL加载到内存当中.而是用CreateFile打开文件之后,还需要将文件读取到内存当中,但是这个时候文件是保持它自身在磁盘里的状态的,也就是说这个时候的文件是没有展开的,需要进行RVA到RAW的地址转化的.
2:究竟哪些地址需要转换?
这个问题其实比较好理解,只要对照着导出表的数据结构进行查看就可以解决,还有一个我自己想的小方法:当你使用指向RVA的指针进行寻址的时候就需要转换.
比如说:函数地址表,也就是DWORD AddressOfFunction这个字段,这个字段里边存储着指向函数地址表的地址的RVA,如果我们要去找到对应函数的地址,就要靠这个RVA地址去索引,但是索引之前我们需要对他它进行地址转换.
3:在调试过程中的小插曲
在这次遍历导出表的编写过程中,发现了一个之前没有发现的问题,那就是
DWORD AddressOfNameOrdinals 这个字段引起的灵异事件.
下边就把我的调试过程简单的说一下:
可以看到上边的源码中,对于这个字段的定义,我定义的是PWORD 类型的
但是在数据结构里边我们可以看到这个字段是DWORD类型的,现在看一下二者的调试状况:

//获取函数序号地址表
	PWORD AddressOfNameOrdinals =(PWORD )(RVA_RAW(ExportTable->AddressOfNameOrdinals, pBuf)+pBuf);

在这里插入图片描述
当定义为PDWORD的时候:
输出结果:
在这里插入图片描述
当定义为PWORD的时候,输出结果:
在这里插入图片描述
调试:
当时PDWORD的时候,我们执行获取名称序号表的操作之后,获取到的地址及该地址处的内容如下:
在这里插入图片描述
在这里插入图片描述
可以看到在该地址处存储的值为262147(跳转到内存地址过去,读取到的数据时4003),其实就是十进制和十六进制的转换
在这里插入图片描述
而问题是PDWORD时4字节的指针类型,当我们指针加1的时候其实是将指针加了4,这样的话,对应的内存处的数据变化为:
在这里插入图片描述
这样明显匹配不到,因此将指针类型转换为PWORD类型的时候,对应的匹配就变成如下图所示:
在这里插入图片描述
这个问题,之前没注意到现在注意到也为时不晚,后来用CFF软件查看了DLL的导出函数表的数据结构发现对应得该字段时WORD类型的,大概也就将这个结果作为这个事情的合理的解释吧
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 广度优先搜索是一种图的遍历算法,它从图的某个顶点开始遍历,先访问该顶点,然后依次访问该顶点的所有邻接点,再依次访问邻接点的邻接点,直到遍历完所有可达的顶点为止。广度优先搜索通常使用队列来实现,每次访问一个顶点时,将其所有未访问的邻接点加入队列中,然后从队列中取出一个顶点进行访问,直到队列为空为止。广度优先搜索可以用于寻找图中的最短路径,也可以用于检测图是否连通,或者寻找图中的环等问题。 ### 回答2: 广度优先搜索(BFS)是一种图的遍历算法,其遍历规则是:从某个定点开始,先访问它的所有邻居节点,然后对于每个邻居节点再访问它们的邻居节点,依此类推,直到遍历完整个图。BFS使用队列来存储待访问的节点,并在队列中按照先进先出(FIFO)的原则进行访问,确保遍历结果是按照最短路径的顺序得到的。在任何时刻,队列中所有已访问过的节点都必须被一个标记,避免重复遍历。 BFS算法可以用于寻找无权图中两个顶点之间最短路径的问题。具体实现过程如下:从起始节点开始,扩展其所有邻居节点,将它们加入队列,并在它们的数据结构中记录下与它相邻的“父节点”。然后从队列中取出下一个节点,重复此过程,直到找到目标节点或者队列为空。如果目标节点被访问过,可以通过跟踪每个节点的父节点信息反向回溯得到最短路径。 需要注意的是,由于BFS需要维护一个队列,且图中节点访问过与否的状态需要用额外的标记进行管理,因此其时间复杂度为O(n+m),其中n为节点数,m为边数。BFS空间复杂度为O(n),主要是队列的存储空间。此外,当图的规模较大时,BFS可能会因为占用大量内存而无法使用,需要使用更高效的算法来解决问题。 总之,BFS是一种简单而有效的图的遍历算法。它可以被广泛应用于各种场景中,如建模地图网络、社交网络、计算机网络等,并且具有简单易懂、实现容易、算法复杂度较低等优点。 ### 回答3: 广度优先搜索(BFS)是一种常用的图遍历算法,其核心思想是“一层一层地遍历”,即先访问起始顶点的邻接顶点,然后再访问邻接顶点的邻接顶点,以此类推,直到图中所有顶点都被访问过为止。广度优先搜索可以用于图的遍历、最短路径等问题。 广度优先搜索的算法流程如下: 1. 创建一个队列,将起始顶点加入队列; 2. 标记起始顶点已被访问; 3. 循环执行以下步骤,直到队列为空: a. 出队一个顶点,并访问该顶点; b. 遍历该顶点的所有邻接顶点,如果邻接顶点未被访问,则标记其已被访问,并将其加入队列中; 4. 如果图中还有未被访问的顶点,从未访问的顶点中选取一个作为起始顶点重复上述步骤。 广度优先搜索是一种较为简单且易于实现的算法,其时间复杂度为O(V+E),其中V为图中顶点的数目,E为图中边的数目。因此,该算法适用于较小规模的图。 除了广度优先搜索外,还有一种常用的图遍历算法是深度优先搜索(DFS),其核心思想是“一条路走到黑”,即从起始顶点出发,沿着路径一直往下走直到走到末端,然后返回上一层继续遍历。DFS也可以用于图的遍历、最短路径等问题。与BFS不同的是,DFS使用的是栈而非队列来存储待访问的顶点,其时间复杂度也为O(V+E)。 总之,广度优先搜索和深度优先搜索是两种常用的图遍历算法,它们在不同的场景下具有不同的优势和应用。因此,我们需要针对具体的问题来选择合适的算法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值