PE文件格式解析学习笔记(3)---PE Header(IMAGE_NT_HEADERS部分)

NT Header(Ⅱ)

NT Header是主要包含三大部分内容PE标志(PE Signature),PE文件头(PE_HEADER),PE可选头(OPTION_PE_HEADER)

PE文件格式概览

图片转自: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 

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变量声明
其次 逐个对t1变量的成员赋值,并同时观察内存数据
对x1成员进行赋值
当对x2变量进行赋值时发现,并未按照预期想的,紧挨x1变量内存地址赋值,而是跳过1Byte进行赋值,char明明只是占1Byte,但是通过调试容易发现,好像是感觉上占2Byte
对x2成员进行赋值
但是 又发现对x3变量赋值时,并未跳转,紧挨这赋值
对x3成员进行赋值
最后同样的,x4成员赋值也是正常的。
对x4成员进行赋值

通过上面追踪分析,可以发现,这就是产生结果为12Byte的原因。

同样的再次跟踪t2变量,及其成员赋值过程

首先 根据输出的内存起始地址,找到变量,在执行t2变量声明时,占用了8Byte,并且初始化为CC,就是汉字的"烫"
t2变量初始化
由于第一个成员变为float类型所以占据4Byte
对x1成员进行赋值
第二个成员变为short类型所以占据2Byte,且紧挨这第一个成员变量地址赋值
对x2成员进行赋值
由于第三个成员变为char类型紧挨第二个赋值为B
对x3成员进行赋值
最后一个成员依旧紧挨赋值为B
对x4成员进行赋值

通过对比,不难发现,变量声明的顺序会导致结构体大小不一致,第一个结构体变量明显存在未使用的内存空间,从而导致空间浪费。这些都是由于内存对齐机制所导致,机制原理主要是起始的内存地址必须是该类型变量所占内存大小的整数倍,例如t1的x2成员为short占2Byte,所以需要从第2,4,6等2的整数倍地方开始存储数据内存,这就是内存对齐机制。

  • 文件对齐
    文件对齐,主要是用来控制PE扩展头部,后面每一个Section的磁盘对齐的因子,必须遵守扩展为该因子的整数倍。
综上所述PE扩展文件头32位,大小固定且为224Byte,共31大成员字段。其中关键字段成员为以上12大字段成员内容。

继续以Kernel32.dll进行分析


PE扩展头部
字段 数值(小端存储) 含义
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

你可能感兴趣的:(PE文件格式解析学习笔记(3)---PE Header(IMAGE_NT_HEADERS部分))