天天看點

WINDOWS+PE權威指南讀書筆記(3)

目錄

32 位系統下的 PE 結構:

PE檔案結構及各部分解析:

32 位系統下的 PE 結構:

DOS MZ 頭中有一個字段非常重要,即 IMAGE DOS_HEADER.e lfanew,沒有它作業系統就定位不到标準的 PE 頭部,可執行程式也就會被作業系統認為是非法的 PE 映像。

1. 定位标準PE頭:

由于 DOS Stub 的長度不國定,導緻了DOS 頭也不是一個固定大小的資料結構。是以定位PE頭要用到字段e_lfanew ,該字段的值是一個相對偏移量,絕對定位時需要加上 DOS MZ 頭的基位址。

通過以下公式可以得出 PE 頭的絕對位置:

PE_start=DOS MZ 基位址+IMAGE_DOS_HEADER.e_lfanew

=13B7:0100+000000B0H

=13B7:01B0

2. PE 檔案結構:

32 位系統下的 PE 結構可以劃分如圖所示:

WINDOWS+PE權威指南讀書筆記(3)

每個節的描述資訊則是個固定值,共 40 個位元組,節表是由不确定數量的節描述資訊組成的,其大小等于節的數量 X40,節的數量由字段 IMAGE_ FILE_HEADER.NumberOfSections 來定義。DOS Stub 和節内容都是大小不确定的。

節表是 PE 中所有節的目錄,每個目錄都是一個“BookStore”,其位元組碼就是節内容。它按照目錄裡的指針指向的位址,分别将節的位元組碼在檔案空間中排列起來,進而組成了一個完整的 PE 檔案。PE 檔案頭部等于DOS 頭 +PE 頭。

PE檔案結構及各部分解析:

WINDOWS+PE權威指南讀書筆記(3)

如圖所示,一個标準的 PE 檔案一般由四大部分組成;

口DOS 頭

口PE 頭 (IMAGE_NT_HEADERS)

口節表〈多個 IMAGE_ SECTION HEADER 結構)

口 節内容

其中,PE 頭的資料結構最為複雜。簡單來說,PE 頭包含:

口 4 個位元組的辨別符号 (Signature)

口 20 個位元組的基本頭資訊 (IMAGE_FILE_HEADER)

口216 個位元組的擴充頭資訊 (IMAGE OPTIONAL_ HEADER32)

說明 如果按照“頭部+身體”的資訊組織方式來看:

PE 檔案頭部 =DOS 頭 +PE 頭+節表

PE 檔案身體=節内容

PE 檔案頭部資料結構解析:

DOS頭:

WINDOWS+PE權威指南讀書筆記(3)

DOS MZ 頭IMAGE_DOS_HEADER:

WINDOWS+PE權威指南讀書筆記(3)

PE頭:

WINDOWS+PE權威指南讀書筆記(3)

PE 頭辨別 Signature:

緊跟在 DOS Stub 後面的是 PE 頭辨別 Signature。與大部分檔案格式的頭部結構一樣,PE頭部資訊中有一個四位元組的辨別,該辨別位于指針 IMAGE_DOS_HEADER.e_lfanew 指向的位置。其内容固定,對應于 ASCII 碼的字元串“PE\0\0 ”。

标準 PE 頭IMAGE_FILE_HEADER:

标準PE頭IMAGE_FILE_HEADER緊跟在PE頭辨別後,即位于IMAGE_DOS_HEADER 的e_lfanew值+4的位置。由此位置開始的20個位元組為資料結構标準PE 頭IMAGE_FILE_HEADER 的内容。

該結構在微軟的官方文檔中被稱為标準通用對象檔案格式(Common Object File Format,COFF) 頭。它記錄了 PE 檔案的全局屬性,如該 PE 檔案運作的平台、PE 檔案類型(是 EXE 檔案還是 DLL 檔案)、檔案中存在的節的總數等。

其詳細定義如下:

WINDOWS+PE權威指南讀書筆記(3)

該結構常用于判斷 PE 檔案是 EXE 類型還是 DLL 類型,不但可以通過該結構得到 PE 檔案中節的總量,還可以當成對節區資訊進行周遊操作時的循環次數。

擴充PE 頭IMAGE_OPTIONAL_HEADER32:

盡管從名字上看好像該部分資料是可選 (optional) 的,但在 PE 檔案結構中,它卻有着比标準 PE 頭更多的内容,讓人感覺似乎它才是真正的 PE 頭。

檔案執行時的入口位址、檔案被作業系統裝入記憶體後的預設基位址,以及節在磁盤和記憶體中的對齊機關等資訊均可以在此結構中找到。對該結構中的某些數值的随意改動可能會造成PE 檔案的加載或運作失敗。

其詳細定義如下:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

PE頭IMAGE_NT_HEADERS:

這個結構是廣義上的PE 頭,在标準的PE檔案中其大小為 456 個位元組。它是 3.4.2 節、3.4.3 節和 3.4.4 節中提到的三個資料結構的組合,即 IMAGE_NT_HEADERS=4 個位元組的 PE辨別 +IMAGE _ FILE_HEADER+IMAGE_OPTIONAL_ HEADER32。

WINDOWS+PE權威指南讀書筆記(3)

該結構的詳細定義如下:

與 DOS 頭一樣,PE 頭開始也是一個标志,用一個雙字的“PE\0\0”來命名,這也是 PE頭的由來。

WINDOWS+PE權威指南讀書筆記(3)

資料目錄項 IMAGE_DATA_DIRECTORY:

IMAGE_OPTIONAL HEADER32 (擴充PE 頭) 結構的最後一個字段為 DataDirectory 。該字段定義了 PE 檔案中出現的所有不同類型的資料的目錄資訊。

如前所述,應用程式中的資料被按照用途分成很多種類,如導出表、導入表、資源、重定位表等。

在記憶體中,這些資料被作業系統以頁為機關組織起來,并賦以不同的通路屬性;

在檔案中,這些資料也同樣被組織起來,按照不同類别分别存放在檔案的指定位置。

該結構就是用來描述這些不同類别的資料在檔案(和記憶體) 中的位置及大小的。

IMAGE_DATA_DIRECTORY 結構隻有兩個字段,結構具體定義如下:

WINDOWS+PE權威指南讀書筆記(3)

從 Windows NT 3.1作業系統開始到現在,該資料目錄中定義的資料類型一直是 16 種。 兩個字段依次為VirtualAddress 和 isize。如下圖所示,總的資料目錄一共由 16 個相同的 IMAGE_DATA_DIRECTORY 結構連續排列在一起組成。

WINDOWS+PE權威指南讀書筆記(3)

這 16 個元組的數組每一項均代表 PE 中的某一個類型的資料,各資料類型詳見下表:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

如果想在 PE 檔案中尋找特定類型的資料,就需要從該結構開始。比如,要想檢視 PE 中都調用了哪些動态連結庫的函數,則需要從資料目錄表的第 2 個元素〈數組編号為1) 的 IMAGE_DATIA_DIRECTORY 結構擷取導和人表在檔案中的起始位置和大小,然後再根據 VirtualAddress_1 字段位址指向的位置找到導入表相關的位元組碼。這種資訊組織方式正是“頭部+ 身體”的資料組織方式。

下面是将這 16 個資料目錄項依次展開後的新結構:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

注意:以上結構在 PE 中并不存在,這裡介紹主要是為了以後程式設計時可作為參考,當然,你也可以使用該結構覆寫字段 IMAGE_ OPTIONAL_HEADER32. DataDirectory。另外要注意的是,該虛拟結構每個字段後的偏移是基于 IMAGE_NT_HEADERS 頭的。

節表:

WINDOWS+PE權威指南讀書筆記(3)

節表項 IMAGE_SECTION_HEADER:

PE 頭 IMAGE_ NT_HEADERS 後緊跟着節表。它由許多個節表項 (IMAGE_SECTION _HEADER) 組成,每個節表項記錄了PE 中與某個特定的節有關的資訊,如節的屬性、節的大小、在檔案和記憶體中的起始位置等。節表中節的數量由字段 IMAGE_FILE_HEADER.NumberOfSections 來定義。

節表項的資料結構詳細定義如下:

WINDOWS+PE權威指南讀書筆記(3)

PE 檔案頭部字段解析:

WINDOWS+PE權威指南讀書筆記(3)

PE頭IMAGE_NT_HEADER 的字段:

1. IMAGE_NT_HEADER.Signature:

+0000h,雙字。PE 檔案辨別,被定義為 00004550h。也就是“P”,“E”加上兩個0,這也是 PE 這個稱呼的由來。

如果更改其中的任何一個位元組,作業系統就無法把該檔案識别為正确的 PE 檔案。通過修改這個字段,會導緻 PE 檔案在 32 位系統中加載失敗,但由于檔案的其他部分〈特别是 DOS 頭) 并沒有被破壞,系統還是可以識别出其為 DOS 系統下的可執行程式,并通過調用純 DOS 環境來運作 DOS Stub 中的程式代碼。

如果你确認作業系統中的某個PE 檔案攜帶病毒,并且開機後會被加載進記憶體運作,最簡單的處理辦法是通過 Windows PE 盤啟動系統。在系統中找到病毒檔案,使用記事本簡單地修改其中任何一個字元,儲存檔案,重新開機啟動後即可防止病毒檔案被加載。

注意:此PE非披PE,Windows PE 是一個作業系統,其全稱為 : Windows PreInstallation Environment,即 Windows 的預安裝環境。該作業系統差別于 Windows XP/2000/Vista 等,可以從CD光牒引導。

2. IMAGE_NT_HEADER.FileHeader:

+0004h,結構。該結構指向IMAGE FILE_ HEADER,由于PE擴充自通用 COFF 規範,是以,該字段在官方文檔中被稱為标準 COFF 頭。

3. IMAGE_NT_HEADER.OptionalHeader:

+0018h,結構。該結構指向 IMAGE OPTIONAL _ HEADER32。Windows 作業系統可執行檔案的大部分特性均在這個結構裡呈現。因為在符合 COFF 規範的“.obj”目标檔案中該部分并不存在,是以該部分被稱為 OptionalHeader (可選的頭部資訊,簡稱“可選頭”),它是作業系統映像檔案所獨有的頭部資訊。

可選頭又分為兩部分,前 10 個字段原屬于 COFF,用來加載和運作一個可執行檔案 , 後21 個字段則是通過連結器追加的。它們作為PE 擴充的部分,用于描述可執行檔案的一些資訊,供 PE 加載器加載使用。

标準PE 頭IAMGE_FILE_HEADER 的字段(由IMAGE_NT_HEADER.FileHeader指向):

1:IMAGE_FILE_HEADER.Machine:

+0004h,單字。用來指定 PE 檔案運作的平台。

由于 Windows 最初被設計為可以運作在Intel、Sun、Dec、IBM 等多種硬體平台上,或者能模拟這些平台的軟體環境中,而不同的硬體平台其指令的機器碼不相同,是以為不同平台編譯的 EXE 檔案是無法通用的。

假設将運作在 Intel 386 機器上的 PE 檔案的該字段設定為01f0h,即指定平台為 IBM POWER PC (小尾方式),則系統會有如圖 3-12 所示的提示。

WINDOWS+PE權威指南讀書筆記(3)

如果使用彙編語言,可以通過如下的僞指令代碼對平台進行定義:

.386

IMAGE_FILE_HEADER.Machine 的常見值:

WINDOWS+PE權威指南讀書筆記(3)

2:IMAGE_FILE_HEADER.NumberoOfSections:

+0006h,單字。檔案中存在的節的總數。在 Windows XP 中,可以有10個節,但數值不能小于1。也不能超過96。如果将該值設定為0,則作業系統裝載時會提示不是有效的Win32 程式。

如果想在 PE 中增加或删除節,必須變更此處的值。另外,這個值既不能比實際記憶體中存在的節多,也不能比它少,否則裝載時會發生錯誤,提示不是有效的 Win32 應用程式。

3:IMAGE_FILE_HEADER.TimeDateStamp:

+0008h,雙字。編譯器建立此檔案時的時間戳。低 32 位存放的值是自 1970年1月1日00:00 時開始到建立時間為止的總秒數。

該數值可以随意修改而不會影響程式運作。是以,有的連結器在這裡填人固定的值,有的則随意寫和任何值,這對使用者建立的檔案并沒有實際的意義。另外,這個時間值與作業系統檔案屬性裡看到的三個時間〈建立時間、修改時間、通路時間) 也沒有任何聯系。

4:IMAGE_FILE_HEADER.PointerToSymbolTable:

+000Ch,雙字。COFF 符号表的檔案偏移。如果不存在 COFF 符号表,此值為0。對于映像檔案來說,此值為0,因為微軟已經不贊成在 PE 中使用 COFF 調試資訊了。

5:IMAGE_FILE_HEADER.NumberOfSymbols:

+0010h,雙字。符号表中元素的數目。由于字元串表緊跟在符号表後,是以可以利用這個值來定位字元串表。對于映像檔案來說,此值為0,主要用于調試。

6:IMAGE_FILE_HEADER.SizeOfOptionalHeader:

+0014h。單字。指定結構 IMAGE _ OPTIONAL_HEADER32 的長度,預設情況下這個值等于 00e0h;, 如果是 64 位 PE 檔案,該結構的預設大小為 00F0h。

使用者可以自己定義這個值的大小,不過需要注意兩點:

1) 更改完以後,需要自行将檔案中 IMAGE_OPITONAL_HEADER32 的大小擴充為你指定的值(一般以0補足)。

2) 擴充完以後,要維持檔案中的對齊特性〈比如在 HelloWorld.exe 中,此處增加了8 個位元組後,一定在後面相應地删除 8 個位元組,以保證 .text 節起始位置處于 0400h )。

10. IMAGE_FILE_HEADER.Characteristics:

+0016h,單字。檔案屬性标志字段,它的不同資料位定義了不同的檔案屬性,有具體内容見表 3-3。這是一個很重要的字段,不同的定義将影響系統對檔案的裝入方式。

比如,當位 13 為1 時,表示這是一個DLL 檔案,那麼系統将使用調用 DLL 入口函數的方式執行檔案入口函數。

當位 13 為0時,表示這是一個普通的可執行檔案,系統直接跳到人口處執行。

對于普通的可執行PE 檔案來說,這個字段的值一般是 010,而對于 DLL 檔案來說,這個字段的值一般是210eh。

表 3-3 IMAGE_FILE_HEADER.Characteristics 屬性位的含義:

WINDOWS+PE權威指南讀書筆記(3)

如表 3-3 所示,當第 0 位為 1時,表明此檔案不包含基址重定位資訊,是以必須将其加載到檔案頭中指定的基位址字段位置。如果程序空間此處的基位址被占用,加載器會報錯。在程式運作前如果發現檔案中存在可重定位資訊,連結器會執行移除可執行檔案中的重定位資訊的操作。

當第 1 位為1時,表明此映像檔案是合法的,可以運作。如果未設定此标志,表明出現了連結器錯誤。

當第 7 位為1時,表示檔案為小尾方式,即記憶體中,最低有效位 LSB 位于最高有效位MSB 的前面,與第 15 位的大尾方式 (MSB 在前,LSB 在後) 一樣,都不贊成使用該标志,最好将其設定為 0。

當第 10 位為 1 時,如果此映像檔案在可移動存儲媒體上,那麼加載器将完全加載它并把它複制到記憶體交換檔案中。

當第 11 位為 1 時,如果此映像檔案在網絡上,那麼加載器也将完全加載它并把它複制到記憶體交換檔案中。

當第 13 位為1時,表明此映像檔案是動态連結庫 (DLL)。這樣的檔案總被認為是可執行檔案,盡管它們并不能直接運作。

可執行檔案的标志位設定為010th,即第 0、1、2、3、8 位分别被設定為1 (如下所示),表示該檔案為可執行檔案,不含重定位資訊,不含符号和行号資訊,檔案隻在 32 位平台運作。

WINDOWS+PE權威指南讀書筆記(3)

擴充 PE 頭IMAGE_OPTIONAL_HEADER32 的字段:

1:IMAGE_OPTIONAL_HEADER32.Magic:

+0018h,單字。魔術字,說明檔案的類型,如果為 010BH,則表示該檔案為 PE32; 如果為0107h,則表示檔案為ROM 映像 , 如果為 020BH,則表示該檔案為 PE32+,即 64 位下的 PE檔案。

2:IMAGE_OPTIONAL_HEADER32.MajorLinkerVersion:

3:IMAGE_OPTIONAL_HEADER32.MinorLinkerVersion:

+001ah,單字。這兩個字段都是位元組型,指定連結器版本号,對執行沒有任何影響。

4:IMAGE_OPTIONAL_HEADER32.SizeOfCode:

+001ch,雙字。所有代碼節的總和(以位元組計算),該大小是基于檔案對齊後的大小,而非記憶體對齊後的大小。稍後還會介紹一個字段 SizeOfImage,它是基于記憶體對齊後的大小。需要注意一點 : 判斷某個節是否包含代碼的方法不是根據節的屬性中是否含有IMAGE _SCN_MEM_EXECUTE 标志,而是根據節的屬性中是否含有IMAGE _SCN_CNT_CODE 标志。

5:IMAGE_OPTIONAL_HEADER32.SizeOflnitializedData:

+0020h,雙字。所有包含已經初始化的資料的節的總大小。

6:IMAGE_OPTIONAL_HEADER32.SizeOfUninitializedData:

+0024h,雙字。所有包含未初始化的資料的節的總大小。這些資料被定義為未初始化,在檔案中不占用空間 ; 但在被加載到記憶體以後,PE 加載程式應該為這些資料配置設定适當大小的虛拟位址空間。

7:IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint:

+0028h,雙字。在 Windows 中,可執行程式運作在虛拟位址空間中,由于 4GB 空間對于程式是唯一的,是以這裡的虛拟空間可以簡單地了解為真實的位址〈我們暫且忘記實體記憶體位址的概念,這樣就不需要了解頁面排程機制了)。該字段的值是一個RVA,它記錄了啟動代碼距離該 PE 加載後的起始位置到底有多少個位元組。

如果在一個可執行檔案中附加了一段自己的代碼,并且想讓這段代碼首先被執行,一般都要修改這裡的值使之指向自己的代碼位置。對于一般程式映像來說,它就是啟動位址; 對于裝置驅動程式來說,它是初始化函數的位址。入口點對于 DLL 來說是可選的,如果不存在人口點,這個字段必須設定為 0。

注意:許多病毒程式、加密程式、更新檔程式都會劫持這裡的值,使其指向其他用途的代碼位址。

8:IMAGE_OPTIONAL_HEADER32.BaseOfCode:

+002Ch,雙字。代碼節的起始RVA,表示映像被加載進記憶體時代碼節的開頭相對于映像基址的偏移位址。一般情況下,代碼節緊跟在PE 頭部後面,節的名稱通常為“.text”。

9:IMAGE_OPTIONAL_HEADER32.BaseOfData:

+0030h,雙字。資料節的起始 RVA,表示映像被加載進記憶體時資料節的開頭相對于映像基位址的偏移位址。一般情況下,資料節位于檔案末尾,節的名稱通常為“.data”。

10:IMAGE_OPTIONAL_HEADER32.ImageBase:

+0034h,雙字。該字段指出了 PE 映像的優先裝入位址。

也就是在IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint中說的程式被加載到記憶體後的起始 VA。

連結器在産生可執行檔案的時候,是對應這個位址來生成機器碼的。如果作業系統也是按照這個位址加載機器碼到記憶體中,那麼指令中的許多重定位資訊就不需要修改了,這樣運作速度就會更快一些。

前面說過,對于 EXE 檔案來說,每個檔案使用的都是獨立的虛拟位址空間,是以,優先裝入的位址通常不會被其他子產品占據。也就是說,EXE 檔案總是能按照這個位址裝入,這就意味着裝入後的 EXE 檔案不需要進行重定位了,例如,在 HelloWorld.exe 中就看不到重定位資訊。

在連結的時候,可以使用參數“-base”來指定優先裝和人的位址,如果不指定,那麼連結器預設裝入 EXE 的位址就是 0x00400000。而相對于 DLL 檔案來說,它預設優先裝入位址則是 0x10000000。如果一個程序用到了多個DLL檔案,其裝入位址可能會發生訓突。PE 加載器會調整其中的位址,使所有的 DLL 檔案都能被正确裝人人。是以,不要錯

誤地認為記憶體中動态連結庫的基位址和其檔案頭字段 IAMGE_OPTIONAL_HEADER32.ImageBase 指定的完全一樣。

可以自己定義這個值,但取值有限制 :

第一,取值不能超出邊界,即取的值必須在程序位址空間中 第二,該值必須是 64KB 的整數倍。

11:IMAGE_OPTIONAL_HEADER32.SectionAlignment

+0038h,雙字。記憶體中節的對齊粒度,該字段指定了節被裝入記憶體後的對齊機關。

記憶體中的資料存取以頁面為機關。Win32 的頁面大小是4KB,是以 Win32 PE 中節的記憶體對齊粒度一般都選擇 4KB 大小。十六進制表示為 01000h 。

SectionAlignment 必須大于或等于 FileAlignment。當它小于系統頁面大小時,必須保證SectionAlignment 和 FileAlignment 相等。

12:IMAGE_OPTIONAL_HEADER32.FileAlignment

+003ch,雙字。檔案中節的對齊粒度。

檔案中的節對齊并不是提高本身代碼的執行效率,同樣也是為了提高檔案從磁盤加載的效率。Windows XP 用來組織硬碟的所有檔案系統都是基于簇〈配置設定單元) 的,每個簇包含幾個實體扇區。扇區是磁盤實體存取的最小機關。簇越大,磁盤存儲資訊的容量就越大,但存取所花費的時間也越長。通常情況下,Windows 會選擇使用 512 位元組的簇大小〈一個實體扇區的大小) 來格式化分區,最大可以達到 4KB。在本書的例子中,檔案對齊粒度選擇了 512 位元組,十六進制表示為 200h。

小技巧:如何檢視目前系統的簇及扇區大小

方法一 ,在磁盤上建立一個 10 個位元組的檔案,然後檢視檔案屬性,其中占用空間顯示的就是簇大小。

13:IMAGE_OPTIONAL_HEADER32.MajorOperatingSystemVersion

14:IMAGE_OPTIONAL_HEADER32.MinorOperatingSystemVersion

+0040h,23和24标注的兩個字段都為單字,共計為雙字。辨別作業系統的版本号,分為主版本号和次版本号兩部分。

15:IMAGE_OPTIONAL_HEADER32.MajorlImageVersion

16:IMAGE_OPTIONAL_HEADER32.MinorlImageVersion

+0044h,雙字。本 PE 檔案映像的版本号。

17:IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion

18:IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion

+0048h,雙字。運作所需要的子系統的版本号。

19:IMAGE_OPTIONAL_HEADER32.Win32VersionValue

+004ch,雙字。子系統版本的值,暫時保留未用,必須設定為0。比如将此處的值更改為696C6971h,程式運作将失敗,提示資訊如圖 3-13 所示。

WINDOWS+PE權威指南讀書筆記(3)

20:IMAGE_OPTIONAL_HEADER32.SizeOflmage

+0050h,雙字。記憶體中整個PE 檔案的映射尺寸。

以加載到記憶體中的 HelloWorld.exe 為例, HelloWorld.exe 中檔案頭占用了 1000h 位元組,三個節各占用 1000h 位元組,是以檔案在記憶體中占用的空間總共大小為 04000h。該值可以比實際的值大,但不能比它小,而且必須保證該值是 SectionAlignment 的整數倍。

21:IMAGE_OPTIONAL_HEADER32.SizeOfHeaders

+0054h,雙字。所有頭+節表按照檔案對齊粒度對齊後的大小(即含補足的0),HelloWorld.exe 中的值為0400h。在 PE 檔案中,該部分資料是嚴格按照 200h 對齊的,如果不對齊,系統在加載時會提示出錯。

22:IMAGE_OPTIONAL_HEADER32.CheckSum

+0058h,雙字。校驗和,在大多數的PE檔案中,該值是0,但在一些核心模式的驅動程式和系統 DLL 中,該值則是必須存在且是正确的,比如 kernel32.dll 中 PE 的校驗和是0011E97Eh。

Windows 系統目錄下有一個動态連結庫 IMAGEHLPDLL,它是 Win32 中專門用來操作 PE 檔案的函數庫,這裡面的函數 CheckSumMappedFile 就是用來計算檔案頭校驗和的,對于整個 PE 檔案也有一個校驗和函數 MapFileAndCheckSum。該動态連結庫中還包括其他一些常用的函數,可以通過小工具 PEInfo 輸出并檢視。關于校驗和的具體計算方法,可以參照3.7 節關于 PE 檔案頭程式設計的部分。

23:IMAGE_OPTIONAL_HEADER32.Subsystem

+005ch,單字。指定使用界面的子系統,它的取值如表 3-4 所示。這個字段決定了系統如何為程式建立初始的界面,連結時使用的參數 -subsystem:xxx 選項指定的就是這個字段的值,如果将子系統指定為 Windows 指令行使用者互動模式 (Command User Interface,CUI),那麼系統會自動為程式建立一個控制台視窗 ,如果指定為 Windows GUI,視窗程式代碼必須由使用者自己建立。

下表為IMAGE_OPTIONAL_HEADER32.Subsystem 的取值:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

MASM32 的link 程式的連結開關 -subsystem 的常見選項如下表所示:

WINDOWS+PE權威指南讀書筆記(3)

24:IMAGE_OPTIONAL_HEADER32.DllCharacteristics:

+005eh,單字。DLL 檔案屬性。它是一個标志集,不是針對 DLL 檔案的,而是針對所有PE 檔案的。

其詳細定義如下表所示:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

25:IMAGE_OPTIONAL_HEADER32.SizeOfStackReserve:

+0060h,雙字。初始化時保留棧的大小。

該字段表示為初始線程的棧而保留的虛拟記憶體數量,然而并不是留出的所有虛拟記憶體都可以做棧〈真正的棧大小由下一個字段 SizeOfStackCommit 決定)。該字段的預設值為0x100000 (1MB),如果調用API函數數CreateThread 時,把 NULL 當做傳入的參數,那麼建立出來的棧大小也會是 1MB。

26:IMAGE_OPTIONAL_HEADER32.SizeoOfStackCommit

+0064h,雙字。初始化時實際送出的棧大小。

保證初始線程的棧實際占用記憶體空間的大小,它是被系統送出的。這些送出的棧不存在于交換檔案裡,而是存在于記憶體中。對于 Microsoft 的連結器來說,這個域的初始值為 0x1000 位元組〈1 頁),而對于 TLINK32,則為2頁。

27:IMAGE_OPTIONAL_HEADER32.SizeOfHeapReserve

+0068h,雙字。初始化時保留的堆大小,用來保留給初始程序堆使用的虛拟記憶體。

這個堆的句柄可以通過調用 GetProcessHeap 函數獲得。每一個程序至少會有一個預設的程序堆,該堆在程序啟動的時候被建立,而且在程序的生命期中永遠不會被删除。預設值為 1MB,我們可以通過連結器的“-heap”參數指定起始的保留堆記憶體大小和實際送出的堆大小。

28:IMAGE_OPTIONAL_HEADER32.SizeOfHeapCommit

+006ch,雙字。初始化時實際送出的堆大小,在程序初始化時設定的堆所占用的記憶體空間。預設值為1頁。

29:IMAGE_OPTIONAL_HEADER32.LoaderFlags

+0070h,雙字。加載标志。

30:IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSize

+0074h,雙字。定義資料目錄結構的數量,一般為 00000010h,即 16 個。該值由字段SizeOfOptionalHeaders 決定,實際應用中可以取2一16的值。

31:IMAGE_OPTIONAL_HEADER32.DataDirectory

+0078h,結構。由 16個IMAGE DATIA_DIRECTORY 結構線性排列而成,用于定義PE中的 16 種不同類别的資料所在的位置和大小。

先給出前面給過的資料類型圖:

WINDOWS+PE權威指南讀書筆記(3)
WINDOWS+PE權威指南讀書筆記(3)

[0] 導出資料所在的節通常被命名為 .edata,它包含一些可被其他 EXE 程式通路的符号的相關資訊,比如導出函數和資源等。這些符号通常出現在 DLL 中,但 DLL 也可以包含導入符号,而且在某些 EXE 中也可以有導出符号。對導出表的詳細介紹請閱讀本書第 5 章。

[1] 導入資料所在的節通常被命名為 .idata,它包含了 PE 映像中所有導入的符号。導入資訊在 EXE 和 DLL 中幾乎都存在。對導入表的詳細介紹請閱讀本書第 4章。

[2] 異常表資料所在的節通常被命名為 .pdata。該節是由用于異常處理的函數表項組成的數組。可選檔案頭中的 ExceptionTable (異常表) 字段指向它。在将它們放進最終的映像檔案之前,這些表項必須按函數位址進行排序,并且這些函數表項的描述必須符合特定的目标平台。該部分資料主要用于基于表的異常處理,适用于除 x86 之外的所有類型的CPU 。

[3] 資源資料所在的節通常被命名為 .rsrc。該節是一個多層的二叉排序樹,該樹的節點指向 PE 中各種類型的資源,如圖示、對話框、菜單等。樹的深度可達 2”層,但是 PE中經常使用的隻有 3 層 : 類型層、名稱層、語言代碼層。對資源表的詳細資訊請參照本書第7章。

[4] 屬性證書資料的作用類似于 PE 檔案的校驗和或者 MD5 碼,通過這種屬性證書方式可以驗證一個PE 檔案是否被非法修改過。為 PE 檔案添加屬性證書表可以使該 PE 與屬性證書相關聯。屬性證書表是由一組連續的按八進制字 (從任意位元組邊界開始的 16 個連續位元組 )邊界對齊的屬性證書表項組成,每個屬性證書表項指向 WIN_CERTIFICATE 結構。此結構可以在 WinTrust.H 檔案中找到,

該結構的詳細定義如下:

WINDOWS+PE權威指南讀書筆記(3)

注意 :該資料并不作為映像的一部分被映射到記憶體,是以,DataDirectory.Certificate_VirtualAddress字段是檔案偏移,而不是 RVA。

DataDirectory.Certificate_ VirtualAddress字段給出了屬性證書表中第一個屬性證書表項在檔案中的偏移。對于後續的屬性證書表項,可以通過将目前屬性證書表項的檔案偏移加上WIN_CERTIFICATE.dwLength 字段的值,并将結果向上舍入為 8 個位元組的倍數來通路。

後續的屬性證書表項可以一直以這種方式通路,直到這些 WIN_CERTIFICATE.dwLength 字段 〈已經向上舍人為8 位元組的倍數) 的和等于可選檔案頭中的 DataDirectory.Certificate_isize 的值。如果上述的值最後不等于 isize 字段的值,要麼是屬性證書表被破壞了,要麼是 isize 域被修改了。

[5] 基址重定位資訊所處的節通常被命名為 .reloc,基址重定位表包含了映像中所有需要重定位的内容。它被劃分成許多塊,每一塊表示一個 4KB 頁面範圍内的基址重定位資訊,它必須從 32 位邊界開始。

一般情況下,Windows 加載器是不需要處理由連結器解析的基址重定位資訊的,除非該映像不能被加載到 IMAGEOPTIONAL_HEADER32.ImageBase 指定的位置。

[6] 調試資料所處的節通常被命名為 .debug,它指向 IMAGE_DEBUG_DIRECTORY 結構數組。其中的每個元素都描述了 PE 中的一些調試資訊。要獲得 IMAGE_DEBUG_DIRECTORY 結構的數目,可以用 isize 字段除以 IMAGE _ DEBUG DIRECTORY 結構的大小。

注意:在預設情況下,調試資訊并不會映射到映像的虛拟位址空間中。調試目錄可以位于一個可丢棄的 .debug 節 (如果存在) 中,或者位于PE 檔案的其他節中,或者不在任何節中。是以,它可能被加載到虛拟記憶體中,而大部分情況下是被丢棄的。

[7] 預留,必須為 0。

[8] Global Ptr 資料描述的是被存儲在全局指針寄存器中的一個值。

[9] 線程本地存儲資料所處的節,通常命名為 ds。線程本地存儲 (TLS) 是 Windows 支援的一種特殊存儲類别,其中的資料對象不是棧變量,而是對應于運作相應代碼的單個線程。是以,每個線程都可以為使用 TLS 定義的變量來維護一個不同于其他線程的值。

當建立線程時,PE 加載器通過将線程環境塊 (CTEB) 的位址放入 FS 寄存器來傳遞線程的TLS 數組位址,距 TEB 開頭 0x2C 的位置處有一個指針指向該 TLS 數組。線程本地存儲技術是特定于 Intel x86 平台的。

[10] 加載配置資訊用于包含保留的 SEH 技術。該技術基于 x86 的 32 位系統,它提供了一個安全的結構化異常處理程式清單,作業系統在進行異常處理時要用到這些異常處理程式。

[11] 綁定導入資料的存在主要是為了優化導入資訊,提高 PE 的加載效率。當 PE 檔案被加載到記憶體時,加載器會先檢查導入表,然後把需要加載的 DLL 載入到位址空間中 。加載器還有一項比較重要的工作是根據導入資訊的摘述使用動态連結庫裡輸入函數的實際位址去替換 IAT 表的内容,這個步驟會花去一部分時間。但是,如果程式員(或者連結

器) 可以完全知道圖數的位址,就可以直接把數組中的元素替換為位址,這能節省相當多的時間。這種方法就稱為綁定 (Binding)。簡單來講,綁定是指由程式員或連結器代替Windows PE 加載器完成了一部分對導入表的處理工作〈在加載時)。

[12] IAT 是導入位址表的英文縮寫。準确地講,它是導入表的一部分,這個雙字數組裡定義了所有導入函數的 VA,程式可以直接通過跳轉指令跳轉到該 VA 處執行。

[13] 延遲導入資料也和動态連結庫調用有關,這種資料的存在是為了給“應用程式直到首次調用某個 DLL 中的函數或資料時才加載這個DLL 〈即延遲加載)”這種行為提供一種統一的通路機制。

[14] CLR 資料所處的節通常被命名為 .cormeta,該資訊是 .NET 架構的一個重要組成部分,所有基于 .NET 架構開發的程式,其初始化部分都是通過通路這部分定義而實作的。PE加載時将通過該結構加載代碼托管機制需要的所有動态連結庫檔案,并完成與CLR 有關的一些其他操作。

[15] 系統預留,未定義。

提示 :要想在PE 中找到特定類型的資料或者資源,就應從這個結構開始的。通過查找不同類别資源的RVA 位址和長度,即可擷取到相關資料,這在前面已經提到過。

資料目錄項 IMAGE_DAITA_DIRECTORY 的字段:

32:IMAGE_DAITIA_DIRECTORY.VirtualAddress

+0000h,雙字。如上所述,這個字段記錄了特定類型資料的起始 RVA。當然,針對不同的資料結構,該字段包含的資料含義并不一樣,有的資料甚至還不是 RVA (如屬性證書資料中該字段的值表示的是FOA )。

33:IMAGE_DATA_DIRECTORY.isize

+0004h,雙字。該字段記錄了特定類型的資料塊的長度。

節表項 IMAGE_SECTION_HEADER 的字段:

WINDOWS+PE權威指南讀書筆記(3)

1:IMAGE_SECTION_HEADER.Name1

+0000h,8 位元組。該字段一共 8 個位元組,一般情況下是一個以“\0”結尾的 ASCII 碼字元串來辨別節的名稱,内容可以自行定義。

該名稱并不遵循 Ansi 字元串必須以“\0”結尾的規律,如果不以“\0”結尾,系統依然會認為它是一個字元串 , 但會根據 8 個位元組的長度對其進行截斷處理。

2:IMAGE_SECTION_HEADER.Misc

+0008h,雙字。該字段是一個 union 型的資料,這是節的資料在沒有對齊前的真實尺寸,不過很多 PE 檔案裡該值并不準确。

3:IMAGE_SECTION_HEADER.VirtualAddress

+000ch,雙字。節區的 RVA 位址。

4:IMAGE_SECTION_HEADER.SizeOfRawData

+0010h,雙字。節在檔案中對齊後的尺寸。在 HelloWorld.exe 中,資料量不大的節,其大小一般為 200h。

5:IMAGE_SECTION_HEADER.PointerToRawData

+0014h,雙字。節區起始資料在檔案中的偏移。比如我們的例子中,.text 節是在偏移0x00000400 處。

6:IMAGE_SECTION_HEADER.PointerToRelocations

+0018h,雙字。在“.obj”檔案中使用,指向重定位表的指針。

7:IMAGE_SECTION_HEADER.PointerToLinenumbers

+001ch,雙字。行号表的位置〈供調試用)。

8:IMAGE_SECTION_HEADER.NumberOfRelocations

+0020h,單字。重定位表的個數〈在 OBJ 檔案中使用)。

9:IMAGE_SECTION_HEADER.NumberOfLinenumbers

+0022h,單字。行号表中行号的數量。

10:IMAGE_SECTION_HEADER.Characteristics

+0024h,雙字。節的屬性。這個字段很重要,這是節的屬性标志字段,其中不同的資料位代表了不同的屬性,具體的定義如表 3-7 所示。這些資料位的組合描述了記憶體中的一個節所擁有的通路屬性。

IMAGE_SECTION_HEADER.Characteristics 資料位含義:

WINDOWS+PE權威指南讀書筆記(3)

代碼節的屬性一般為 60000020h,也就是可執行、可讀和 “節中包含代碼”,資料節的屬性一般為 c0000040h,也就是可讀、可寫和 “包含已初始化資料” ,而常量節〈對應源代碼中的 .const段) 的屬性為 40000040h,也就是可讀和“包含已初始化資料” ;資源節的屬性和常量節的屬性一般是相同的。

當然,節屬性的定義不一定必須是這些值。比如,當 PE 檔案被壓縮工具壓縮以後,包含代碼的節往往被同時設定成具有可執行、可讀和可寫屬性,因為解壓部分需要将解壓後的代碼回寫到代碼段中。大家可以做個實驗: 先往代碼段中寫資料,編譯連結完成後執行,這時肯定會引發異常如果用壓縮軟體壓縮後再執行,就會發現檔案可以正常執行了,這就是因為壓縮軟體為了解壓的需要而将節的屬性設定為可寫了。

繼續閱讀