PE 格式详解与试验
Posted Debroon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE 格式详解与试验相关的知识,希望对你有一定的参考价值。
可执行文件结构分析
可执行文件有:
- windows 的 PE,如 .exe、.dll、.ocx、.sys、.com。
- Linux 的 ELF,如 .out、.so。
PE 整体结构图:
具体结构:
比较重要的几个部分:DOS 头、文件头、可选头、数据目录、节头和节区。
- DOS头:32位签名以及魔数
0x00004550
- 文件头:说明文件在什么平台、分几个区段、链接的时间、是可执行文件还是 DLL
- 可选头:包含大量 PE 文件的重要信息
- 数据目录:包含许多指向各节数据的指针
- 节头和节区:PE文件的核心,病毒检测相关特征主要集中于此
DOS头
DOS头,是 PE 文件第一个结构,总共占 64 个字节。
第一个成员 e_magic
,叫魔数,占 2 个字节,固定俩个字符 MZ
的内存编码,这是发明人的首字母缩写。
- M 的内存编码:4D
- Z 的内存编码:5A
但在 x86 平台,是低位优先,看到的是 5A4D,而不是 4D5A。
第二个成员 e_cbip
,PE 文件最后那一页所包含的字节数。
第三个成员 e_cp
,PE 文件包含多少页。
第四个成员 e_crlc
,PE 文件有多少重地位个数。
第五个成员 e_cparhdr
,PE 文件有多少断头。
第六个成员 e_minalloc
,所需的最小附加段。
第七个成员 e_maxalloc
,所需的最大附加段。
第八个成员 e_ss
,栈底寄存器的初始值。
第九个成员 e_sp
,栈顶寄存器的初始值。
第十个成员 e_csum
,校验码,验证 PE 文件有木有被改。
第十一成员 e_ip
,指令寄存器的初始值。
第十二成员 e_cs
,段寄存器的初始值。
第十三成员 e_lfarlc
,重定向表的文件地址。
第十四成员 e_ovno
,覆盖号,早期内存小,运行大程序就需要这个。
第十五成员 e_res[4]
,保留字段。
第十六成员 e_oemid
,oem 的 id。
第十七成员 e_oeminfo
,oem 的信息。
第十八成员 e_res2[10]
,保留字段。
第十九成员 e_lfanew
,PE 头的起始地址 0x0000003C。
- 判断 PE 文件的有效性:
e_magic
(5A4D)、e_lfanew
( PE,0,0,(x00004550) )
解析 DOS Header:
int peDosHeaderAnalyze(char *file)
if(file==NULL)
return -1;
FILE *fp = NULL;
IMAGE_DOS_HEADER dosheader; // PE 结构体公开的
unsigned long pesig;
fp = fopen(file, "r+b"); // 打开文件
if(fp == NULL)
return -1;
fread(&dosheader, sizeof(dosheader), 1, fp); // 读出 dos header
fseek(fp, dosheader.e_lfanew,SEEK_SET); // 读出 dos header 最后一个成员
fread(&pesig, 4, 1, fp); // 读 4 个字节,PE 头签名 PE,0,0
fclose(fp);
printf("IMAGE_DOS_HEADER info:\\n");
printf("e_magic : %04x\\n", dosheader.e_magic); // "MZ"-->"ZM":0x5A4D
printf("e_cblp : %04x\\n", dosheader.e_cblp);
printf("e_cp : %04x\\n", dosheader.e_cp);
printf("e_crlc : %04x\\n", dosheader.e_crlc);
printf("e_cparhdr : %04x\\n", dosheader.e_cparhdr);
printf("e_minalloc: %04x\\n", dosheader.e_minalloc);
printf("e_maxalloc: %04x\\n", dosheader.e_maxalloc);
printf("e_ss : %04x\\n", dosheader.e_ss);
printf("e_sp : %04x\\n", dosheader.e_sp);
printf("e_csum : %04x\\n", dosheader.e_csum);
printf("e_ip : %04x\\n", dosheader.e_ip);
printf("e_cs : %04x\\n", dosheader.e_cs);
printf("e_lfarlc : %04x\\n", dosheader.e_lfarlc);
printf("e_ovno : %04x\\n", dosheader.e_ovno);
printf("e_res[0] : %04x\\n", dosheader.e_res[0]);
printf("e_oemid : %04x\\n", dosheader.e_oemid);
printf("e_oeminfo : %04x\\n", dosheader.e_oeminfo);
printf("res2[0] : %04x\\n", dosheader.e_res2[0]);
printf("lfanew : %08x\\n", dosheader.e_lfanew);
return 0;
判断 PE 有效性:
bool isPeValid(char *file)
if(file == NULL)
return FALSE;
FILE *fp = NULL;
IMAGE_DOS_HEADER dosheader; // PE 结构体公开的
unsigned long pesig;
fp = fopen(file, "r+b"); // 打开文件
if(fp == NULL)
return false;
fread(&dosheader, sizeof(dosheader), 1, fp); // 读出 dos header
fseek(fp, dosheader.e_lfanew, SEEK_SET); // 读出 dos header 最后一个成员 e_lfanew
fread(&pesig, 4, 1, fp); // 读 4 个字节,PE 头签名 PE,0,0
fclose(fp);
if((dosheader.e_magic ==IMAGE_DOS_SIGNATURE) &&
(pesig == IMAGE_NT_SIGNATURE))
//dos header e_magic和PE头部里的signature必须为固定值:MZ(0x5A4D )和PE00(0x00004550)
return true;
return false;
调用:
int main(int argc, char* argv[])
char *pefile="D:\\\\mfpedemo_x64debug.exe";
if(isPeValid(pefile))
peDosHeaderAnalyze(pefile);
else
printf("not a valid pe file\\n");
return 0;
文件头
file_header 结构,三部分组成:
文件头用来说明该二进制文件将运行在何种机器之上、分几个区段、链接的时间、是可执行文件还是DLL等等。
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;
#ifdef _WIN64
typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS;
#endif
IMAGE_FILE_HEADER 占 20 个字节:
typedef struct _IMAGE_FILE_HEADER
WORD Machine; // 运行平台
WORD NumberOfSections; // 块数目
DWORD TimeDataStamp; // 时间日期标记
DWORD PointerToSymbolTable; // COFF 符号指针,这是程序调试信息
DWORD NumberOfSymbols; // 符号数
WORD SizeOfOptionalHeader; // 可选部首长度,是 IMAGE_FILE_HEADER 的长度
WORD Characteristics; // 文件属性
IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine(机器码)指定运行平台:
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64(K8)
Characteristics 文件属性取值,文件是否为可运行的状态,是否为DLL文件等信息:
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001
// Relocation info stripped from file.
#define IMAGE_FILE_ EXECUTABLE_IMAGE 0x0002
// File is executable. - 文件是否可以执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004
// Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008
// Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010
// Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AMARE 0x0020
// App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080
// Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100
// 32 bit word nachine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200
// Debugging info stripped .DBG file - 是否包含调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400
#define IMAGE_FILE_NET_RUN _FROH _SWAP 0x0800
// If Image is on Net.
#define IMAGE_FILE_SYSTEM 0x1000
// System File. - 是否是系统文件
#define IMAGE_FILE_DLL 0x2000
// File is a DLL. - 是否是 DLL 文件
#define IMAGE_FILE_UP_SYSTEM_CNLY 0x4000
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000
// Bytes of machine word are reversed.
从 PE 头进入 IMAGE_FILE_HEADER,只需要在成员 e_ifanew
+ 4 个字节即可。
可选头
可选头虽然叫作可选,但是却包含了大量PE文件的重要信息。
Optioal_header 分 32、64 位,俩者相差不大。
可以下载软件 CFF Explorer 直接查看。
其中,比较重要的几个字段:
- Magic,标记32位和64位可选头,为IMAGE_OPTIONAL_HEADER32时,magic码为10B,为IMAGE_OPTIONAL_HEADER64时,magic码为20B
- MajorLinkerVersion和MinorLinkerVersion,链接器的版本号
- SizeOfCode,代码段的长度
- MajorOperatingSystemVersion、MinorOperatingSystemVersion,所需操作系统的版本号
- MajorImageVersion、MinorImageVersion,映像的版本号
- MajorSubsystemVersion、MinorSubsystemVersion,所需子系统版本号
- SizeOfImage,映像的大小
- SizeOfHeaders,所有文件头的大小
- Subsystem,运行该PE文件所需的子系统。
PE RVA 地址与文件地址转换
这些节在内存中是按照 0x1000 对齐,
PE 首先加载到一个基地址:ImageBase(0x40000,也可能随机)
- VA:虚拟地址
- RVA:相对虚拟地址,RVA = VA - ImageBase
- VOffset:该节起始地址对于 ImageBase 的偏移量,如 .text 块(0x0401000) - 基地址(0x0400000)
- ROffset:该节起始地址对于文件起始位置的偏移量,如 .text 块(0x0401000) - 文件起始地址(0x0000000)
比如你获得了一条虚拟地址,这些条件都知道 ImageBase、VOffset、ROffset、VA,请算某一个虚拟地址 VA,对应的文件偏移地址:fRVA。
- fRVA - ROffset = RVA - VOffset
- fRVA = RVA - VOffset + ROffset
- RVA = VA - ImageBase
- fRVA = VA - ImageBase - VOffset + ROffset
上述等式原理:虚拟地址和文件地址对于字起始的偏移是固定的。
块表 Section Header
块表结构:
可以下载软件 CFF Explorer 直接查看。
导入表 Data Directory
typedef struct _IMAGE_DATA_DIRECTORY
DWORD VirtualAddress; // RVA 相对虚拟地址
DWORD Size;
IMAGE_DATA_DIRECTORY, *PIMAGEDATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTOF_ENTRIES 16
Data Directory[16],那 16 个元素是那些:
#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
// 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 Deseriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
// COM RuntimE_Descriptor
可以下载软件 CFF Explorer 直接查看。
基址重定位 reloc
EXE的默认加载地址是 0x400000,DLL的默认加载地址是 0x100000。
如果加载的基地址不是默认的地址(比如多个 DLL 加载),那么使用固定地址就需要重新定位。
这些代码地址就会记录在 reloc
表里。
C语言解析 PE 结构
#include <windows.h>
#include <stdio.h>
#pragma warning(disable:4996)
bool isPeValid(char *file)
if(file==NULL)
return FALSE;
FILE *fp;
IMAGE_DOS_HEADER dosheader;
unsigned long pesig;
fp = fopen(file,"r+b");
if(fp == NULL)
return FALSE;
fread(&dosheader,sizeof(dosheader),1,fp);
fseek(fp,dosheader.e_lfanew,SEEK_SET);
fread(&pesig,4,1,fp);
fclose(fp);
if((dosheader.e_magic ==IMAGE_DOS_SIGNATURE) &&
(pesig == IMAGE_NT_SIGNATURE))
// dos header e_magic和PE头部里的signature必须为固定值:MZ(0x5A4D )和PE00(0x00004550)
return TRUE;
return FALSE;
int peDosHeaderAnalyze(char *file)
if(file==NULL)
return -1;
FILE *fp;
IMAGE_DOS_HEADER dosheader;
unsigned long pesig;
fp = fopen(file,"r+b");
if(fp == NULL)
return -1;
fread(&dosheader,sizeof(dosheader),1,fp);
fseek(fp,dosheader.e_lfanew,SEEK_SET);
fread(&pesig,4,1,fp);
fclose(fp);
printf("IMAGE_DOS_HEADER info:\\n");
printf("e_magic : %04x\\n",dosheader.e_magic);//"MZ"-->"ZM":0x5A4D
printf("e_cblp : %04x\\n",dosheader.e_cblp);
printf("e_cp : %04x\\n",dosheader.e_cp);
printf("e_crlc : %04x\\n",dosheader.e_crlc);
printf("e_cparhdr : %04x\\n",dosheader.e_cparhdr);
printf("e_minalloc: %04x\\n",dosheader.e_minalloc);
printf("e_maxalloc: %04x\\n",dosheader.e_maxalloc);
printf("e_ss : %04x\\n",dosheader.e_ss);
printf("e_sp : %04x\\n",dosheader.e_sp);
printf("e_csum : %04x\\n",dosheader.e_csum);
printf("e_ip : %04x\\n",dosheader.e_ip);
printf("e_cs : %04x\\n",dosheader.e_cs);
printf("e_lfarlc : %04x\\n",dosheader.e_lfarlc);
printf("e_ovno : %04x\\n",dosheader.e_ovno);
printf("e_res[0] : %04x\\n",dosheader.e_res[0]);
printf("e_oemid : %04x\\n",dosheader.e_oemid);
printf("e_oeminfo : %04x\\n",dosheader.e_oeminfo);
printf("res2[0] : %04x\\n",dosheader.e_res2[0]);
printf("lfanew : %08x\\n",dosheader.e_lfanew);
return 0;
int peFileHeaderAnalyze(char *file)
if(file==NULL)
return -1;
FILE *fp;
IMAGE_DOS_HEADER dosheader;
IMAGE_FILE_HEADER fileheader;
unsigned long pesig;
fp = fopen(file,"r+b");
if(fp == NULL)
return -1;
fread(&dosheader,sizeof(dosheader),1,fp);
fseek(fp,dosheader.e_lfanew,SEEK_SET);
fread(&pesig,4,1,fp);
fread(&fileheader, sizeof(fileheader), 1, fp);
fclose(fp);
printf("IMAGE_FILE_HEADER info:\\n");
printf("Sinature\\t\\t: %08x\\n",pesig);
printf("Machine\\t\\t\\t: %04x\\n",fileheader.Machine);
printf("NumberOfSections\\t: %08x\\n",fileheader.NumberOfSections);
printf("TimeDateStamp\\t\\t: %08x\\n",fileheader.TimeDateStamp);
printf("PointerToSymbolTable\\t: %08x\\n",fileheader.PointerToSymbolTable);
printf("NumberOfSymbols\\t\\t: %04x\\n",fileheader.NumberOfSymbols);
printf("SizeOfOptionalHeader\\t: %04x\\n",fileheader.SizeOfOptionalHeader);
printf("Characteristics\\t\\t: %04x\\n",fileheader.Char以上是关于PE 格式详解与试验的主要内容,如果未能解决你的问题,请参考以下文章