PE文件结构
由于一些原因,需要学习安全方面的一些知识,所以学习了PE文件的结构,这篇随笔是学习的一些心得,作为复习。希望对各位有所帮助,如果行文中有什么错误,还请各位指正。
Microsoft Windows Portable Executable是微软可迁移可执行软件的全称,简称PE文件。对PE文件结构的学习虽然并不是程序员必须学的,但是学习对PE文件结构的学习过程中能够提升我们的个人编程能力,从中学到很多东西。而对PE文件的详细学习对我们在安全、病毒检测的工作有很大的帮助。
PE 文件结构
PE文件的结构如下表所示
+-------------------+
| DOS MZ header |
+-------------------+
| DOS-stub |
+-------------------+
| file-header |
+-------------------+
| optional header |
|- - - - - - - - - -|
| |
| data directories |
| |
+-------------------+
| |
| section headers |
| |
+-------------------+
| |
| section 1 |
| |
+-------------------+
| |
| section 2 |
| |
+-------------------+
| |
| ... |
| |
+-------------------+
| |
| section n |
| |
+-------------------+
DOS-header & DOS-stub
所有的PE文件都是由一个简单DOS MZ header
头开始,后面紧跟着一个DOS-stub
。这么做的目的是为了向后兼容性。当全世界在从DOS到Win32转变时
为了DOS的向后兼容性,引入了这两个区域在PE文件的开头。DOS MZ header
和DOS-stub
是用来兼容DOS运行环境的。当DOS看到这个头时,便知道这是一个可执行文件,接着DOS便会紧接着运行DOS-stub
的内容,DOS-stub
实际上就是一个EXE文件。在那个时候,有一些程序将DOS版本和Win32版本整合到一个PE文件中,具体的做法就是在DOS-stub
中存放DOS版本的程序。现在的DOS-stub
中的内容仅仅只是一句话 "This program cannot run in DOS mode",现在DOS-stub
这个块的内容已经很少被关注。为并且有一些工作试图将DOS-stub
块删除。下图显示的是分析rufus程序的DOS-header
和DOS-stub
截图
其中在DOS-header
的最后一个字给出了PE-header的偏移量(0x00000080)。根据这个就能够找到PE-header。即PE文件中最重要的部分。
PE-header
PE-header是PE文件中最重要的内容,PE-header中包含了PE加载程序需要的许多字段。PE-header实际上是一个名为IMAGE_NT_HEADERS
的结构体。结构体的定义如下:
IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
字段:
- Signature:PE-header的签名是一个魔数(0x00004550),转换成文本就是"PE"。该成员是PE签名,因此我们将使用它来验证给定文件是否是有效的PE文件。
- FileHeader:FileHeader包含的是PE的物理布局信息(physical layout)例如Section的数量,PE文件所针对的机器等。
- OptionalHeader:虽然说这个头部是可选的(Optional),但是在所有的PE文件中都有出现。
微软定义了几个常量用来作为MZ头和PE头的签名,其中IMAGE_DOS_SIGNATURE是DOS头签名,IMAGE_NT_SIGNATURE是PE头签名
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文件是否是有效的,只需要知道PE-header前两个字节是不是等于0x4550就能够知道是不是合法的PE文件。验证一个文件是不是合法的PE文件,步骤如下所示:
- 比较一个给定的文件是否有MZ头。
- 如果包含了MZ头,使用MZ头的
e_lfanew
成员,查找PE-header的位置,e_lfanew
在0x3C地址上。 - 将PE-header的第一第二个字节和
IMAGE_NT_SIGNATURE
进行对比,如果相等,则证明是有效的PE文件。
FileHeader
FileHeader是PE-header 中的一个结构体,虽然我们最感兴趣的部分在于OptionalHeader中,但是FileHeader也包含了一些重要的字段,对FileHeader的定义如下
IMAGE_FILE_HEADER STRUCT
Machine WORD ?
NumberOfSections WORD ?
TimeDateStamp dd ?
PointerToSymbolTable dd ?
NumberOfSymbols dd ?
SizeOfOptionalHeader WORD ?
Characteristics WORD ?
IMAGE_FILE_HEADER ENDS
下表将表示各个字段的意思
字段名 | 意义 |
---|---|
Machine | PE文件所针对的CPU平台。 |
NumberOfSections | PE文件中Section的数量 |
TimeDateStamp | PE文件创建的时间,基本没什么用 |
PointerToSymbolTable | DEBUG 用途 |
NumberOfSymbols | DEBUG 用途 |
SizeOfOptionalHeader | OptionalHeader 的大小 |
Characteristics | 包含文件的标识,例如这个文件是dll还是exe |
Optional Header
虽说Optional Header
名称中含有Optional字样,但是却是PE header中最大、最重要的一个部分。Optional Header
是IMAGE_NT_HEADERS
结构体中的最后一个元素,包含了PE文件的逻辑内容。在OptionalHeader
中有31个字段,其中某些字段对我们十分有用。在Optional Header
中存储的位置,都是RVA。关于RVA的详细内容,参考这篇笔记
下面列举在Optional Header中对我们比较有用的字段:
字段名 | 意义 |
---|---|
AddressOfEntryPoint | 这个字段存储的是PE文件加载到内存后第一个执行命令的RVA。如果想要从开始就改变PE文件的执行顺序,就可以修改这一个字段。 |
ImageBase | PE文件加载的首选的RVA。假如这个值是0x400000h那么PE-loader将在该虚拟地址没有被占用的情况下,将映像文件加载到0x400000h地址上。 |
SizeOfImage | PE映像在内存中的大小。 |
还有其他的字段信息,可以查看这个教程 |
Section Table
Section Table是一个跟在PE header后的数组。该数组的长度由IMAGE_FILE_HEADER
中的NumberOfSection
决定。Section Table包含了这些有用的信息
字段 | 意义 |
---|---|
Name1 | 实际上这个字段应该称为"name",但是由于"name"是MSAM关键字,所以这个字段就成为Name1。这个字段中包含的是Section的名称,并且最大的长度是8个字节。可以使用任何名称,甚至将这个字段留为空。 |
VirtualAddress | Section的RVA,所以如果这个字段的值是1000h,而PE文件中FileHeader中ImageBase字段是400000h的话,这个Section将会加载到内存为401000h的地址上。 |
SizeOfRawData | 见名知意 |
PointerToRawData | 见名知意 |
Characteristics | 包含了一些标志,这些标志说明了Section中是否包含可执行代码/初始化数据/未初始化数据,以及可读性。 |
Import Table
Import Table内容比较多,重新开一个笔记来记录