Windows编程系列: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      // MZ
 3  
 4 typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
 5     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

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

 

可选头的结束位置是0x000001100x000000F0 -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 Directory
 2 #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
 3 #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
 4 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
 5 #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
 6 #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
 7 #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
 8 //      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

https://learn.microsoft.com/en-us/windows/win32/debug/pe-format

https://www.aldeid.com/wiki/PE-Portable-executable

endian

https://learn.microsoft.com/en-us/cpp/standard-library/bit-enum?view=msvc-170

IMAGE_FILE_HEADER介绍

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header

IMAGE_OPTIONAL_HEADER介绍

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64

IMAGE_SECTION_HEADER介绍

https://learn.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-image_section_header

posted @ 2024-05-13 16:11  zhaotianff  阅读(97)  评论(0编辑  收藏  举报