接著我們來談談 IMAGE_OPTIONAL_HEADER 結構,正如名字的意思,這是一個可選映像頭,是一個可選的結構,但是呢,實際上上節課我們講解的 IMAGE_FILE_HEADER 結構遠遠不足以來定義 PE 檔案的屬性。因此,這些屬性在 IMAGE_OPTIONAL_HEADER 結構中進行定義。
因此這兩個結構聯合起來,才是一個完整的 “pe檔案結構” 。
那么我們接著就應該順理成章地來談談
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
+18h WORD Magic; // 標誌字, ROM 映像(0107h),普通執行檔(010Bh)
+1Ah BYTE MajorLinkerVersion; // 連結程式的主版本號
+1Bh BYTE MinorLinkerVersion; // 連結程式的次版本號
+1Ch DWORD SizeOfCode; // 所有含代碼的節的總大小
+20h DWORD SizeOfInitializedData; // 所有含已初始化數據的節的總大小
+24h DWORD SizeOfUninitializedData; // 所有含未初始化數據的節的大小
+28h DWORD AddressOfEntryPoint; // 程式執行入口RVA
+2Ch DWORD BaseOfCode; // 代碼的區塊的起始RVA
+30h DWORD BaseOfData; // 數據的區塊的起始RVA
//
// NT additional fields. 以下是屬於NT結構增加的領域。
//
+34h DWORD ImageBase; // 程式的首選裝載地址
+38h DWORD SectionAlignment; // 記憶體中的區塊的對齊大小
+3Ch DWORD FileAlignment; // 檔案中的區塊的對齊大小
+40h WORD MajorOperatingSystemVersion; // 要求作業系統最低版本號的主版本號
+42h WORD MinorOperatingSystemVersion; // 要求作業系統最低版本號的副版本號
+44h WORD MajorImageVersion; // 可運行於作業系統的主版本號
+46h WORD MinorImageVersion; // 可運行於作業系統的次版本號
+48h WORD MajorSubsystemVersion; // 要求最低子系統版本的主版本號
+4Ah WORD MinorSubsystemVersion; // 要求最低子系統版本的次版本號
+4Ch DWORD Win32VersionValue; // 莫須有欄位,不被病毒利用的話一般為0
+50h DWORD SizeOfImage; // 映像裝入記憶體後的總尺寸
+54h DWORD SizeOfHeaders; // 所有頭 + 區塊表的尺寸大小
+58h DWORD CheckSum; // 映像的校檢和
+5Ch WORD Subsystem; // 執行檔期望的子系統
+5Eh WORD DllCharacteristics; // DllMain()函式何時被調用,默認為 0
+60h DWORD SizeOfStackReserve; // 初始化時的棧大小
+64h DWORD SizeOfStackCommit; // 初始化時實際提交的棧大小
+68h DWORD SizeOfHeapReserve; // 初始化時保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化時實際提交的堆大小
+70h DWORD LoaderFlags; // 與調試有關,默認為 0
+74h DWORD NumberOfRvaAndSizes; // 下邊數據目錄的項數,這個欄位自Windows NT 發布以來 // 一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 數據目錄表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
事實上,這個結構中的大部分欄位都不重要,大家可以從注釋中理解它們的含義,小甲魚將比較重要的欄位在下邊跟大家詳細講解。另外,這玩意千萬不要去背啊,我們要把絕大多數的時間拿來改變,而不是記住。不用做筆記,把這篇文章轉載到您的部落格就行。
指出檔案被執行時的入口地址,這是一個RVA地址(RVA的含義在下一節中詳細介紹)。如果在一個執行檔上附加了一段代碼並想讓這段代碼首先被執行,那么只需要將這個入口地址指向附加的代碼就可以了。
ImageBase欄位指出檔案的優先裝入地址。也就是說當檔案被執行時,如果可能的話,Windows優先將檔案裝入到由ImageBase欄位指定的地址中,只有指定的地址已經被**模組使用時,檔案才被裝入到**地址中。連結器產生執行檔的時候對應這個地址來生成機器碼,所以當檔案被裝入這個地址時不需要進行重定位操作,裝入的速度最快,如果檔案被裝載到**地址的話,將不得不進行重定位操作,這樣就要慢一點。
對於EXE檔案來說,由於每個檔案總是使用獨立的虛擬地址空間,優先裝入地址不可能被**模組占據,所以EXE總是能夠按照這個地址裝入,這也意味著EXE檔案不再需要重定位信息。對於DLL檔案來說,由於多個DLL檔案全部使用宿主EXE檔案的地址空間,不能保證優先裝入地址沒有被**的DLL使用,所以DLL檔案中必須包含重定位信息以防萬一。因此,在前面介紹的 IMAGE_FILE_HEADER 結構的 Characteristics 欄位中,DLL 檔案對應的 IMAGE_FILE_RELOCS_STRIPPED 位總是為0,而EXE檔案的這個標誌位總是為1。
在連結的時候,可以通過對link.exe指定/base:address選項來自定義優先裝入地址,如果不指定這個選項的話,一般EXE檔案的默認優先裝入地址被定為00400000h,而DLL檔案的默認優先裝入地址被定為10000000h。
SectionAlignment欄位指定了節被裝入記憶體後的對齊單位。也就是說,每個節被裝入的地址必定是本欄位指定數值的整數倍。而FileAlignment欄位指定了節存儲在磁碟檔案中時的對齊單位。
Subsystem欄位指定使用界面的子系統,它的取值如表17.3所示。這個欄位決定了系統如何為程式建立初始的界面,連結時的/subsystem:**選項指定的就是這個欄位的值,在前面章節的編程中我們早已知道:如果將子系統指定為Windows CUI,那么系統會自動為程式建立一個控制台視窗,而指定為Windows GUI的話,視窗必須由程式自己建立。
界面子系統的取值和含義
取 值 | Windows.inc中的預定義值 | 含 義 |
0 | IMAGE_SUBSYSTEM_UNKNOWN | 未知的子系統 |
1 | IMAGE_SUBSYSTEM_NATIVE | 不需要子系統(如驅動程式) |
2 | IMAGE_SUBSYSTEM_WINDOWS_GUI | Windows圖形界面 |
3 | IMAGE_SUBSYSTEM_WINDOWS_CUI | Windows控制台界面 |
5 | IMAGE_SUBSYSTEM_OS2_CUI | OS2控制台界面 |
7 | IMAGE_SUBSYSTEM_POSIX_CUI | POSIX控制台界面 |
8 | IMAGE_SUBSYSTEM_NATIVE_WINDOWS | 不需要子系統 |
9 | IMAGE_SUBSYSTEM_WINDOWS_CE_GUI | Windows CE圖形界面 |
這個欄位可以說是最重要的欄位之一,它由16個相同的IMAGE_DATA_DIRECTORY結構組成,雖然PE檔案中的數據是按照裝入記憶體後的頁屬性歸類而被放在不同的節中的,但是這些處於各個節中的數據按照用途可以被分為導出表、導入表、資源、重定位表等數據塊,這16個IMAGE_DATA_DIRECTORY結構就是用來定義多種不同用途的數據塊的(如表17.4所示)。IMAGE_DATA_DIRECTORY結構的定義很簡單,它僅僅指出了某種數據塊的位置和長度。
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD ? ;數據的起始RVA
isize DWORD ? ;數據塊的長度
IMAGE_DATA_DIRECTORY ENDS
數據目錄列表的含義
索 引 | 索引值在Windows.inc中的預定義值 | 對應的數據塊 |
0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 導出表 |
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 導入表 |
2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | 資源 |
3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | 異常(具體資料不詳) |
4 | IMAGE_DIRECTORY_ENTRY_SECURITY | 安全(具體資料不詳) |
5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 重定位表 |
6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 調試信息 |
7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 版權資訊 |
8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 具體資料不詳 |
9 | IMAGE_DIRECTORY_ENTRY_TLS | Thread Local Storage |
10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 具體資料不詳 |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 具體資料不詳 |
12 | IMAGE_DIRECTORY_ENTRY_IAT | 導入函式地址表 |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 具體資料不詳 |
14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | 具體資料不詳 |
15 | 未使用 | |
在PE檔案中尋找特定的數據時就是從這些IMAGE_DATA_DIRECTORY結構開始的,比如要存取資源,那么必須從第3個IMAGE_DATA_DIRECTORY結構(索引為2)中得到資源數據塊的大小和位置;同理,如果要查看PE檔案導入了哪些DLL檔案的哪些API函式,那就必須首先從第2個IMAGE_DATA_DIRECTORY結構得到導入表的位置和大小。