PE文件格式详细解析
本篇文章将会详解Windows操作平台下PE文件格式,同时以具体的示例辅佐大家更好理解PE文件格式。本文章主要使用到以下两个工具:WinHex:用于将PE文件以16进制和ASCII码显示;PE tools解析和修改PE文件的工具。
1.PE文件格式
PE(Portable Executable,可移植的可执行文件),是Windows操作系统下可执行文件的总称。
PE文件往往指32位系统下的可执行文件,亦称PE32。64位的可执行文件称为PE+或PE32+,为PE文件的扩展格式。
PE文件主要分为以下几种:
种类 | 主扩展名 |
---|---|
可执行系列 | .exe .scr |
库系列 | .dll .ocx .cpl .drv |
驱动程序系列 | .sys .vxd |
对象文件系列 | .obj |
2.PE文件位置标识
由于PE文件在加载到内存时需要进行内存对齐,因此一个PE文件在磁盘中和在内存中所占大小不一致,需要以下两种位置标识方式标识具体数据在磁盘和内存中的位置信息。
-
RAW offset
标识数据的物理地址,即一个数据在文件中相对于文件头的偏移量。
如下图,使用WinHex打开某一具体可执行文件最左边的位置标识。
-
VA和RVA
VA(Virtual Address,虚拟地址),指PE文件装载在内存中某一数据在虚拟内存中的绝对地址。32位Windows操作系统默认分配4G的虚拟内存,因此其VA值的范围为0x0000000~0xFFFFFFFF。
RVA(Relative Virtual Address,相对虚拟地址),指从某个基准位置开始的相对地址。
由于DLL(Dynamic Linked Library,动态链接库)在加载到进程的虚拟地址空间时,该位置可能已加载了其他的DLL,因此需要加载到没有使用的某一基地址上,然后使用RVA进行地址映射,将DLL装载到内存中。因此PE文件中的地址信息大多以RVA的形式存放。
VA和RVA满足以下关系:
V A = R V A + I m a g e B a s e VA=RVA+ImageBase VA=RVA+ImageBase -
RVA to RAW
当PE文件加载到内存中时,PE文件需要将每个节区准确的进行内存地址与文件偏移的映射(这里涉及到PE文件节区结构的相关知识,将在下一节中讲解)。该映射方法如下:
-
查找该数据的RVA所属节区
-
查找该节区头结构中的VirtualAddress(标识该节区起始的RVA)和PointerToRawData(文件中节起始的偏移量)
-
使用以下公式计算该数据的物理地址RAW
R A W − P o i n t e r T o R a w D a t a = R V A − V i r t u a l A d d r e s s RAW - PointerToRawData = RVA - VirtualAddress RAW−PointerToRawData=RVA−VirtualAddress
该公式其实表示的是一个数据在内存与磁盘中相对节区起始的偏移量一致。
例如:查看一个PE文件的EP(Entry Point,入口地址)的物理地址。
使用PEtools查看可执行文件的相关信息,直接给出了EP的物理地址为0x09A8
-
该地址使用上述的方法计算:
通过查看可选头结构找到EP的RVA地址为0x15A8,然后查看.text节的相关信息
.text节区的VirtualAddress为0x1000,PointerToRawData为0x0400,使用公式计算
RAW-0x0400=0x15A8-0x1000,得出RAW为0x09A8即为上述所给的物理地址。
3.PE文件的结构
(PE文件的结构在WinNT.h头文件中标识)
PE文件的整体结构如图所示:
3.1 DOS头
DOS头由两部分组成,分别为DOS Header和DOS Stub。DOS头的作用是兼容MS-DOS操作系统,使得DOS识别出这是有效的执行体,然后运行紧随之后的DOS Stub。
-
DOS Header
DOS Header由IMAGE_DOS_HEADER结构体构成,该结构体大小为0x40字节,该结构的具体内容如下
typedef unsignedlong DWORD; //大小为4B typedef unsignedchar BYTE; //大小为1B typedef unsignedshort WORD; //大小为2B
typedef struct _IMAGE_DOS_HEADER { WORD e_magic; // 魔术数字 WORD e_cblp; // 文件最后页的字节数 WORD e_cp; // 文件页数 WORD e_crlc; // 重定义元素个数 WORD e_cparhdr; // 头部尺寸,以段落为单位 WORD e_minalloc; // 所需的最小附加段 WORD e_maxalloc; // 所需的最大附加段 WORD e_ss; // 初始的SS值(相对偏移量) WORD e_sp; // 初始的SP值 WORD e_csum; // 校验和 WORD e_ip; // 初始的IP值 WORD e_cs; // 初始的CS值(相对偏移量) WORD e_lfarlc; // 重分配表文件地址 WORD e_ovno; // 覆盖号 WORD e_res[4]; // 保留字 WORD e_oemid; // OEM标识符(相对e_oeminfo) WORD e_oeminfo; // OEM信息 WORD e_res2[10]; // 保留字 LONG e_lfanew; // 新exe头部的文件地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
该结构中只有两个重要的成员变量,分别是e_magic(offset:0x0000)和e_lfanew(offset:0x003c)。
补充一个知识点Intel CPU采用小端模式,例如0x12345678在二进制文本中实际显示为78 56 34 12。
e_magic其值是一个常数为0x5A4D,其ASCII码为MZ,标识着该文件为可执行文件。
e_lfanew的值表示NT头在文件中的偏移地址raw
可以看到图中可执行文件的DOS头中e_lfanew的值为0x00e0,可以看到该地址确实为NT头部。
-
DOS Stub
DOS Stub实际上是个有效的EXE,由代码和数据混合而成,大小不固定,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示。在Windows OS下不会运行其中的代码,但在DOS环境中可以运行。多数情况下DOS Stub由汇编器/编译器自动生成。
可以看到该可执行文件的DOS stub的ASCII码中包含This program cannot be run in DOS mode这句提示