PE格式详解

PE(Portable Executable,可移植的执行体)是微软Win32环境可移植可执行文件(如exe、dll、vxd、sys和vdm等)的标准文件格式。PE格式衍生于早期建立在VAX(R)VMS(R)上的COFF(Common Object File Format,通用对象文件格式)文件格式。它是 Win32环境自身所带的执行体文件格式。"portable executable"(可移植的执行体)意味着此文件格式是跨win32平台的 : 即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不同的CPU上PE执行体必然得有一些改变所有 win32执行体 (除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode drivers)。因而研究PE文件格式给了我们洞悉Windows结构的良机。

PE文件使用的是一个平面地址空间,所有代码和数据都合并在一起,组成一个很大的结构。主要有:

简化PE文件格式

DOS MZ Header

DOS stub

PE Header

Section Table

Section 1

Section 2

...

Section n

 

备注:

平面存储器模式(flat memory model)或称线性内存模型(linear memory model),是指在计算机体系结构中一种组织存储器定址空间的方式。在这种模式下,应用程序看到的内存是一个单独的连续地址空间。CPU可以直接(且线性)寻址所有可利用的内存位置,无需诉诸任何内存分段或分页机制。

平面存储器模型的优点是,应用程序在访问他们的数据时候不需要切换不同的区块。大多数早期的处理器体系结构都是平面内存模型,如早期的8位处理器, Motorola 68K处理器等。然而,16位的Intel 808680286中,这种方式是不可行的,因为这些处理器支持访问超过64KB的数据。在32位的芯片,应用程序可以访问最大到4GB的数据。但这也变成现在大的数据库以及像是影片编辑应用程序的问题。

定址空间(英语:Address space),又称为地址空间,定义了某个范围内的离散地址,这些地址可能分别对应到某个网上节点、周边设备、扇区或是某个实体或是逻辑器件等等。在操作系统中,地址空间指的是某个特定进程,在存储器中所能够使用与控制的地址区块

Dos Mz head 和Dos stub合称Dos文件头PE文件的第一个字节起始于MS-DOS头部,被称作DOS映像头(IMAGE_DOS_HEADER)。紧随Dos stub的是PE文件头(PE Header),PE Header是PE相关结构NT映像头(IMAGE_NT_HEADERS)的简称,其中包含许多PE装载器用到的重要字段。

DOS头部分分为两个部分:

1、DOS MZ头

2、DOS块

1、DOS MZ头是一个IMAGE_DOS_HEADER结构体,其大小占64个字节

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number(魔术字,一直是MZ,是一个工程师名字的首字母)
    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;                    // 指向PE文件头的位置为中的PE文件头标志的地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;//同时定义了这个结构体和指向这个结构体类型的指针类型

该结构体是给16位的程序看的,现在的程序都是运行在x32 x64系统上!

但是有两个例外e_magic 和 e_lfanew,这两个成员必须存在 并且不可以改

e_magic:WORD类型,操作利用利用这个来进行识别

e_lfanew:LONG类型,指向PE文件头的位置为中的PE文件头标志的地址

在DOS MZ头之中,前两个字节 和 后四个字节不能修改(不是一定不能,如果有能力可以改,而PE结构中都需要进行修改,牵一发动全身),其他都可以任意的修改!

2、DOS块:是链接器来进行填写的,所以没有作用,可以进行任意的修改并且不会影响程序运行!

所有 PE文件(甚至32位的 DLLs) 必须以一个简单的 DOS MZ header 开始。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ header 之后的 DOS stub。DOS stub实际上是个有效的 EXE在不支持 PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串 "This program requires Windows" 或者程序员可根据自己的意图实现完整的 DOS代码。大多数情况下它是由汇编器/编译器自动生成。通常,它简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"。

紧接着 DOS stub 的是PE header。 PE header 是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。当我们更加深入研究PE文件格式后,将对这些重要域耳目能详。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header 中找到 PE header 的起始偏移量。因而跳过了 DOS stub 直接定位到真正的文件头 PE header。

PE文件的真正内容划分成块,称之为sections(节)每节是一块拥有共同属性的数据,比如代码/数据、读/写等。我们可以把PE文件想象成一逻辑磁盘,PE header 是磁盘的boot扇区,而sections就是各种文件,每种文件自然就有不同属性如只读、系统、隐藏、文档等等。 值得我们注意的是 ---- 节的划分是基于各组数据的共同属性: 而不是逻辑概念。重要的不是数据/代码是如何使用的,如果PE文件中的数据/代码拥有相同属性,它们就能被归入同一节中。不必关心节中类似于"data", "code"或其他的逻辑概念:如果数据和代码拥有相同属性,它们就可以被归入同一个节中。(译者注:节名称仅仅是个区别不同节的符号而已,类似"data", "code"的命名只为了便于识别,惟有节的属性设置决定了节的特性和功能)如果某块数据想赋为只读属性,就可以将该块数据放入置为只读的节中,当PE装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。

如果我们将PE文件格式视为一逻辑磁盘,PE header是boot扇区而sections是各种文件,但我们仍缺乏足够信息来定位磁盘上的不同文件,譬如,什么是PE文件格式中等价于目录的东东?别急,那就是 PE header 接下来的数组结构section table(节表)。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果PE文件里有5个节,那么此结构数组内就有5个成员。因此,我们便可以把节表视为逻辑磁盘中的根目录,每个数组成员等价于根目录中目录项

以上就是PE文件格式的物理分布,下面将总结一下装载一PE文件的主要步骤:

当PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header。 
PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。 
紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时赋上节表里指定的节属性。 
PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(引入表)逻辑部分。 

如何才能校验指定文件是否为一有效PE文件呢? 这个问题很难回答,完全取决于想要的精准程度。您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。下面我们就来实现前面的假设。

我们要验证的重要数据结构就是 PE header。从编程角度看,PE header 实际就是一个 IMAGE_NT_HEADERS 结构。定义如下:

IMAGE_NT_HEADERS STRUCT 
   Signature dd ? 
   FileHeader IMAGE_FILE_HEADER <> 
   OptionalHeader IMAGE_OPTIONAL_HEADER32 <> 
IMAGE_NT_HEADERS ENDS 

Signature 一dword类型,值为50h, 45h, 00h, 00h(PE\0\0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。
FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。
OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

IMAGE_DOS_SIGNATURE equ 5A4Dh 
IMAGE_OS2_SIGNATURE equ 454Eh 
IMAGE_OS2_SIGNATURE_LE equ 454Ch 
IMAGE_VXD_SIGNATURE equ 454Ch 
IMAGE_NT_SIGNATURE equ 4550h 

接下来的问题是: 如何定位 PE header? 答案很简单: DOS MZ header 已经包含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构IMAGE_DOS_HEADER 。查询windows.inc,我们知道 IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向 PE header 的文件偏移量。 

现在将所有步骤总结如下:

首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。 
一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。 
比较 PE header 的第一个字的值是否等于IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顺其自然~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值