NT Header(Ⅱ)
NT Header是主要包含三大部分內(nèi)容PE標(biāo)志(PE Signature),PE文件頭(PE_HEADER),PE可選頭(OPTION_PE_HEADER)
圖片轉(zhuǎn)自:https://blog.csdn.net/evileagle/article/details/11693499
PE可選頭
PE可選頭作為NT頭的最后一部分,也是定義字段最多,占據(jù)存儲(chǔ)最多的一部分結(jié)構(gòu)體定義的頭部信息,在定義的結(jié)構(gòu)體中一般分為兩種32位PE可選頭或者64位可選頭
32位PE可選頭,定義結(jié)構(gòu)體為typedef struct _IMAGE_NT_HEADERS占據(jù)大小為224Byte,31個(gè)字段
64位PE可選頭,定義結(jié)構(gòu)體為typedef struct _IMAGE_NT_HEADERS64占據(jù)大小為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; // 程序執(zhí)行入口RVA
+0x14 DWORD BaseOfCode; // 代碼的區(qū)塊的起始RVA
+0x18 DWORD BaseOfData; // 數(shù)據(jù)的區(qū)塊的起始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定義重要關(guān)鍵字段[12個(gè)]詳解(共計(jì)224Byte)
| 字段 | 含義 | 大小 | 成員位置 |
|---|---|---|---|
| Magic | 識(shí)別可執(zhí)行程序32位或64位 | 2Byte | 1 |
| AddressOfEntryPoint | 程序入口地址 | 4Byte | 7 |
| ImageBase | 內(nèi)存鏡像基址 | 4Byte | 10 |
| SectionAlignment | 內(nèi)存對(duì)齊 | 4Byte | 11 |
| FileAlignment | 文件對(duì)齊 | 4Byte | 12 |
| SizeOfImage | 內(nèi)存中整個(gè)PE文件,就是PE文件展開后大小,是內(nèi)存對(duì)齊整數(shù)倍 | 2Byte | 20 |
| SizeOfHeaders | PE文件的所有頭部之和大小,是文件對(duì)齊整數(shù)倍 | 4Byte | 21 |
| CheckSum | 系統(tǒng)重要的dll會(huì)有,判斷文件是否被進(jìn)行修改 | 4Byte | 22 |
| SizeOfStackReserve | 初始化保留棧的大小 | 4Byte | 25 |
| SizeOfStackCommit | 初始化時(shí)實(shí)際提交棧的大小 | 4Byte | 26 |
| SizeOfHeapReserve | 初始化保留堆的大小 | 4Byte | 27 |
| SizeOfHeapCommit | 初始化時(shí)實(shí)際提交堆的大小 | 4Byte | 28 |
Magic字段補(bǔ)充說明
常見的一些Magic字段取值說明,均為小端存儲(chǔ) PE32(10B): [0B 10] PE32+(20B): [0B 20]
程序入口補(bǔ)充說明
真實(shí)的入口地址 ==(內(nèi)存鏡像基址)ImageBase+(程序入口地址)AddressOfEntryPoint
其中ImageBase屬于相對(duì)虛擬地址(RVA),在內(nèi)存中開始位置距離.程序的首選裝載地址,如代碼區(qū)中注釋部分內(nèi)容所示
對(duì)齊相關(guān)內(nèi)容補(bǔ)充
對(duì)齊主要包括兩大部分內(nèi)存對(duì)齊以及文件對(duì)齊
- 內(nèi)存對(duì)齊
內(nèi)存對(duì)齊原因:
1. 為了程序可移植性,在某些平臺(tái)只能在特定的地址處訪問特定類型的數(shù)據(jù),為了更好的兼容不同平臺(tái)
2. 數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能在自然邊界上對(duì)齊。
3. 為了訪問未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而未對(duì)齊的內(nèi)存僅需要訪問一次
內(nèi)存對(duì)齊原理:先查看C語言代碼及其執(zhí)行結(jié)果
#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);
}
執(zhí)行結(jié)果如下圖:

可以從結(jié)果發(fā)現(xiàn),兩個(gè)結(jié)構(gòu)體的成員變量都是只有2個(gè)char類型,1個(gè)short類型,1個(gè)float類型,但是得到的結(jié)構(gòu)體大小不一致,這就是由于內(nèi)存對(duì)齊導(dǎo)致的結(jié)果
為了探究內(nèi)存對(duì)齊原理,可以通過查看內(nèi)存地址的數(shù)據(jù),以及反匯編調(diào)試進(jìn)行,為了方便,我打出來了相關(guān)的存儲(chǔ)地址分別為0x0019ff24與0x0019ff1c,通過打斷點(diǎn)進(jìn)行調(diào)試分析,并對(duì)其進(jìn)行賦值來觀察內(nèi)存使用情況。
對(duì)t1變量在內(nèi)存中的地址分配分析
首先 根據(jù)輸出的內(nèi)存起始地址,找到變量,在執(zhí)行t1變量聲明時(shí),占用了12Byte,并且初始化為CC,就是漢字的"燙"

其次 逐個(gè)對(duì)t1變量的成員賦值,并同時(shí)觀察內(nèi)存數(shù)據(jù)

當(dāng)對(duì)x2變量進(jìn)行賦值時(shí)發(fā)現(xiàn),并未按照預(yù)期想的,緊挨x1變量內(nèi)存地址賦值,而是跳過1Byte進(jìn)行賦值,char明明只是占1Byte,但是通過調(diào)試容易發(fā)現(xiàn),好像是感覺上占2Byte

但是 又發(fā)現(xiàn)對(duì)x3變量賦值時(shí),并未跳轉(zhuǎn),緊挨這賦值

最后同樣的,x4成員賦值也是正常的。

通過上面追蹤分析,可以發(fā)現(xiàn),這就是產(chǎn)生結(jié)果為12Byte的原因。
同樣的再次跟蹤t2變量,及其成員賦值過程
首先 根據(jù)輸出的內(nèi)存起始地址,找到變量,在執(zhí)行t2變量聲明時(shí),占用了8Byte,并且初始化為CC,就是漢字的"燙"

由于第一個(gè)成員變?yōu)閒loat類型所以占據(jù)4Byte

第二個(gè)成員變?yōu)閟hort類型所以占據(jù)2Byte,且緊挨這第一個(gè)成員變量地址賦值

由于第三個(gè)成員變?yōu)閏har類型緊挨第二個(gè)賦值為B

最后一個(gè)成員依舊緊挨賦值為B

通過對(duì)比,不難發(fā)現(xiàn),變量聲明的順序會(huì)導(dǎo)致結(jié)構(gòu)體大小不一致,第一個(gè)結(jié)構(gòu)體變量明顯存在未使用的內(nèi)存空間,從而導(dǎo)致空間浪費(fèi)。這些都是由于內(nèi)存對(duì)齊機(jī)制所導(dǎo)致,機(jī)制原理主要是起始的內(nèi)存地址必須是該類型變量所占內(nèi)存大小的整數(shù)倍,例如t1的x2成員為short占2Byte,所以需要從第2,4,6等2的整數(shù)倍地方開始存儲(chǔ)數(shù)據(jù)內(nèi)存,這就是內(nèi)存對(duì)齊機(jī)制。
- 文件對(duì)齊
文件對(duì)齊,主要是用來控制PE擴(kuò)展頭部,后面每一個(gè)Section的磁盤對(duì)齊的因子,必須遵守?cái)U(kuò)展為該因子的整數(shù)倍。
綜上所述PE擴(kuò)展文件頭32位,大小固定且為224Byte,共31大成員字段。其中關(guān)鍵字段成員為以上12大字段成員內(nèi)容。
繼續(xù)以Kernel32.dll進(jìn)行分析

| 字段 | 數(shù)值(小端存儲(chǔ)) | 含義 |
|---|---|---|
| Magic | 0B01 | PE為32位 |
| AddressOfEntryPoint | 705F0100 | 程序入口地址確定為0010F570 |
| ImageBase | 0000806B | 內(nèi)存鏡像基址為6B800000 |
| SectionAlignment | 00000100 | 內(nèi)存對(duì)齊大小單位為0x00010000H |
| FileAlignment | 00100000 | 文件對(duì)齊大小單位為0x00001000H |
| SizeOfImage | 00000000 | PE文件內(nèi)存展開后大小為0x00000000H |
| SizeOfHeaders | 00000E00 | PE文件的所有頭部之和大小為0x000E0000H |
| CheckSum | 00100000 | 校驗(yàn)和為00100000 |
| SizeOfStackReserve | 03004041 | 初始化保留棧的大小0x41400003 |
| SizeOfStackCommit | 00000400 | 實(shí)際提交棧的大小為0x00400000 |
| SizeOfHeapReserve | 00100000 | 初始化保留堆的大小0x00001000 |
| SizeOfHeapCommit | 00001000 | 實(shí)際提交堆的大小為0x00100000 |