1. 介绍
PE文件是Window操作系统下使用的可执行文件格式.PE文件是指32位的可执行文件,也称为PE32.64位的可执行文件称为PE+或PE32+,是PE(pe32)文件的一种扩展形式.
2. PE文件格式
种类 | 主扩展名 |
---|---|
可执行系列 | EXE / SCR |
库系列 | DLL / OCX / CPL / DRV |
驱动程序系列 | SYS / VXD |
对象文件序列 | OBJ |
OBJ(对象)文件之外的所有文件都是可执行文件,DLL / SYS 文件等虽然不能直接在Shell(Explorer.exe)中运行,但可以使用其它方法执行.
下面以打开notepad.exe
为例,notepad.exe文件运行需要的所有信息都存储在PE头中,如果加载到内存,从何处开始运行,运行中需要什么DLL,有哪些堆栈内存等,大量信息以结构体形式存储在PE头中, 学习PE文件格式就是学习PE头中的结构体.
2.1 基本结构
从 DOS 头(Dos header)到节区头(Section header)是PE头部分,其下的节区合称PE体.文件中使用偏移(offset),内存中使用VA(Virtual Address ,虚拟地址)来表示位置.文件加载到内存时,情况就会发生变化(节区的大小,位置等).文件的内容一般可分为代码(.text) / 数据(.data) / 资源(.rsrc)节,分别保存.
各节区头定义了各节区在文件或内存中的大小,位置,属性等.
PE头与各节区的尾部存在一个区域,称为NULL填充(NULL padding).在计算机中,为了提高处理文件,内存,网络包的效率.文件 / 内存中各节区的起始位置应该在各个文件 / 内存 最小单位的倍数位置上,空白区域将用NULL填充.
2.2 VA & RVA
VA指的是进程虚拟内存的绝对地址,RVA(Relative Virtual Address,相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址.VA 与 RVA 满足下面的换算关系.RVA + ImageBase = VA
PE头内部信息大多以RVA形式存在.原因在于,PE文件(主要是DLL)加载到进程虚拟内存的特定位置时,该位置可能已经加载了其他PE文件(DLL).此时必须通过重定位(Relocation)将其加载到其他空白的位置,若PE头信息使用的是VA,则无法正常访问,因此使用RVA来定位信息,即是发生了重定位,只要相对于基准位置的相对地址没有变化,就能正常访问到指定信息,不会出现任何问题.
3. PE头
3.1 DOS 头
在PE头的最前面添加了一个IMAGE_DOS_HEADER结构体,用来扩展已经有的DOS EXE头.
|
|
IMAGE_DOS_HEADER 结构体的大小为40个字节,从该结构体必须知道两个重要成员,第一个字段e_magic与最后一个字段e_lfanew.
e_magic : DOS签名(signature, 4D5A=>ASCII值”MZ”)
e_lfanew : 指示NT头的偏移(根据不同文件拥有可变值).
所有PE文件在开始部分(e_magic)都有DOS签名(“MZ”).e_lfanew值指向NT头所在位置(NT头的名称在IMAGE_DOS_HEADERS).
使用 Hex Editor 打开 notepad.exe ,查看IMAGE_DOS_HEADERS结构体.
根据 PE 规范,文件开头的2个字节为4D5A,e_lfanew值为000000E0,假若修改了这些值,可以发现程序无法正常运行,因为根据PE的规范它已经不是PE文件了.
3.2 DOS 存根
DOS存根(stub)在DOS头下方,是个可选项,且大小不固定(即使没有DOS存根,文件也能正常运行).DOS存根由代码与数据混合而成.
文件偏移40~4D区域为16位的汇编指令.
打开命令窗口,输入debug c:\windows\notepad.exe
3.3 NT 头
|
|
IMAGE_DOS_HEADERS结构体由三个成员组成, 第一个成员的签名为(Signature)结构体,其值为50450000h(“PE”00).另外两个成员分别为文件头(File Header)与可选头(Optional Header)结构体.
3.4 NT头 : 文件头
|
|
IMAGE_DOS_HEADERS结构体中有以下四种重要成员(若设置不对,不能正常运行.)
- Machine
每个CPU都拥有唯一的Machine码,兼容32位 Intel x86 芯片的Machine码为14C. NumberOfSections
NumberOfSections用来指出文件中存在的节区数量.该值一定要大于0,且当定义的节区数量与实际节区不同时,将发生运行错误.SizeOfOptionalHeader
IMAGE_DOS_HEADERS结构体的最后一个成员为IMAGE_DOS_HEADER32结构体.SizeOfOptionalHeader成员用来指定出IMAGE_DOS_HEADER32结构体的长度.IMAGE_DOS_HEADER32结构体由C语言编写而成,故其大小已确认.但是Windows的PE装载器需要查看IMAGE_DOS_HEADER的SizeOfOptionalHeader值,从而识别出IMAGE_DOS_HEADER32结构体的大小.Characteristics
该字段用来标识文件的属性,文件是否可以运行状态,是否为DLL文件等信息.以 bit OR 形式组合起来.
3.5 NT头 : 可选头
在IMAGE_DOS_HEADER32结构体中需要关注以下成员,这些值文件是必须运行的,设置错误将导致文件无法正常运行.
Magic
为IMAGE_DOS_HEADER32结构体时,Magic码为10B;为IMAGE_DOS_HEADER64结构体时,Magic码为20B;AddressOfEntryPoint
AddressOfEntryPoint持有EP的RVA值,该值指出程序最先执行的代码起始地址,相当重要.ImageBase
进程虚拟内存的范围是 0~FFFFFFFF(32位系统).PE文件被加载到如此大的内存中时,ImageBase指出文件的优先载入地址.
EXE,DLL文件被装载到用户内存的0~7FFFFFFF中,SYS文件被载入内核内存的80000000~FFFFFFFF中.
SectionAlignment,FileAlignment
PE文件的Body部分划分为若干节区,这些节存储着不同类别的数据.FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中最小单位.磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment值的整数倍.SizeOfImage
加载到PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占空间的大小.SizeOfHeader
SizeOfHeader用来指定整个PE头的大小,该值也必须是FileAlignment的整数倍.SubSystem
该SubSystem值用来区分系统驱动文件(.sys)与普通的可执行文件(.exe,*.dll).Subsystem成员可拥有的值如表.
值 | 含义 | 备注 |
---|---|---|
1 | Driver文件 | 系统驱动(如:ntfs.sys) |
2 | GUI文件 | 窗口应用程序(如:notepad.exe) |
3 | CUI文件 | 控制台应用程序(如:cmd.exe) |
NumberOfRvaAndSize
NumberOfRvaAndSize用来指定DataDirectory(IMAGE_OPTIONAL_HEADER32结构体的最后一个成员)数组的个数.DataDirectory
DataDirectory是由IMAGE_DATA_DIRECTORY结构体组成的数组,数组的每项都要被定义的值.
IMAGE_OPTIONAL_HEADER
HEX Editor 描述的是notepad.exe的IMAGE_OPTIONAL_HEADER结构体区域.
3.6 节区头
把具有相拟属性的数据统一保存在一个被称为”节区”的地方,然后需要把各节区属性记录在节区头中.
需要为每个code/data/resource分别设置不用的特性,访问权限等.
类别| 访问权限
—|—
code| 执行,读取权限
data | 非执行,读取权限
resource | 非执行,读取权限
IMAGE_SECTION_HEADER
节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区.
4. RVA TO RAW
PE文件加载到内存时,每个节区都要能准确完成内存地址与文件偏移间的映射.这种映射一般称为 RVA to RAW.
- 查找RVA所在节区.
- 换算公式
RAW - PointtoRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointtoRawData
5. IAT
IAT (Import Address Table,导入地址表).IAT保存的内容与Windows操作系统的核心进程,内存,DLL结构等有关.简单理解 : IAT是一种表格,用来记录程序正在使用那些库中的那些函数.
5.1 DLL
- 不要把库包含到程序中,单独组成DLL文件,需要时调用即可.
- 内存映射技术使加载后的DLL代码,资源在多个进程中实现共享.
- 更新库时只要替换相关DLL文件即可.
加载DLL文件时有两种方式 : 一种是”显式链接”(Explicit Linking),程序使用DLL时加载,使用完毕后释放内存;另一种是”隐式链接(Implicit Linking)”,程序开始时即一同加载DLL,程序终止时再释放占用的内存.
5.2 IMAGE_IMPORT_DESCRIPTOR
IMAGE_IMPORT_DESCRIPTOR结构体中记录着PE文件要导入那些库文件.
提示
Import : 导入,向库提供服务(函数)
Export : 导出,从库向其他PE文件提供服务(函数)