C语言遍历导入表导出表

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

// 同时遍历PE文件的导入表、导出表的函数名
#include<Windows.h>
#include<stdio.h>
#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 效果如下

在这里插入图片描述
在这里插入图片描述
`

0x03 只遍历导出表

#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>

// 流程:获取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<numName; i++){
		ordinal = *(Ordinals + i);			// 取(序号表+1)的值,即在序号表中找到是第几个作为索引值

		RVA = *(RVAFunctions + ordinal);	// 在函数地址表中找到对应函数的地址为RVA
		VAFunction = baseAddr + RVA;		// 函数绝对地址就是DOS头基地址 + RVA

		RVA = *(RVANames + i);				// 名称地址表中找到索引
		VA = baseAddr + RVA;				// 绝对地址为DOS头基址+RVA

		printf("%d\t\t%s\t0x%x\n", ordinal, (char*)VA, VAFunction);
	}

	return 0;
}

·

0x04 效果如下

0x05 判断一个有效的PE文件·

/*********************************************************
 * 说明:判断一个文件是否为一个有效的 PE 文件
 * 关键字段:
 *  - IMAGE_DOS_HEADER 中的 e_magic
 *  - IMAGE_NT_HEADERS 中的 Signature
 * 两个字段的值分别要为 0x00005A4D 和 0x00004550
 * 相应的宏为 IMAGE_DOS_SIGNATURE 和 IMAGE_NT_SIGNATURE
 *********************************************************/
#include <windows.h>
#include <stdio.h>

int main(void)
{
	// 1.首先须打开一个文件
	HANDLE hFile = CreateFile(
		TEXT("x86.exe"),
		GENERIC_ALL,
		NULL,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL
	);
	// 2.判断文件句柄是否有效,若无效则提示打开文件失败并退出
	if (hFile == INVALID_HANDLE_VALUE)
	{
		printf("打开文件失败!\n");
		CloseHandle(hFile);
		exit(EXIT_SUCCESS);
	}
	// 3.若打开文件成功,则获取文件的大小
	DWORD dwFileSize = GetFileSize(hFile, NULL);
	// 4.申请内存空间,用于存放文件数据
	BYTE * FileBuffer = new BYTE[dwFileSize];
	// 5.读取文件内容
	DWORD dwReadFile = 0;
	ReadFile(hFile, FileBuffer, dwFileSize, &dwReadFile, NULL);
	// 6.判断这个文件是不是一个有效的PE文件
	//    6.1 先检查DOS头中的MZ标记,判断e_magic字段是否为0x5A4D,或者是IMAGE_DOS_SIGNATURE
	DWORD dwFileAddr = (DWORD)FileBuffer;
	auto DosHeader = (PIMAGE_DOS_HEADER)dwFileAddr;
	if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		// 如果不是则提示用户,并立即结束
		MessageBox(NULL, TEXT("这不是一个有效PE文件"), TEXT("提示"), MB_OK);
		delete FileBuffer;
		CloseHandle(hFile);
		exit(EXIT_SUCCESS);
	}
	//    6.2 若都通过的话再获取NT头所在的位置,并判断e_lfanew字段是否为0x00004550,
    //        或者是IMAGE_NT_SIGNATURE
	auto NtHeader = (PIMAGE_NT_HEADERS)(dwFileAddr + DosHeader->e_lfanew);
	if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		// 如果不是则提示用户,并立即结束
		MessageBox(NULL, TEXT("这不是一个有效PE文件"), TEXT("提示"), MB_OK);
		delete FileBuffer;
		CloseHandle(hFile);
		exit(EXIT_SUCCESS);
	}
	// 7.若上述都通过,则为一个有效的PE文件
	MessageBox(NULL, TEXT("这是一个有效PE文件"), TEXT("提示"), MB_OK);
	delete FileBuffer;
	CloseHandle(hFile);
	// 8.结束程序
	return 0;
}

·

0x06 相关重要资料

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值