3( ᇄଚঅဵྟඏ�
By: QQ1462433585
PE 文件格式入门教程 By: QQ1462433585
说明:
几年前学习 PE 文件格式的时候,看了几个比较流行的 PE 文件教程,发现这
些教程要么不够详细,要么太深奥,要么干脆就是纯英文的,所以就想把这几个
教程好好整理综合一下,就得到了这篇文章。
其中大部分内容都是从已有教程中直接复制过来的,并不是本人原创(只可
惜当时没有注明出处)。我所做的工作就是综合了这些中文教程,把英文教程里的
英文说明翻译了一下,调整好了格式,排好了版,有几张图片里面自己加上了说
明,还又画了几张图,比如输入表结构图、输出表机构图、重定位表结构图等。
因为当时也是初学 PE 格式,文中肯定也会有不少的错误,大家看的时候不要
盲信,对有疑问的地方多查查资料。
本人 QQ:1462433585
另外附上那时候看过的一些相关教程和文章的名字,文中的内容应该是从这
些教程中复制过来的。
《深度探索 Win32 可执行文件格式》 姜庆东译
《加密解密 技术内幕》PE 格式学习总结
《【NB5 免杀】PE 结构图解》
《PE 头结构 》
《PE 文件区段表详解—精通 PE 文件格式》
《体验修改 PE 文件的乐趣》
《学习笔记之 PE 文件结构导入表》
《手工构造 PE 文件》 - 看雪软件安全论坛
《手工构造典型 PE 文件》
《也谈 PE 重定位表》 - 看雪软件安全论坛
《再写手工打造可执行程序》 - 看雪软件安全论坛
《关于重定位表》 - CSDN 博客频道 Code Space
《PE 文件重定位表详解》 - CSDN 博客频道 matianlong_080 的专栏
《PE 重定位表学习》 - justnow0506 的日志 - 网易博客
《PE 重定位表学习手记》
目录
第一节 PE 文件格式一览..............................................................................................1
第二节 深入了解 PE 文件与文件头.............................................................................4
第三节 PE header(PE 头)............................................................................................7
第四节 File Header (文件头)...............................................................................9
4.1 文件头..............................................................................................................9
4.2 PE 头移动 ....................................................................................................... 11
4.3 修改 PE 头 ...................................................................................................... 11
第五节 Optional Header(可选头).......................................................................12
5.1 可选头............................................................................................................12
5.2 数据目录........................................................................................................14
5.3 相对虚拟地址 RVA .......................................................................................15
5.3 对齐值............................................................................................................17
第六节 Section Table(区段表)...........................................................................18
第七节 Import Table(输入表、引入表或者导入表).........................................21
7.1 输入表结构....................................................................................................21
7.2 输入表绑定....................................................................................................29
第八节 Export Table(输出表或叫引出表、导出表).........................................32
第九节 重定位表.........................................................................................................38
第十节 关于区段.........................................................................................................42
第十一节 几个小知识.................................................................................................44
一、PE 相关名词解释如下 .................................................................................44
二、文件中大量的空白 .......................................................................................44
三、高存高低存低原则 .......................................................................................45
四、DOS MZ Header 中的 MZ .........................................................................46
PE 文件格式入门教程 By: QQ1462433585
1
第一节 PE 文件格式一览
其实 PE 文件就是指 Windows 里的 DLL 与 EXE 文件,PE 的意思就是 Portable
Executable(可移植的执行体)。它是 Win32 环境自身所带的执行体文件格式。它
的一些特性继承自 Unix 的 Coff (common object file format)文件格式。"portable
executable"(可移植的执行体)意味着此文件格式是跨 win32 平台的,即使 Windows
运行在非 Intel 的 CPU 上,任何 win32 平台的 PE 装载器都能识别和使用该文件格
式。当然,移植到不同的 CPU 上 PE 执行体必然得有一些改变。所有 win32 执行
体 (除了 VxD 和 16 位的 Dll)都使用 PE 文件格式,包括 NT 的内核模式驱动程序
(kernel mode drivers)。我们现今组成 Windows 大家庭的主要成员就是 PE 文件
了,里面包括 EXE、DLL、OCX、SYS 等一切最有价值的文件都是 PE 文件格
式。因而研究 PE 文件格式给了我们洞悉 Windows 结构的良机。
PE 文件总的来说是由 dos 文件头、dos 加载模块、PE 头、区段表与区段 5 部
分构成。其实,如果在纯 Windows 环境下运行,dos 文件头、dos 加载模块根本是
用不上的,加上两个 dos 相关的结构完全是为了兼容性问题。
图一
为了方便观察与理解,我们可以通过观察图二大体了解 PE 文件的结构。
PE 文件格式入门教程 By: QQ1462433585
2
图二
如上图可知,整个程序就是以 dos 文件头“MZ”开始的,接下来就是 dos 加载
模块“This program cannot be run in dos mode”,几乎每个 Windows 程序的前面都是
这样一些信息!
下面有一个以字母“PE”为开头部分,这就是大名鼎鼎的 PE 头了,PE 头的标
准大小为 248 个字节,由图可见,里面有一个画了横线标记的问号与左面的十六
进制信息“E0”相对应,这便是 PE 头中可选头体积的描述标记,十六进制的“E0”
等于十进制的“224”,这是 PE 头中可选头的大小(一般都是 E0,即 224 字节),
再加上 PE 标识符(4 个字节)和文件头(20 个字节),正好是 248 个字节。
再往下就是以“.text”、“ .data”与“.rsrc”组成的区段表了。区段表也称节表,它
PE 文件格式入门教程 By: QQ1462433585
3
的作用就相当于一本
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
中的目录,你想看哪一章哪一节,只要按着目录标注的页
数去找就可以,PE 文件的区段表也是起同样的作用,但是区段表除此之外还包含
有各个区段的读写权限信息。而图中的“.text”、“ .data”与“.rsrc”则是这个程序里的
区段名称,也称为“节”。由此可见这个程序是由“.text”、“ .data”与“.rsrc”这 3 个区
段组成的,如下图。
图三
其实通过区段名称就可以大体猜出来这个节里包含什么信息,在整个程序中
能起到什么作用等等。
PE 文件格式入门教程 By: QQ1462433585
4
第二节 深入了解 PE 文件与文件头
下面让我们抽象的了解一下 PE 文件的构成:
dos MZ header <
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
大小为
112 字节。
PE header:这就是需要我们着重研究的 PE 头了,PE header 是 PE 相关结构
IMAGE_NT_HEADERS 的简称,其中包含了许多 PE 装载器用到的重要域。执行
体在支持 PE 文件结构的操作系统中执行时,PE 装载器将从 DOS MZ header 中找
到 PE header 的起始偏移量,因而跳过了 DOS stub 直接定位到真正的文件头 PE
header。PE header 是一段以关键字“PE”为开头的数据,默认大小 248 字节。我们
沿着 PE 头开始标志符“PE”往下找,在关键字“PE”之后的第 19、20 个字节处的数
据的倒序就是代表 PE 头中可选头的大小,一般为 224。
sections:PE 文件的真正内容划分成块,称之为 sections(区段或节)。每节
是一块拥有共同属性的数据,比如代码/数据、读/写等。我们可以把 PE 文件想象
成一逻辑磁盘,PE header 是磁盘的 boot 扇区,而 sections 就是各种文件,每种文
件自然就有不同属性如只读、系统、隐藏、文档等等。 值得我们注意的是:节的
划分是基于各组数据的共同属性,而不是逻辑概念。重要的不是数据/代码是如何
使用的,如果 PE 文件中的数据/代码拥有相同属性,它们就能被归入同一节中。
之所以这样做是因为,Win32 中可以对每个内存页分别指定可执行、可读写等属
性,在 PE 文件中将同样属性的数据分类放在一起是为了统一描述这些数据装入
内存后的页面属性。所以不必关心节中类似于"data", "code"或其他的逻辑概念: 如
果数据和代码拥有相同属性,它们就可以被归入同一个节中。
注意:节名称仅仅是个区别不同节的符号而已,类似"data", "code"的命名只
为了便于识别,惟有节的属性设置决定了节的特性和功能。如果某块数据想赋为
只读属性,就可以将该块数据放入属性为只读的节中,当 PE 装载器映射节内容
时,它会检查相关节属性并置对应内存块为指定属性。
section table:如果我们将 PE 文件格式视为一逻辑磁盘,PE header 是 boot 扇
区而 sections 是各种文件,但我们仍缺乏足够信息来定位磁盘上的不同文件,譬
如,什么是 PE 文件格式中等价于目录的东东?别急,那就是 PE header 接下来
的数组结构 section table(区段表或者叫区段表)。 每个结构包含对应节的属性、
文件偏移量、虚拟偏移量等。如果 PE 文件里有 5 个节,那么此结构数组内就有 5
个成员。因此,我们便可以把区段表视为逻辑磁盘中的根目录,每个数组成员等
价于根目录中的目录项。
以上就是 PE 文件格式的物理分布,下面将总结一下装载 PE 文件的主要步骤:
1、当 PE 文件被执行,PE 装载器检查 DOS MZ header 里的 PE header 偏移
量。如果找到,则跳转到 PE header。
PE 文件格式入门教程 By: QQ1462433585
6
2、PE 装载器检查 PE header 的有效性并获取信息(比如 OEP)。如果有效,
就跳转到 PE header 的尾部。
3、紧跟 PE header 的是区段表。PE 装载器读取其中的节信息,并采用文件
映射方法将这些节映射到内存,一同赋上区段表里指定的节属性。
4、PE 文件映射入内存后,PE 装载器将处理 PE 文件中类似 import table(输
入表)逻辑部分。
5、跳转到 OEP 开始执行程序。
PE 文件格式入门教程 By: QQ1462433585
7
第三节 PE header(PE 头)
如何才能校验指定文件是否为一有效 PE 文件呢? 这个问题很难回答,完全
取决于想要的精准程度。你可以检验 PE 文件格式里的各个数据结构,或者仅校
验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,
只要一些关键数据结构有效,我们就认为是有效的 PE 文件了。下面我们就来实
现前面的假设。
我们要验证的重要数据结构就是 PE header。从编程角度看,PE header 实际
就是一个 IMAGE_NT_HEADERS 结构。定义如下:
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature; //PE 标识符,共 4 个字节,此字段被设置为 0X00004550,ASCII 码
是“PE00”
IMAGE_FILE_HEADER FileHeader; //文件头结构,共 20 个字节
IMAGE_OPTIONAL_HEADER OptionalHeader; //可选头结构,共 224 个字节
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS,PeHeader;
<1>Signature:PE 标识符,DWORD 类型,4 个字节,值为 50h,45h,00h,
00h(PE\0\0)。如果 Signature 域值等于"PE\0\0",那么就是有效的 PE 文件。
<2>FileHeader:即文件头,20 个字节。该结构域包含了关于 PE 文件物理分
布的信息,比如节数目、文件执行机器等。
<3>OptionalHeader:即可选头,一般为 224 个字节(可以通过更改文件头中
表示大小的变量来改变它的大小)。该结构域包含了关于 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?
答案
八年级地理上册填图题岩土工程勘察试题省略号的作用及举例应急救援安全知识车间5s试题及答案
很简单: DOS MZ header 已经包
含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构 IMAGE_
PE 文件格式入门教程 By: QQ1462433585
8
DOS_HEADER 。查询 windows.inc,我们知道 IMAGE_DOS_HEADER 结构的
e_lfanew 成员就是指向 PE header 的文件偏移量。
现在将所有步骤总结如下:
1) 首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则
DOS MZ header 有效。
2) 一旦证明文件的 DOS header 有效后,就可用 e_lfanew 来定位 PE header 了。
3) 比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。如果前后
两个值都匹配,那我们就认为该文件是一个有效的 PE 文件。
PE 文件格式入门教程 By: QQ1462433585
9
第四节 File Header (文件头)
4.1 文件头
File Header(IMAGE_FILE_HEADER 结构)包含在 PE Header(IMAGE_NT_
HEADERS)里面,其结构定义:
typedef struct _IMAGE_FILE_HEADER
{
WORD Machine; //该文件运行所要求的 CPU,Intel 80386 以上处理器必须为 4C 01
WORD NumberOfSections; //文件中区段的数目
DWORD TimeDateStamp; //文件创建日期和时间,免杀时可随便改
DWORD PointerToSymbolTable;// 指向符号表,用于调试,免杀时可随便改
DWORD NumberOfSymbols; // 符号表中的符号数量,用于调试,免杀时可随便改
WORD SizeOfOptionalHeader; //可选头的大小
WORD Characteristics; //文件的执行属性,该字段很重要,EXE 文件此值一般为 010Fh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_FILE_HEADER 结构成员含义:
结构成员 含义
Machine
该文件运行所要求的 CPU。对于 Intel 平台,该值是 14Ch,对于 MIPS
R3000 平台为 162h,对于 MIPS R4000 平台为 166h,对于 Alpha AXP
平台为 184h,对于 Power PC 平台为 1F0h。更改这个值会使程序不能
正确执行。看来除了禁止程序执行之外,本域对我们来说用处不大。
NumberOfSections
这是一个很重要的字段,用来确定文件中区段的数目。如果我们要在
文件中增加或删除一个区段,就需要修改这个值。
TimeDateStamp
文件创建日期和时间。其格式是自从 1969年 12 月 31 日 4:00 P.M. 之
后的总秒数。据计算,0xFFFFFFFFh 是 136.19251950152207001522
070015221 年。将这个值翻译为易读的字符串的方法是用_ctime 函数
(它是时区敏感型的),另一个对此字段计算有用的函数是 gmtime。
PointerToSymbolTable 用于调试。
NumberOfSymbols 用于调试。
SizeOfOptionalHeader 指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值。
Characteristics
此值为文件的执行属性。它影响系统对文件的装入方式,比如,位 13
为 1 时,表示这是一个 DLL 文件,系统将使用调用 dll 入口函数的方
式调用文件入口,如果为 0,就是普通的可执行文件,系统直接跳到
入口处执行。EXE 文件此值一般为 010Fh,DLL 文件此值一般为
PE 文件格式入门教程 By: QQ1462433585
10
210Eh。各位的含义见下图
简言之,只有三个域对我们有一些用 : Machine, NumberOfSections 和
Characteristics。通常不会改变 Machine 和 Characteristics 的值,但如果要遍历区
段表就得使用 NumberOfSections。为了更好阐述 NumberOfSections 的用处,这
里简要介绍一下区段表。
区段表是一个 IMAGE_SECTION_HEADER 结构的数组,每个结构包含一个
节的信息。该数组中成员的个数由 File Header (IMAGE_FILE_HEADER) 结构中
NumberOfSections 域的域值来定。区段表中的成员是 IMAGE_SECTION_
HEADER 结构,IMAGE_SECTION_ HEADER 结构的长度固定,长 40 个字节。
整个 Section Table 的长度不固定,等于 NumberOf Sections*sizeof(IMAGE_
SECTION_HEADER),即 NumberOf Sections*40 字节。因此如果程序有 3 个节,
这个数组就有 3 个成员。 我们需要 NumberOfSections 值来了解该数组中到底有
几个成员。也许你会想检测结构中的全 0 成员起到同样效果。Windows 确实采用
了这种方法。为了证明这一点,可以增加 NumberOfSections 的值,Windows 仍然
可以正常执行文件。据我们的观察,Windows 读取 NumberOfSections 的值然后
检查区段表里的每个结构,如果找到一个全 0 结构就结束搜索,否则一直处理完
NumberOfSections 指定数目的结构。
为什么我们不能忽略 NumberOfSections 的值? 有 2 个原因:
PE 文件格式入门教程 By: QQ1462433585
11
1)PE 说明中没有指定区段表必须以全 0 结构结束,这样可能会出现最后的
那个数组成员与节 1 之间没有全 0 结构的情形。
2)如果此 PE 文件进行了 bound imports(即输入表绑定),那么在区段表最
后一个成员之后紧接着的不是全 0 结构,而是进行输入表绑定时所使用的 dll 文件
的版本信息。(输入表绑定见 7.2 节)
因此你仍然需要 NumberOfSections。
4.2 PE 头移动
因为在 windows 中 DOS Stub 部分根本没用,所以我们可以讲 PE 头往上移动,
占用 DOS Stub 的位置,PE 头原先的位置空出来的地方都用 00 填充。这时候要修
改两个位置的数据:
1、表示 PE 头位置的偏移量为 3C、3D、3E、3F 四个字节处的数据为 PE 头
现在的新位置;
2、关键字“PE”之后的第 19、20 个字节处的数据,也就是文件头结构中的
SizeOfOptionalHeader 域的值,因为我们把 PE 头往上移动了,系统在可选头之后
紧挨着可选头的地方找不到区段表就会报错无法运行,因此我们要把刚才移动之
后在可选头和区段表之间空出的区域包含到可选头中,把新的可选头的大小写到
SizeOfOptionalHeader 中(也就是关键字“PE”之后的第 19、20 个字节处)。
PE 头甚至可以移动到 DOS 文件头的空间里,关键是要让 PE 头中一个没用的
DWORD 字段正好占用掉偏移量为 3C、3D、3E、3F 四个字节,然后把这里写上
PE 头新的位置,更改 SizeOfOptionalHeader 域的值就可以了。
如果不想修改可选头的大小可以把区段表一块往上移动,区段表空出来的地
方用 00 填充。
移动 PE 头之后的程序用 OD 载入会提示不是有效的 WIN32 程序,点击确定
之后会来到系统领空,这可以作为简单的反调试。此时打开内存窗口,在程序的
PE 头上下断,F9 运行,又断在系统领空,Alt+F9,程序就运行起来了。
4.3 修改 PE 头
通过修改 PE 头可以使 OD 载入时提示错误,并且在内存窗口中只能看到 PE
头,有两种方法:
方法一、手动移动 PE 头
方法二、修改数据目录的项数。可以手动的修改,但是要改动的地方比较多,
较简单的方法是用 LordPE 的 PE 编辑器载入程序,其中的 RVA 数及大小就是指
的数据目录的项数,因为数据目录的项数自 NT 系统发布以来一直是 16,用十六
进制表示就是 10H,所以一般这一项总为 10,可以把这个数改大,如果改小了就
会导致程序无法运行。
PE 文件格式入门教程 By: QQ1462433585
12
第五节 Optional Header(可选头)
5.1 可选头
Optional Header 结构是 IMAGE_NT_HEADERS 中的最后成员。包含了 PE
文件的逻辑分布信息。该结构共有 31 个域,一些是很关键,另一些不太常用。其
结构定义:
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
WORD Magic; //幻数,用于描述次 PE 文件的映像类型,ROM 映像为 0107h,普通可执行
映像 010Bh;PE32+则是 020Bh。
BYTE MajorLinkerVersion; //链接程序的主版本号,免杀时可随便改
BYTE MinorLinkerVersion; //链接程序的次版本号,免杀时可随便改
DWORD SizeOfCode; //★所有含代码的区段的总大小,改变此值不影响运行,免杀可随便改
DWORD SizeOfInitializedData; //所有含已初始化数据的区段的总大小,免杀时可随便改
DWORD SizeOfUninitializedData;// 所有含未初始化数据的区段的总大小,免杀时可随便改
DWORD AddressOfEntryPoint; //★程序开始执行的入口地址,这是一个 RVA(相对虚拟地址)
DWORD BaseOfCode; //代码段的起始 RVA,典型的代码段名有“.text”、“.code”、
“AUTO”等,这个值不重要,因为在区段表中还有起相同作
用的值,免杀时可随便改
DWORD BaseOfData; //数据段的起始 RVA,典型的数据节名有“.data”、“.idata”、
“DATA”等,这个值不重要,因为在区段表中还有起相同作
用的值,免杀时可随便改
//
// NT additional fields.
DWORD ImageBase; //★可执行文件默认装入的基地址,一般为 00400000,这个值不要更改
DWORD SectionAlignment; //★内存中块的对齐值(默认的块对齐值为 1000H,即 CPU 的页
尺寸 4KB 字节)
DWORD FileAlignment; // ★文件中块的对齐值(默认值为 200H 或 1000H 字节,为了保证
块总是从磁盘的扇区开始的,这个值必须是 2 的幂,并且最小
为 200h)
WORD MajorOperatingSystemVersion;//要求操作系统的最低版本号的主版本号,免杀时可随
便改
WORD MinorOperatingSystemVersion;//要求操作系统的最低版本号的次版本号,免杀时可随
便改
WORD MajorImageVersion; //该可执行文件的主版本号,免杀时可随便改
PE 文件格式入门教程 By: QQ1462433585
13
WORD MinorImageVersion; //该可执行文件的次版本号,免杀时可随便改
WORD MajorSubsystemVersion; //★要求最低之子系统版本的主版本号,值为 0004h
WORD MinorSubsystemVersion;//★要求最低之子系统版本的次版本号,一般情况下都为 0
DWORD Win32VersionValue; //保留字,免杀时可随便改
DWORD SizeOfImage; //★映像装入内存后的总尺寸,它是所有头和节经过节对齐处
理后的大小
DWORD SizeOfHeaders; //★所有头部和区段表的大小(包括对齐填入的 00),等于文件
尺寸减去文件中所有节的尺寸,也等于第一个区段的文件
偏移量
DWORD CheckSum; //CRC 检验和,只在 sys 文件中有用,免杀时可随便改
WORD Subsystem; //★程序使用的用户接口子系统,0 表示未知,1 表示不需要子系统
(如驱动程序),2 表示图形接口子系统(GUI),3 表示字符子系
统(CUI),5 表示 OS/2 字符子系统,7 表示 POSIX 字符子系统,
8 为保留,9 表示 Windows CE 图形界面,WIN32 程序就是 2
WORD DllCharacteristics; //DLLmain 函数何时被调用,默认为 0,对 EXE 文件免杀时可
随便改
DWORD SizeOfStackReserve; //初始化时堆栈大小,免杀时可随便改
DWORD SizeOfStackCommit; //初始化时实际提交的堆栈大小,免杀时可随便改
DWORD SizeOfHeapReserve; //初始化时保留的堆大小,免杀时可随便改
DWORD SizeOfHeapCommit; //初始化时实际提交的堆大小,免杀时可随便改
DWORD LoaderFlags; //与调试有关,默认为 0,免杀时可随便改
DWORD NumberOfRvaAndSizes;//★数据目录的项数,此字段自 NT 系统发布以来一直是 16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//★数据目录表
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
IMAGE_OPTIONAL_HEADER 结构几个比较重要的成员含义:
成员 含义
SizeOfCode
所有 code section 的总和大小。大部分程序只有一个 code section,所以
此域通常就是 .text section 的大小。
AddressOfEntryPoint
PE 装载器准备运行的 PE 文件的第一个指令的 RVA。通常会落在 .text
section.此域适用于 exe 或 dll。OEP
ImageBase
PE 文件的优先装载地址。比如,如果该值是 400000h,PE 装载器将尝试
把文件装到虚拟地址空间的 400000h 处。字眼"优先"表示若该地址区域
已被其他模块占用,那 PE 装载器会选用其他空闲地址。
SectionAlignment
内存中节对齐的粒度。例如,如果该值是 4096 (1000h),那么每节的起
始地址必须是 4096 的倍数。若第一节从 401000h 开始且大小是 10 个字
PE 文件格式入门教程 By: QQ1462433585
14
节,则下一节必定从 402000h 开始,即使 401000h 和 402000h 之间还有
很多空间没被使用。
FileAlignment
文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址
必须是 512 的倍数。若第一节从文件偏移量 200h 开始且大小是 10 个字
节,则下一节必定位于偏移量 400h: 即使偏移量 512 和 1024 之间还有
很多空间没被使用/定义。
MajorSubsystemVersi
on
MinorSubsystemVersi
on
win32 子系统版本。若 PE 文件是专门为 Win32 设计的,该子系统版本必
定是 4.0 否则对话框不会有 3 维立体感。MajorSubsystemVersion 的值是
0004H
SizeOfImage
内存中整个 PE 映像体的尺寸。它是所有头和节经过节对齐处理后的大
小。
SizeOfHeaders
所有头+区段表的大小(包括对齐填入的 00),也就等于文件尺寸减去文
件中所有节的尺寸,也等于 PE 文件第一个区段的文件偏移量。
Subsystem NT 用来识别 PE 文件属于哪个子系统。 对于大多数 Win32 程序,只有
两类值: Windows GUI 和 Windows CUI (控制台)。
DataDirectory IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结
构的 RVA,比如引入地址表等。
5.2 数据目录
所谓数据目录就是指可选头中的 data directory 字段,这个字段可以说是可选
头中最重要的字段之一,它是由 16 个相同的 IMAGE_DATA_DIRECTORY 结构组
成的结构数组。虽然 PE 文件中的数据是按照装入内存后的页属性归类而被放在
不同的区段中的,但是这些处于各个区段中的数据按照用途可以分为输入表、输
出表、资源、重定位表等数据块,这 16 个 IMAGE_DATA_DIRECTORY 结构就是
用来定位不同用途的数据块的。我们知道,区段表可以看作是 PE 文件各区段的
根目录,同样的, data directory 是存储在这些区段里的逻辑元素的根目录。
明确点说,输入表、输出表等重要数据结构是按照它们各自不同的属性放置
在对应属性的区段里的,它们的位置和尺寸信息就保存在 data directory (数据
目录)中。 数据目录中每个成员都包含了一个重要数据结构的信息,如表 5.1 所
示。
表 5.1
成员的索引号 Info inside
0 Export symbols(导出表)
1 Import symbols(导入表)
2 Resources(资源目录)
PE 文件格式入门教程 By: QQ1462433585
15
3 Exception(异常目录)
4 Security(安全目录)
5 Base relocation(重定位基本表)
6 Debug(调试目录)
7 Copyright string(版权信息)
8 Unknown(机器值)
9 Thread local storage (TLS)(TLS 目录)
10 Load configuration(载入配值目录)
11 Bound Import(绑定输入)
12
Import Address Table(IAT 表,但不是必需
的,修改这个值不影响 IAT 表)
13 Delay Import
14 COM descriptor
15 未使用
上面那些金色显示的是常用的。
data directory 的每个成员都是一个 IMAGE_DATA_DIRECTORY 结构,其定
义如下所示:
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress; //4 个字节,该数据结构的相对虚拟地址(RVA)
DWORD isize; //4 个字节,该数据结构的大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
VirtualAddress 实际上是该数据结构的相对虚拟地址(RVA)。比如,如果该结
构是关于 Import symbols(输入表 )的,该域就包含指向 IMAGE_IMPORT_
DESCRIPTOR 数组(即输入表)的 RVA。
isize 含有 VirtualAddress 所指向数据结构的字节数。
在 PE 文件中寻找特定的数据时就是从这些 IMAGE_DATA_DIRECTORY 结
构开始的,比如要存取资源,那么必须要从第 3 个 IMAGE_DATA_DIRECTORY
结构(索引为 2)中得到资源数据块的位置和大小。同理,如果要查看 PE 文件导
入 了 哪 些 DLL 文 件 的 哪 些 API 函 数 , 那 就 必 须 首 先 从 第 2 个
IMAGE_DATA_DIRECTORY 结构得到输入表的位置和大小。
5.3 相对虚拟地址 RVA
RVA 是相对虚拟地址(Relative Virtual Address)的缩写,顾名思义,它是一
PE 文件格式入门教程 By: QQ1462433585
16
个“相对”地址,PE 文件的各种数据结构中涉及到地址的字段大部分都是以 RVA
表示的。准确地说,RVA 就是当 PE 文件被装载到内存中后,某个数据的位置相
对于 MZ 头的偏移量。
文件映射到内存:
在可执行程序运行之前,PE 加载器将把 PE 文件加载到进程空间的内存中去,
并且初始化每个区段(比如对齐、赋上属性等)。那么加载到内存中的哪个地址去
呢?这将由 IMAGE_OPTIONAL_HEADER32 结构中的 ImageBase 的值指出加载
的起始地址(又叫基址)。这个值通常是 00400000h,那么 PE 文件的首地址“00000”
就被映射到内存地址“00400000”处,而相对于文件开头偏移 10 个字节的地址为
“00010”,被映射到内存后的偏移也应该是 10 个字节,映射后的地址应该为
“00400010”。如果某个区段中的某个数据载入内存后被装入到了 0040xxxxh 处,
那么这个数据的 RVA 就是(0040xxxxh- 00400000h)=xxxxh,反过来说,将 RVA
的值加上文件被装载的基地址,就可以找到数据在内存中的实际地址。
PE 文件中出现 RVA 的概念是因为 PE 的内存映像和磁盘文件映像是不同的,
同一数据相对于文件头的偏移量在内存中和在磁盘文件中可能是不同的,为了提
高效率,PE 文件头中使用的都是内存映像中的偏移量,也就是 RVA。
RVA 是指内存中,不是指文件中。是指相对于载入点的偏移而不是一个内存
地址,只有 RVA 加上载入点的地址,才是一个实际的内存地址。
RVA 仅仅是对于身处于区段中的数据而言的,对于文件头和节表来说无所谓
RVA 和文件偏移,因为它们在被映射到内存中后不管是大小还是偏移都不会有任
何改变。
为什么 PE 文件格式要用到 RVA 呢? 这是为了减少 PE 装载器的负担。因为
每个模块都有可能被重载到任何虚拟地址空间,如果让 PE 装载器修正每个重定
位项,这肯定是个梦魇。相反,如果所有重定位项都使用 RVA,那么 PE 装载器
就不必操心那些东西了: 它只要将整个模块重定位到新的起始 VA。这就象相对路
径和绝对路径的概念: RVA 类似相对路径,VA 就像绝对路径。
在 PE 文件中大多数地址多是 RVAs 而 RVAs 只有当 PE 文件被 PE 装载器装
入内存后才有意义。如果直接将文件映射到内存而不是通过 PE 装载器载入,则
不能直接使用那些 RVAs。必须先将那些 RVAs 转换成文件偏移量。
代码的文件虚拟偏移地址和文件物理偏移地址的计算公式如下:
假如代码的虚拟地址 VA = 00401325,ImageBase = 00400000 (基地址),
某 代 码 所 在 区 段 的 RVA : VOffset=00001000 , 区 段 的 物 理 地 址
ROffset=00000400
PE 文件格式入门教程 By: QQ1462433585
17
则:
代码的物理偏移地址 = 代码的虚拟 RVA -本区段的 RVA+本区段的物理偏移
=(代码的虚拟 VA-ImageBase)- VOffset + ROffset
= (00401325 - 00400000)- 00001000 + 00000400 = 725
代码的虚拟 RVA = 代码的物理偏移 - 本区段的物理偏移 + 本区段的 RVA。
5.3 对齐值
文件对齐值分在内存中的对齐值和在文件中的对齐值。PE 文件中的节无论是
在磁盘中或内存映像中都是按照一定单位进行对齐的。
那么对齐的具体情况是什么样的呢?
假如在内存中的对齐单位是 1000h,即 4 Kb,装入内存时的基址是 00400000h,
那么 Windows 将 PE 文件装入内存时先把内存中的空间从地址 00400000h 开始,
每 4KB 作为一页,然后把 PE 文件的 D