[轉載]淺析.NET Framework對PE檔案格式的擴充套件

看雪資料發表於2004-08-26

淺析.NET Framework對PE檔案格式的擴充套件
              WebCrazy(http://webcrazy.yeah.net)

   Microsoft .NET Framework出來小陣子了,我也自從其Beta 1以來,第一次接觸。本文將從.NET生成的一個小PE檔案著手,旨在理解.NET Framework對PE檔案格式的擴充套件。這種擴充套件目的是讓Windows系統識別Common Language Runtime(CLR)。

   PE檔案是Windows系列作業系統的可執行檔案格式。本文假設您對這一檔案格式有相當的理解,文中未涉及PE在之前的win16及之後的win64上的討論。在CLR出現之前,PE檔案格式僅簡單的由PE Header與Native Image(相對於以下介紹的CLR Header與CLR Data部分)組成。Native Image由各個section組成,如.text,.data,.rdate等等,需要指出的是PE檔案的這些section名命名規則並不要求一定要以句點開頭,事實上這只是Microsoft的對於程式碼段或資料段的預設說法,像Borland等其他編譯器則相應分別命名為CODE,DATA等等。Native Image含有已編譯的相應處理器的機器程式碼。

   在CLR出現後PE檔案擴充套件出了另外一部分,即CLR Header與CLR Data組成的供.NET Framework執行的支撐部分。CLR Header由.NET Framework SDK的CorHdr.h中的IMAGE_COR20_HEADER結構定義。從CorHdr.h或是IMAGE_COR20_HEADER的命名中Cor的全稱Com+ Runtime即可隱隱約約的看到.NET Framework的發展過程,其與COM+的淵源關係了。事實上IMAGE_COR20_HEADER在平臺SDK的winnt.h中也有定義,我查閱的了隨Windows XP DDK Build 2505發行的winnt.h中Microsoft在給出這個定義時的註釋為COM+ 2.0 header structure,而在.NET Framework SDK中即修改為CLR 2.0 header structure了。CLR Data則包含.NET metadata, IL method bodies等等。metadata及IL method是.NET中很關鍵的術語。IL即Microsoft Intermediate Language的縮寫。她是為了.NET跨平臺、跨語言的特性而引入的,有其自身的指令集。.NET SDK中的opcode.def列出的其支援的指令集。粗粗看來這些指令集與Intel的X86指令集十分的相像,也是由Prefix指定的的雙位元組進行編碼。

   下面的我將透過底下列出的這一段C# Console程式碼來簡述C#編譯器生成的PE檔案的執行流程及PE檔案的on disk結構。代只是簡單的輸出Hi,如下所示:

   public class App {
     static public void Main(System.String[] args) {
      System.Console.WriteLine("Hi");
     }
   }

   我們簡單的使用csc /out:app.exe app.cs對其編譯。生成的PE檔案,與.NET出現前傳統的編譯器生成的PE檔案一致,也含有IMAGE_DOS_HEADER,我們知道這部分的作用即是早期的DOS在遇到PE檔案格式時,能判定這個可執行檔案不能執行於DOS下而存在的。IMAGE_DOS_HEADER與將要談及的一些結構在winnt.h中均有詳細定義。Windows OS Loader根據IMAGE_DOS_HEADER中的e_lfanew成員定位緊挨著的IMAGE_NT_HEADERS。其定義如下:

   typedef struct _IMAGE_NT_HEADERS {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER32 OptionalHeader;
   } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

   我們知道IMAGE_OPTIONAL_HEADER32的成員AddressOfEntryPoint 是PE可執行檔案的入口,在.NET中其仍為執行入口,這應該是很好理解的。對於一個COMIMAGE_FLAGS_ILONLY(由IMAGE_COR20_HEADER 的成員Flags 指定)的Image,如我們生成的App.exe,這個入口也即間接定位至App.exe的Import表的_CorExeMain函式。_CorExeMain對應EXE檔案,由mscoree.dll匯出。mscoree.dll位於%WINNT%\system32下,是Microsoft .NET Runtime Execution Engine,應該指出的她是一個Native Image,負責呼叫IMAGE_COR20_HEADER中的 EntryPointToken 指定的.NET Token。這才是真正IL語言的入口。

   Native Image部分的各個Section的定位,已經有很多文件介紹,而且winnt.h中都有詳細的定義。我只簡單的闡述一下:

   .text、.data等section定位是由IMAGE_OPTIONAL_HEADER32中的DataDirectory成員指定。DataDirectory是一個IMAGE_DATA_DIRECTORY陣列,個數為MAGE_NUMBEROF_DIRECTORY_ENTRIES(當前為16)個。各個DataDirectory功能分別由IMAGE_DIRECTORY_ENTRY_***指定,如EXPORT、IMPORT等等。因為IMAGE_DATA_DIRECTORY由VirtualAddress(RVA)與Size組成,所以我們即可以很容易的找到這些Section的位置。與這些Section一樣,CLR Header的定位也是DataDirectory指定,其為IMAGE_DIRECTORY_ENTRY_COMHEADER(值為14,.NET Framework SDK V1 CorHdr.h中稱謂,在DDK 2505的winnt.h中為IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)。我們生成的App.exe有如下的格式:

         .
         .
         .
   AddressOfEntryPoint: 0x000022CE (+0x10)
         .
         .
         .
   DataDirectory[0] - IMAGE_DIRECTORY_ENTRY_EXPORT
     VirtualAddress: 0x00000000 (+0x60)
      Size: 0x00000000 (+0x64)
   DataDirectory[1] - IMAGE_DIRECTORY_ENTRY_IMPORT
     VirtualAddress: 0x0000227C (+0x68)
     Size: 0x0000004F (+0x6C)
         .
         .
         .
   DataDirectory[14] - IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
     VirtualAddress: 0x00002008 (+0xD0)
     Size: 0x00000048 (+0xD4)
         .
         .
         .
   OK,從DataDirectory[14]我們即可以很容易的定位CLR Header。CLR Header可以被合併到其它任何為只讀屬性的Section中。前面已經提及到CLR Header由IMAGE_COR20_HEADER結構定義。

   // CLR 2.0 header structure.
   typedef struct IMAGE_COR20_HEADER
   {
      // Header versioning
      ULONG cb;
      USHORT MajorRuntimeVersion;
      USHORT MinorRuntimeVersion;

      // Symbol table and startup information
      IMAGE_DATA_DIRECTORY MetaData;
      ULONG Flags;
      ULONG EntryPointToken;
      // Binding information
      IMAGE_DATA_DIRECTORY Resources;
      IMAGE_DATA_DIRECTORY StrongNameSignature;

      // Regular fixup and binding information
      IMAGE_DATA_DIRECTORY CodeManagerTable;
      IMAGE_DATA_DIRECTORY VTableFixups;
      IMAGE_DATA_DIRECTORY ExportAddressTableJumps;

      // Precompiled image info (internal use only - set to zero)
      IMAGE_DATA_DIRECTORY ManagedNativeHeader;

   } IMAGE_COR20_HEADER;
   這個結構的Flags與EntryPointToken上面已經提及。從這麼多的IMAGE_DATA_DIRECTORY上看,這個定義很像IMAGE_OPTIONAL_HEADER32,後者可以理解成PE檔案頭的精華,其用於定位.text等Section,由Windows OS Loader執行。而前者用於定位.NET CLR Data,如MetaData、Resources、StrongNameSignature等等。不同的是IMAGE_COR20_HEADER是由mscoree.dll中的_CorExeMain(對應於EXE檔案)負責呼叫(MSIL語言需經過JIT編譯成機器碼才可執行)。

   雖然EnrtyPointToken與上面的AddressOfEntryPoint均是執行入口,但卻有非常大的區別。AddressOfEntryPoint是一RVA,直接指向執行地址(相對於Image Base),其只能指向一本地機器程式碼用於裝載NET Runtime(如mscoree.dll中的_CorExeMain,對於DLL檔案其可以置為0)。而EntryPointToken只是一個.NET TOKEN。TOKEN是.NET Type的唯一識別,是一個DWORD值。其最高的8bit指明何種TOKEN。其由CorHdr.h中的CorTokenType enum定義。如mdtMethodDef為0x06000000,mdtEvent為0x14000000等等,而餘下的24bit則為此類TOKEN的唯一識別。EnrtyPointToken只能是一METHOD,而不能是EVENT等等。如App.Exe的EnrtyPointTokeno為0x06000001,其對應於Main Method。您可以使用ildasm.exe(隨.NET Framework SDK提供)進行驗證。

   App.exe的CLR Header如下(只列出了部分非空欄位):

    Size: 0x00000048
    MajorRuntimeVersion: 0x0002
    MinorRuntimeVersion: 0x0000
    MetaData
    VirtualAddress: 0x0000207C
    Size: 0x00000200
    Flags: 0x00000001
    COMIMAGE_FLAGS_ILONLY
    EntryPointToken: 0x06000001

   .NET MetaData由MetaData成員指定。Microsoft在CorHdr.h中給出了ILMETHOD的on disk組織結構(IMAGE_COR_ILMETHOD)。隨.NET Framework SDK也提供了一個例子metainfo用於分析Metadata。隨QuickStart例子的Class Browser的ASP.NET範例也是.NET Framework很好的學習材料。Metainfo使用常規的COM方法,而Class Browser使用.NET Framework的System.Reflection Namespace。關於.NET的SOAP,Web Services,Web Forms,XML等等QuickStart真不愧為QuickStart,.NET看來是下陣子學習的方向啊。

   最後應該說明的是對於.NET我真有一種很自清新的感覺,自身也是剛剛接觸,本文僅是抱著學習的態度,權當自己的學習筆記,與各位進行交流。文中有誤之處或是有所建議,請聯絡tsu00@263.net。

相關文章