最近在参考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字节的内容0x5A4D(ASCII的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_HEADER的e_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_HEADERS由IMAGE_NT_SIGNATURE、IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER三部分组成。
Signature
Signature是IMAGE_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_HEADER是IMAGE_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类型,取值如下:
Value | Meaning |
---|---|
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字节,指定文件的类型。取值如下:
Value | Meaning |
---|---|
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_HEADER是IMAGE_NT_HEADERS结构体中的一个结构,紧接在IMAGE_FILE_HEADER类型之后。
可选头是对文件头的一个补充。文件头主要描述文件的相关信息,而可选头主要用来管理PE文件被操作系统装载时所需要的信息。
IMAGE_OPTIONAL_HEADER在现在的大部分资料中都被称为可选头。但该头部实际上是一个必须存在的头。
所以这里应该是最初在翻译时或者其它方面导致的错误,后人一直沿用了。Option也有选项之类的意思 。
IMAGE_OPTIONAL_HEADER的大小在IMAGE_FILE_HEADER的SizeOfOptionalHeader字段中给出。
我们这里的值是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:数据目录表,由NumberOfRvaAndSizes个IMAGE_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:
节表项的属性,定义了以下值:
测试文件
本文所用到的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