托管PE文件
文/玄魂
?
?中間語言
在.NET框架中,公共語言基礎結構使用CLS來綁定不同的語言。通過要求不同的語言至少要實現CTS包含在CLS中的部分,公共語言基礎結構允許不同的語言使用.NET框架。因此,在.NET框架中,所有的語言(C#、VB.NET、Effil.NET等)最后都被轉換為了一種通用語言:微軟中間語言(Microsoft Intermediate Language,MSIL,以下簡稱IL)。
IL是一種介于高級語言和基于Intel的匯編語言的中間語言,是.NET平臺的匯編語言。當用戶編譯一個.NET程序時,編譯器將源代碼翻譯成一組可以有效地轉換為本機代碼且獨立于CPU的指令。當執行這些指令時,實時編譯器將它們轉化為CPU特定的代碼。由于CLR支持多種實時編譯器,因此同一段IL代碼可以被不同的編譯器實時編譯并運行在不同的結構上。
IL包括用于加載、存儲和初始化對象以及對對象調用方法的指令,還包括用于算術和邏輯運算、控制流、直接內存訪問、異常處理和其他操作的指令。要使代碼可運行,必須先將 IL 轉換為特定于 CPU 的代碼,這通常是通過實時(JIT) 編譯器來完成的。由于CLR為它支持的每種計算機結構都提供了一種或多種JIT編譯器,因此同一組IL可以在所支持的任何結構上JIT編譯和運行。
當編譯器產生IL時,它也產生元數據。元數據描述代碼中的類型,包括每種類型的定義、每種類型的成員的簽名、代碼引用的成員和運行庫在執行時使用的其他數據。IL和元數據包含在一個可移植可執行(PE)文件中,下面重點介紹托管PE文件,以及元數據的相關知識。
1.3.1 ????? 托管PE文件
PE(Portable Execute,可移植執行體)是微軟Windows操作系統上的程序文件,常見的如EXE、DLL、OCX、SYS、COM。圖1-3展示了標準的PE/COFF文件頭部格式。
?
圖1-3 標準的PE/COFF文件頭部格式
MS DOS頭是DOS系統的遺傳內容,表示一個應用程序可以在DOS環境下運行。MS DOS根(stub)是一段代碼,如果Windows程序在DOS環境下運行,會給出“該程序不能在DOS環境下運行”(This program cannot be run in DOS mode)的提示。在偏移量0x3c處,MS DOS頭指向了PE標識(PE Signature)的地址。
PE標識表示該文件是一個PE文件。其值始終為00004550h,45h代表字符E,50h代表字符P。
COFF頭(COFF Header)提供了COFF或者可執行文件的最一般的信息。
PE頭(PE Header)提供了操作系統加載文件所需的信息。這對于PE文件是最重要的地方,其間包含了數據索引表和節。
關于標準PE文件的詳細內容請讀者閱讀相關資料,本節內容只關注托管PE文件的特殊信息。CLR對傳統的PE文件進行了擴展,如圖1-4所示是托管PE文件的格式。
?
圖1-4 托管PE文件的格式
標準的Windows PE文件頭和COFF(通用對象文件格式)頭類似,分為PE32和PE32+兩種。如果文件頭采用PE32格式,則該文件可運行在32位或64位操作系統上。如果文件頭采用PE32+格式,則該文件只能在64位的操作系統上運行。PE32 或者 PE32+ 頭也包含文件類型信息:GUI、CUI或者DLL。如果包含本地CPU代碼的模塊,則PE32或者PE32+ 頭將包含有關本地CPU代碼的相關信息。
CLR頭包含使這個模塊被托管的相關信息。這些信息包括CLR需要的版本信息、一些標識、入口方法的元數據信息、模塊的元數據位置和大小信息、資源信息、強名稱和一些其他信息。
每一個托管模塊都包含元數據表。元數據表有兩種,一種是描述源代碼中的類型描述和成員描述的元數據表,另一種是包含源代碼引用的類型描述和成員描述的元數據表。
IL代碼是編譯器編譯產生的中間代碼,程序運行時CLR負責將中間代碼編譯成本地代碼執行。
CLR頭定義在.NET Framework的CorHdr.h中,代碼如代碼清單1-4所示。
代碼清單1-4 CLR 頭定義
1 typedef struct IMAGE_COR20_HEADER 2 3 { 4 5 ULONG cb; 6 7 USHORT MajorRuntimeVersion; 8 9 USHORT MinorRuntimeVersion; 10 11 // Symbol table and startup information 12 13 IMAGE_DATA_DIRECTORY MetaData; 14 15 ULONG Flags; 16 17 union { 18 19 DWORD EntryPointToken; 20 21 DWORD EntryPointRVA; 22 23 }; 24 25 // Binding information 26 27 IMAGE_DATA_DIRECTORY Resources; 28 29 IMAGE_DATA_DIRECTORY StrongNameSignature; 30 31 // Regular fixup and binding information 32 33 IMAGE_DATA_DIRECTORY CodeManagerTable; 34 35 IMAGE_DATA_DIRECTORY VTableFixups; 36 37 IMAGE_DATA_DIRECTORY ExportAddressTableJumps; 38 39 IMAGE_DATA_DIRECTORY ManagedNativeHeader; 40 41 } IMAGE_COR20_HEADER;?
關于CLR頭中的各個字段的解釋見表1-1,后文會對PE文件中的節信息做簡要介紹,關于PE文件的詳細信息參看書后附錄中的參考書籍。
表1-1? CLR頭字段說明
| 偏移(offset) | 大小(size) | 字段(field) | 描述(description) |
| 0 | 4 | Cb | 頭的長度(bytes) |
| 4 | 2 | MajorRuntimeVersion | CLR運行程序所必須的最小版本(Minimum Version)信息的主碼(Major Number) |
| 6 | 2 | MinorRuntimeVersion | CLR運行程序所需要的版本信息中的次要編碼(Minor Number) |
| 8 | 8 | MetaData | 相對虛擬地址(RAV)和元數據的大小 |
| 16 | 4 | Flags | 二進制標志位組合,包含系統相關,程序調用等相關信息 |
| 20 | 4 | EntryPointToken/EntryPointRVA | 文件的入口點元數據標識符,對于DLL文件可以被設置為0 |
| 24 | 8 | Resources | 托管資源的大小和相對虛擬地址 |
| 32 | 8 | StrongNameSignature | 當前PE問件的hash數據的大小和相對偏移地址,被加載器用來綁定和版本驗證 |
| 40 | 8 | CodeManagerTable | Code Manager table的大小和相對偏移地址。目前為保留字段被設置為0 |
| 48 | 8 | VTableFixups | 一組V-Table的大小和相對虛擬地址信息 |
| 56 | 8 | ExportAddressTableJumps | 用于C++的輸出跳轉地址表的RVA和size,大多數情況為0 |
| 64 | 8 | ManagedNativeHeader | 為本地映像的保留字段,設置為0 |
| ? | ? | ? | ? |
下面通過ILDasm查看HelloWorld.exe的文件頭信息。單擊菜單“view-headers”,如圖1-5所示。
?
圖1-5 查看文件頭信息
頭信息的主要代碼如代碼清單1-5所示。
代碼清單1-5 HelloWorld.exe 頭信息
----- DOS Header:Magic: 0x5a4dBytes on last page: 0x0090......(省略)File addr. of COFF header: 0x0080----- COFF/PE Headers:Signature: 0x00004550----- COFF Header:Machine: 0x014cNumber of sections: 0x0003Time-date stamp: 0x4b1b1d3aPtr to symbol table: 0x00000000Number of symbols: 0x00000000Size of optional header: 0x00e0Characteristics: 0x0102----- PE Optional Header (32 bit):Magic: 0x010b......(省略)Directory: ......(省略)Table: 0x00000000 [0x00000000] address [size] of Delay Load IAT: 0x00002008 [0x00000048] address [size] of CLR Header: ......(節信息,略)Base Relocation Table0x00002000 Page RVA0x0000000c Block Size0x00000002 Number of EntriesEntry 1: Type 0x3 Offset 0x000007a0Entry 2: Type 0x0 Offset 0x00000000Import Address TableDLL : mscoree.dll......(省略)Delay Load Import Address Table// No data.Entry point code:FF 25 00 20 40 00----- CLR Header:Header size: 0x00000048Major runtime version: 0x0002Minor runtime version: 0x0005......(省略) Metadata HeaderStorage Signature:......(省略)Storage Header:0x00 Flags0x0005 Number of StreamsStream 1:0x0000006c Offset0x000001e8 Size'#~' Name......(省略)Stream 5:0x00000510 Offset0x00000130 Size'#Blob' NameMetadata Stream Header:0x00000000 Reserved0x02 Major0x00 Minor0x00 Heaps0x01 Rid0x0000000900001547 MaskValid0x000016003325fa00 SortedCode Manager Table:defaultExport Address Table Jumps:// No data.?
上面代碼中涉及很多節信息,下面做簡要論述。
1.?????? Relocation(重定位)
映像文件的.reloc節包括了Fixup表,它包含了映像文件中的所有定位項。.reloc節的RVA和大小都由PE頭的基地址重定位(Base Relocation)表目錄定義。Fixup表由定位塊組成,每個塊都包括了一個4KB頁的定位。這些塊都是4字節對齊的。
每個定位都描述了映像文件中特定地址的位置,以及操作系統加載程序在將映像文件載入內存的時候,應該如何修改這個位置上的地址。
每個定位塊都開始于兩個4字節無符號整數:頁面的RVA,這個頁面包含了需要定位的地址、塊的大小。緊隨其后的是頁面的定位項每個項都是16位寬的,其中的4個最高權重位包括了所需要的重定位類型,剩下的12位包括了頁面中重定位地址的偏移量。
為了對地址進行重定位,操作系統加載程序會計算出首選的基地址(PE頭的ImageBase字段)和實際加載映像文件的基地址之間的差異(delta)。接著根據重定位的類型,將這個delta應用到地址上。如果在首選位置加載映像文件,就不需要進行定位。
說明 Windows XP或者更新的版本都是支持CLR的操作系統,既不需要CLR的啟動Stub,也不需要IAT來調用CLR。因此,如果CLR頭標志指出映像文件是純IL(COMIMAGE_FLAGS_ ILONLY),那么操作系統就會完全地忽略.reloc節。
2.?????? Text(文本)
PE文件的.text節是只讀節。在托管PE文件中,它包括了元數據表、IL代碼、導入表、CLR頭、CLR非托管啟動Stub。在由IL匯編器生成的映像文件中,這個節還包括了托管資源、強簽名的散列值、調試數據以及非托管導出Stub。所以.text節是托管PE文件對傳統PE文件改變最多的地方。
圖1-6總結了由IL匯編器生成的映像文件的.text節的通用結構。
?
圖1-6 .text節的通用結構
3.?????? Data(數據)
由IL匯編器生成的映像文件的數據節(.sdata)是可讀寫的節,它包括了數據常量、V表、非托管導出表以及TLS的目錄結構。聲明為特定于線程的數據位于一個不同的節,也就是.tls節。
4.?????? Data Constants(數據常量)
數據常量代表了靜態字段的映射,通常包括映射字段的初始化數據。
字段映射是一種使用ANSI字符串、Blob或結構來初始化靜態字段的方法。另一種初始化靜態字段的方法(對于CLR來說更正式的方法)是通過在類的構造函數中顯式地進行初始化。
一方面,映射到數據節的字段就像類型控制和垃圾收集那樣,是CLR控制機制所觸及不到的;另一方面,它是完全開放的,可以不受限制地訪問和修改。這將導致加載程序阻止特定的字段類型被映射。映射字段的類型不能包括對象引用、向量、數組或任何非公共的子結構。如果為靜態字段初始化使用類的構造函數,就不會出現這樣的問題。
5.?????? V-Table(V表)
在純粹的托管代碼模塊中,V表用于將托管方法公開給非托管代碼來調用。V表由一些項組成,每個項又由一個或多個槽組成。V表的這些項和槽都定義在V表定位中。每個定位指定了每個項中槽的數量和寬度(4字節或8字節)。V表的每個槽都包含各個方法的元數據標記,這些元數據標記在運行期間將會替換成方法本身的地址或者封送thunk,用于提供方法的非托管入口。因為這些定位是在運行期間執行的,所以托管PE文件的V表必須駐留于可讀寫的節中。IL匯編器將這個V表放在了.sdata節中,不像VTFixup表是駐留于.text節中的。
非托管映像文件的V表完全在鏈接期間定義,并只需操作系統加載程序執行的基地址重定位。因為在執行期間不需要改變V表(例如把方法標記替換成托管映像中的地址),所以非托管映像文件可以把它們的V表放在只讀節中。
6.?????? Unmanaged Export Table(非托管導出表)
在非托管映像文件中的非托管導出表占據一個單獨的節——.edata。在IL匯編器生成的映像文件中,非托管導出表和它引用的V表都駐留于.sdata節中。
7.?????? Thread Local Storage(線程局部存儲)
ILAsm和VC++允許用戶定義屬于TLS的數據常量,并將靜態字段映射到這些數據常量上。TLS是一種特殊的存儲類,類中的數據對象不是棧變量而是各個獨立線程的局部變量。因此,每個線程都可以為這樣的變量維護不同的值。
TLS數據在TLS目錄中描述,IL匯編器將其放置于.sdata節中。32位映像文件的TLS目錄結構定義在Winnt.h中,如代碼清單1-6所示。
代碼清單1-6 32位映像文件的TLS目錄結構
typedef struct _IMAGE_TLS_DIRECTORY32 {ULONG StartAddressOfRawData;ULONG EndAddressOfRawData;ULONG AddressOfIndex;ULONG AddressOfCallBacks;ULONG SizeOfZeroFill;ULONG Characteristics;} IMAGE_TLS_DIRECTORY32;?
64位映像(IMAGE_TLS_DIRECTORY64)的TLS目錄結構是類似的,除了開頭的4個字段是8字節無符號整數(ULONGLONG),而不是4字節無符號整數(ULONG)。
TLS目錄結構的RVA和大小存儲在PE頭的第10個數據目錄(TLS)中。構成了TLS模板的TLS數據常量,駐留于映像文件的.tls節中。
8.?????? Resources(資源)
在托管PE文件中可以嵌入兩種不同的資源:特定于平臺的非托管資源和特定于CLR的托管資源。它們駐留于托管映像文件的不同節,并通過不同的API進行訪問。
(1)?????? Unmanaged Resources(非托管資源)
非托管資源在PE文件的.rsrc節中。嵌入的非托管資源的起始RVA和大小都在PE頭的資源數據目錄中表示。
非托管資源由類型、名稱和語言進行索引,并根據這三個特征的順序進行二進制排序。
IL匯編器創建.rsrc節,并且會嵌入命令行選項指定的.res文件中的非托管資源。編譯器只能為每個模塊嵌入一個非托管資源文件。
當IL反匯編器分析托管PE文件并找到.rsrc節的時候,它會從這個節中讀取數據和結構,并流并釋放出包括在PE文件中所有非托管資源的.res文件。
(2)?????? Managed Resources(托管資源)
CLR頭的Resource字段包括了內嵌在PE文件中的托管資源的RVA和大小。它與PE頭的Resource目錄無關,后者指定了特定于平臺的非托管資源的RVA和大小。
在IL匯編器生成的PE文件中,非托管資源駐留于映像文件的.rsrc節中,而托管資源和元數據、IL代碼等都位于.text節中。托管資源在.text節中連續地存放。元數據攜帶著ManifestResource記錄,每一筆記錄對應著一個托管資源,包括了托管資源的名稱以及從CLR頭的Resources字段中指定的起始RVA算起的資源開始處的偏移量。在這個偏移位置上,會使用4字節無符號整數指出資源的字節長度。緊跟其后的是資源本身。
當IL反匯編器處理托管映像文件并找到嵌入的托管資源時,它會將每個資源各自寫到根據資源名稱命名的單獨文件中。
當IL匯編器創建PE文件時,它會根據資源名稱讀取在源代碼中定義為嵌入資源的所有托管資源,將它們寫到.text節中,并在每個資源的前面放置該資源的指定長度。
-----------------------注:本文改編自《.net 安全揭秘》1.3節
?
?
轉載于:https://www.cnblogs.com/xuanhun/archive/2012/05/23/2515392.html
總結
- 上一篇: Could not find resul
- 下一篇: django 1.3下关于静态文件sta