NT Header(Ⅱ)
NT Header是主要包含三大部分内容PE标志(PE Signature),PE文件头(PE_HEADER),PE可选头(OPTION_PE_HEADER)
图片转自:https://blog.csdn.net/evileagle/article/details/11693499
PE可选头
PE可选头作为NT头的最后一部分,也是定义字段最多,占据存储最多的一部分结构体定义的头部信息,在定义的结构体中一般分为两种32位PE可选头或者64位可选头
32位PE可选头,定义结构体为typedef struct _IMAGE_NT_HEADERS
占据大小为224Byte,31个字段
64位PE可选头,定义结构体为typedef struct _IMAGE_NT_HEADERS64
占据大小为240Byte
32位PE可选头
typedef struct _IMAGE_OPTIONAL_HEADER
{
+0x00 WORD Magic;
+0x02 BYTE MajorLinkerVersion;
+0x03 BYTE MinorLinkerVersion;
+0x04 DWORD SizeOfCode;
+0x08 DWORD SizeOfInitializedData;
+0x0C DWORD SizeOfUninitializedData;
+0x10 DWORD AddressOfEntryPoint; // 程序执行入口RVA
+0x14 DWORD BaseOfCode; // 代码的区块的起始RVA
+0x18 DWORD BaseOfData; // 数据的区块的起始RVA
+0x1C DWORD ImageBase; // 程序的首选装载地址
+0x20 DWORD SectionAlignment;
+0x24 DWORD FileAlignment;
+0x28 WORD MajorOperatingSystemVersion;
+0x2A WORD MinorOperatingSystemVersion;
+0x2C WORD MajorImageVersion;
+0x2E WORD MinorImageVersion;
+0x30 WORD MajorSubsystemVersion;
+0x32 WORD MinorSubsystemVersion;
+0x34 DWORD Win32VersionValue;
+0x38 DWORD SizeOfImage;
+0x3C DWORD SizeOfHeaders;
+0x40 DWORD CheckSum;
+0x44 WORD Subsystem;
+0x46 WORD DllCharacteristics;
+0x48 DWORD SizeOfStackReserve;
+0x4C DWORD SizeOfStackCommit;
+0x50 DWORD SizeOfHeapReserve;
+0x54 DWORD SizeOfHeapCommit;
+0x58 DWORD LoaderFlags;
+0x5C DWORD NumberOfRvaAndSizes;
+0x60 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
PE-Option-Header定义重要关键字段[12个]详解(共计224Byte)
字段 | 含义 | 大小 | 成员位置 |
---|---|---|---|
Magic | 识别可执行程序32位或64位 | 2Byte | 1 |
AddressOfEntryPoint | 程序入口地址 | 4Byte | 7 |
ImageBase | 内存镜像基址 | 4Byte | 10 |
SectionAlignment | 内存对齐 | 4Byte | 11 |
FileAlignment | 文件对齐 | 4Byte | 12 |
SizeOfImage | 内存中整个PE文件,就是PE文件展开后大小,是内存对齐整数倍 | 2Byte | 20 |
SizeOfHeaders | PE文件的所有头部之和大小,是文件对齐整数倍 | 4Byte | 21 |
CheckSum | 系统重要的dll会有,判断文件是否被进行修改 | 4Byte | 22 |
SizeOfStackReserve | 初始化保留栈的大小 | 4Byte | 25 |
SizeOfStackCommit | 初始化时实际提交栈的大小 | 4Byte | 26 |
SizeOfHeapReserve | 初始化保留堆的大小 | 4Byte | 27 |
SizeOfHeapCommit | 初始化时实际提交堆的大小 | 4Byte | 28 |
Magic字段补充说明
常见的一些Magic字段取值说明,均为小端存储 PE32(10B): [0B 10] PE32+(20B): [0B 20]
程序入口补充说明
真实的入口地址 ==(内存镜像基址)ImageBase+(程序入口地址)AddressOfEntryPoint
其中ImageBase属于相对虚拟地址(RVA),在内存中开始位置距离.程序的首选装载地址,如代码区中注释部分内容所示
对齐相关内容补充
对齐主要包括两大部分内存对齐以及文件对齐
- 内存对齐
内存对齐原因:
1. 为了程序可移植性,在某些平台只能在特定的地址处访问特定类型的数据,为了更好的兼容不同平台
2. 数据结构(尤其是栈)应该尽可能在自然边界上对齐。
3. 为了访问未对齐的内存,处理器需要作两次内存访问;而未对齐的内存仅需要访问一次
内存对齐原理:先查看C语言代码及其执行结果
#include <stdio.h>
struct test
{
char x1;
short x2;
float x3;
char x4;
}TEST;
struct test2
{
float x1;
short x2;
char x3;
char x4;
}TEST2;
void main()
{
test t1;
test2 t2;
printf("this test size of : %d\n",sizeof(t1));
printf("this test start address : %x\n",&t1);
printf("this test2 size of : %d\n",sizeof(t2));
printf("this test2 start address : %x\n",&t2);
}
执行结果如下图:
可以从结果发现,两个结构体的成员变量都是只有2个char类型,1个short类型,1个float类型,但是得到的结构体大小不一致,这就是由于内存对齐导致的结果
为了探究内存对齐原理,可以通过查看内存地址的数据,以及反汇编调试进行,为了方便,我打出来了相关的存储地址分别为0x0019ff24与0x0019ff1c,通过打断点进行调试分析,并对其进行赋值来观察内存使用情况。
对t1变量在内存中的地址分配分析
首先 根据输出的内存起始地址,找到变量,在执行t1变量声明时,占用了12Byte,并且初始化为CC,就是汉字的"烫"
其次 逐个对t1变量的成员赋值,并同时观察内存数据
当对x2变量进行赋值时发现,并未按照预期想的,紧挨x1变量内存地址赋值,而是跳过1Byte进行赋值,char明明只是占1Byte,但是通过调试容易发现,好像是感觉上占2Byte
但是 又发现对x3变量赋值时,并未跳转,紧挨这赋值
最后同样的,x4成员赋值也是正常的。
通过上面追踪分析,可以发现,这就是产生结果为12Byte的原因。
同样的再次跟踪t2变量,及其成员赋值过程
首先 根据输出的内存起始地址,找到变量,在执行t2变量声明时,占用了8Byte,并且初始化为CC,就是汉字的"烫"
由于第一个成员变为float类型所以占据4Byte
第二个成员变为short类型所以占据2Byte,且紧挨这第一个成员变量地址赋值
由于第三个成员变为char类型紧挨第二个赋值为B
最后一个成员依旧紧挨赋值为B
通过对比,不难发现,变量声明的顺序会导致结构体大小不一致,第一个结构体变量明显存在未使用的内存空间,从而导致空间浪费。这些都是由于内存对齐机制所导致,机制原理主要是起始的内存地址必须是该类型变量所占内存大小的整数倍,例如t1的x2成员为short占2Byte,所以需要从第2,4,6等2的整数倍地方开始存储数据内存,这就是内存对齐机制。
- 文件对齐
文件对齐,主要是用来控制PE扩展头部,后面每一个Section的磁盘对齐的因子,必须遵守扩展为该因子的整数倍。
综上所述PE扩展文件头32位,大小固定且为224Byte,共31大成员字段。其中关键字段成员为以上12大字段成员内容。
继续以Kernel32.dll进行分析
字段 | 数值(小端存储) | 含义 |
---|---|---|
Magic | 0B01 | PE为32位 |
AddressOfEntryPoint | 705F0100 | 程序入口地址确定为0010F570 |
ImageBase | 0000806B | 内存镜像基址为6B800000 |
SectionAlignment | 00000100 | 内存对齐大小单位为0x00010000H |
FileAlignment | 00100000 | 文件对齐大小单位为0x00001000H |
SizeOfImage | 00000000 | PE文件内存展开后大小为0x00000000H |
SizeOfHeaders | 00000E00 | PE文件的所有头部之和大小为0x000E0000H |
CheckSum | 00100000 | 校验和为00100000 |
SizeOfStackReserve | 03004041 | 初始化保留栈的大小0x41400003 |
SizeOfStackCommit | 00000400 | 实际提交栈的大小为0x00400000 |
SizeOfHeapReserve | 00100000 | 初始化保留堆的大小0x00001000 |
SizeOfHeapCommit | 00001000 | 实际提交堆的大小为0x00100000 |