介绍
PE是指32位可执行文件,也称PE32。64位可执行文件称为PE+或PE32+,是PE文件的一种扩展形式。
PE文件格式
PE文件种类如下:
种类 | 主扩展名 |
---|---|
可执行系列 | exe scr |
库系列 | dll ocx cpl drv |
驱动程序系列 | sys vxd |
对象文件系列 | obj |
严格来讲,obj文件之外的所有文件都是可执行的。ddl、sys文件虽然不能直接在shell执行,但可以使用其他方法(调试器、服务等)执行。
PE文件基本结构
从DOS头到节区头是PE头部分,下面的节区合称为PE体。文件中使用偏移,内存中使用VA虚拟地址来表示位置。文件加载到内存后,节区的位置大小会发生变化。文件内容一般可分为代码、数据、资源节,分别保存。
各节区头分别定义各节区在文件或内存中的大小、位置、属性等。
PE头和各节区尾部存在NULL填充区域,这是因为为了提高处理效率,计算机中使用“最小基本单位的概念”。
VA:进程虚拟内存的绝对地址。
RVA:从某一个基准位置(ImageBase)开始的相对地址。
VA=RVA+ImageBase,两者都指的是装载进入内存后的地址。
PE头内部信息大多以RVA形式存在:因为PE文件(主要是DLL)加载到进程虚拟内存的特定位置时,该位置可能已经加载了其他DLL,此时必须通过重定位将其加载到其他空白区域。发生重定位后,只要相对于基准位置的相对地址没有变化,就能正常访问。
PE头
包括:
- DOS头
- DOS存根
- NT头
- 各节区头
DOS头
PE头最前面的40H字节是一个IMAGE_DOS_HEADER结构体——DOS头
两个重要成员:
- e_magic:多有PE文件开始部分都有的DOS签名(‘MZ’,4D5A)
- e_lfanew:指示NT头的偏移。
DOS存根
在DOS头下方,可选项且大小不固定,由代码和数据混合而成。
文件偏移40~4D区域为16位汇编指令:功能为输出偏移0E处字符串。在DOS调试器debug.exe中运行:
若使用得当,可以在一个可执行文件中创建另一个文件,在16位DOS和32位Windows中都能运行。
NT头
NT头IMAGE_NT_HEADERS结构体由3个成员组成:签名、文件头、可选头。其结构体大小为F8H。起始位置由DOS头的e_lfanew确定(因为DOS存根大小不确定,这里为0000 00E0h)
签名:50450000h(‘PE’00)
NT头:文件头
文件头是表现文件大致属性的IMAGE_FILE_HEADER结构体。
四个重要成员:
- Machine:每个CPU唯一拥有的机器码,这里为Intel 386芯片14C。
- NumberOfSections:节区数,此处为3。
- SizeOfOptionalHeader:可选头大小,用来指出IMAGE_OPTIONAL_HEADER32/64的长度,此处是e0。
- Characteristics表示文件属性,记住0002h与2000h。此处为010F,包含了重定向信息删除,可执行文件,行数移除,本地符号表移除以及系统文件等属性。
NT头:可选头
IMAGE_OPTIONAK_HEADER是PE头结构中最大的。其大小在IMAGE_FILE_HEADER的SizeOfOptionalHeader中标明,此处为00E0。
几个重要成员:
- Magic:可选头是32结构体时,该值为10B。可选头是64位结构体时,该值为20B。
- AddressOfEntryPoint:EP的RVA值,指出程序最先执行的代码起始地址。
- ImageBase:文件优先装入的虚拟内存地址。32位系统进程虚拟内存范围为0~FFFFFFFF。一般来说,EXE文件ImageBase值为00400000,DLL文件为10000000。
PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设为ImageBase+AddressOfEntryPoint。所以说EIP的初始值由可执行文件描述部分决定。 - SectionAlignment、FileAligment:SectionAlignment指定了节区在内存中的最小单位,FileAligment指定了节区在磁盘文件中的最小单位。磁盘文件或内存的节区大小必定为对应最小单位的整数倍。
- SizeOfImage:PE文件在虚拟内存中所占空间的大小,即加载后大小。
- SizeOfHeaders:PE头的大小,是FileAlignment的整数倍(磁盘文件中)。
- Subsystem:区分系统驱动文件与普通可执行文件。
- NumberOfRvaAndSizes:指定最后一个成员DataDirectory数组的个数,以这个为准,而不是IMAGE_NUMBEROF_DIRECTERY_ENTRIES(16)。
- DataDirectory:以IMAGE_DATA_DIRECTORY结构体数组,数组每一项都有被定义的值。DataDirectory[n]结构体中的每一个都有两个属性:一个是相对虚拟地址,一个是大小,其中每个结构体的大小为8字节。
更正:程序EP的RVA应该是0000739D,上图框选应该向右移动两字节。
节区头
节区头中定义了各节区属性。PE文件中的code(代码)、data(数据)、resourse(资源)等按照属性分类存储在不同节区,这样可以保证程序的安全性。
节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区。大小固定为40字节。
几个重要成员:
- VirtualSize:内存中节区所占实际大小
- VirtualAddress:内存中节区起始地址(RVA),运行时会加上ImageBase值。按SectionAlignment对齐。
- SizeofRawData:磁盘中节区所占大小,通常是VirtualSize按照FileAligment值对齐后得到的。
- PointerToRawData:磁盘中节区起始地址。按FileAligment对其。
- Characteristics:节区属性,由bit OR组合而成。
下图显示notpad.exe的三个节区头数组:
RVA与RAW
PE文件加载到内存时,每个节区都要准确完成内存地址与磁盘文件偏移之间的映射,这种映射称为RVA to RAW。方法为:(1)查找RVA所在节区(2)使用下方公式计算处文件偏移RAW。
RAW-PointerToRawData(磁盘中节区偏移)=RVA-VirtualAddress(内存中节区偏移)