参考资料

1. PE文件格式由来

微软在推出PE文件格式之前的操作系统都是用的LE文件格式, 推出PE文件格式是为了让一个程序在所有Windows x86平台通用一个文件格式,这样系统的装载器和编程工具无需对新出的CPU进行重写,在WindowsNT之后就拥有了相同的文件格式。

64位Windows只是对PE进行简单的修饰新格式叫PE32+,没有新的结构增加只是简单的把32位字段扩展为64位

PE文件的框架结构

2.PE文件基本概念

PE文件所有代码与数据都合并在了一起, 组成了一个很大的结构,文件的内容会被区分成不同的区块,每个块都会有自己的内存属性,例如这个这个块师傅包含代码,是否可读或者可写

3.DOS头

DOS头的结构体

typedef struct _IMAGE_DOS_HEADER { // DOS-MZ文件头
    WORD e_magic;      // DOS 可执行文件的标识符
    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;      // ChecksumWORD e_ip;      // Initial IP valueWORD 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;     // PE 签名的文件偏移量
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

WORD 占2个字节 LONG占用4 个字节 142+24+2*10+4=60 字节 但是并不止占60字节,实际上占用64字节,为了内存对齐的目的,_IMAGE_DOS_HEADER的大小会被舍入为下一个最接近的按4字节对齐的值。

DOS头只需要看重两个位置: e_magic DOS头标识 e_lfanew 指向PE文件头位置

左下角的大小的64字节 DOS标识头是 4D 5A 第二个红框 指向PE文件头偏移位置 00 00 00

4.PE文件头

PE签名

PE签名占4个字节 PE文件分为三个部分

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; //该结构体中的Signature就是PE签名,标识该文件是否是PE文件。该部分占4字节,即“50 45 0000”。
    IMAGE_FILE_HEADER FileHeader; //标准头PE
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展头PE
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;  

PE签名Signature占4个字节, FileHeader 是PE的标准头20个字节,OptionalHeader 是PE的扩展头 大小不定 。


标准PE头

是PE的标准头20个字节。

typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;  // 标识目标计算机类型的数字。
    WORD NumberOfSections;  // 区段数量。
    DWORD TimeDateStamp;  // 自1970年1月1日00:00起的秒数的低 32 位 (C 运行时time_t值) ,该值指示文件创建时间。
    DWORD PointerToSymbolTable; // COFF符号表的文件偏移量,如果没有COFF符号表,则为零。映像的此值应为零,因为 COFF 调试信息已弃用。
    DWORD NumberOfSymbols;  // 符号表中的条目数。此数据可用于查找紧跟在符号表后面的字符串表。映像的此值应为零,因为 COFF 调试信息已弃用。
    WORD SizeOfOptionalHeader;  // 可选标头的大小,这是可执行文件所必需的。
    WORD Characteristics;  // 指示文件属性的标志。
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

主要的四个变量

Machine
说明PE文件只能在哪些CPU类型或模拟指定计算机的系统上运行。

NumberOfSections
区段数量 5 个

SizeOfOptionalHeader
PE可选头的大小 00 E0 十进制就是 224

Characteristics
指示文件属性的标志 0102 然后对照表进行解读 这是一个平台为32位机器,文件可执行

分辨一个程序平台架构可以,综合两个参数来看一个是Machine字段和SizeOfOptionalHeader字段

Machine 一般情况下,如果Machine字段的值为0x014C,表示程序是32位的,它适用于x86架构。如果Machine字段的值为0x8664,表示程序是64位的,适用于x64架构。

SizeOfOptionalHeader PE32是32位的可选头格式,用于支持x86架构的应用程序。它的大小固定为224字节。 PE32+是64位的可选头格式,用于支持x64架构的应用程序。它的大小固定为240字节。


扩展PE头

数据结构

typedef struct _IMAGE_ROM_OPTIONAL_HEADER {
    WORD   Magic;  //标识头 PE32 还是PE32+ 或 ROM文件
    BYTE   MajorLinkerVersion; //连接器主要版本
    BYTE   MinorLinkerVersion;  //连接器次要版本
    DWORD  SizeOfCode;  //所有文本和代码的总和 
    DWORD  SizeOfInitializedData; //初始化数据部分大小 
    DWORD  SizeOfUninitializedData;  //未初始化数据部分大小
    DWORD  AddressOfEntryPoint; //程序载入内存中的映像基地址地址
    DWORD  BaseOfCode; //PE载入内存中的相对于模块的基地址偏移
    DWORD  BaseOfData;
    DWORD  BaseOfBss;
    DWORD  GprMask;
    DWORD  CprMask[4];
    DWORD  GpValue;
} IMAGE_ROM_OPTIONAL_HEADER, *PIMAGE_ROM_OPTIONAL_HEADER;

Magic PE格式
0x10b PE32
0x20b PE32+
0x107 ROM映像