PE文件详解 - 延时导入表
1、延时导入表
1.1、IMAGE_DELAYLOAD_DESCRIPTOR
typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {
union {
DWORD AllAttributes;
struct {
DWORD RvaBased : 1; // Delay load version 2
DWORD ReservedAttributes : 31;
} DUMMYSTRUCTNAME;
} Attributes;
DWORD DllNameRVA; // RVA to the name of the target library (NULL-terminate ASCII string)
DWORD ModuleHandleRVA; // RVA to the HMODULE caching location (PHMODULE)
DWORD ImportAddressTableRVA; // RVA to the start of the IAT (PIMAGE_THUNK_DATA)
DWORD ImportNameTableRVA; // RVA to the start of the name table (PIMAGE_THUNK_DATA::AddressOfData)
DWORD BoundImportAddressTableRVA; // RVA to an optional bound IAT
DWORD UnloadInformationTableRVA; // RVA to an optional unload info table
DWORD TimeDateStamp; // 0 if not bound,
// Otherwise, date/time of the target DLL
} IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;
1.2、通过代码获取延时导入表
#include<stdio.h>
#include<Windows.h>
#include "Entry.h"
int main()
{
FILE* pFile = NULL;
char* buffer;
int nFileLength = 0;
errno_t err = fopen_s(&pFile, "E:\\C_code\\MyDemo\\Debug\\MyDemo.exe", "rb");
if (err != 0)
{
// 处理文件打开错误
printf("Failed to open file.\n");
return 1;
}
// fseek()设置文件指针的位置。这里将文件指针移动到文件末尾
// SEEK_END 指定偏移量为0
fseek(pFile, 0, SEEK_END);
// ftell() 获取文件指针的当前位置,也就是文件的长度
nFileLength = ftell(pFile);
// rewind() 将文件指针重置到文件开头
rewind(pFile);
// 存储缓冲区大小
// 计算方式:文件长度乘以sizeof(char)(sizeof(char)表示char类型的大小,通常是1字节),再加1字节用于存储字符串结束符\0
int imageLength = nFileLength * sizeof(char) + 1;
// 动态分配内存 : malloc函数动态分配了一块内存,大小为imageLength字节
buffer = (char*)malloc(imageLength);
// memset() :将分配的内存初始化为0。
memset(buffer, 0, nFileLength * sizeof(char) + 1);
// fread函数用于从文件中读取数据。这里将文件中的内容读取到之前分配的缓冲区buffer中,每次读取1字节,总共读取imageLength个字节。
fread(buffer, 1, imageLength, pFile);
// 读取DOS头
// 定义指向 PIMAGE_DOS_HEADER的指针 ReadDosHeader
PIMAGE_DOS_HEADER ReadDosHeader;
// 因为buffer是char类型,所以要转成指针类型
// 目的是将缓冲区中的数据解释为IMAGE_DOS_HEADER结构
ReadDosHeader = (PIMAGE_DOS_HEADER)buffer;
// 打印指针中的数据
printf("MS-DOS Info:\n");
printf("MZ标志位:%x\n", ReadDosHeader->e_magic);
printf("PE头位置:%x\n", ReadDosHeader->e_lfanew);
// PE头
// 要注意你读取的EXE是32位还是64位的,64位要使用 PIMAGE_NT_HEADERS64
printf("PE Header Info:\n");
PIMAGE_NT_HEADERS ReadNTHeaders;
// 因为要跳过DOS头,读取到PE开头,所以要加e_lfanew
ReadNTHeaders = (PIMAGE_NT_HEADERS)(buffer + ReadDosHeader->e_lfanew);
printf("PE标志位:%x\n", ReadNTHeaders->Signature);
// 打印标准PE头里的东西
printf("运行平台:%x\n", ReadNTHeaders->FileHeader.Machine);
// 打印拓展PE头里的东西
printf("ImageBase: %x\n",ReadNTHeaders->OptionalHeader.ImageBase);
// 区段解析遍历
printf("Section Header Info:\n");
// Windows定义了宏来解析区段,使用IMAGE_FIRST_SECTION
// 定位到区段表
PIMAGE_SECTION_HEADER ReadSectionHeader = IMAGE_FIRST_SECTION(ReadNTHeaders);
// 在标准PE头中有个字段可以遍历区段的数量,在 PIMAGE_FILE_HEADER中的NumberOfSections字段
PIMAGE_FILE_HEADER pFileHeader = &ReadNTHeaders->FileHeader;
for (int i = 0; i < pFileHeader->NumberOfSections; i++)
{
printf("Name(区段名称):%s\n", ReadSectionHeader[i].Name);
printf("VOffset(起始相对虚拟地址):%08X\n", ReadSectionHeader[i].VirtualAddress);
printf("VSize(区段大小):%08X\n", ReadSectionHeader[i].SizeOfRawData);
printf("ROffset(文件偏移):%08X\n", ReadSectionHeader[i].PointerToRawData);
printf("RSize(文件中区段大小):%08X\n", ReadSectionHeader[i].Misc.VirtualSize);
printf("标记(区段的属性):%08X\n\n", ReadSectionHeader[i].Characteristics);
}
printf("=================================================\n");
// 获取导出表
ImportTable(buffer);
printf("=================================================\n");
// 获取导入表
ExportTable(buffer);
printf("=================================================\n");
// 获取重定位表
RelocTable(buffer);
// 释放动态内存
free(buffer);
// 关闭文件
fclose(pFile);
return 0;
}
// dwRva 是某个数据目录表的起始位置
// buffer 读取到的PE文件缓冲区
DWORD RvaToOffset(DWORD dwRva, char* buffer)
{
// 从Dos头解析
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 区段表
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
//判断是否落在头部当中
if (dwRva < pSection[0].VirtualAddress)
{
return dwRva;
}
// VirtualAddress 起始地址
// Size 长度
// VirtualAddress + Size = 结束地址
// 判断是否落在某个区段内
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
if (dwRva >= pSection[i].VirtualAddress && dwRva <= pSection[i].VirtualAddress+pSection[i].Misc.VirtualSize)
{
// dwRva - pSection[i].VirtualAddress 是数据目录表起始地址到区段起始地址的偏移(OFFSET)
// pSection[i].PointerToRawData 区段到文件头的偏移
// 这一串返回的是数据目录表起始地址到文件头的偏移
return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
}
// 解析导入表
void ImportTable(char* buffer)
{
// 从DOS头开始解析
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 定位导入表
PIMAGE_DATA_DIRECTORY pImportDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);
// 填充结构
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToOffset(pImportDir->VirtualAddress,buffer)+buffer);
// 判断导入表的动态链接库是否遍历完
while (pImport->Name != NULL)
{
// 获取名字,因为NAME是一个相对虚拟地址,所以要计算
char *szDllName = (char *)(RvaToOffset(pImport->Name, buffer) + buffer);
printf("DLL名称:%s\n",szDllName);
printf("日期时间标志:%08X\n",pImport->TimeDateStamp);
printf("ForwarderChain:%08X\n",pImport->ForwarderChain);
printf("名称OFFSET:%08X\n",pImport->Name);
printf("FirstThunk:%08X\n",pImport->FirstThunk);
printf("OriginalFirstThunk:%08X\n\n",pImport->OriginalFirstThunk);
// 解析里面的函数
// 指向导入地址表的RVA 通过OriginalFirstThunk字段来计算偏移
PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(RvaToOffset(pImport->OriginalFirstThunk, buffer) + buffer);
DWORD index = 0;
DWORD ImportOffset = 0;
// 被导入函数的序号
while (pIat->u1.Ordinal != 0)
{
printf("ThunkRva:%08X\n",pImport->OriginalFirstThunk+index);
ImportOffset = RvaToOffset(pImport->OriginalFirstThunk,buffer);
printf("ThunkOffset:%08X\n", ImportOffset + index);
index += 4;
if ((pIat->u1.Ordinal & 0x80000000) != 1)
{
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(RvaToOffset(pIat->u1.AddressOfData,buffer)+buffer);
printf("API名称:%s\n",pName->Name);
// 序号
printf("Hint:%04X\n",pName->Hint);
// 被导入函数地址 pIat->u1.Function
printf("ThunkValue:%08X\n\n",pIat->u1.Function);
}
// 指向下一个
pIat++;
}
// 判断完后,跳到下个结构
pImport++;
}
}
void ExportTable(char* buffer)
{
// 从DOS头开始解析
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 定位数据库目录中的导出表
PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
// 填充导出表 返回的是文件头到导出表的偏移
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RvaToOffset(pExportDir->VirtualAddress, buffer) + buffer);
// 计算DLL名字
char* szName = (char*)(RvaToOffset(pExport->Name,buffer),buffer);
// 判断
if (pExport->AddressOfFunctions == 0)
{
printf("当前没有导出表\n");
return;
}
printf("导出表OFFSET:%08X\n",RvaToOffset(pExportDir->VirtualAddress,buffer));
printf("特征值:%08X\n",pExport->Characteristics);
printf("基数:%08X\n",pExport->Base);
printf("名称OFFSET:%08X\n",pExport->Name);
printf("名称:%s\n",szName);
printf("函数数量:%08X\n",pExport->NumberOfFunctions);
printf("函数名数量:%08X\n",pExport->NumberOfNames);
printf("函数地址:%08X\n",pExport->AddressOfFunctions);
printf("函数名称地址:%08X\n",pExport->AddressOfNames);
printf("函数名称序号地址:%08X\n",pExport->AddressOfNameOrdinals);
// 解析里面的值
// 函数地址数量
DWORD dwNumOfFun = pExport->NumberOfFunctions;
// 函数名数量
DWORD dwNumOfNames = pExport->NumberOfNames;
// 基数
DWORD dwBase = pExport->Base;
// 导出地址表
PDWORD pEat32 = (PDWORD)(RvaToOffset(pExport->AddressOfFunctions,buffer)+buffer);
// 导出名称表
PDWORD pEat32 = (PDWORD)(RvaToOffset(pExport->AddressOfNames, buffer) + buffer);
// 导出序号表
PWORD pId = (PWORD)(RvaToOffset(pExport->AddressOfNameOrdinals, buffer) + buffer);
for (DWORD i = 0; i < dwNumOfFun; i++)
{
if (pEat32[i] == 0)
{
continue;
}
// 查找函数名是否存在
DWORD Id = 0;
for (; Id < dwNumOfNames; Id++)
{
if (pId[Id] == i)
{
break;
}
}
if (Id == dwNumOfNames)
{
printf("Id:%x Address:0x%08X Name[NULL]\n",i+dwBase,pEat32[i]);
}
else
{
char* szFunName = (char*)(RvaToOffset(pEat32[Id],buffer)+buffer);
printf("Id:%x Address:0x%08X Name[%s]\n", i + dwBase, pEat32[i],szFunName);
}
}
}
void RelocTable(char* buffer)
{
// 重新定义TypeOffset
typedef struct _TYPE {
WORD Offset : 12; // 12字节
WORD Type : 4; // 4字节
}TYPE,*PTYPE;
// 从DOS头开始
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 定位重定位表,根据扩展头中的数据目录
PIMAGE_DATA_DIRECTORY pRelocDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
// 填充重定位表的结构
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)(RvaToOffset(pRelocDir->VirtualAddress,buffer)+buffer);
// 定位区段
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
while (pReloc->SizeOfBlock != 0)
{
// 找到本0x1000个字节的起始位置
// 得到重定位的个数
DWORD dwCount = (pReloc->SizeOfBlock - 8) / 2;
DWORD dwRva = pReloc->VirtualAddress;
PTYPE pRelocArr = (PTYPE)(pReloc +1);
printf("区段:%s\n", pSection->Name);
printf("RVA:%08X\n",dwRva);
// 进制不一样,前面是十六进制,后面是十进制
printf("项目:%X H / %d D\n",pReloc->SizeOfBlock, pReloc->SizeOfBlock);
// 遍历里面的东西
// 找到下一个0x1000字节的结构体
pReloc = (PIMAGE_BASE_RELOCATION)((char *)pReloc + pReloc->SizeOfBlock);
for (int i = 0; i < dwCount; i++)
{
PDWORD pData = (PDWORD)(RvaToOffset(pRelocArr[i].Offset + dwRva,buffer),buffer);
DWORD pDataOffset = RvaToOffset(pRelocArr[i].Offset + dwRva, buffer);
printf("RVA:%08X\n", pRelocArr[i].Offset + dwRva);
printf("区段:%08X\n", *pData);
printf("偏移:%08X\n\n", pDataOffset);
}
}
}
void TLSTbale(char* buffer)
{
// 从DOS头开始
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 定位数据目录表中的TLS表
PIMAGE_DATA_DIRECTORY pTLSDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_TLS);
// 填充TLS结构
PIMAGE_TLS_DIRECTORY pTLS = (PIMAGE_TLS_DIRECTORY)(RvaToOffset(pTLSDir->VirtualAddress, buffer), buffer);
printf("数据块开始VA:%08X\n",pTLS->StartAddressOfRawData);
printf("数据块结束VA:%08X\n",pTLS->EndAddressOfRawData);
printf("索引变量VA", pTLS->AddressOfIndex);
printf("回调表VA:%08X\n", pTLS->AddressOfCallBacks);
printf("填零大小:%08X\n", pTLS->SizeOfZeroFill);
printf("特征值:%08X\n", pTLS->Characteristics);
}
void DelayImportTable(char* buffer)
{
// 从DOS头开始
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
// PE
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
// 定位数据目录表中的延时导入表
PIMAGE_DATA_DIRECTORY pDelayLoadDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
// 填充延时导入表的数据结构
PIMAGE_DELAYLOAD_DESCRIPTOR pDelayLoad = (PIMAGE_DELAYLOAD_DESCRIPTOR)(RvaToOffset(pDelayLoadDir->VirtualAddress,buffer),buffer);
while (pDelayLoad->DllNameRVA !=NULL)
{
char* szDllName = (char*)(RvaToOffset(pDelayLoad->DllNameRVA, buffer) + buffer);
printf("DLLName:%s\n",szDllName);
printf("Attributes:%08X\n",pDelayLoad->Attributes);
printf("ModuleHandleRVA:%08X\n",pDelayLoad->ModuleHandleRVA);
printf("ImportAddressTableRVA:%08X\n",pDelayLoad->ImportAddressTableRVA);
printf("ImportNameTableRVA:%08X\n",pDelayLoad->ImportNameTableRVA);
printf("BoundImportAddressTableRVA:%08X\n",pDelayLoad->BoundImportAddressTableRVA);
printf("UnloadInformationTableRVA:%08X\n",pDelayLoad->UnloadInformationTableRVA);
printf("TimeDateStamp:%08X\n",pDelayLoad->TimeDateStamp);
pDelayLoad++;
}
}
推荐阅读: