Windows PE文件结构初学,本次学习目标在于了解PE结构并能够解析出其中的内容。
内容如有不妥之处,希望大家能够在评论区或私信我指出,谢谢大家。
先挂个本文的思维导图:
概述:
PE文件:Portable Executable,可移植的可执行文件,常见的有exe、dll、sys、com、ocx
PE结构包括:
- MS-DOS头
- 标准PE头
- 扩展PE头
- 数据目录
- 节表
MS-DOS头结构解析
PE文件结构
MS-DOS头是为了兼容MS-DOS16位程序而保留,对于32位或者64位程序,就不会执行DOS头
MS-DOS头在每个可执行文件都有,用二进制编辑器(这里我用的010 Editor)随便打开一个exe文件:
可以看到程序分了三个部分:
-
最上面标红的四行就是MS-DOS头
-
中间的部分是DOS-Stub部分:就是在DOS系统下所运行的东西
在DOS系统下运行,会显示“This program cannot be run in DOS mode”
-
下面那个标红的部分是PE头结构这一块了
MS-DOS头结构
这个结构,操作系统在winnt.h里已经定义了,在使用的时候包含windows.h头文件即可
#include<Windows.h>
MS-DOS头的结构体:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
其中最重要的是第一个和最后一个
第一个是MS-DOS头的标志,判断是不是可执行文件,基本上都是4D5A
最后一个是指向PE头的字段的偏移量
使用C语言打印MS-DOS头信息
#include<stdio.h>
#include<Windows.h>
int main() {
FILE* pFile = NULL;
char* buffer;
int nFileLength = 0;
pFile = fopen("D:\\VS2019_repos\\cppbook\\Release\\cppbook.exe", "rb");
fseek(pFile, 0, SEEK_END);
nFileLength = ftell(pFile);
rewind(pFile);
int imageLength = nFileLength * sizeof(char) + 1;
buffer = (char*)malloc(imageLength);
memset(buffer, 0, nFileLength * sizeof(char) + 1);
fread(buffer, 1, imageLength, pFile);
PIMAGE_DOS_HEADER ReadDosHeader; //定义一个_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);
free(buffer);
fclose(pFile);
return 0;
}
PE头结构解析
PE头包括 标准PE头 和 扩展PE头
PE头结构
PE头结构体:分为64位和32位的
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
他们的成员都有三个:
-
第一个成员Signature,PE的标识是5045
Virtual Studio提供了宏:IMAGE_NT_SIGNATURE 表示这个值,可以用宏来对比
-
第二个成员FileHeader,标准的NT头
-
第三个成员OptionalHeader是可选的头
从010Editor打开PE文件可以看到如下:
标准头结构
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台,也有宏来表示, IMAGE_FILE_MACHINE_
WORD NumberOfSections; //区段的数量
DWORD TimeDateStamp; //文件创建时间,格里尼治时间
DWORD PointerToSymbolTable; //符号表地址,用的少
DWORD NumberOfSymbols; //符号表中符号的数量
WORD SizeOfOptionalHeader; //可选头的大小
WORD Characteristics; //文件属性,也有一组宏来表示, IMAGE_FILE_EXECUTABLE_
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
这里重点关注运行平台、可选头的大小、文件属性
扩展PE头结构
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; /*机器型号,判断是PE是32位还是64位*/
BYTE MajorLinkerVersion; /*连接器版本号高版本*/
BYTE MinorLinkerVersion; /*连接器版本号低版本,组合起来就是 5.12 其中5是高版本,C是低版本*/
DWORD SizeOfCode; /*代码节的总大小(512为一个磁盘扇区)*/
DWORD SizeOfInitializedData; /*初始化数据的节的总大小,也就是.data*/
DWORD SizeOfUninitializedData; /*未初始化数据的节的大小,也就是 .data ? */
DWORD AddressOfEntryPoint; /*程序执行入口(OEP) RVA(相对偏移)(相对虚拟内存地址)*/
DWORD BaseOfCode; /*代码的节的起始RVA(相对偏移)也就是代码区的偏移,偏移+模块首地址定位代码区*/
DWORD BaseOfData; /*数据结的起始偏移(RVA),同上*/
DWORD ImageBase; /*入口点,程序的建议模块基址(意思就是说作参考用的,模块地址在哪里)*/
DWORD SectionAlignment; /*内存中的节对齐*/
DWORD FileAlignment; /*文件中的节对齐*/
WORD MajorOperatingSystemVersion; /*操作系统版本号高位*/
WORD MinorOperatingSystemVersion; /*操作系统版本号低位*/
WORD MajorImageVersion; /*PE版本号高位*/
WORD MinorImageVersion; /*PE版本号低位*/
WORD MajorSubsystemVersion; /*子系统版本号高位*/
WORD MinorSubsystemVersion; /*子系统版本号低位*/
DWORD Win32VersionValue; /*32位系统版本号值,注意只能修改为4 5 6表示操作系统支持nt4.0 以上,5的话依次类推*/
DWORD SizeOfImage; /*整个程序在内存中占用的空间(PE映尺寸)*/
DWORD SizeOfHeaders; /*所有头(头的结构体大小)+节表的大小*/
DWORD CheckSum; /*校验和,对于驱动程序,可能会使用*/
WORD Subsystem; /*文件的子系统 :重要*/
WORD DllCharacteristics; /*DLL文件属性,也可以成为特性,可能DLL文件可以当做驱动程序使用*/
DWORD SizeOfStackReserve; /*预留的栈的大小*/
DWORD SizeOfStackCommit; /*立即申请的栈的大小(分页为单位)*/
DWORD SizeOfHeapReserve; /*预留的堆空间大小*/
DWORD SizeOfHeapCommit; /*立即申请的堆的空间的大小*/
DWORD LoaderFlags; /*与调试有关*/
DWORD NumberOfRvaAndSizes; /*下面的成员,数据目录结构的项目数量*/
IMAGE_DATA_DIRECTORY DataDirectory[16]; /*数据目录,默认16个,16是宏,这里方便直接写成16*/
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
使用C语言打印PE头信息
在上一节的基础上加入如下代码:
printf("PE Header Info:\n");
PIMAGE_NT_HEADERS ReadNTHeaders;
ReadNTHeaders = (PIMAGE_NT_HEADERS)(buffer + ReadDosHeader->e_lfanew);
printf("PE标志位:%x", ReadNTHeaders->Signature);
/*打印标准头或者扩展头
printf("运行平台:%x", ReadNTHeaders->FileHeader.Machine);
printf("ImageBase:%x", ReadNTHeaders->OptionalHeader.ImageBase);
*/
区段信息及其遍历
几个概念
常用的区段:
.text .code
代码段
.data
可读写的数据段:全局变量,静态变量
.rdata
只读数据段
.idata
导入表数据段
.edata
导出表数据段
.rsrc
资源段
.bss
放未初始化的数据
.crt
.tls
.reloc
三个地址概念:
- VA:Virtual Address虚拟内存地址,范围00000000h - 0fffffffh,就是进程的基地址 + RVA相对虚拟内存地址
- RVA:相对虚拟内存地址,RVA是相对某个模块而存在的
- FOA:文件偏移地址,文件头的偏移地址
区段的结构
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //区段的名字,是个字符串
union {
DWORD PhysicalAddress; //
DWORD VirtualSize; //区段大小,没做对齐之前的大小
} Misc;
DWORD VirtualAddress; //区段载入RVA,是按照内存页对齐的
DWORD SizeOfRawData; //在磁盘中的体积,按照文件页进行对齐的
DWORD PointerToRawData; //文件中的偏移
DWORD PointerToRelocations; //重定位的偏移
DWORD PointerToLinenumbers; //行号的偏移
WORD NumberOfRelocations; //区段重定位的数量
WORD NumberOfLinenumbers; //行号表的数量
DWORD Characteristics; //属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
区段的遍历
步骤:
- 定位到第一个区段的地址(Windows自带宏能实现)
- 获取区段的数量(标准NT头里有)
- 循环遍历区段信息
//区段解析遍历,每个区段都这么个结构,所以需要遍历
printf("Section Header Info:\n");
PIMAGE_SECTION_HEADER ReadSectionHeader = IMAGE_FIRST_SECTION(ReadNTHeaders);//定位区段表的宏,参数是NT头的指针 , 定位到第一个区段的地址
PIMAGE_FILE_HEADER pFileHeader = &ReadNTHeaders->FileHeader; //标准NT头地址
for (int i = 0; i < pFileHeader->NumberOfSections; i++) { //标准NT头里存放的:区段数
printf("name(区段名):%s \n", ReadSectionHeader[i].Name); //区段信息是连续存储的,所以用数组的方式遍历即可
printf("VOffset(起始的相对虚拟地址RVA):%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("Characteristics(区段的属性):%08x \n\n", ReadSectionHeader[i].Characteristics);//属性有宏
}
数据目录表结构
数据目录表结构:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //起始地址
DWORD Size; //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
数据目录表有宏来表示:
// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory 导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory 资源表
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory 数字签名,安全证书
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory 调试
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data 版权信息
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP 全局指针偏移目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory 线程局部存储
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers 绑定存储目录
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors 延迟导入描述符表
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor COM运行时描述符目录
地址转换函数
计算某个地址到文件头的偏移
//判断Rva偏移
DWORD Rva2Offset(DWORD dwRva, char* buffer){ //Rva是某个数据目录表的起始位置 Buffer是读取到的PE文件缓冲区
//Dos头解析
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//PE头
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);//偏移+buffer,NT头首地址
//区段表
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNT);
//是否落在头部当中
if (dwRva < pSection[0].VirtualAddress); {
return dwRva;
}
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 区段到文件头的偏移(OFFSET
// 加起来就是 数据目录表到文件头的偏移(OFFSET)
return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
}
数据目录表中的几个重要的表
导入表
导入表是用来调用系统API的,导入表记录了调用的dll,以及每个dll里调用的函数
结构如下:
- 数据目录表指向导入表
- 导入表指向记录所有dll信息的结构
- 这个结构里又指向包含dll调用函数的结构
- 导入表指向记录所有dll信息的结构
导入表的结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; //重要 // RVA to original unbound IAT (PIMAGE_THUNK_DATA)指向输入名称表INT的RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //重要,是一个RVA,需要计算地址
DWORD FirstThunk; //重要 // RVA to IAT (if bound this IAT has actual addresses) 指向IAT的RVA
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
导入表引导系统找到保存有导入信息的其他结构:IMAGE_THUNK_DATA和IMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD 导入函数实际的RVA地址
DWORD Ordinal; // 被导入函数的序号,有序号就不等于0
DWORD AddressOfData; // 指向 PIMAGE_IMPORT_BY_NAME 结构
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //需要导入函数的函数序号
CHAR Name[1]; //需要导入函数的函数名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
导入表及其API的遍历
void ImportTable(char* buffer)
{
//DOS
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//NT
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)(Rva2Offset(pImportDir->VirtualAddress, buffer) + buffer);
while (pImport->Name != NULL) {
char* szDllName = (char*)(Rva2Offset(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("导入地址表的RVA:%08x\n", pImport->FirstThunk);
printf("OriginalFirstThunk:%08x\n\n", pImport->OriginalFirstThunk);
//指向导入地址表RVA
PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(Rva2Offset(pImport->FirstThunk, buffer) + buffer);
DWORD dwindex = 0;
DWORD dwImportOffset = 0;
while (pIat->u1.Ordinal != 0) {
printf("THUNKRva:%08x\n",pImport->OriginalFirstThunk +dwindex);
dwImportOffset = Rva2Offset(pImport->OriginalFirstThunk, buffer);
printf("ThunkOffset:%08x\n", dwImportOffset + dwindex);
dwindex += 4;
if (pIat->u1.Ordinal & 0x80000000 != 1) {
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(Rva2Offset(pIat->u1.AddressOfData, buffer) + buffer);
printf("API名称:%s\n", pName->Name);//函数名
printf("Hint:%04x\n",pName->Hint);//序号
printf("ThunkValue:%08x\n\n", pIat->u1.Function);//被导入函数的地址
}
pIat++;
}
pImport++;
}
}
导出表
正常情况下,dll才有导出表
结构如下:
- 数据目录表指向导出表
- 导入表导出信息
- 导出函数表RVA
- 导出名称表RVA
- 导出序号表RVA
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //保留段,值为00,特征值
DWORD TimeDateStamp; //导出表创建时间
WORD MajorVersion; //导出表主版本号*
WORD MinorVersion; //导出表子版本号*
DWORD Name; //指向模块名的RVA
DWORD Base; //输出API索引值的基数(不懂)
DWORD NumberOfFunctions; //导出表的成员数量
DWORD NumberOfNames; //导出名称表的成员数量(有的函数没名称)
DWORD AddressOfFunctions; // RVA from base of image,导出地址表RVA
DWORD AddressOfNames; // RVA from base of image,导出名称表RVA
DWORD AddressOfNameOrdinals; // RVA from base of image,序列号的数组RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
导出表解析:
void ExportTable(char* buffer){
//DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//NT头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(Rva2Offset(pDos->e_lfanew, buffer) + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
//填充导出表结构
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(Rva2Offset(pExportDir->VirtualAddress, buffer) + buffer);
char* szName = (char*)(Rva2Offset(pExport->Name, buffer) + buffer);
//判断是否有导出表
if (pExport->AddressOfFunctions == 0) {
printf("没有导出表!\n");
return;
}
printf("导出表的Offset:%08x\n", Rva2Offset(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("函数地址RVA:%08X\n", pExport->AddressOfFunctions);
printf("函数名称地址RVA:%08X\n", pExport->AddressOfNames);
printf("函数名称序号地址RVA:%08X\n", pExport->AddressOfNameOrdinals);
DWORD dwNumOfFun = pExport->NumberOfFunctions;//函数数量
DWORD dwNumOfNames = pExport->NumberOfNames;//函数名数量
DWORD dwBase = pExport->Base;//基
PDWORD pEat32 = (PDWORD)(Rva2Offset(pExport->AddressOfFunctions, buffer) + buffer);//导出地址表
PDWORD pEnt32 = (PDWORD)(Rva2Offset(pExport->AddressOfNames, buffer) + buffer);//导出名称表
PWORD pId = (PWORD)(Rva2Offset(pExport->AddressOfNameOrdinals, buffer) + buffer);//导出序号表
for (int 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*)(Rva2Offset(pEnt32[Id], buffer) + buffer);
printf("Id:%x Address:0x%08X Name[%s]\n", i + dwBase, pEat32[i],szFunName);
}
}
}
重定位表
基址重定位表主要用在dll里
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; //0x1000的倍数,指向PE中需要重定位的RVA
DWORD SizeOfBlock; //ImageBase体积总和
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
重定位表示干嘛的???
重定位表解析:
void RelocTable(char* buffer){
typedef struct _TYPE {
WORD Offset : 12;
WORD Type : 4;
}TYPE,*PTYPE;
//DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//NT头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(Rva2Offset(pDos->e_lfanew, buffer) + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pRelocDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC;
//填充重定位表的结构
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)(Rva2Offset(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("Section:%s\n",pSection->Name);
printf("RVA:%08X\n", dwRva);
printf("Items:%x H / %d D", pReloc->SizeOfBlock, pReloc->SizeOfBlock);
//找到下一个0x1000的结构体
pReloc = (PIMAGE_BASE_RELOCATION)((char*)pReloc + pReloc->SizeOfBlock);
for (int i = 0; i < dwCount; i++){
PDWORD pData = (PDWORD)(Rva2Offset(pRelocArr[i].Offset + dwRva, buffer) + buffer);
DWORD pDataOffset = Rva2Offset(pRelocArr[i].Offset + dwRva, buffer);
printf("RVA:%08X\n", pRelocArr[i].Offset + dwRva);
printf("Sections:%08X\n", *pData);
printf("Offset:%08X\n\n", pDataOffset);
}
}
}
TLS表
存在TSL区段里:线程局部存储,会在程序运行之前执行完毕,用于反调试
TLS表的结构:
typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData; // 内存起始地址
DWORD EndAddressOfRawData; //内存结束地址
DWORD AddressOfIndex; // PDWORD
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
DWORD SizeOfZeroFill; // 0填充区域长度
union {
DWORD Characteristics; //保留字段
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;
解析TLS表:
void TLSTable(char* buffer){
//DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//NT头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(Rva2Offset(pDos->e_lfanew, buffer) + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pTLSlDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_TLS;
//填充TLS表结构
PIMAGE_TLS_DIRECTORY pTLS = (PIMAGE_TLS_DIRECTORY)(Rva2Offset(pTLSlDir->VirtualAddress, buffer) + buffer);
printf("数据块开始VA:%08X\n", pTLS->StartAddressOfRawData);
printf("数据块结束VA:%08X\n", pTLS->EndAddressOfRawData);
printf("索引变量的VA:%08X\n", pTLS->AddressOfIndex);
printf("回调表的的VA:%08X\n", pTLS->AddressOfCallBacks);
printf("填零大小的值:%08X\n", pTLS->SizeOfZeroFill);
printf("特 征 值:%08X\n", pTLS->Characteristics);
}
延迟导入表
用于加快PE文件加载速度
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;
解析延迟导入表:
void DelayImportTable(char* buffer){
//DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//NT头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(Rva2Offset(pDos->e_lfanew, buffer) + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pDelayLoadDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT;
//填充延迟导入表的结构
PIMAGE_DELAYLOAD_DESCRIPTOR pDelayLoad = (PIMAGE_DELAYLOAD_DESCRIPTOR)(Rva2Offset(pDelayLoadDir->VirtualAddress, buffer) + buffer);
while (pDelayLoad->DllNameRVA != NULL) {
char* szDllName = (char*)(Rva2Offset(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\n", pDelayLoad->TimeDateStamp);
pDelayLoad++;
}
}