PE學習筆記(一) (轉)

worldblog發表於2008-01-25
PE學習筆記(一) (轉)[@more@]

PE學習筆記

 PE 的意思就是 Portable Executable(可移植的體)。PE結構的總體層次分佈圖:
 
 --------------
|DMZ Header |
|--------------|
|DOS Stub  |
|--------------|
|PE Header  |
|--------------|
|Section Table |
|--------------|
|Section 1  |
|--------------|
|Section 2  |
|--------------|
|Section ...  |
|--------------|
|Section n  |
 --------------
 
一、PE檔案格式的概要

1.1、DOS MZ Header:
 所有 PE檔案(甚至32位的 DLLs)必須以一個簡單的 DOS MZ Header 開始。有了它,一旦在DOS下執行,DOS就能識別出這是有效的執行體,然後執行緊隨 MZ Header 之後的 DOS Stub。

1.2、DOS Stub:
 DOS Stub(存根)實際上是個有效的 MS-DOS .EXE 或者.程式(如果檔案格式不對會報錯),在不支援 PE檔案格式的操作中,它將透過簡單中斷21h服務9來顯示字串"This program cannot run in DOS mode"或者根據程式設計師自己的意圖實現完整的 DOS 程式碼。它的大小一般不能確定。利用連結器(linker)的 /STUB:filename 選項可以替換這個程式。

1.3、PE Header:
 緊接著 DOS Stub 的是 PE Header。PE Header 是PE相關結構 IMAGE_NT_HEADERS 的簡稱,其中包含了許多PE裝載器用到的重要域。執行體在支援PE檔案結構的中執行時,PE裝載器將從 DOS MZ Header (IMAGE_DOS_HEADER)中找到 PE Header 的起始偏移量。因而跳過了DOS Stub 直接定位到真正的檔案頭PE Header。

1.4、Section Table:
 PE Header 接下來的陣列結構 Section Table (節表)。如果PE檔案裡有5個節,那麼此 Section Table 結構陣列內就有5個成員,每個成員包含對應節的屬性、檔案偏移量、虛擬偏移量等。

1.5、Sections:
 PE檔案的真正內容被劃分成塊,稱之為Section(節)。每個標準節的名字均以圓點開頭。Sections 是以其起始位址來排列,而不是以其字母次序來排列。下面是常見的節名及作用:
 
節名 作用
.arch 最初的構建資訊(Alpha Architecture Information)
.bss  未經初始化的資料
.CRT  C執行期只讀資料
.data  已經初始化的資料
.de  資訊
.didata  延遲輸入檔名錶
.edata 匯出檔名錶
.idata 匯入檔名錶
.pdata  異常資訊(Exception Information)
.rdata 只讀的初始化資料
.reloc 重定位表資訊
.rsrc 資源
.text  .exe或.dll檔案的可執行程式碼
.tls 執行緒的本地器
.xdata 異常處理表
 
 節的劃分是基於各組資料的共同屬性,而不是邏輯概念。每節是一塊擁有共同屬性的資料,比如程式碼/資料、讀/寫等。如果PE檔案中的資料/程式碼擁有相同屬性,它們就能被歸入同一節中。節名稱僅僅是個區別不同節的符號而已,類似"data", "code"的命名只為了便於識別,惟有節的屬性設定決定了節的特性和功能。

1.6、裝載一PE檔案的主要步驟:

1.當PE檔案被執行,PE裝載器檢查 DOS MZ Header 裡的 PE Header 偏移量。如果找到,則跳轉到 PE Header。
2.PE裝載器檢查 PE Header 的有效性。如果有效,就跳轉到PE Header的尾部。
3.緊跟 PE Header 的是節表。PE裝載器讀取其中的節資訊,並採用檔案對映方法將這些節對映到,同時付上節表裡指定的節屬性。
4.PE檔案對映入記憶體後,PE裝載器將處理PE檔案中類似 Import Table(匯入表)邏輯部分。


二、DOS MZ Header 和 PE Header

2.1、DOS MZ Header 定義成結構 IMAGE_DOS_HEADER(64位元組) 。結構定義如下:

typedef struct _IMAGE_DOS_HEADER {  // DOS .EXE Header
    e_magic;  // Magic number
  WORD  e_cblp;  // Bytes on last page of file
  WORD  e_cp;  // Pages in file
  WORD  e_crlc;  // Relocations
  WORD  e_cparhdr;  // Size of Header in paragraphs
  WORD  e_minalloc;  // Minimum extra paragraphs needed
  WORD  e_maxalloc;  // Maximum extra paragraphs needed
  WORD  e_ss;  // Initial (relative) SS value
  WORD  e_sp;  // Initial SP value
  WORD  e_csum;  // Checksum
  WORD  e_ip;  // Initial value
  WORD  e_cs;  // Initial (relative) CS value
  WORD  e_lfarlc;  // File address of relocation table
  WORD  e_ovno;  // Overlay number
  WORD  e_res[4];  // Reserved words
  WORD  e_oemid;  // OEM ntifier (for e_oeminfo)
  WORD  e_oeminfo;  // OEM information; e_oemid specific
  WORD  e_res2[10];  // Reserved words
  LONG  e_lfanew;  // File address of new exe Header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
 
IMAGE_DOS_HEADER 結構的e_lfanew成員就是指向 PE Header 的 RVA。e_magic 包含字串"MZ"。

2.2、PE Header 實際就是一個 IMAGE_NT_HEADERS 結構。定義如下:

typedef struct _IMAGE_NT_HEADERS {
  DWORD Signature;
  IMAGE_FILE_HEADER FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

 IMAGE_NT_HEADERS 結構成員含義:

1.Signature:一DWORD 型別,值為50h, 45h, 00h, 00h(PE)。如果IMAGE_NT_HEADERS的Signature域值等於"PE",那麼就是有效的PE檔案。定義了常量IMAGE_NT_SIGNATURE供我們使用,定義如下:

#define IMAGE_DOS_SIGNATURE  0x5A4D  // MZ
#define IMAGE_OS2_SIGNATURE  0x454E  // NE
#define IMAGE_OS2_SIGNATURE_LE  0x454C  // LE
#define IMAGE_VXD_SIGNATURE  0x454C  // LE
#define IMAGE_NT_SIGNATURE  0x00004550  // PE00

2.FileHeader:該結構域包含了關於PE檔案物理分佈的資訊,比如節數目、檔案執行機器等。

3.OptionalHeader:該結構域包含了關於PE檔案邏輯分佈的資訊,雖然域名有"可選"字樣,但實際上本結構總是存在的。

2.3、檢驗PE檔案的有效性步驟總結如下:

1.首先檢驗檔案頭部第一個字的值是否等於 IMAGE_DOS_SIGNATURE,是則 DOS MZ Header 有效。
2.一旦證明檔案的 DOS Header 有效後,就可用e_lfanew來定位 PE Header 了。
3.比較 PE Header 的第一個字的值是否等於 IMAGE_NT_HEADER。如果前後兩個值都匹配,那我們就認為該檔案是一個有效的PE檔案。

 下面將透過一個VC++ 6.0的例子來檢驗PE檔案的有效性:

 我們首先呼叫開啟檔案通用對話方塊(GetOpenFileName),選擇開啟一個檔案並對映到記憶體(CreateFile,CreateFileMap、MapViewOfFile等),獲得目標檔案大小(m_buffer = new unsigned char[m_size];)。然後獲取目標檔案的頭2個位元組(((unsigned short*)m_buffer)[0];),看是否為"MZ"。如果相同,獲得目標檔案PE header的位置(((unsigned int*)(2*m_buffer + 0x3c));), 與0x00004550(PE)比較。由此驗證PE有效性。

三、File Header(檔案頭)

 File Header(IMAGE_FILE_HEADER)包含在PE Header(IMAGE_NT_HEADERS)裡面,其結構定義:

typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;
  WORD NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD SizeOfOptionalHeader;
  WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 IMAGE_FILE_HEADER 結構成員含義:

1.Machine:該檔案執行所要求的。對於平臺,該值是IMAGE_FILE_MACHINE_I386 (14Ch)。我們嘗試了LUEVELSMEYER的pe.txt宣告的14Dh和14Eh,但不能正確執行。

一些CPU識別碼的定義:

Intel I386  0x14C
Intel i860  0x14D
MIPS R300  0x162
MIPS R400  0x166
DEC Alpha A 0x184
Power PC  0x1F0(little endian)
Motorola 68000  0x268
PA RISC  0x290(Precision Architecture)

#define IMAGE_FILE_MACHINE_UNKNOWN  0
#define IMAGE_FILE_MACHINE_I386  0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000  0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000  0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000  0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2  0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA  0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC  0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_SH3  0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3E  0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4  0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_ARM  0x01c0  // ALittle-Endian
#define IMAGE_FILE_MACHINE_THUMB  0x01c2
#define IMAGE_FILE_MACHINE_IA64  0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16  0x0266  // MIPS
#define IMAGE_FILE_MACHINE_MIPPU  0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16  0x0466  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64  0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_AXP64  IMAGE_FILE_MACHINE_ALPHA64

2.NumberOfSections:檔案的節數目。如果我們要在檔案中增加或刪除一個節,就需要修改這個值。

3.TimeDateStamp:檔案建立日期和時間。其格式是自從1969年12 月31 日4:00 P.M. 之後的總秒數。據我計算,0xFFFFFFFFh是136.19251950152207001522070015221 年。

4.PointerToSymbolTable:COFF 符號表格的偏移位置。此域只對COFF 除錯資訊有用。

5.NumberOfSymbols:COFF 符號表格中的符號個數。

6.SizeOfOptionalHeade:指示緊隨本結構之後的 Optional Header(IMAGE_OPTIONAL_HEADER)結構大小,必須為有效值。

7.Chracteristics:關於本檔案資訊的標記。一些比較重要的性質如下:

0x0001 檔案中沒有重定位(relocation)
0x0002 檔案是一個可執行程式exe(也就是說不是OBJ 或LIB)
0x2000 檔案是dll,不是exe。

 一般情況下,如果要遍歷節表就得使用 NumberOfSections,其它的幾個域作用不大。
 

四、Optional Header

4.1、RVA 及其相關概念:

 RAV 代表相對虛擬地址。RVA是虛擬空間中到參考點的一段距離。RVA就是類似檔案偏移量的東西。當然它是相對虛擬空間裡的一個地址,而不是檔案頭部。舉例說明,如果PE檔案裝入虛擬地址(VA)空間的400000h處,且程式從虛址401000h開始執行,我們可以說程式執行起始地址在RVA 1000h。每個RVA都是相對於模組的起始VA的。虛址(VA)0x401000h - 基址(BA)0x400000h = RVA 0x1464h。基址(Base Address)用來描述被對映到記憶體中的exe或者dll的起始位置。

 為什麼PE檔案格式要用到RVA呢? 這是為了減少PE裝載器的負擔。因為每個模組都有可能被過載到任何虛擬地址空間,如果讓PE裝載器修正每個重定位項,這肯定是個夢魘。相反,如果所有重定位項都使用RVA,那麼PE裝載器就不必操心那些東西了: 它只要將整個模組重定位到新的起始VA。這就象相對路徑和絕對路徑的概念: RVA類似相對路徑,VA就象絕對路徑。

 在PE檔案中大多數地址多是RVAs 而 RVAs只有當PE檔案被PE裝載器裝入記憶體後才有意義。如果直接將檔案對映到記憶體而不是透過PE裝載器載入,則不能直接使用那些RVAs。必須先將那些RVAs轉換成檔案偏移量。

4.2、Optional Header 結構是 IMAGE_NT_HEADERS 中的最後成員。包含了PE檔案的邏輯分佈資訊。該結構共有31個域,一些是很關鍵,另一些不太常用。其結構定義:

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD  Magic;
  BYTE  MajorLinkerVersion;
  BYTE  MinorLinkerVersion;
  DWORD  SizeOfCode;
  DWORD  SizeOfInitializedData;
  DWORD  SizeOfUninitializedData;
  DWORD  AddressOfEntryPoint;
  DWORD  BaseOfCode;
  DWORD  BaseOfData;
  DWORD  ImageBase;
  DWORD  SectionAlignment;
  DWORD  FileAlignment;
  WORD  MajorOperatingSystemVersion;
  WORD  MinorOperatingSystemVersion;
  WORD  MajorImageVersion;
  WORD  MinorImageVersion;
  WORD  MajorSubsystemVersion;
  WORD  MinorSubsystemVersion;
  DWORD  VersionValue;
  DWORD  SizeOfImage;
  DWORD  SizeOfHeaders;
  DWORD  CheckSum;
  WORD  Subsystem;
  WORD  DllCharacteristics;
  DWORD  SizeOfStackReserve;
  DWORD  SizeOfStackCommit;
  DWORD  SizeOfHeapReserve;
  DWORD  SizeOfHeapCommit;
  DWORD  LoaderFlags;  DWORD  NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

 IMAGE_OPTIONAL_HEADER 結構成員含義:
 
1.Magic:用來定義 image 的狀態

0x0107(IMAGE_ROM_OPTIONAL_HDR_MAGIC):一個 ROM image
0x010B(IMAGE_NT_OPTIONAL_HDR_MAGIC): 一個正常的(一般的)EXE image。大部份PE 檔案都含此值。

2.MajorLinkerVersion、MinorLinkerVersion:產生此PE檔案的連結器的版本。以十進位制而非十六進位制表示。例如2.23 版。

3.SizeOfCode:所有code section 的總和大小。大部分程式只有一個 code section,所以此域通常就是 .text section 的大小。
4.SizeOfInitializedData:所有包含初始化內容的 sections(但不包括 code section)的總和大小。似乎不包括 initialized data sections 在內。

5.SizeOfUninitializedData:所有需要PE裝載器將記憶體地址空間賦予它但是卻不佔用空間的所有 sections 的大小總和。這些 sections 在程式啟動時並不需要特別內容,所以導致 Uninitialized Data 這種叫法。為初始化的內容通常放在 .bss section 中。

6.AddressOfEntryPoint:這是PE檔案開始執行的位置。這是一個RVA,通常會落在 .text section.此域適用於 exe 或 dll。

7.BaseOfCode:一個RVA,表示程式中的 code section 從何開始。code section 通常在 data section 之前,在PE 表頭之後。連結器所產生的exes 中,此值通常為0x1000。Borland 的TLINK32則通常指定此值為0x10000。因為預設情況下TLINK時以64k為對齊粒度的,而MS用的是4k。

8.BaseOfData:一個RVA,表示程式中的 data section 從何開始。data section 一般位於code section 和 PE 表頭之後。
 
9.ImageBase:PE檔案的優先裝載地址(Base Address)。比如,如果該值是400000h,PE裝載器將嘗試把檔案裝到虛擬地址空間的400000h處。字眼"優先"表示若該地址區域已被其他模組佔用,那PE裝載器會選用其他空閒地址。

10.SectionAlignment:記憶體中節對齊的粒度。例如,如果該值是4096 (1000h),那麼每節的起始地址必須是4096的倍數。若第一節從401000h開始且大小是10個位元組,則下一節必定從402000h開始,即使401000h和402000h之間還有很多空間沒被使用。

11.FileAlignment:檔案中節對齊的粒度。例如,如果該值是(200h),,那麼每節的起始地址必須是512的倍數。若第一節從檔案偏移量200h開始且大小是10個位元組,則下一節必定位於偏移量400h,即使偏移量512和1024之間還有很多空間沒被使用或定義。預設值就是0x200h。

12.MajorOperatingSystemVersion/MinorOperatingSystemVersion:使用此可執行程式的作業系統的最小版本。WIN32程式的這兩個域通常指定為1.0。

13.MajorSubsystemVersion/MinorSubsystemVersion:WIN32子系統版本。若PE檔案是專門為WIN32設計的,該子系統版本必定是4.0否則對話方塊不會有3維立體感。

14.MajorImageVersion/MinorImageVersion:使用者自定義的域,允許你擁有不同版本的exe或dll。可以利用連結器的 /VERSION 選項設定其值。例如:LINK /VERSION:2.0 myobj.obj。

15.Reserved1:似乎總是0。

16.SizeOfImage:記憶體中整個PE映像體的尺寸。它是所有頭和節經過節對齊處理後的大小。也就是從image base 開始,直到最後一個 section為止。最後一個section 的尾端必需是SectionAlignment 的倍數。
 
17.SizeOfHeaders:所有頭 + 節表的大小,也就等於檔案尺寸減去檔案中所有節的尺寸。可以以此值作為PE檔案第一節的檔案偏移量。

18.CheckSum:此程式的一個CRC 校驗和。PE中此域通常被忽略並被設為0。然而,所有的 DLLs、所有在開機時載入的DLLs、以及server DLLs 都必須有一個合法的 CheckSum。其演演算法可以在IMAGEHLP.DLL中獲得。IMAGEHLP.DLL 的程式碼可以在WIN32 SDK中找到。

19.Subsystem:用來識別PE檔案屬於哪個子系統。對於大多數Win32程式,只有兩類值: Windows GUI 和 Windows CUI (控制檯)。WINNT.h中定義如下:

#define IMAGE_SUBSYSTEM_UNKNOWN  0  Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE  1  不需要子系統(例如程式)
#define IMAGE_SUBSYSTEM_WINDOWS_GUI  2  在Windows GUI 子系統中執行
#define IMAGE_SUBSYSTEM_WINDOWS_CUI  3  在Windows 字元子系統中執行(也就是console 應用程式)
#define IMAGE_SUBSYSTEM_OS2_CUI  5  在OS/2 字元模式子系統中執行(也就是OS/2 1.x 應用程式)
#define IMAGE_SUBSYSTEM_POSIX_CUI  7  在Posix 字元模式子系統中執行
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS  8  一個驅動
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI  9  在Win CE 子系統中執行

20.DllCharacteristics:一組標誌位,用來指出dll的初始化(例如 DllMain)在什麼環境下被呼叫。這個值總是0,但是作業系統會在四種情況發生式呼叫dll的初始化函式。此值的四個值的意義如下:

0x0001:當DLL被載入一個程式的地址空間時
0x0002:當一個執行緒結束時
0x0004:但一個執行緒開始時
0x0008:當DLL退出時
0x2000:一個WDM驅動

21.SizeOfStackReserve:執行緒初始堆疊的保留大小。然而並不是所有的這些記憶體都被系統指定。此值預設為0x100000(1MB)。如果你的程式中呼叫CreateThread 並指定其堆疊大小為0,獲得的執行緒就有一個與此值相同大小的堆疊。

22.SizeOfStackCommit:一開始就被指定給執行執行緒初始堆疊的記憶體數量。微軟的連結器預設此值為0x1000(一個page),Borland 的TLINK32把它設為0x2000(兩個page)。

23.SizeOfHeapReserve:保留給最初的程式堆(process heap)的虛擬記憶體數量。這個堆的控制程式碼可以利用GetProcessHeap 獲得。並不是所有的這些記憶體都被指定。

24.SizeOfHeapCommit:一開始就被指定給程式堆(process heap)的記憶體數量。此值預設為0x1000個位元組(位元組)。

25.LoaderFlags:Debug用。可能作用:
a.在開始這個程式之前引發一箇中斷?
b.在程式被載入之後引發一個除錯器執行?

26.NumberOfRvaAndSizes:在DataDirectory(下一個域)陣列的成員結構個數。目前的工具總是把此值設為16。

27.DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:一個IMAGE_DATA_DIRECTORY 結構陣列。每個結構給出一個重要資料結構的RVA。陣列的第一個元素代表 Exported Function Table(如果有的話)的地址和大小,第二個元素代表Imported Function Table 的地址和大小,依此類推。下面是其順序的完整列表:

// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT  0  // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT  1  // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RE  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
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT  7  // Description String
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR  8  // Machine Value (MIPS 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

96/112  8  Export Table Export Table address and size.
104/120  8  Import Table Import Table address and size
112/128  8  Resource Table Resource Table address and size.
120/136  8  Exception Table Exception Table address and size.
128/144  8  Certificate Table Attribute Certificate Table address and size.
136/152  8  Base Relocation Table Base Relocation Table address and size.
144/160  8  Debug Debug data starting address and size.
152/168  8  Architecture Architecture-specific data address and size.
160/176  8  Global Ptr Relative virtual address of the value to be stored in the global pointer register. Size member of this structure must be set to 0.
168/184  8  TLS Table Thread Local Storage (TLS) Table address and size.
176/192  8  Load Config Table Load Configuration Table address and size.
184/200  8  Bound Import Bound Import Table address and size.
192/208  8  IAT Import Address Table address and size.
200/216  8  Delay Import Descriptor Address and size of the Delay Import Descriptor.
208/224  8  COM+ Runtime Header COM+ Runtime Header address and size
216/232  8  Reserved

  rivershan原創於.1.18


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-998443/,如需轉載,請註明出處,否則將追究法律責任。

相關文章