PE

来自osdev
Zhang3讨论 | 贡献2021年12月17日 (五) 02:17的版本 (创建页面,内容为“{{File formats}} 对于Windows 95/NT,需要新的可执行文件类型。 于是,“PE”可移植可执行文件诞生了,目前仍在使用中。 与以前的版本不同,WIN-PE是一种真正的32位文件格式,支持可重定位代码。 它确实区分了文本、数据和BSS。 事实上,它是COFF格式的一个低级版本。 如果您确实在Windows计算机上设置了Cygwin环境,“PE”是Cygwin GCC工具链的目标格式,…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索
可执行文件格式
Microsoft

16 bit:
COM
MZ
NE
32/64 bit:
PE
Mixed (16/32 bit):
LE

*nix

对于Windows 95/NT,需要新的可执行文件类型。 于是,“PE”可移植可执行文件诞生了,目前仍在使用中。 与以前的版本不同,WIN-PE是一种真正的32位文件格式,支持可重定位代码。 它确实区分了文本、数据和BSS。 事实上,它是COFF格式的一个低级版本。

如果您确实在Windows计算机上设置了Cygwin环境,“PE”是Cygwin GCC工具链的目标格式,在尝试将Cygwin下的部件构建与Linux或BSD(默认情况下使用ELF目标)下的部件构建链接时,这会引起一些头痛。(提示:您必须构建GCC交叉编译器

PE格式用于Windows 95及更高版本、Windows NT 3.1及更高版本、Mobius、ReactOS和UEFI。 PE格式还用作的容器。NET程序集。Net CLI和Mono,在这种情况下,它实际上并不存储可执行数据,而是。Net元数据及其附加的IL。

在PE文件中

下面将尝试解释构成PE文件的各种概念和部分,而不是其中的确切数据结构,因为这肯定会占用太多空间。 原因是PE文件上的大多数资源往往只是在你面前转储一堆数据结构,而没有充分解释它们的用途。 因此,通过阅读以下内容并了解PE文件的组成,您将更好地了解预期内容以及如何利用PE文件资源。

概述

PE文件由几个部分组成。下面对它们进行简要总结,然后在每个部分上输入更多细节。 以下是字段类型的定义。PE文件以小端顺序存储,与x86相同的字节顺序。

An overview of the format

DOS存根

PE格式以MS-DOS存根(一个头加上可执行代码)开始,使其成为有效的MS-DOS可执行文件。 MS-DOS标头以魔术代码0x5A4D开始,长度为64字节,后跟实模式可执行代码。 几乎普遍使用的标准存根长度为128字节(包括头和可执行代码),只输出“此程序无法在DOS模式下运行”。“尽管许多带有PE文件的实用程序都是硬编码的,希望PE头在中正好以128字节开始,但这是不正确的,因为在某些链接器中,包括Microsoft自己的Link,可以用您自己选择的一个来替换MS-DOS存根,许多旧程序这样做是为了允许开发人员将MS-DOS和Windows版本捆绑到单个文件中。 正确的方法是读取位于0x3C(通常称为e_lfanew的字段)的MS-DOS头中以前保留的4字节地址,该地址包含找到PE文件签名的地址,PE文件头立即跟随。 通常这是一个非常标准的值(大多数情况下,默认link.exe存根会将此字段设置为0xE8)。微软似乎建议将PE头对齐在8字节的边界上(http://msdn.microsoft.com/en-us/gg463119.aspx,第10页,图1)。

PE头

PE头包含与整个文件有关的信息,而不是稍后将出现的单个文件。 最小裸头包含4字节签名(0x00004550)、内部可执行代码的机器类型/架构、时间戳、符号指针、,以及各种标志(文件是否为可执行文件、DLL、应用程序是否可以处理2GB以上的地址、是否需要将文件复制到交换文件(如果从可移动设备运行),等等)。除非您使用的是一个真正精简的静态链接PE文件,以硬编码的入口点和没有资源来节省内存,否则仅PE头是不够的。

// 1 byte aligned
struct PeHeader {
	uint32_t mMagic; // PE\0\0 or 0x00004550
	uint16_t mMachine;
	uint16_t mNumberOfSections;
	uint32_t mTimeDateStamp;
	uint32_t mPointerToSymbolTable;
	uint32_t mNumberOfSymbols;
	uint16_t mSizeOfOptionalHeader;
	uint16_t mCharacteristics;
};

可选标题

可选PE标头紧跟在标准PE标头之后。 它的大小在PE标头中指定,您也可以使用它来判断可选标头是否存在。 可选的PE头以表示体系结构的2字节幻码开始(0x010B表示PE32,0x020B表示PE64,0x0107 ROM)。这可以与机器类型一起使用,以查看PE头中的内容,以检测PE文件是否在兼容系统上运行。 还有一些其他有用的与内存相关的变量,包括代码和数据的大小和虚拟基,以及应用程序的版本号(完全由用户指定,一些更新实用程序使用它来检测是否有新版本可用)、入口点和目录数(见下文)。

可选标头的一部分特定于NT。 这包括子系统(控制台、驱动程序或GUI应用程序)、要保留多少堆栈和堆空间,以及所需的最低操作系统、子系统和Windows版本。 您可以根据操作系统的需要使用自己的值来实现所有这些功能。

// 1 byte aligned
struct Pe32OptionalHeader {
	uint16_t mMagic; // 0x010b - PE32, 0x020b - PE32+ (64 bit)
	uint8_t  mMajorLinkerVersion;
	uint8_t  mMinorLinkerVersion;
	uint32_t mSizeOfCode;
	uint32_t mSizeOfInitializedData;
	uint32_t mSizeOfUninitializedData;
	uint32_t mAddressOfEntryPoint;
	uint32_t mBaseOfCode;
	uint32_t mBaseOfData;
	uint32_t mImageBase;
	uint32_t mSectionAlignment;
	uint32_t mFileAlignment;
	uint16_t mMajorOperatingSystemVersion;
	uint16_t mMinorOperatingSystemVersion;
	uint16_t mMajorImageVersion;
	uint16_t mMinorImageVersion;
	uint16_t mMajorSubsystemVersion;
	uint16_t mMinorSubsystemVersion;
	uint32_t mWin32VersionValue;
	uint32_t mSizeOfImage;
	uint32_t mSizeOfHeaders;
	uint32_t mCheckSum;
	uint16_t mSubsystem;
	uint16_t mDllCharacteristics;
	uint32_t mSizeOfStackReserve;
	uint32_t mSizeOfStackCommit;
	uint32_t mSizeOfHeapReserve;
	uint32_t mSizeOfHeapCommit;
	uint32_t mLoaderFlags;
	uint32_t mNumberOfRvaAndSizes;
};

数据目录

从技术上讲,它是可选标题的一部分,紧跟其后的是指向数据目录的条目列表(仅在可执行映像和DLL中)。由于可选标头的大小可能会有所不同,因此您只需注意现有的和预期的目录,因为将来可能会将新的数据目录添加到PE规范中(.Net是最近添加的目录的一个示例)。每个数据目录在可选标头中作为8字节条目引用。 前4个字节是目录的相对虚拟地址或RVA(请参见下面的部分),最后4个字节是目录的大小。

条目指向的每个数据目录都有自己的格式。 数据目录用于描述动态链接的导入表、嵌入在PE文件中的资源表、调试信息(行号和断点)、CLI。净页眉。

Position (PE/PE32+) Section
96/112 The export table address and size. Same format as .edata
104/120 The import table address and size. Same format as .idata
112/128 The resource table address and size. Same format as .rsc
120/136 The exception table address and size. Same format as .pdata
128/144 The attribute certificate table offset (not RVA) and size. See Signed PE below
136/152 The base relocation table address and size. Same format as .reloc
144/160 The debug data starting address and size. Same format as .debug
152/168 Architecture, reserved MBZ
160/176 Global Ptr, the RVA of the value to be stored in the global pointer register. The size member of this structure must be set to zero.
168/184 The thread local storage (TLS) table address and size. Same format as .tls

小节

PE文件由节组成,这些节包括名称、文件内的偏移量、要复制到的虚拟地址,以及文件和虚拟内存中节的大小(可能不同,在这种情况下,差异应清除0),以及相关标志。 节通常遵循通用命名(“.text”、“.rsrc”等),但这在链接器之间也可能有所不同,并且在某些情况下可以是用户定义的,因此最好依靠标志来判断节是可执行的还是可写的。 但是,如果您希望将自定义数据嵌入到可执行文件中,那么将其放入节中并通过节的名称进行标识是一个好主意,因为您不会更改PE格式,并且您的可执行文件将与PE工具保持兼容。

相对虚拟基础是PE文档中经常出现的一个短语。 RVA是加载到内存中后存在的地址,而不是文件中的偏移量。 要从RVA计算文件地址,而不实际将节加载到内存中,可以使用节条目表。 通过使用每个节的虚拟地址和大小,可以找到RVA所属的节,然后减去节的虚拟地址和文件偏移量之间的差值。

节头

每个节在节标题表中都有一个条目。

struct IMAGE_SECTION_HEADER { // size 40 bytes
	char[8]  mName;
	uint32_t mVirtualSize;
	uint32_t mVirtualAddress;
	uint32_t mSizeOfRawData;
	uint32_t mPointerToRawData;
	uint32_t mPointerToRelocations;
	uint32_t mPointerToLinenumbers;
	uint16_t mNumberOfRelocations;
	uint16_t mNumberOfLinenumbers;
	uint32_t mCharacteristics;
};

在asm连接中

如果在nasm中声明如下代码块:

段密码
aAsmFunction:
;做任何事
mov字节[aData],0
ret
段数据
aData:db 0xFF

“段”将显示为“节”。使用此选项可以将C和Asm分开,因为链接器不会自动合并“”。代码“”和“”。文本“”,这是C编译器的正常输出。

位置独立代码=

如果每个部分都指定将其加载到哪个虚拟地址,您可能会想知道如何在一个虚拟地址空间中存在多个DLL而不发生冲突。的确,在PE文件(DLL或其他)中找到的大多数代码都是位置相关的,并且链接到特定的地址。但是,为了解决这个问题,存在一个称为重定位表的结构,该结构附加到每个节条目。该表基本上是存储在该节中的每个地址的一个巨大的长列表,因此您可以将其偏移到加载该节的位置。

因为地址可以跨节边界指向,所以应该在将每个节加载到内存中后进行重新定位。 然后在每个节上重复,遍历重定位表中的每个地址,找出RVA存在于哪个节中,并添加/减去该节的链接虚拟地址和加载到其中的节的虚拟地址之间的偏移量。

带属性证书表的已签名PE

许多PE可执行文件(最明显的是所有Microsoft更新)都使用证书签名。 这些信息存储在属性证书表中,由数据目录的第5个条目指向。 重要的是,对于属性证书表,不存储RVA,而是存储一个简单的文件偏移量。 格式为串联签名,每个签名具有以下结构:

Offset Size Field Description
0 4 dwLength Specifies the length of the attribute certificate entry.
4 2 wRevision Contains the certificate version number, magic 0x0200 (WIN_CERT_REVISION_2_0)
6 2 wCertificateType Specifies the type of content in bCertificate, magic 0x0002 (WIN_CERT_TYPE_PKCS_SIGNED_DATA)
8 x bCertificate Contains a PKCS#7 SignedData structure

For Secure Boot under EFI such a signature is a must. It worth nothing that the PE format allows multiple certificates to be embedded in a single PE file, but UEFI firmware implementations usually only allow one, which must be signed by the Microsoft KEK. If the firmware allows installing more KEK (not typical), then you can use other certificates as well.

The bCertificate data is a PKCS#7 signature with certificate, encoded in ASN.1 format. Microsoft uses signtool.exe to create these signature entries, but an Open Source solution exists, called sbsigntool (also available on github with debian packaging).

CLI / .Net

CLI与PE格式一起工作。 它不是该格式的扩展,而是作为其自身的格式存在于一种格式中,具有完全不同的存储表和值的方式。 所有的。Net数据和标头存在于加载到内存中的节中(它们被加载到内存中,因为CLI涉及大量的语言反射,需要元数据而不影响磁盘)。第二个原因是。Net元数据存在于节中,而不是PE头中,这是因为PE加载程序实际上不具有元数据的概念。净的。 (Exception: There is a data directory entry pointing to the RVA of the CLI header so tools can easily access the .Net data without loading it into virtual memory.)事实上,我非常怀疑Windows内核是否有任何类型的。净的。 顺便问一下。Net works是通过动态链接来实现的。Net运行时(mscoree.dll),并将入口点设置为解析为mscoree内部位置的符号(_CorExeMain)。dll而不是本地可执行文件。 这意味着Windows CE、WINE和ReactOS都可以加载。Net程序集。Net framework可以在没有任何特定代码的情况下安装。

加载PE文件

加载PE文件非常简单;

1.从标头中提取入口点、堆和堆栈大小。

2.遍历每个节并将其从文件复制到虚拟内存中(虽然不是必需的,但最好将内存中的节大小与文件中的节大小之间的差异清除为0)。

3.通过在符号表中查找正确的条目来查找入口点的地址。

4.在该地址创建一个新线程并开始执行!

要加载需要动态DLL的PE文件,可以执行相同的操作,但请检查导入表(由数据目录引用)以查找需要哪些符号和PE文件,该PE文件中的导出表(也被数据目录引用),以查看这些符号的位置,并在将该PE的节加载到内存(并重新定位它们!)后将它们匹配起来最后,请注意,您还必须递归地解析每个DLL的导入表,并且一些DLL可以使用技巧在加载DLL时引用DLL中的符号,因此请确保您的加载程序不会陷入循环!注册加载的符号并使其全局化可能是一个很好的解决方案。

检查“Machine”和“Magic”字段的有效性也是一个好主意,而不仅仅是PE签名。 这样,您的加载程序就不会尝试将64位二进制文件加载到32位模式(这肯定会导致异常)。

64 bit PE

64 bit PE's are extremely similar to normal PE's, but the machine type, if AMD64, is 0x8664, not 0x14c. This field is directly after the PE signature. The magic number also changes from 0x10b to 0x20b. The magic field is at the beginning of the optional header. Plus, the BaseOfData member of the optional header is non existent. This is because the ImageBase member is expanded to 64 bits. The BaseOfData is removed to make room for it

另见

de:Microsoft Portable Executable and Common Object File Format