遍历延迟导入表

不多比比,直接上代码:

// 同时遍历PE文件的导入表、导出表的函数名
#include<Windows.h>
#include<stdio.h>
#include <DelayImp.h>
#pragma warning(disable : 4996)

DWORD RVA_2_RAW(char* buf, DWORD Rva, DWORD Raw, BOOL flag);
DWORD DelayImport(char* buf);
typedef struct ImgDelayDescr {
	DWORD           grAttrs;        // attributes  
	DWORD             rvaDLLName;     // RVA to dll name  
	DWORD             rvaHmod;        // RVA of module handle  
	DWORD             rvaIAT;         // RVA of the IAT  
	DWORD             rvaINT;         // RVA of the INT  
	DWORD             rvaBoundIAT;    // RVA of the optional bound IAT  
	DWORD             rvaUnloadIAT;   // RVA of optional copy of original IAT  
	DWORD           dwTimeStamp;    // 0 if not bound,  
									// O.W. date/time stamp of DLL bound to (Old BIND)  
} ImgDelayDescr, * PImgDelayDescr;
typedef const ImgDelayDescr* PCImgDelayDescr;
void main()
{
	char PATH[] = "C:\\Users\\admin\\Desktop\\crypt32\\crypt32.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字节
	DelayImport(buf);												// 这样将PE文件读入内存实际上就让buf指向了PE文件的基地址ImageBase
	fclose(fp);
}

// 文件已经读取到内存中了
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 DelayImport(char* buf)
{
	PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)buf;
	PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(buf + pDOS->e_lfanew);
	DWORD DelayImportTableRVA = pNT->OptionalHeader.DataDirectory[13].VirtualAddress;					// 获得延迟导入表的RVA,DataDirectory[13]是延迟导入表
	DWORD DelayImportDLL_FA = RVA_2_RAW(buf, DelayImportTableRVA, 0, 1);									// 计算导入DLL在磁盘文件中的地址,这个变量专门保存DLL地址
	PImgDelayDescr pDelayImportTable = (PImgDelayDescr)(buf + DelayImportDLL_FA);	// pImportTable指向导入的DLL在磁盘文件中的地址
	printf("延迟导入表:\n");
		int i = 0;
		while (pDelayImportTable->rvaDLLName)
		{
			// 打印DLL名字
			DWORD DelayImportDLLName_RVA = pDelayImportTable->rvaDLLName;											// 指向被输入的DLL的名称(ASCII字符串)的RVA,注意只针对DLL
			DelayImportDLL_FA = RVA_2_RAW(buf, DelayImportDLLName_RVA, 0, 1);									// 求出导入函数名在磁盘文件中的物理地址
			printf("%s--------------------------我是DLL哟\n", buf + DelayImportDLL_FA);				// 打印这个DLL的名字
			DWORD DelayApiRva = pDelayImportTable->rvaINT;//获取API函数名称的RVA
			// 打印普通API名字
			DWORD DelayApiRva_FA = RVA_2_RAW(buf, DelayApiRva, 0, 1);//获取API函数名称的FA
			PIMAGE_THUNK_DATA DelayApiINT = (PIMAGE_THUNK_DATA)(buf + DelayApiRva_FA);//指向INT表
			while(DelayApiINT != 0)
			{
				DWORD Delay_Name = DelayApiINT->u1.AddressOfData;
				DWORD Delay_Name_FA = RVA_2_RAW(buf, Delay_Name, 0, 1);
				PIMAGE_IMPORT_BY_NAME Delay_Import_Name = (PIMAGE_IMPORT_BY_NAME)(buf + Delay_Name_FA);
				if (!strcmp(Delay_Import_Name->Name, "CveEventWrite"))
				{
					printf("匹配到目标函数\n");
				}
				printf("函数序号为%d--------------------------函数名称:%s\n", Delay_Import_Name->Hint, Delay_Import_Name->Name);
				DelayApiINT++;
				if (DelayApiINT->u1.AddressOfData == 0)
				{
					break;
				}
			}
			pDelayImportTable++;																				// 指针++就是指向下一个内存地址,即指向下一个IMAGE_IMPORT_DESCRIPTOR结构
		}
	return 0;
}

其中涉及到的结构体ImgDelayDescr可以直接包含头文件DelayImp.h
rvaINT这个字段指向INT表,进而指向IMAGE_IMPORT_BY_NAME结构
但是比较好奇,pNT->OptionalHeader.DataDirectory[13].Size这个字段我理解是表示延迟导入的函数的个数,但是我用这个字段控制循环的时候不太理想

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值