PE 文件的最开始便是 PE 文件头,它由 MS-DOS 文件头 和 IMAGE_NT_HEADERS 结构体组成。
MS-DOS 文件头
MS-DOS 文件头 包含 IMAGE_DOS_HEADER 和 DOS Stub 两个部分。
IMAGE_DOS_HEADER 结构体的定义如下:
IMAGE_DOS_HEADER 结构体中有 2 个重要成员:
e_magic 单字。DOS 签名 "4D5A",即 ASCII 值 "MZ"。所有 PE 文件的开头都有 DOS 签名。
e_lfanew 单字。IMAGE_NT_HEADER相对于文件起始处的偏移。
示例程序的 IMAGE_DOS_HEADER 如图 2 所示:
IMAGE_DOS_HEADER
IMAGE_DOS_HEADER 结构体后紧接着是 DOS Stub,它的作用很简单,当系统为 MS-DOS 环境时,输出 This program cannot be run in DOS mode. 并退出程序,表明该程序不能在 MS-DOS 环境下运行。这使得所有的 PE 文件都对 MS-DOS 环境兼容。利用该特性可以创建出一个在 MS-DOS 和 Windows 环境中都能运行的程序,在 MS-DOS 中执行 16-bit MS-DOS 代码,在 Windows 中执行 32-bit Windows 代码。
示例程序的 DOS Stub 如图 3 所示:
DOS Stub
IMAGE_NT_HEADERS
IMAGE_NT_HEADERS 结构体,俗称 NT 头。紧跟在 DOS Stub 之后,其定义如下:
示例程序的 IMAGE_NT_HEADERS 如图 4 所示:
NT 头
接下来详细说一下 NT 头。
PE Signature
NT 头的第一个成员是PE Signature,它是一个4字节大小的ASCII码字符串 PE\0\0,用于指明当前文件是一个 PE 格式的映像文件。其位置可以通过 IMAGE_DOS_HEADER 的 e_lfanew 成员的值确定。
IMAGE_FILE_HEADER
PE Signature 后紧跟着是 IMAGE_FILE_HEADER 结构体,又称作 COFF 头(标准通用文件格式头)。其定义如下:
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic; // "MZ"
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // NT 头相对于文件起始处的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; /* +0000h PE 标识 */
IMAGE_FILE_HEADER FileHeader; /* +0004h PE 标准头 */
IMAGE_OPTIONAL_HEADER32 OptionalHeader; /* +0018h PE 可选头 */
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; /* +0004h 目标机器类型 */
WORD NumberOfSections; /* +0006h PE 中节的数量 */
DWORD TimeDateStamp; /* +0008h 时间戳 */
DWORD PointerToSymbolTable; /* +000ch 指向符号表的指针 */
DWORD NumberOfSymbols; /* +0010h 符号表中符号数目 */
WORD SizeOfOptionalHeader; /* +0012h 可选头的大小 */
WORD Characteristics; /* +0014h 文件属性标志 */
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;