selph
selph
Published on 2020-08-20 / 2,842 Visits
0
0

Windows PE文件结构笔记:初见

Windows PE文件结构初学,本次学习目标在于了解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文件:

image-20200805164957141

可以看到程序分了三个部分:

  • 最上面标红的四行就是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文件可以看到如下:

image-20200805185620524

标准头结构

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;

区段的遍历

步骤:

  1. 定位到第一个区段的地址(Windows自带宏能实现)
  2. 获取区段的数量(标准NT头里有)
  3. 循环遍历区段信息
	//区段解析遍历,每个区段都这么个结构,所以需要遍历
	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调用函数的结构

导入表的结构:

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++;
	}
}


Comment