Window PE -- 文件头

Window PE – 文件头

PE文件头

紧跟着DOSstub的是PE文件头(PEHeader)。“PEHeader,,是PE关结构NT映像头(IMAGE_NT_HEADERS)的简称,其中包含许多PE装载器能用到的重要字段。当执行体在支持PE文件结构的操作系统中执行时,PE装载器将从IMAGE_DOS_HEADER结构的e_lfanew字段里找到PEHeader的起始偏移量,用其加上基址,得到PE文件头的指针。

PNTHeader = lmageBase + dosHeader->e_Ifanew

实际上有两个版本的IMAGENT_HEADER结构,一个是为PE32(32位版本)可执行文件准备的,另一个是PE32+(64位版本)。因为它们几乎没有区别,所以在以后的讨论中将不作区分。

IMAGE_NT_HEADER是由3个字段(左边的数字是到PE文件头的偏移量)组成的。

1
2


PE32+的IMAGE_NT_HEADER64结构如下。

1
2


Signature字段

在一个有效的PE文件里,Signature字段被设置为Ox00004550,ASCII码字符是“PE\0\0。”,“#define IMAGE_NT_SIGNATURE”定义了这个值,示例如下。

1
#define IMAGE_NT_SIGNATURE   0x00004550

PE\0\0”是PE文件头的开始,MS一DOS头部的ιlianew字段正是指向“PE\0\0”的。

IMAGE_FILE_HEADER结构

lMAGE_FILE_HEADER(映像文件头)结构包含PE文件的一些基本信息,最重要的是,其中的一个域指出了IMAGE_OPTIONAL_HEADER的大小。下面介绍IMAGE_FILE_HEADER结构的各个字段,并对这些字段进行说明。这个结构也能在COFF格式的OBJ文件的开始处找到,因此也称其为“COFF File Header”注释中的偏移量是基于PE文件头(IMAGE_NT_HEADERS)的。

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

用十六进制工具查看IMAGE_FILE_HEADER结构的情况,如图所示,图中的标号对应于以下字段。

1554716150866

  1. Machine:可执行文件的目标CPU类型。PE文件可以在多种机器上使用,不同平台上指令的机器码不同。如表是几种典型的机器类型标志。1554716220204
  2. NumberOfSections:区块(Section)的数目,块表紧跟在IMAGE_NT_HEADERS后面。
  3. TimeDateStamp:表示文件的创建时间。这个值是自1970年l月1日以来用格林威治时间(GMT)计算的秒数,是一个比文件系统的日期/时间更精确的文件创建时间指示器。将这个值翻译为易读的字符串需要使用_clime函数(它是时区敏感型的)。另一个对此字段计算有用的函数是gmtime。
  4. PointerToSymbolTable:COFF符号表的文件偏移位置(参见Microsoft规范的5.4节)。因为采用了较新的debug格式,所以COFF符号表在PE文件中较为少见。在VisualStudio.NET出现之前,COFF符号表可以通过设置链接器开关(/DEBUGTYPE:COFF)来创建。COFF符号表几乎总能在目标文件中找到,若没有符号表存在,将此值设置为0。
  5. NumberOISymbols:如果有COFF符号表,它代表其中的符号数目。COFF符号是一个大小固定的结构,如果想找到COFF符号表的结束处,需要使用这个域。
  6. SizeOfOptionalHeader:紧跟IMAGEFILE_HEADER,表示数据的大小。在PE文件中,这个数据结构叫作IMAGE_O阿IONAL_HEADER,其大小依赖于当前文件是32位还是64位文件。对32位PE文件,这个域通常是OOEOh;对64位PE32+文件,这个域是OOFOh。不管怎样,这些是要求的最小值,较大的值也可能会出现。
  7. Characteristics:文件属性,有选择地通过几个值的运算得到。这些标志的有效值是定义于winnt.h内的IMAGE_FILE_xxx值,具体如表l1.2所示。普通EXE文件的这个字段的值一般是010仙,DLL文件的这个字段的值一般是2102h。

1554716339696

IMAGE_OPTIONAL_HEADER结构

尽管可选映像头(IMAGE_OPTIONAL_HEADER)是一个可选的结构,但IMAGE_FILE_HEADER结构不足以定义PE文件属性,因此可选映像头中定义了更多的数据,完全不必考虑两个结构的区别在哪里,将两者连起来就是一个完整的“PE文件头结构飞IMAGE_OPTIONAL_HEADER32结构如下,字段前的数字标出了字段相对于PE文件头的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

IMAGE_OPTIONAL_HEADER64结构有少许变化,PE32中的BaseOfData不存在于PE32+中,在PE32+中Magic的值是020Bh。IMAGE_OPTIONAL_HEADER64结构如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

用十六进制工具查看IMAGE_OPTIONAL_HEADER32结构,如下图,图中的标号对应于以下字段。

1554723046973

  1. Magic:这是一个标记字,说明文件是ROM映像(0107h)还是普通可执行的映像(010Bh),一般是010Bh,如果是PE32+,则是020Bh。

  2. MajorLinkerVersion:链接程序的主版本号。

  3. MinorLinkerVersion:链接程序的次版本号。

  4. SizeOfCode:有IMAGE_SCN_CNT_CODE属性的区块的总大小(只入不舍),这个值是向上对齐某一个值的整数倍。例如,本例是200h,即对齐的是一个磁盘扇区字节数(200h)的整数倍。在通常情况下,多数文件只有1个Code块,所以这个字段和.text块的大小匹配。

  5. SizeOflnitializedData:已初始化数据块的大小,即在编译时所构成的块的大小(不包括代码段)。但这个数据不太准确。

  6. SizeOfUninitializedData:未初始化数据块的大小,装载程序要在虚拟地址空间中为这些数据约定空间。这些块在磁盘文件中不占空间,就像“UninitializedData”这一术语所暗示的一样,这些块在程序开始运行时没有指定值。未初始化数据通常在.bss块中。

  7. AddressOfE川ryPoint:程序执行人口RVA。对于DLL,这个人口点在进程初始化和关闭时及线程创建和毁灭时被调用。在大多数可执行文件中,这个地址不直接指向Main、WinMain或DllMain函数,而指向运行时的库代码井由它来调用上述函数。在DLL中,这个域能被设置为0,此时前面提到的通知消息都无法收到。链接器的/NOENTRY开关可以设置这个域为0。

  8. BaseOfCode:代码段的起始RVA。在内存中,代码段通常在PE文件头之后,数据块之前。在Microsoft链接器生成的可执行文件中,RVA的值通常是lOOOh。Borland的白ink32用lrnageBase加第l个CodeSection的RVA,并将结果存入该字段。

  9. BaseOfData:数据段的起始RVA。数据段通常在内存的末尾,即PE文件头和CodeSection之后。可是,这个域的值对于不同版本的Microsoft链接器是不一致的,在64位可执行文件中是不会出现的。

  10. lmageBase:文件在内存中的首选载入地址。如果有可能(也就是说,如果目前没有其他文件占据这块地址,它就是正确对齐的并且是一个合法的地址),加载器会试图在这个地址载入PE文件。如果PE文件是在这个地址载人的,那么加载器将跳过应用基址重定位的步骤。

  11. SectionAlignment:载入内存时的区块对齐大小。每个区块被载入的地址必定是本字段指定数值的整数倍。默认的对齐尺寸是目标CPU的页尺寸。对运行在Windows9x/Me下的用模式可执行文件,最小的对齐尺寸是每页1000h(4KB)。这个字段可以通过链接器的/ALIGN开关来设置。在IA-64上,这个字段是按8KB排列的。

  12. FileAlignment:磁盘上PE文件内的区块对齐大小,组成块的原始数据必须保证从本字段的倍数地址开始。对于x86可执行文件,这个值通常是200h或1000h,这是为了保证块总是从磁盘的扇区开始,这个字段的功能等价于NE格式文件中的段/资源、对齐因子使用不同版本的Microsoft链接器,默认值会改变。这个值必须是2的幕,其最小值为200h而且,如果SectionAlignment小于CPU的页尺寸,这个域就必须与SectionA!ignment匹配。链器开关/OPT:WIN98设置x86可执行文件的对齐值为1000h,/OPT:NOWIN98设置对齐值为200h。

  13. MajorOperatingSystemVersion:要求操作系统的最低版本号的主版本号。随着这么多版本的Windows的出现,这个字段显然变得不切题了

  14. MinorOperatingSystemVersion:要求操作系统的最低版本号的次版本号。

  15. MajorOperatingSystemVersion:该可执行文件的主版本号,由程序员定义。它不被系统使用,并可以设置为0,可以通过链接器的NERSION开关来设置。

  16. MinorlmageVersion:该可执行文件的次版本号,由程序员定义。

  17. MajorSubsystemVersion:要求最低子系统版本的主版本号。这个值与下一个字段一起,通常被设置为4,可以通过链接器开关/SUBSYSTEM来设置。

  18. MinorSubsystemVersion:要求最低子系统版本的次版本号。

  19. Win32VersionValue:另一个从来不用的字段,通常被设置为0。

  20. SizeOflmage:映像载入内存后的总尺寸,是指载入文件从Irr吨eBase到最后一个块的大小。最后一个块根据其大小向上取整。

  21. SizeOfHeaders:MS-DOS头部、PE文件头、区块表的总尺寸。这些项目出现在PE文件中的所有代码或数据区块之前,域值四舍五入至文件对齐值的倍数。

  22. CheckSum:映像的校验和。IMAGEHLP.DLL中的CheckSumMappedFile函数可以计算该值。一般的EXE文件该值可以是0,但一些内核模式的驱动程序和系统DLL必须有一个校验和。当链接器的/RELEASE开关被使用时,校验和被置于文件中。

  23. Subsystem:一个标明可执行文件所期望的子系统(用户界面类型)的枚举值。这个值只对EXE重要,如下表

    1554723738509

  24. DllCharacteristics:DllMain()函数何时被调用。默认值为0。

  25. SizeOfStackReserve:在EXE文件里为线程保留的拢的大小。它在一开始只提交其中一部,只有在必要时才提交剩下的部分。

  26. SizeOfStackCommit:在EXE文件里,一开始即被委派给拢的内存,默认值是4KB。

  27. SizeOfHeapReserve:在EXE文件里,为进程的默认堆保留的内存,默认值是1MB。

  28. SizeOfHeapCommit:在EXE文件里,委派给堆的内存,默认值是4KB 。

  29. LoaderFlags:与调试有关,默认值为0。

  30. NumberOfRvaAndSizes:数据目录的项数。这个字段的值从WindowsNT发布以来一直是16。

  31. DataDirect01y[l6]:数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块等数据。IMAGE_DATADIRECTORY的结构定义如下。

1
2


根据目录表成员的结构如下表所示,各项成员含义后面会详细介绍。

1554723877710

PE文件定位输出表、输入表和资源等重要数据时,就是从IMAGE_DATA_DIRECTORY结构开始的。

例PE.exe的数据目录表位于128h~1A7h,每个成员占8字节,分别指向相关的结构,如图11.6所示。128h数据目录表的第1项,其值为0,即这个实例的输出表地址与大小皆为0,表示无输出表。130h是第2项,表示输入表地址为2040h(RVA),大小为3Ch。

1554723908440

PE编辑工具(例如LordPE)查看实例PE.exe文件的E结构。单LordPE的“PEEditor”按钮,打开E_Offset文件,在面板上会直接显示PE结构中的主要字段,如图11.7所示。单击“Directories”按钮,可以打开数据目录表查看面板,如图

1554723928180

1571159906781

Author: YuanBi
Link: https://www.basicbit.cn/2018/11/01/2018-11-01-Windows PE 文件头/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.