文章目录
参考
什么是PE文件
PE(Portable Executable)即可移植的执行体,使用了PE文件结构的可执行文件被称为PE文件,Win下包括EXE、DLL、SYS、OCX等
PE结构图
PE文件结构简介
1. DOS头
1.1 MZ头
MZ头是真正的DOS头,其开始处的两个字节为MZ。此部分用于程序在DOS系统下加载,它的结构被定义为IMAGES_DOS_HEADER
1.2 DOS残留
一段简单的程序,主要用于输出"This program cannot be run in DOS mode."的提示字符串
2. PE头
2.1 PE标识
宏:IMAGE_NT_SIGNATRUE
2.2 文件头
结构体:IMAGE_FILE_HEADER
2.3 可选头
结构体:IMAGE_OPTIONAL_HEADER
3. 节表
节表与节
程序的组织按照不同的属性,被保存在不同的节中,每个节用一个IMAGE_SECTION_HEADER
表示。如果PE文件中有N个节,那么节表就是由N个IMAGE_SECTION_HEADER
结构体和一个表示结束的空IMAGE_SECTION_HEADER
结构体组成的数组。
空结构体不计入节区数 pimageNtHeaders->FileHeader.NumberOfSections
节表中存储了各节的属性、文件位置、内存位置等信息,是节的索引。
4. 节
节事实上就是相同属性数据的组合,一个节中可能有多种类型的数据,它们只是因为读写属性相同而被装入同一节。当节被装入到内存中的时候,相同一个节所对应的内存页都将被赋予相同的页属性,事实上,Windows 系统对内存属性的设置是以页为单位进行的,所以节在内存中的对齐单位必须至少是一个页的大小。(小甲鱼温馨提示:对于32位操作系统来说,这个值一般是4KB即1000H; 对于64位操作系统这个值一般是8KB即2000H)
结构体关系图
PE详解
1. DOS头
DOS头部是用于装载DOS残留用的,且其中的一个字段保存着指向PE头部的位置
此结构体中
e_magic
:2B,DOS可执行文件的标识符
该标识符在winnt.h中有一个宏定义
#define IMAGES_DOS_SIGNATURE 0x5A4D
因为系统是小端方式存储,所以实际保存时前两个字节是4D 5A
e_lfanew
:2B,PE头的起始位置
保存于偏移为0x0000003C的位置
它的值为0x0110
2. PE头
2.1 PE标识
查看0x00000110位置
Signature
:4B,值是0x00004550,即PE\0\0
2.2 文件头
PE标识后20个字节为文件头,主要描述文件的相关信息
结构体_IMAGE_FILE_HEADER
定义:
Machine
:2B,可执行文件的目标处理器类型
如图中0x8664,代表x64处理器
NumberOfSections
:2B,节区个数
图中0x0008,表示有8个节区
TimeDataStamp
:4B,从1970年至文件被创建时间的秒数
SizeOfOptionHeader
:2B,可选头大小。图中为0x00f0,转十进制为240字节
Characteristics
:2B,文件类型。图中为0x0022
2.3 可选头
可选头主要用来管理PE文件被操作系统装载时需要的信息。
注意:可选头之所以被称为可选头,是因为其数据目录数组中,有的数据目录项是可有可无的,但是此头必须存在。
由文件头的SizeOfOptionHeader
知可选头大小为240字节,紧挨文件头。文件头的结束位置为0x00000127,则可选头的地址范围为:0x00000128 ~ 0x00000217
结构体定义如下:
SizeOfCode
:4B,所有代码节大小的总和。图中为0x001c6400
AddressOfEntryPoint
:
DataDirectory
:
如导入表的RVA:
8000H
,大小:1964
当要从PE文件中读取所需要的区块(节)时,不能以区块名作为定位的标准,应该以DataDirectory
的VirtualAddress
作为标准,因为区块名没有任何意义。
3. 节表
紧挨在可选头之后,由文件头标识有8个节区
一个IMAGE_SECTION_HEADER
大小为40字节,图中从0x00000218开始,下图为第一个节区对应的节表
ISH结构体定义如下:
Name
:8B,多余的自动截断。图中为
VirtualSize
:该区块的数据没有进行对齐处理前的实际大小。
VirtualAddress
:装入内存中的RVA地址,按内存页对齐,因此其数值是OptionalHeader->SectionAlignment
的整数倍
如内存分页大小为4096B
两个节的RVA分别为4096和12288
4.节
一个区块(节)中的数据仅仅只是由于属性相同而放在一起,并不一定是同一种用途的内容。如输入表、输出表等就有可能和只读常量一起被放在同一个区块中,因为他们的属性都是可读不可写的
几种地址
文件偏移地址FileOffset
PE文件中某处相对于文件头的地址
基地址ImageBase
内存中PE文件的头地址
虚拟地址VA
内存中某处相对于基地址的位置
相对虚拟地址RVA
内存中某一虚拟地址相对于基地址的偏移量
PE文件映射到内存
注意:块与块之间有空隙,因为要对齐
映射与装载的区别:
RVA转FileOffset
PE文件在磁盘上和内存上的结构是一致的,略有不同的是,在磁盘上PE文件是按照可选头的FileAlignment
值对齐的,在内存中,PE文件是按可选头的SectionAlignment
值对齐的。
FA的最小值是512B,Win32下的SA值一般为4096B
如果PE文件的SA值和FA值相等,则其磁盘文件结构与内存映像的结构是完全一样的。如果不相等,则略有差异。
当知道某数据的RVA,想要在文件中读取同样的数据时,就要将RVA转换为FileOffset,反之同理。
如:
观察输入表
RVA:6000H
ImageBase:40000H
SectionAlignment:1000H
FileAlignment:200H
内容:
接下来进行RVA到FileOffset的转换:
总体的思路是:
输入表属于节表数据的部分,所以首先要判断此RVA在哪一节。
依次计算各节的RVA范围:起始RVA 到 起始RVA+节数据大小
判断出在哪一节之后,用此RVA减去此节的起始RVA,得到一个偏移量。
无论是在内存中还是在文件中,这个偏移量都是相同的。
而节的FileOffset在节表中存着(IMAGE_SECTION_HEADER->PointerTORawData)
所以可以得到此RVA的FileOffset = 节FileOffset + 目标RVA相对于节的偏移量。
- 查看区段表
.text节的RVA范围是:VisualOffset 到 VisualOffset + RawDataSize
即:1000H到2788H
.data节的RVA范围是:3000H到3030H
…
.idata节的RVA范围是6000H到659EH - 输入表的RVA为6000H,属于.idata节。
- 输入表的RVA相对于.idata节的偏移量为6000H - 6000H = 0
- .idata节的ROffset (2400H)即此节的文件偏移地址
- 用2400H + 0H 即得到导入表的FileOffset 2400H
使用C32ASM查看其文件偏移地址为2400H的内容,一致