PE文件结构详解(非常详细)

2024-09-01 02:36
文章标签 详解 详细 结构 非常 pe

本文主要是介绍PE文件结构详解(非常详细),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近在参考OpenShell为任务栏设置图片背景时,发现里面使用了IAT Hook,这一块没有接触过,去查资料的时候发现IAT Hook需要对PE文件结构有一定的了解,索性将PE文件结构的资料找出来,系统学习一下。

PE文件结构

Portable Executable (PE),可移植的可执行文件。在Windows平台下,所有的可执行文件(包括.exe, .dll, .sys, .ocx, .com等)均使用PE文件结构。这些使用了PE文件结构的可执行文件也称为PE文件。

PE结构包含的结构体有DOS头,PE标识 、文件头、可选头、目录头、目录结构、节表等。

整体结构如下

从上图可以看出PE结构分为4大部分,其中每个部分又进行了细分。

从数据管理的角度来看,可以把PE文件大致分为两部分,

1、DOS头、PE头和节表属于PE文件的数据管理结构或数据组织结构部分,

2、节表数据才是PE文件真正的数据部分,其中包含着代码、数据、资源等内容。

DOS头

DOS头分为“MZ头部”和"DOS存根“。

MZ头部“是真正的DOS头部,由于其开始处的两个字节为"MZ",因此DOS头也可以叫作MZ头部

这个我们用十六进制编辑器随便打开一个exe就可以看到

该部分用于程序在DOS系统下加载,它的结构被定义为IMAGE_DOS_HEADER

IMAGE_DOS_HEADER定义

 1 //大小为: 0x40(64)字节2  #define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ3  4 typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header5     WORD   e_magic;                     // MZ标记 0x5a4d 6     WORD   e_cblp;                      // 最后(部分)页中的字节数7     WORD   e_cp;                        // 文件中的全部和部分页数8     WORD   e_crlc;                      // 重定位表中的指针数9     WORD   e_cparhdr;                   // 头部尺寸以段落为单位
10     WORD   e_minalloc;                  // 所需的最小附加段
11     WORD   e_maxalloc;                  // 所需的最大附加段
12     WORD   e_ss;                        // 初始的SS值(相对偏移量)
13     WORD   e_sp;                        // 初始的SP值
14     WORD   e_csum;                      // 补码校验值
15     WORD   e_ip;                        // 初始的IP值
16     WORD   e_cs;                        // 初始的SS值
17     WORD   e_lfarlc;                    // 重定位表的字节偏移量
18     WORD   e_ovno;                      // 覆盖号
19     WORD   e_res[4];                    // 保留字
20     WORD   e_oemid;                     // OEM标识符(相对m_oeminfo)
21     WORD   e_oeminfo;                   // OEM信息
22     WORD   e_res2[10];                  // 保留字
23     LONG   e_lfanew;                    // NT头(PE标记)相对于文件的偏移地址
24   } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS存根是一段简单的程序,主要用于输出“This program cannot be run in DOS mode.”类似的提示字符串。

为什么PE结构的最开始位置有这样一段DOS头部呢?

为了该可执行程序可以兼容DOS系统。通常情况下,Win32下的PE程序不能在DOS下运行,因此保留了这样一个简单的DOS程序用于提示“不能运行于DOS模式下”。

DOS头部IMAGE_DOS_HEADER详解

IMAGE_DOS_HEADER的定义在前面我们列出来了,该结构体中需要掌握的字段 只有两个分别是第一个字段 e_magic和最后一个字段e_lfanew

e_magic:DOS可执行文件的标识,占用2字节,该位置保存着字符是“MZ",该标识符在Winnt.h头文件中有一个宏定义,如下所示:

1 #define IMAGE_DOS_SIGNATURE 0x5A4D

我们创建一个简单的控制台程序

1 #include <iostream>
2 
3 int main()
4 {
5     std::cout << "Hello World!\n";
6 }

使用16进制编辑器(我这里用的是ImHex,使用个人习惯的软件即可)打开编译出来的二进制文件(.exe)。

可以看到在0x00000000的位置保存着2字节的内容0x5A4DASCII的MZ)这里使用的是小尾(小端)方式存储,即高位保存高字节,低位保存低字节,所以上图中写的是4D 5A,这也是适合阅读顺序。

----------------------------------------------

说明:

  • 大端模式(Big-Endian):在内存中,多字节数据类型的高位字节存储在低地址处,而低位字节存储在高地址处。这种模式与我们阅读数字的方式相似,即先读高位,后读低位。

  • 小端模式(Little-Endian):在内存中,多字节数据的低位字节存储在低地址处,而高位字节存储在高地址处。这种模式与我们阅读数字的方式相反,即先读低位,后读高位。

如0x0102这样一个数据,

使用大端方式存储,存储方式为:01 02

使用小端方式存储,存储方式为:02 01

我们可以看到下面这样一段程序

 1 #include <iostream>2 #include<Windows.h>3 #include<winnt.h>4 5 int main()6 {7     8     HANDLE hFile = CreateFile(L"a.bin", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);9 
10     BYTE buffer[4] = { 1,2,3,4 };  //写入 1 2 3 4 4个字节
11 
12     DWORD dwHexValue = 0x5D4A; //写入0x5D4A(实际存储是4A 5D)
13     DWORD dwDecValue = 1234;   //写入1234  (0x04D2 实际存储D2 04)
14 
15     LPOVERLAPPED lv{};
16     if (hFile)
17     {
18         WriteFile(hFile, buffer, 4, NULL, NULL);
19         WriteFile(hFile, (PVOID)&dwHexValue, 4, NULL, NULL);
20         WriteFile(hFile, (PVOID)&dwDecValue, 4, NULL, NULL);
21         CloseHandle(hFile);
22     }
23 }

用十六进制编辑器打开可以看到

为什么是这种情况,因为对于 Microsoft Visual C++ 的目标平台(x86、x64、ARM、ARM64),所有本机标量类型都是小字节序。

-------------------------

好的,让我们继续回归主题,在0x0000003C 位置处,也就是IMAGE_DOS_HEADERe_lfanew字段,该字段保存着PE头部的起始位置。

说明:IMAGE_DOS_HEADER的大小是64个字节,也就是0-63(0x3F),e_flanew的大小是4,所以 0x3F - 0x04 + 0x01 = 0x3C 

e_lfanew字段是LONG类型,所以这里是4个字节,值为F8 00 00 00,因为是使用的小端字节序,所以我们可以在0x000000F8位置,看到50 45 00 00,与之对应的ASCII字符为”PE\0\0“,这里就是PE头部开始的位置。

IMAGE_DOS_HEADER(e_lfanew字段之后)到"PE\0\0"之间的内容就是DOS存档,可以将该部分删除,然后将PE头部整体向前移动,也可以将一些配置数据保存在此处等。

我们将这里全部填充为0,程序也是可以正常执行的。

我写了下面这样一段测试程序:

 1 #include <iostream>2 #include<Windows.h>3 #include<winnt.h>4 5 int main()6 {7     8     HANDLE hFile = CreateFile(L"exepath", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);9 
10     if (hFile)
11     {
12         SetFilePointer(hFile, 0x00000040, NULL, FILE_BEGIN);
13 
14         BYTE buffer[8] = { 1,2,3,4,5,6,7,8 };
15         WriteFile(hFile, buffer, 8, NULL, NULL);
16 
17         CloseHandle(hFile);
18     }    
19 }

e_lfanew字段之后写入了8个字节的数据,程序也是可以照常执行的。

PE 头

DOS头是为了兼容DOS系统而遗留的,DOS头的最后一个字段(e_lfanew)给出了PE头的位置。

PE头部保存着Windows系统加载可执行文件的重要信息(用来装载Win32程序)。它的结构被定义为IMAGE_NT_HEADERS

IMAGE_NT_HEADERS是一个宏,它分为32位和64位版本。

IMAGE_NT_HEADERS64定义如下:

1 typedef struct _IMAGE_NT_HEADERS64 {
2     DWORD Signature;
3     IMAGE_FILE_HEADER FileHeader;
4     IMAGE_OPTIONAL_HEADER64 OptionalHeader;
5 } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

IMAGE_NT_HEADERSIMAGE_NT_SIGNATUREIMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER三部分组成。

Signature

SignatureIMAGE_NT_HEADERS的第一个字段, Signature是PE标识符,标识该文件是否是PE文件。占用4个字节,即 50 45 00 00

Signature在winnt.h中有一个宏定义IMAGE_NT_SIGNATURE

IMAGE_NT_SIGNATURE定义如下:

1 #define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

这个值非常重要。

如果要简单的判断一个文件是否是PE文件,先判断DOS头部的开始字节是否是MZ,再根据DOS头,找到PE头,判断PE头前四个字节是否是“PE\0\0”。如果是的话,就说明该文件是一个有效的PE文件。

文件头IMAGE_FILE_HEADER

IMAGE_FILE_HEADERIMAGE_NT_HEADERS结构体中的一个结构,紧接在PE标识符(Signature字段)的后面。

IMAGE_FILE_HEADER占用20个字节

IMAGE_FILE_HEADER定义如下:

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

字段说明:

Machine:WORD 2字节,该字段表示可执行文件的目标CPU类型,取值如下:

ValueMeaning

IMAGE_FILE_MACHINE_I386

0x014c

x86

IMAGE_FILE_MACHINE_IA64

0x0200

Intel Itanium

IMAGE_FILE_MACHINE_AMD64

0x8664

x64

这里我们编译的是一个x64版本,可以在十六进制编辑器中看到64 86

NumberOfSections:WORD 2字节,该字段表示PE文件的节区的个数.请注意,Windows 加载程序将节区限制为 96。

该字段的值为06 00,即为0x00000006,表示该PE文件的节区有6个。

TimeDataStamp:DWORD 4字节,该字段表示 文件是何时被创建的,这个值是自1970/1/1以来用格林威治时间计算的秒数。

 界面上可以看到这个字段的取值是0x6641BD1E,我们写一个简单的程序计算一下

1  static void Main(string[] args)
2  {
3      var dt = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(0x6641BD1E).AddHours(8);
4      Console.WriteLine(dt.ToString("yyyy-MM-dd HH:mm:ss"));
5  }

PointerToSymbolTable:DWORD 4字节,符号表的偏移量(以字节为单位),如果没有 COFF 符号表,则为零。

NumberOfSymbols:DWORD 4字节,符号表中的符号数。

SizeOfOptionalHeader:WORD 2字节,该字段 指定IMAGE_OPTIONAL_HEADER结构的大小。

我们这里的值是F0 00,也就是0x000000F0。

注意:在计算IMAGE_OPTIONAL_HEADER的大小时,应该取SizeOfOptionalHeader的值,而不是使用sizeof()函数。

因为IMAGE_OPTIONAL_HEADER结构体的大小可能是会改变的。

Characteristics:WORD 2字节,指定文件的类型。取值如下:

ValueMeaning

IMAGE_FILE_RELOCS_STRIPPED

0x0001

Relocation information was stripped from the file. The file must be loaded at its preferred base address. If the base address is not available, the loader reports an error.

IMAGE_FILE_EXECUTABLE_IMAGE

0x0002

The file is executable (there are no unresolved external references).

IMAGE_FILE_LINE_NUMS_STRIPPED

0x0004

COFF line numbers were stripped from the file.

IMAGE_FILE_LOCAL_SYMS_STRIPPED

0x0008

COFF symbol table entries were stripped from file.

IMAGE_FILE_AGGRESIVE_WS_TRIM

0x0010

Aggressively trim the working set. This value is obsolete.

IMAGE_FILE_LARGE_ADDRESS_AWARE

0x0020

The application can handle addresses larger than 2 GB.

IMAGE_FILE_BYTES_REVERSED_LO

0x0080

The bytes of the word are reversed. This flag is obsolete.

IMAGE_FILE_32BIT_MACHINE

0x0100

The computer supports 32-bit words.

IMAGE_FILE_DEBUG_STRIPPED

0x0200

Debugging information was removed and stored separately in another file.

IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP

0x0400

If the image is on removable media, copy it to and run it from the swap file.

IMAGE_FILE_NET_RUN_FROM_SWAP

0x0800

If the image is on the network, copy it to and run it from the swap file.

IMAGE_FILE_SYSTEM

0x1000

The image is a system file.

IMAGE_FILE_DLL

0x2000

The image is a DLL file. While it is an executable file, it cannot be run directly.

IMAGE_FILE_UP_SYSTEM_ONLY

0x4000

The file should be run only on a uniprocessor computer.

IMAGE_FILE_BYTES_REVERSED_HI

0x8000

The bytes of the word are reversed. This flag is obsolete.

 我们这里是22 00 ,也就是0x00000022,这是一个组合值(IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE),代表这是一个可执行文件,且能处理超过2GB的内存。

可选头IMAGE_OPTIONAL_HEADER

可选头IMAGE_OPTIONAL_HEADERIMAGE_NT_HEADERS结构体中的一个结构,紧接在IMAGE_FILE_HEADER类型之后。

可选头是对文件头的一个补充。文件头主要描述文件的相关信息,而可选头主要用来管理PE文件被操作系统装载时所需要的信息。

IMAGE_OPTIONAL_HEADER在现在的大部分资料中都被称为可选头。但该头部实际上是一个必须存在的头。

所以这里应该是最初在翻译时或者其它方面导致的错误,后人一直沿用了。Option也有选项之类的意思 。

IMAGE_OPTIONAL_HEADER的大小在IMAGE_FILE_HEADERSizeOfOptionalHeader字段中给出。

我们这里的值是F0 00,也就是IMAGE_OPTIONAL_HEADER的大小是0x000000F0

IMAGE_OPTIONAL_HEADER(可选头)在IMAGE_FILE_HEADER(文件头)之后

IMAGE_FILE_HEADER的结束位置在0x000010F,那么可选头的起始位置是0x00000110

通过下面的图可以比较清晰的看到

可选头的结束位置是0x00000110 + 0x000000F0 -1 = 0x000001FF,如下图所示

通常情况下,可选头的结尾后面跟的是第一项节表的名称,就是值为.text的位置。

如上图所示:文件偏移0x0000200处的节点名称为".text",也就是说,可选头的结束位置在0x0000200偏移的前一字节,即0x00001FF

下面我们来详细看一下IMAGE_OPTIONAL_HEADER的定义

IMAGE_OPTIONAL_HEADER实际是一个宏,定义如下:

1 #ifdef _WIN64
2 typedef IMAGE_OPTIONAL_HEADER64             IMAGE_OPTIONAL_HEADER;
3 #else
4 typedef IMAGE_OPTIONAL_HEADER32             IMAGE_OPTIONAL_HEADER;
5 #endif

这里我们以x64为例,所以使用的是IMAGE_OPTIONAL_HEADER64类型,定义如下

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

成员说明:

Magic:WORD 2字节,文件的状态类型,它可以是以下值之一

含义

IMAGE_NT_OPTIONAL_HDR_MAGIC

该文件是可执行映像。 此值在 32 位应用程序中定义为 IMAGE_NT_OPTIONAL_HDR32_MAGIC ,在 64 位应用程序中 定义为IMAGE_NT_OPTIONAL_HDR64_MAGIC 。

IMAGE_NT_OPTIONAL_HDR32_MAGIC

0x10b

该文件是可执行映像。

IMAGE_NT_OPTIONAL_HDR64_MAGIC

0x20b

该文件是可执行映像。

IMAGE_ROM_OPTIONAL_HDR_MAGIC

0x107

该文件是 ROM 映像。

 我们这里的值是0B 02,实际取值是0x0000020B,所以这是一个64位的可执行文件。

MajorLinkerVersion:链接器的主版本号。

MinorLinkerVersion:链接器的次版本号

SizeOfCode:代码节的大小(以字节为单位),如果有多个代码块,则为所有此类块的总和。

SizeOfInitializedData:初始化的数据块的大小(以字节为单位),如果有多个已初始化的数据块,则为所有此类块的总和。

SizeOfUninitializedData:未初始化数据块的大小(以字节为单位),如果有多个未初始化的数据块,则为所有此类块的总和。

AddressOfEntryPoint:指向入口点函数(相对于映像基址)的指针。 对于可执行文件,这是起始地址。 对于设备驱动程序,这是初始化函数的地址。 入口点函数对于 DLL 是可选的。 如果没有入口点,则此成员为零。

BaseOfCode:指向代码部分开头(相对于映像基址)的指针。

ImageBase:文件被装入内存后的首选建议装载地址。对于EXE文件来说,通常情况下,该地址就是装载地址;对于DLL文件来说,可能就不是其装入内存后的地址了。

SectionAlignment:节表被装入内存后的对齐值。节表被映射到内存中需要对其的单位。在Win32下,通常情况下,该值为0x1000,也就是4KB大小。Windows操作系统的内存分页一般为4KB。此值必须大于或等于 FileAlignment 成员。

FileAlignment:节表在文件中的对齐值。通常情况下,该值为0x1000或0x200。在文件对齐值为0x1000时,由于与内存对齐值相同,可以加快装载速度。

而文件对齐值为0x200时,可以占用相对较少的磁盘空间。0x200是512字节,通常磁盘的一个扇区即为512字节。

说明:

程序无论是在内存中还是磁盘上,都无法恰好满足SectionAlignment和FileAlignment值的倍数,在不足的情况下需要补0值,这样就导致节与节之间存在了无用的空隙。这些空隙对于病毒之类程序而言就有了可利用的价值。

MajorOperatingSystemVersion:要求最低操作系统的主版本号。

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

MajorImageVersion:可执行文件的主版本号。

MinorImageVersion:可执行文件的次版本号。

MajorSubsystemVersion子系统的主版本号。

MinorSubsystemVersion子系统的次要版本号。

Win32VersionValue:此成员为保留成员,必须为 0。

SizeOfImage:可执行文件装入内存后的总大小。该大小按内存对齐方式(SectionAlignment )对齐。

SizeOfHeaders:整个PE头部的大小。这个PE头部泛指DOS头、PE头、节表的总和大小。舍入为 FileAlignment 成员中指定的值的倍数。

CheckSum:校验和值。对于EXE文件通常为0;对于SYS文件,则必须有一个校验和。

SubSystem:运行此映像所需的子系统。 定义了以下值。

含义

IMAGE_SUBSYSTEM_UNKNOWN

0

未知子系统。

IMAGE_SUBSYSTEM_NATIVE

1

无需子系统 (设备驱动程序和本机系统进程) 。

IMAGE_SUBSYSTEM_WINDOWS_GUI

2

windows 图形用户界面 (GUI) 子系统。

IMAGE_SUBSYSTEM_WINDOWS_CUI

3

Windows 字符模式用户界面 (CUI) 子系统。

IMAGE_SUBSYSTEM_OS2_CUI

5

OS/2 CUI 子系统。

IMAGE_SUBSYSTEM_POSIX_CUI

7

POSIX CUI 子系统。

IMAGE_SUBSYSTEM_WINDOWS_CE_GUI

9

Windows CE系统。

IMAGE_SUBSYSTEM_EFI_APPLICATION

10

可扩展固件接口 (EFI) 应用程序。

IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER

11

具有启动服务的 EFI 驱动程序。

IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER

12

具有运行时服务的 EFI 驱动程序。

IMAGE_SUBSYSTEM_EFI_ROM

13

EFI ROM 映像。

IMAGE_SUBSYSTEM_XBOX

14

Xbox 系统。

IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION

16

启动应用程序。

DllCharacteristics:指定DLL文件的特征,该值大部分时候为0,系统定义了以下值

含义

0x0001

保留。

0x0002

保留。

0x0004

保留。

0x0008

保留。

IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE

0x0040

可以在加载时重新定位 DLL。

IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY

0x0080

强制进行代码完整性检查。 如果设置了此标志,并且某个节仅包含未初始化的数据,请将该节的 IMAGE_SECTION_HEADER 的 PointerToRawData 成员设置为零;否则,映像将无法加载,因为无法验证数字签名。

IMAGE_DLLCHARACTERISTICS_NX_COMPAT

0x0100

该映像与 DEP) (数据执行防护兼容。

IMAGE_DLLCHARACTERISTICS_NO_ISOLATION

0x0200

映像可感知隔离,但不应隔离。

IMAGE_DLLCHARACTERISTICS_NO_SEH

0x0400

映像不使用 SEH) (结构化异常处理。 在此映像中不能调用任何处理程序。

IMAGE_DLLCHARACTERISTICS_NO_BIND

0x0800

请勿绑定映像。

0x1000

保留。

IMAGE_DLLCHARACTERISTICS_WDM_DRIVER

0x2000

WDM 驱动程序。

0x4000

保留。

IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE

0x8000

映像是终端服务器感知的。

SizeOfStackReserve:要为堆栈保留的字节数。 加载时只提交 由 SizeOfStackCommit 成员指定的内存;其余部分一次提供一页,直到达到此保留大小。

SizeOfStackCommit:要为堆栈提交的字节数。

SizeOfHeapReserve:要为本地堆保留的字节数。 加载时仅提交 由 SizeOfHeapCommit 成员指定的内存;其余部分一次提供一页,直到达到此保留大小。

SizeOfHeapCommit:要为本地堆提交的字节数。

LoaderFlags:此成员已过时。

NumberOfRvaAndSizes:数据目录项的个数。该个数在PSDK中有一个宏定义,

定义如下:

1 #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

DataDirectory:数据目录表,由NumberOfRvaAndSizesIMAGE_DATA_DIRECTORY结构体组成。该数组包含输入表、输出表、资源、重定位等数据目录项的RVA(相对虚拟地址)和大小。IMAGE_DATA_DIRECTORY结构体的定义如下:

1 typedef struct _IMAGE_DATA_DIRECTORY {
2     DWORD   VirtualAddress;
3     DWORD   Size;
4 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

成员说明:

VirtualAddress:该目录项的相对虚拟地址的起始值。

Size:目录项的长度。

数据目录中的成员在数据中的索引如下定义所示:

 1 #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory2 #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory3 #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory4 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory5 #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory6 #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table7 #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory8 //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)9 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
10 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
11 #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
12 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
13 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
14 #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
15 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
16 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

在数据目录中,并不是所有的目录项都会有值很多目录项的值都为0。因为很多目录项的值为0,所以说数据目录项是可选的。

可选头的成员介绍完毕了,因为这里涉及的成员较多,我就没有一个一个在十六进制 编辑器中进行匹配了。后面我们在使用代码进行读取的时候,再进行展示 。

IMAGE_SECTION_HEADER详解

节表的位置在IMAGE_OPTIONAL_HEADER(可选头)后面。节表中的每个IMAGE_SECTION_HEADER中都存放着可执行文件被映射到内存中所在位置的信

息,节的个数由IMAGE_FILE_HEADER中的NumberOfSections给出。这个值我们在前面读取过,它是06 00,也就是0x00000006,所以该文件有6个节表。

IMAGE_SECTION_HEADER的大小为40字节,所以节表总共占用240个字节。

所以节表的起始位置在0x00000200,终止位置在0x00000200 + 0x000000F0(240) -1 = 0x000002EF处。如下图所示

 IMAGE_SECTION_HEADER结构体的定义如下

 1 typedef struct _IMAGE_SECTION_HEADER {2     BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];3     union {4             DWORD   PhysicalAddress;5             DWORD   VirtualSize;6     } Misc;7     DWORD   VirtualAddress;8     DWORD   SizeOfRawData;9     DWORD   PointerToRawData;
10     DWORD   PointerToRelocations;
11     DWORD   PointerToLinenumbers;
12     WORD    NumberOfRelocations;
13     WORD    NumberOfLinenumbers;
14     DWORD   Characteristics;
15 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

成员介绍:

Name:节表项的名称,节的名称用ASCII编码来保存。节名称的长度为IMAGE_SIZE_SHORT_NAME(8个字节),这是一个宏,定义如下:

1 #define IMAGE_SIZEOF_SHORT_NAME              8

如果字符串长度正好为 8 个字符,则没有终止字符。否则最后一个字符为终止字符。 对于较长的名称,此成员包含 (/) 的正斜杠,后跟十进制数的 ASCII 表示形式,该数字是字符串表中的偏移量。 可执行文件不使用字符串表,并且不支持长度超过 8 个字符的节名称。

我们这里第一个节表项前面8个字节的数据为2E 74 65 78 74 00 00 00,对应的字符为 “.text”

Misc

Misc是一个联合体,它有以下两个成员

Misc.PhysicalAddress

文件地址。

Misc.VirtualSize

加载到内存中的节的总大小(以字节为单位)。 如果此值大于 SizeOfRawData 成员,则节将填充零。

此字段仅对可执行文件有效,对于对象文件,应设置为 0。

SizeOfRawData:该值为数据实际的节表项大小。 此值必须是 IMAGE_OPTIONAL_HEADER 结构的 FileAlignment 成员的倍数。 如果此值小于 VirtualSize 成员,则部分的其余部分将填充零。 如果节仅包含未初始化的数据,则成员为零。

PointerToRawData:该节表项在磁盘文件上的偏移地址。此值必须是 IMAGE_OPTIONAL_HEADER 结构的 FileAlignment 成员的倍数。 如果节仅包含未初始化的数据,请将此成员设置为零。

PointerToRelocations

A file pointer to the beginning of the relocation entries for the section. If there are no relocations, this value is zero.

这里的翻译总感觉差点意思 ,直接贴MSDN上的英文原版。

PointerToLinenumbers

A file pointer to the beginning of the line-number entries for the section. If there are no COFF line numbers, this value is zero.

NumberOfRelocations

该节表项重定向的条数。如果是可执行文件,该值应该设置为0

NumberOfLinenumbers

The number of line-number entries for the section.

Characteristics

节表项的属性,定义了以下值:

标志含义

0x00000000

保留。

0x00000001

保留。

0x00000002

保留。

0x00000004

保留。

IMAGE_SCN_TYPE_NO_PAD

0x00000008

不应将节填充到下一个边界。 此标志已过时,由 IMAGE_SCN_ALIGN_1BYTES 取代。

0x00000010

保留。

IMAGE_SCN_CNT_CODE

0x00000020

节包含可执行代码。

IMAGE_SCN_CNT_INITIALIZED_DATA

0x00000040

节包含初始化数据。

IMAGE_SCN_CNT_UNINITIALIZED_DATA

0x00000080

节包含未初始化数据。

IMAGE_SCN_LNK_OTHER

0x00000100

保留。

IMAGE_SCN_LNK_INFO

0x00000200

节包含注释或其他信息。 它仅对对象文件有效。

0x00000400

保留。

IMAGE_SCN_LNK_REMOVE

0x00000800

节不会成为映像的一部分。 它仅对对象文件有效。

IMAGE_SCN_LNK_COMDAT

0x00001000

节包含 COMDAT 数据。 它仅对对象文件有效。

0x00002000

保留。

IMAGE_SCN_NO_DEFER_SPEC_EXC

0x00004000

重置本部分的 TLB 条目中处理位的推理异常。

IMAGE_SCN_GPREL

0x00008000

节包含通过全局指针引用的数据。

0x00010000

保留。

IMAGE_SCN_MEM_PURGEABLE

0x00020000

保留。

IMAGE_SCN_MEM_LOCKED

0x00040000

保留。

IMAGE_SCN_MEM_PRELOAD

0x00080000

保留。

IMAGE_SCN_ALIGN_1BYTES

0x00100000

在 1 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_2BYTES

0x00200000

在 2 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_4BYTES

0x00300000

在 4 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_8BYTES

0x00400000

对齐 8 字节边界上的数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_16BYTES

0x00500000

在 16 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_32BYTES

0x00600000

在 32 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_64BYTES

0x00700000

在 64 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_128BYTES

0x00800000

在 128 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_256BYTES

0x00900000

在 256 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_512BYTES

0x00A00000

在 512 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_1024BYTES

0x00B00000

在 1024 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_2048BYTES

0x00C00000

在 2048 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_4096BYTES

0x00D00000

在 4096 字节边界上对齐数据。 它仅对对象文件有效。

IMAGE_SCN_ALIGN_8192BYTES

0x00E00000

对齐 8192 字节边界上的数据。 它仅对对象文件有效。

IMAGE_SCN_LNK_NRELOC_OVFL

0x01000000

节包含扩展重定位。 节的重定位计数超过了节标头中为其保留的 16 位。 如果节标题中的 NumberOfRelocations 字段0xffff,则实际重定位计数将存储在第一次重定位的 VirtualAddress 字段中。 如果设置了 IMAGE_SCN_LNK_NRELOC_OVFL,并且节中的重定位数少于 0xffff,则为错误。

IMAGE_SCN_MEM_DISCARDABLE

0x02000000

可以根据需要丢弃节。

IMAGE_SCN_MEM_NOT_CACHED

0x04000000

无法缓存节。

IMAGE_SCN_MEM_NOT_PAGED

0x08000000

该节不能分页。

IMAGE_SCN_MEM_SHARED

0x10000000

可以在内存中共享节。

IMAGE_SCN_MEM_EXECUTE

0x20000000

节可以作为代码执行。

IMAGE_SCN_MEM_READ

0x40000000

可以读取节。

IMAGE_SCN_MEM_WRITE

0x80000000

可以写入节。

测试文件

本文所用到的exe文件,可以在这里下载

参考资料

pe format

PE Format - Win32 apps | Microsoft Learn

PE-Portable-executable - aldeid

endian

endian enum | Microsoft Learn

IMAGE_FILE_HEADER介绍

IMAGE_FILE_HEADER (winnt.h) - Win32 apps | Microsoft Learn

IMAGE_OPTIONAL_HEADER介绍

IMAGE_OPTIONAL_HEADER64 (winnt.h) - Win32 apps | Microsoft Learn

IMAGE_SECTION_HEADER介绍

IMAGE_SECTION_HEADER (winnt.h) - Win32 apps | Microsoft Learn

这篇关于PE文件结构详解(非常详细)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125702

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

LabVIEW FIFO详解

在LabVIEW的FPGA开发中,FIFO(先入先出队列)是常用的数据传输机制。通过配置FIFO的属性,工程师可以在FPGA和主机之间,或不同FPGA VIs之间进行高效的数据传输。根据具体需求,FIFO有多种类型与实现方式,包括目标范围内FIFO(Target-Scoped)、DMA FIFO以及点对点流(Peer-to-Peer)。 FIFO类型 **目标范围FIFO(Target-Sc

019、JOptionPane类的常用静态方法详解

目录 JOptionPane类的常用静态方法详解 1. showInputDialog()方法 1.1基本用法 1.2带有默认值的输入框 1.3带有选项的输入对话框 1.4自定义图标的输入对话框 2. showConfirmDialog()方法 2.1基本用法 2.2自定义按钮和图标 2.3带有自定义组件的确认对话框 3. showMessageDialog()方法 3.1

沁恒CH32在MounRiver Studio上环境配置以及使用详细教程

目录 1.  RISC-V简介 2.  CPU架构现状 3.  MounRiver Studio软件下载 4.  MounRiver Studio软件安装 5.  MounRiver Studio软件介绍 6.  创建工程 7.  编译代码 1.  RISC-V简介         RISC就是精简指令集计算机(Reduced Instruction SetCom