C语言遍历导入表、导出表

0x01 不多比比,直接上源码

// 同时遍历PE文件的导入表、导出表的函数名
#include
#include
#pragma warning(disable : 4996)

DWORD RVA_2_RAW(char *buf, DWORD Rva, DWORD Raw, BOOL flag);
DWORD Import(char *buf);
DWORD Export(char *buf);

void main() 
{
	char PATH[] = "C:\\Windows\\System32\\atl.dll";	// 目标PE文件
	long PEFileSize;								// 偏移字节数
	FILE *fp = fopen(PATH, "rb");					// 尝试读取(r)一个二进制(b)文件,成功则返回一个指向文件结构的指针,失败返回空指针
	if (fp == NULL) 
	{
		printf("PE文件读取失败!\n");
	}
	fseek(fp, 0, SEEK_END);							// 设置文件流指针指向PE文件的结尾处
	PEFileSize = ftell(fp);							// 得到文件流指针当前位置相对于文件头部的偏移字节数,即获取到了PE文件大小(字节)

	char *buf = new char[PEFileSize];				// 新建一个数组指针buf,指向一个以PE文件字节数作为大小的数组
	memset(buf, 0, PEFileSize);						// buf指针指向内存中数组的开始位置
													// 在这里用0初始化一块PEFileSize大小的内存,即为数组分配内存
	fseek(fp, 0, SEEK_SET);							// 将文件流指针指向PE文件头部
	fread(buf, 1, PEFileSize, fp);					// 从给定输入流fp中读取PEFILESize大小个数据项保存到buf字符数组中,每项大小为1字节
													// 这样将PE文件读入内存实际上就让buf指向了PE文件的基地址ImageBase
	fclose(fp);	

	Import(buf);									// 这个buf在接下来的操作中将一直指向文件的首地址,也就是ImageBase文件基址
	Export(buf);
	delete buf;
}

// 文件已经读取到内存中了
DWORD RVA_2_RAW(char *buf, DWORD RVA, DWORD RAW, BOOL flag)				// RVA为导入表或导出表的RVA;flag为1,RVA转偏移,为0反过来——事实上此程序只用到了1
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)buf;					// 获取DOS头,pDos为PIMAGE_DOS_HEADER结构体的实例,buf则指向PE文件的基地址
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(buf + pDOS->e_lfanew);	// 获取NT头,pNT为PIMAGE_DOS_HEADER的实例,DOS头的e_lfanew成员指示了NT头的偏移量
	PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)(buf + pDOS->e_lfanew + 0x18 + pNT->FileHeader.SizeOfOptionalHeader);
																		// 获取区块表头,pSection为PIMAGE_SECTION_HEADER的实例
																		// +0x18指向了可选头,加上可选头的大小即指向了Section表头的首部,可选头的大小存放在文件头的成员中

	DWORD SectionNumber = pNT->FileHeader.NumberOfSections;							// 通过文件头获取区块数(节区数)
	DWORD SizeOfAllHeadersAndSectionList = pNT->OptionalHeader.SizeOfHeaders;		// 所有头(DOS+NT)+区块表的大小,是一个大小而不是地址
	DWORD Imp_Exp_FA = 0;															// 导入导出表在磁盘文件中的地址
	DWORD SectionRVA = 0;															// 暂存每个节区表的RVA

	int i = 0;
	if (flag)
	{
		if (RVA < SizeOfAllHeadersAndSectionList)									// 如果导入导出表的RVA连节区表都没出,直接返回,因为(DOS+NT头+节区表)在内存中不展开
		{
			Imp_Exp_FA = RVA;															
		}
		for (; i < SectionNumber;i++)												// 有多少节区就循环几次,从第一个节区表开始操作,如果PE文件有N个节,那么区块表就是由N个IMAGE_SECTION_HEADER组成的数组
		{
			SectionRVA = pSection[i].VirtualAddress;								// 该区块加载到内存中的RVA
			// 计算该导入导出表的RVA位于哪个区块内
			if (RVA > SectionRVA && SectionRVA + pSection[i].Misc.VirtualSize > RVA)// &&后面为:该区块的RVA(该区块在内存中的起始地址) + 该区块没有对齐处理之前的实际大小(磁盘中的大小。Misc是共用体)
			{
				Imp_Exp_FA = RVA - SectionRVA + pSection[i].PointerToRawData;				// (导入导出表的RVA - 所在节区的基址)得到导入导出表相对该节区的偏移量offset,然后offset + 该节区在磁盘文件中的VA = FOA,得到了文件偏移地址(即导入导出表在磁盘文件中的地址)
				break;																// 找到了就不再遍历节区了
			}
		}
	}
	else
	{
		if (RAW < SizeOfAllHeadersAndSectionList)									// 这里就是通过RAW求RVA了(该程序并未用到)  注意文件偏移地址就是在磁盘文件中的地址:RAW==FOA==FA   (其实一共就3个概念:VA RVA FA,分别是虚拟绝对地址,虚拟相对地址,文件绝对地址)
		{
			Imp_Exp_FA = RAW;
		}
		for (; i < SectionNumber; i++)
		{
			SectionRVA = pSection[i].PointerToRawData;
			if (RAW > SectionRVA && SectionRVA + pSection[i].SizeOfRawData > RAW) 
			{
				Imp_Exp_FA = RAW - SectionRVA + pSection[i].VirtualAddress;
				break;
			}
		}
	}
	return Imp_Exp_FA;
}

// 导入表一般包含DLL和普通API两部分,因此要分别考虑
DWORD Import(char *buf)
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)buf;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(buf + pDOS->e_lfanew);
	DWORD ImportTableRVA = pNT->OptionalHeader.DataDirectory[1].VirtualAddress;					// 获得导入表的RVA,DataDirectory[1]是导入表,0是导出表
	DWORD ImportDLL_FA = RVA_2_RAW(buf, ImportTableRVA, 0, 1);									// 计算导入DLL在磁盘文件中的地址,这个变量专门保存DLL地址
	IMAGE_IMPORT_DESCRIPTOR *pImportTable = (IMAGE_IMPORT_DESCRIPTOR *)(buf + ImportDLL_FA);	// pImportTable指向导入的DLL在磁盘文件中的地址
	printf("导入表:\n");
	while (pImportTable->FirstThunk)															// FirstThunk为IMAGE_IMPORT_DESCRIPTOR的成员,是一个PIMAGE_THUNK_DATA类型的实例,指向IAT的RVA,IAT其实就是一个由许多IMAGE_THUNK_DATA结构组成的数组
	{
		// 打印DLL名字
		DWORD ImportDLLName_RVA = pImportTable->Name;											// 指向被输入的DLL的名称(ASCII字符串)的RVA,注意只针对DLL
		ImportDLL_FA = RVA_2_RAW(buf, ImportDLLName_RVA, 0, 1);									// 求出导入函数名在磁盘文件中的物理地址
		printf("	%s--------------------------我是DLL哟\n", buf + ImportDLL_FA);				// 打印这个DLL的名字

		// 打印普通API名字
		IMAGE_THUNK_DATA *pThunk = (IMAGE_THUNK_DATA *)(buf + RVA_2_RAW(buf, pImportTable->OriginalFirstThunk, 0, 1));
																								// OriginalFirstThunk指向包含IMAGE_THUNK_DATA(输入函数名称表)结构的数组 // FirstThunk指向IAT的RVA
		while (pThunk->u1.Function)																// u1.Function为被输入函数的内存地址
		{
			char *psFuncName = (char *)buf + RVA_2_RAW(buf, pThunk->u1.AddressOfData + 2, 0, 1);// u1.AddressOfData指向了IMAGE_IMPORT_BY_NAME,+2则指向了Name成员
			printf("	%s\n", psFuncName);														// 打印函数名
			pThunk++;																			// IAT其实就是一个由许多IMAGE_THUNK_DATA结构组成的数组,因此指向下一个IMAGE_THUNK_DATA结构
		}
		pImportTable++;																			// 指针++就是指向下一个内存地址,即指向下一个IMAGE_IMPORT_DESCRIPTOR结构
	}
	return 0;
}

DWORD Export(char *buf)
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)buf;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(buf + pDOS->e_lfanew);
	DWORD ExportTableRVA = pNT->OptionalHeader.DataDirectory[0].VirtualAddress;					// 获得导出表的RVA,DataDirectory[1]是导入表,0是导出表
	DWORD ExportAPI_FA = RVA_2_RAW(buf, ExportTableRVA, 0, 1);									// 计算导出函数在磁盘文件中的地址
	IMAGE_EXPORT_DIRECTORY *pExportTable = (IMAGE_EXPORT_DIRECTORY *)(buf + ExportAPI_FA);		// 指向导出函数在磁盘文件中的地址

	PDWORD ExportAPIName_FA = (PDWORD)(buf + RVA_2_RAW(buf, pExportTable->AddressOfNames, 0, 1));					// 将 指向函数名地址表的RVA 转化为FA
	DWORD ExportAPINameOriginals_FA = (DWORD)(buf + RVA_2_RAW(buf, pExportTable->AddressOfNameOrdinals, 0, 1));		// 将 指向函数名序号表的RVA 转化为FA
	DWORD index = 0;
	printf("\n导出表:\n");
	while (DWORD(ExportAPIName_FA + index) < ExportAPINameOriginals_FA)							// AddressOfNameOridinals(0x24)在结构体中的位置就在AddressOfNames(0x20)下面
	{
		printf("	%s\n", buf + RVA_2_RAW(buf, (DWORD)(*(ExportAPIName_FA + index)), 0, 1));
		index++;
	}
	return 0;
}

`

0x02 效果如下

C语言遍历导入表、导出表_第1张图片
C语言遍历导入表、导出表_第2张图片
`

0x03 只遍历导出表

#include "stdafx.h"
#include 
#include 

// 流程:获取PE文件——获取DOS头地址——获取PE头地址——获取数据目录项地址——获取导出表地址——获取成员变量

int main(){
	HMODULE hDll = LoadLibraryA("C:\\Windows\\twain_32.dll");	// 获取PE文件的名称
	if (!hDll)	return 0;				// 是否获取到
	IMAGE_EXPORT_DIRECTORY* exportDir;	// 定义一个导出表结构类型的指针,包括成员为好多重要的字段
	int baseAddr = (int)hDll;			// 将获取到的库文件名转化为int型基地址,即DOS头地址VA
	int RVA, VA;	
	RVA = *((int*)(baseAddr + 0x3c));	// 通过DOS头找到PE头(NT头)相对于DOS头的偏移量RVA,DOS头偏移0x3c处的成员的值是PE头部的相对虚拟地址,*取其值
	VA = baseAddr + RVA;				// PE头的绝对地址VA
	RVA = *((int*)(VA += 0x78));		// 首先获取DataDirectory(数据目录项)绝对地址VA,因为PE头部的DataDirectory相对于PE头部的偏移为0x78,而且DataDirectory的第一项为导出表目录,即0x78,所以*取其值即取到了导出表的偏移地址RVA
	exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddr + RVA);	// 加上DOS头基地址得到导出表绝对地址VA

	// 下面三个都是指向各自表的首地址
	int* RVAFunctions = (int*)(baseAddr + exportDir->AddressOfFunctions);		// VA = 基址 + 导出函数地址表RVA
	int* RVANames = (int*)(baseAddr + exportDir->AddressOfNames);				// VA = 基址 + 导出函数名称表RVA
	short* Ordinals = (short*)(baseAddr + exportDir->AddressOfNameOrdinals);	// VA = 基址 + 导出函数名称序号表

	int i, VAFunction, ordinal;
	int numName = exportDir->NumberOfNames;	// 以名称导出的函数的总数,作为循环打印次数
	printf("函数名序号\t函数名\t\t地址VA\n");
	for (i=0; i

·

0x04 效果如下

C语言遍历导入表、导出表_第3张图片
·

0x05 相关重要资料

1、VA、RVA、RAW、FOA、FA 是什么鬼?
https://www.cnblogs.com/iBinary/p/7653693.html

2、导入函数中几个重要结构
https://www.cnblogs.com/lanuage/p/7725699.html

3、PE文件结构图,一个带偏移量、一个不带,都要看
https://pan.baidu.com/s/161-6ZKgtm1AR4kP76Yx4eA

你可能感兴趣的:(Windows编程)