不多比比,直接上代码:
// 同时遍历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
这个字段我理解是表示延迟导入的函数的个数,但是我用这个字段控制循环的时候不太理想