近日对2K /XP .NET比较 有兴趣,找到几篇不错的文章,大家共赏 :)
浅析.NET Framework对PE文件格式的扩展
WebCrazy
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组成的供.NETFramework运行的支撑部分。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。