之前的博文中介紹了IMAGE_FILE_HEADER結構,現在來討論比較復雜的“可選文件頭”結構體。(轉載請指明來自breaksoftware的csdn博客)先看下其聲明
[cpp]
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
.
.
.
DWORD BaseOfData; // not exist in PE32+
//
// NT additional fields.
//
DWORD ImageBase;
.
.
.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
看一下64位版本該結構體www.2cto.com
[cpp]
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
.
.
.
DWORD BaseOfCode;
ULONGLONG ImageBase;
.
.
.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
我們觀察這個32位版本結構體,可以看到該結構體包含兩塊數據:Standard fields和NT additional fields。我們可以猜想到,該結構體應該在第一個NT操作系統之前就存在了,只是當時其內容只有Standard fields(以後稱為標准域)下的內容,後來NT系統增加了NT additional fields(以後稱為擴展域)下元素。
此處需要特別注意一點,我們看兩個在WinNT.h中定義的結構體
[cpp]
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
該結構給出了PE文件頭的結構體布局,但是切記,這僅僅是布局。我們千萬不要想當然的認為直接從PE頭部開始將IMAGE_NT_HEADERS32(64)結構體大小的數據拷貝到該結構體對象中。
[cpp]
memcpy( &ImageNTHeader32,lpPEStart,sizeof(IMAGE_NT_HEADERS32 );// 這是錯誤的!!
為什麼?因為一個文件中不一定有完整的IMAGE_OPTIONAL_HEADER32(64)結構體對象信息。原因在《可選文件頭1》做了介紹,IMAGE_FILE_HEADER中字段SizeOfOptionalHeader指定了該文件中保存的“可選文件頭”真實長度,我們應該根據該元素來給IMAGE_OPTIONAL_HEADER32(64)對象賦值。
我們的文件是使用IMAGE_OPTIONAL_HEADER32還是IMAGE_OPTIONAL_HEADER64結構體呢?可能有人會記起,我們在《可選文件頭1》中介紹了判斷文件是32位還是64位的方法,我們是否可以通過該判斷的結果來判斷是哪種結構體呢?最開始我也是這麼想的,後來我發現我電腦上Microsoft Visual Studio 10.0\VC\lib\amd64\Microsoft.VisualC.STLCLR.dll文件是個64位文件但是使用了IMAGE_OPTIONAL_HEADER32結構體!!!是不是很驚訝!我不知道微軟這麼設計的原因,但是我知道了通過之前判斷是否為64位文件來決定可選文件頭結構體類型是錯誤的。那如何判斷呢?
其實是有標記的。緊跟著IMAGE_FILE_HEADER結構體的肯定是IMAGE_OPTIONAL_HEADER32(64)的Magic字段。如果該字段是0x010B,則是使用了IMAGE_OPTIONAL_HEADER32(稱為PE32);如果是0x020B,則使用了IMAGE_OPTIONAL_HEADER32(稱為PE32+)。切記PE32和PE32+和這個文件是32為文件還是64位文件是沒有關系的!它們是兩種不同的概念!切記要分清。
[cpp]
BOOL CGetPEInfo::GetOptionalHeader(){
CHECKOPHEADER();
GETFILETYPE();
size_t unDwordSize = sizeof(DWORD);
size_t unImgFileHeaderSize = sizeof(IMAGE_FILE_HEADER);
size_t unImgOpHeaderSize = 0;
LPBYTE lpImgOpHeaderAddr = m_lpPEStart + unDwordSize + unImgFileHeaderSize;
LPVOID lpOpHeaderStart= NULL;
WORD dwOptionHeader = 0;
if ( FALSE == SafeCopy( &dwOptionHeader, lpImgOpHeaderAddr, sizeof(WORD) ) ) {
return FALSE;
}
if ( E64Bit == m_eFileType && PE32MAGICNUM == dwOptionHeader ) {
// D:\Microsoft Visual Studio 10.0\VC\lib\amd64\Microsoft.VisualC.STLCLR.dll
//_ASSERT(FALSE);
}
if ( IMAGE_NT_OPTIONAL_HDR32_MAGIC == dwOptionHeader ) {
// 64位系統文件也存在該格式可選頭
m_eFileOpType = EOp32;
unImgOpHeaderSize = sizeof(IMAGE_OPTIONAL_HEADER32);
lpOpHeaderStart = &m_OptionalHeader32;
}
else if ( IMAGE_NT_OPTIONAL_HDR64_MAGIC== dwOptionHeader ) {
m_eFileOpType = EOp32Plus;
unImgOpHeaderSize = sizeof(IMAGE_OPTIONAL_HEADER64);
lpOpHeaderStart = &m_OptionalHeader64;
}
else {
_ASSERT(FALSE);
return FALSE;
}
memset( lpOpHeaderStart, 0 , unImgOpHeaderSize);
// 根據鏡像文件頭中可選文件頭大小拷貝數據
BOOL bSuc = SafeCopy( lpOpHeaderStart, lpImgOpHeaderAddr, m_FileHeader.SizeOfOptionalHeader );
if ( bSuc ) {
m_dwInfoMask |= OPHEADER;
}
else {
_ASSERT(FALSE);
}
if ( EOp32 == m_eFileOpType ) {
m_dwFileAlignment = m_OptionalHeader32.FileAlignment;
}
else {
m_dwFileAlignment = m_OptionalHeader64.FileAlignment;
}
return bSuc;
}
現在我們將重心放到IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];這個數組元素,我在《可選文件頭1》中對此有了點描述,而且我還說可選文件頭大小要看這個數組元素的“位置”(而不是個數)來決定的。現在我來細說下。先看下微軟的聲明
[cpp]
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
DataDirectory保存了指向“塊信息”的目錄信息,其中包括偏移(除了IMAGE_DIRECTORY_ENTRY_SECURITY元素是相對文件偏移RA,其他都是相對虛擬首地址偏移RVA)和大小。如果某文件只包含IMAGE_DIRECTORY_ENTRY_EXPORT(0) 、IMAGE_DIRECTORY_ENTRY_IMPORT(1) 和IMAGE_DIRECTORY_ENTRY_BASERELOC(5)等三個目錄,則IMAGE_DIRECTORY_ENTRY_EXCEPTION(2)、IMAGE_DIRECTORY_ENTRY_SECURITY(3)和IMAGE_DIRECTORY_ENTRY_SECURITY(4)的信息都要被填充0。於是IMAGE_FILE_HEADER::SizeOfOptionalHeader所指定的可選文件頭大小為DataDirectory之前的元素總大小加上6(最後一個目錄IMAGE_DIRECTORY_ENTRY_BASERELOC所在的位置5+1)*sizeof(IMAGE_DATA_DIRECTORY)。這就說明了為什麼可選文件頭大小是根據目錄的位置而不是數量來決定的。
下篇博文我們將詳細說一下IMAGE_OPTIONAL_HEADER32和IMAGE_OPTIONAL_HEADER64中其他元素的意義。