2026年pe文件是啥(pe文件是什么)

pe文件是啥(pe文件是什么)Windows 操作系统是只能运行以内存 4D 5A 开头 翻译是 MZ 的可执行文件 该文件也叫做 PE 结构文件 是以 exe sys dll 等等作为后缀的文件 而不同的操作系统能运行的可执行文件都是各自特有的 比如 Linux 可运行的可执行文件叫做 elf 结构文件 在以后 pe 结构文件的知识中 我们均以 exe 文件进行 pe 文件的演示与讲解 32 位计算机中任何一个



Windows操作系统是只能运行以内存4D 5A开头,翻译是MZ的可执行文件,该文件也叫做PE结构文件,是以.exe,.sys,.dll等等作为后缀的文件。而不同的操作系统能运行的可执行文件都是各自特有的,比如Linux可运行的可执行文件叫做elf结构文件

在以后pe结构文件的知识中,我们均以.exe文件进行pe文件的演示与讲解

32位计算机中任何一个.exe文件在运行时所在的内存叫做虚拟内存,这些文件都有自己独立的4GB的虚拟内存,其中2GB用于应用程序,其余2GB用于操作系统

.exe文件有两种状态:

一种是在硬盘(未运行时)的状态:在硬盘上的.exe文件打开后内存首地址是从0开始的(逻辑地址)

另一种是在内存中(运行时)的状态:正在运行时的.exe文件在内存(即虚拟内存)中的内存首地址是从0x开始的(物理地址),该内存首地址根据不同的文件有不同的地址

我们常见的txt文件是可以打开运行它,但它并不是可执行文件,这是因为该文件实际是在exe程序中(比如word)打开的

PE结构文件内容是分节的,每一节之间以0作为空白区域,其分节有以下几种原因:

1.节省硬盘空间

在此之前我们应该了解到在早期的编译器编译的文件,其硬盘对齐是200h字节,这是因为早期硬盘很昂贵,为了节省硬盘空间导致的。而内存对齐是1000h字节。但在现代的编译器中编译器编译的文件在硬盘和内存中的对齐都是1000h字节

我们首先观察一下早期编译器生成的.exe文件(如notepad.exe)在硬盘和内存中的存储分布

3031259e11e44d9497d2d07103000105.png

如图可知,一个pe文件在存储中分为了多个段,这些段也叫做节。图中左侧是pe文件在硬盘中的存储分布,分布空间紧密。右侧是pe文件在内存中的存储分布,其分布空间稀疏,这是由于pe文件从硬盘到内存有一个拉伸的过程。由图对比可知在硬盘上存储空间更小。如此看来是符合节省硬盘空间的原因的

现在我们观察一下现代编译器生成的pe文件在硬盘和内存中的存储分布:

c4ec7bb41bd74f7da53a27d5999aaa73.png

同一份文件在内存中和在硬盘中的内容是一样的,但是他们文件内存起始位置是不一样的,它们分节之间的空白区域大小是一样的,这似乎与我们之前所作的文件硬盘与内存分布图有所不同,这是由于对齐的机制。对齐是为了提高读写速度,比如一本书一章的结尾可能会在新的一页留一个‘完’字,这个字会单独占有一页,而不是在下一章的内容一起占据同一页,这样的设置让我们更容易的去查找我们想要的内容

到目前为止看来,pe结构分节可能并不只是因为节省硬盘空间

2.多开,比如我们挂几个

假设我们现在有一个.exe文件(比如),它的文件结构存在只读数据和可读可写数据部分,如下图表示,其每个部分都占有100兆的内存

ab87cd7b6add45a0a98794bd8bc8f69d.png

此时我们多开一个该文件,内存分布是这样的:系统只会为我们多创建一个可读可写的数据的内存

979c4cacca464ea98296ff225e6d1b60.png

这是我们可以发现,正是因为pe文件结构分节,我们才能占用更少的内存,发挥更大的作用

如下是早期编译器编译的pe文件在硬盘和内存中的结构图

71a79b39dc9d47dca60d7766b08e0005.png

无论哪个块的内容有多大,它所被分配的大小在硬盘中都只有200h,内存中1000h,而块中的数据不论在硬盘还是内存中都是一样的,只是因为内存1000h对齐的原因,所以需要用0填充空出来的区域

如图可知:文件中我们能看到的数据(即.data,.text,.rdata)都被存储在块中,每个块在硬盘和内存中都被分配了200h和1000h的大小。

每一个节,都有一个对应的节表(图中块表)用于记录节的相关信息,如每一个节的概要性信息。这些节表是挨着存放在一个指定的区域的,所以广义上我们称这片区域为节表。

除此以外,pe文件还有两个结构:PE文件头和DOS头,这两个结构记录了该pe文件的概要性信息和特征:比如在内存中拉伸后占多大空间,或此程序启动后要分多大的堆、堆栈等

该部分内容我们学习如何手动查找DOS头,NT头

DOS头和NT头中指定位置和宽度的数据都规定了不同含义,图中左边一列地址是相对于DOS头或PE文件头起始地址的的地址,如图所示,该图也是完整的pe结构图

6a71954b5d4a47aca6366ecb08d7e50a.jpeg

一.DOS头的作用

1.我们解析一个文件时会看最开始的两个字节(e_magic)是不是4D 5A(MZ),用于判断该文件是不是pe文件

2. 找到DOS头的最后4字节数据(e_Ifanew),它指向真正的PE文件开始的地址

3.其他DOS头中的数据可以不用理会,这是因为DOS头最初是给16位操作系统使用的,对于32位系统,DOS的作用就是上述两个

从DOS头结尾到PE签名(即NT头开始)之间,有一些空出来的空间。这个空间用于存放不同的编译器存放不同的数据,对于我们来说其实就是一些垃圾数据,而且程序本身也不会使用到这块空间。所以我们可以在这个空间加入我们自己的数据,并且该数据随着文件一起装入内存中,并分配了内存地址。因此虽然程序本身运行时不会使用这块空间,但我们可以利用一些方法访问该内存,如指针

二.手动解析DOS头

我们将ipmsg.exe程序用winhex打开,根据DOS头的结构来分析数据,找出DOS头对应的字节代码(DOS头大小为64字节,十六进制为0x40)

注意:winhex显示的文件数据是按不同含义的字段宽度顺序存的,并且其数据以小端序排列

bcb6981b94b74962bce53d65d0753cb0.png

如图便是我们打开程序所显示的文件的内存数据,接下来我们将按照DOS的结构,依次查找每个成员所对应的数据代码

struct _IMAGE_DOS_HEADER {

    0x00 WORD e_magic;   *   //0x5A4D    MZ,即表示此文件是可执行文件

    0x02 WORD e_cblp;        //0x0090

    0x04 WORD e_cp;          //0x0003

    0x06 WORD e_crlc;        //0x0000

    0x08 WORD e_cparhdr;     //0x0040

    0x0a WORD e_minalloc;    //0x0000

    0x0c WORD e_maxalloc;    //0xffff

    0x0e WORD e_ss;          //0x0000

    0x10 WORD e_sp;          //0x00B8

    0x12 WORD e_csum;        //0x0000

    0x14 WORD e_ip;          //0x0000

    0x16 WORD e_cs;          //0x0000

    0x18 WORD e_lfarlc;      //0x0040

    0x1a WORD e_ovno;        //0x0000

 0x1c WORD e_res[4];      //0x0000000000000000,此处是4个字节数组

    0x24 WORD e_oemid;       //0x0000

    0x26 WORD e_oeminfo;     //0x0000

    0x28WORDe_res2[10]; //0x0000000000000000000000000000000000000000

    0x3c DWORD e_lfanew;  *  //0x000000e0    表示真正的PE文件开始地址为0xe0,即PE签名所在地址

};

紧接着DOS头便是NT头,现在我们开始讲解NT头

NT头是由三部分组成:PE签名,标准PE头,可选PE头。在NT头中,首先是PE签名字段然后是标准PE头,最后紧跟着就是可选PE头

现在我们开始寻找NT头:

1.找PE签名

ac68e3911f1d4c1b8fd58e320ba27210.png

我们之前在找DOS头时,DOS头以0x000000e0结尾, 指向了左侧地址e0的地方,从图中可知,e0的地址数据为5045,即最右侧的PE两字,即PE签名。这种现象正好说明了此处是pe文件真正开始的地方,即NT头开始,但这个e0并不是一直固定的。

NT头不直接挨着DOS,而是在DOS头的e_lfanew数据指向e0地址开始的原因是,从DOS头结尾到NT头开始(即PE签名字段)之间,不同的编译器会存放不同的数据,但是对于我们来说就是一些垃圾数据,而且程序本身也不会使用到这块空间。所以我们可以在这个空间加入我们自己的数据,并且该数据随着文件一起装入内存中,并分配了内存地址。因此虽然程序本身运行时不会使用这块空间,但我们可以利用一些方法访问该内存,如指针

如下我们将对应PE签名结构体在上图所对应的内存地址进行展示

struct _IMAGE_NT_HEADERS {

0x00 DWORD Signature;   //0x00004550 即PE的签名占4字节

0x04 _IMAGE_FILE_HEADER FileHeader;   //结构体中存在结构体类型的数据,此处是标准pe头

0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader; //此处可选pe头

};

2)找标准PE头

PE文件头(大小为20字节,0x12)

struct _IMAGE_FILE_HEADER {

    0x00 WORD Machine;  *                //0x014c

    0x02 WORD NumberOfSections;  *       //0x0004

    0x04 DWORD TimeDateStamp;  *         //0x4d74bc7e

    0x08 DWORD PointerToSymbolTable;     //0x00000000

    0x0c DWORD NumberOfSymbols;          //0x00000000

    0x10 WORD SizeOfOptionalHeader;  *   //0x00e0

    0x12 WORD Characteristics;  *        //0x010f

};

3)找可选PE头

PE可选头(大小不确定,在32位平台下为E0,在64位平台下位F0,需要根据标准PE头中的SizeOfOptionalHeader的值来判断),如下便是可选pe头对应上图的地址

struct _IMAGE_OPTIONAL_HEADER {

    0x00 WORD Magic;  *                    //0x010b

    0x02 BYTE MajorLinkerVersion;          //0x06

    0x03 BYTE MinorLinkerVersion;          //0x00

    0x04 DWORD SizeOfCode;  *              //0x00021000

    0x08 DWORD SizeOfInitializedData;  *   //0x0001b000

    0x0c DWORD SizeOfUninitializedData;  * //0x00000000

    0x10 DWORD AddressOfEntryPoint;  *     //0x0001d26f

    0x14 DWORD BaseOfCode;  *              //0x00001000

    0x18 DWORD BaseOfData;  *              //0x00022000

    0x1c DWORD ImageBase;  *               //0x00

    0x20 DWORD SectionAlignment;  *        //0x00001000

    0x24 DWORD FileAlignment;  *           //0x00001000

    0x28 WORD MajorOperatingSystemVersion; //0x0004

    0x2a WORD MinorOperatingSystemVersion; //0x0000

    0x2c WORD MajorImageVersion;           //0x0000

    0x2e WORD MinorImageVersion;           //0x0000

    0x30 WORD MajorSubsystemVersion;       //0x0004

    0x32 WORD MinorSubsystemVersion;       //0x0000

    0x34 DWORD Win32VersionValue;          //0x00000000

    0x38 DWORD SizeOfImage;  *             //0x0003d000

    0x3c DWORD SizeOfHeaders;  *           //0x00001000

    0x40 DWORD CheckSum;  *                //0x00000000

    0x44 WORD Subsystem;                   //0x0002

    0x46 WORD DllCharacteristics;          //0x0000

    0x48 DWORD SizeOfStackReserve;  *      //0x00

    0x4c DWORD SizeOfStackCommit;  *       //0x00001000

    0x50 DWORD SizeOfHeapReserve;  *       //0x00

    0x54 DWORD SizeOfHeapCommit;  *        //0x00001000

    0x58 DWORD LoaderFlags;                //0x00000000

    0x5c DWORD NumberOfRvaAndSizes;        //0x00000010

    0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; //这个先不分析

};

本次作业我们以notepad为例,进行讲解

如图便是我们notepad的硬盘内存

struct _IMAGE_DOS_HEADER {

    0x00 WORD e_magic;    0x5A4D

    0x02 WORD e_cblp;      0X0090

    0x04 WORD e_cp;         0X0003

    0x06 WORD e_crlc;     0X0000

    0x08 WORD e_cparhdr;    0X0004

    0x0a WORD e_minalloc;    0X0000

    0x0c WORD e_maxalloc;   0XFFFF

    0x0e WORD e_ss;      0X0000

    0x10 WORD e_sp;   0X00B8

    0x12 WORD e_csum;    0X0000

    0x14 WORD e_ip;          0X0000

    0x16 WORD e_cs;      0X0000

    0x18 WORD e_lfarlc;   0X0040

    0x1a WORD e_ovno;      0X0000

    0x1c WORD e_res[4];    0X0000000000000000

    0x24 WORD e_oemid;       0X0000

    0x26 WORD e_oeminfo;     0X0000

    0x28 WORDe_res2[10];    0X00000000000000000000

    0x3c DWORD e_lfanew;  0X000000F0

};

64个字节

0x00 DWORD Signature;  0X00004550

struct _IMAGE_FILE_HEADER {

    0x00 WORD Machine;  0X8664

    0x02 WORD NumberOfSections; 0X0007

    0x04 DWORD TimeDateStamp; 0X4678EC68

    0x08 DWORD PointerToSymbolTable;  0X00000000

    0x0c DWORD NumberOfSymbols;  0X00000000

    0x10 WORD SizeOfOptionalHeader;  0X00F0

    0x12 WORD Characteristics;  0X0022

};

20字节

struct _IMAGE_OPTIONAL_HEADER {

    0x00 WORD Magic;  0X020B

    0x02 BYTE MajorLinkerVersion;  0X0E

    0x03 BYTE MinorLinkerVersion;   0X1E

    0x04 DWORD SizeOfCode; 0X00028000

    0x08 DWORD SizeOfInitializedData; 0X00031000

    0x0c DWORD SizeOfUninitializedData; 0X00000000

    0x10 DWORD AddressOfEntryPoint; 0X000019A0

    0x14 DWORD BaseOfCode;  0X00001000

    0x18 DWORD BaseOfData; 0X

    0x1c DWORD ImageBase;  0X00000001

    0x20 DWORD SectionAlignment;   0X00001000

    0x24 DWORD FileAlignment;  0X00001000

    0x28 WORD MajorOperatingSystemVersion;  0X000A

    0x2a WORD MinorOperatingSystemVersion; 0X0000

    0x2c WORD MajorImageVersion;       0X000A

    0x2e WORD MinorImageVersion;     0X0000

    0x30 WORD MajorSubsystemVersion; 0X000A

    0x32 WORD MinorSubsystemVersion; 0X0000

    0x34 DWORD Win32VersionValue;    0X00000000

    0x38 DWORD SizeOfImage;      0X0005A000

    0x3c DWORD SizeOfHeaders;  0X00001000

    0x40 DWORD CheckSum;  0X0005C43F

    0x44 WORD Subsystem;        0X0002

    0x46 WORD DllCharacteristics;       0XC160

    0x48 DWORD SizeOfStackReserve;   0X00080000

    0x4c DWORD SizeOfStackCommit; 0X00000000

    0x50 DWORD SizeOfHeapReserve;  0X00011000

    0x54 DWORD SizeOfHeapCommit; 0X00000000

    0x58 DWORD LoaderFlags;      0X00

    0x5c DWORD NumberOfRvaAndSizes;    0X00000000

    0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16];  这个先不分析

};

因NumberOfRvaAndSizes值为0,则DataDirectory[16]成员没有结构体,所以该可选pe头96字节。但不同的文件不同分析。

最后我们利用petool进行检查

由于该工具具有一定的bug,所以仅供参考。但由于绝大部分都可以匹配的上,所以可以判断我们手动查找dos头和nt头无误

今天的文章 2026年pe文件是啥(pe文件是什么)分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2026-02-07 17:51
下一篇 2026-02-07 18:06

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ri-ji/41305.html