Metadata探秘

一、初探MetaData

     把支持CLR的编程语言(如C++/CLI、C#、VB等)编写的源代码文件通过微软的或者自己写的编译器可以编译为一个托管模块,它实际上是一个标准的PE文件,其结构可以参见深入了解CLR的加载过程一文。Metadata(元数据)与IL代码都存在于该PE文件的Sections中,Metadata与IL是同时生成且永远同步的,本文主要讨论Metadata的内容,并以如下代码为例:

namespace HelloWorld
{
    using System;
    using System.Runtime.InteropServices;

    public class Hello
    {
        [DllImport("User32.dll")]
        public static extern int MessageBox(int a,string b,string c, int d);

        public static void Main()
        {
            int num = MessageBox(0, "2", "3", 4);
            Console.Write("Please enter your name: ");
            string str = Console.ReadLine();
            num = MessageBox(0, str, "Welcome to use IL Assembly", 1);
        }
    }
}

     我认为可以将Metadata所包含的内容分为“宏观内容”和“微观内容”两部分。

     宏观内容包含Manifest,它包含了如下信息:构成程序集的文件、依赖的外部Module或Assembly(包括对GAC中Assembly或者自定义Assembly的依赖,任何一个.Net程序都将依赖Mscorlib.dll,所以对该Assembly的应用一定会出现在Manifest中)、当前Module的标识(一个程序集至少要有一个Module)、程序集的public key、image base等等,如图所示:

Metadata探秘

 

图1 Manifest 内容

     微观内容包括类型、方法、特性(Attribute),例如对于类型而言,包括类型名、类型所包含的字段和方法、每个方法的参数等等详细信息。

二、分析Metadata

     从逻辑上来说,Metadata由若干Stream组成,这些Stream可以分为两类:Metadata堆和Metadata表。

     Metadata堆包含:

     1) String堆:存储UTF-8编码的且以0结尾的字符串,堆中第一个字符串为空字符串,所以堆的第一个字节存储的一定是0,而堆的最后一个字节也一定是0;

     2) GUID堆:存储16个字节的二进制对象,因其长度固定,所以不需要额外字节来标识其长度或者结束位置;

     3) Blob堆:存储任意大小二进制对象,对齐方式如下:设对象长度(是一个无符号整型)为length,如果length<=0x7F则用一个字节来存储,如果0x80<=length<=0x3FFF则用2个字节来存储,如果0x4000<=length<=0x1FFFFFFF则用4个字节来存储;

     我们可以直观的从下面的图中得到Stream的信息:用ILDASM打开上述代码生成的程序集。

     1、在菜单“视图(View)”选项中单击“标头(Header)”选项,如图:

Metadata探秘

 

图2 元数据头

     2、在菜单“视图(View)”中单击“元信息(MetaInfo)”选项并在选中“原始:标头,架构,行(Raw:Header,Schema,Rows)”和“原始:堆(Raw:Heaps)”,然后单击“显示(Show!)”,如图:

Metadata探秘

 

图3 Metadata信息

Metadata探秘

 

图4 Metadata信息

     3、用UltraEdit 打开上述程序集,如图

Metadata探秘

 

图5 PE文件信息

     从图2中可以看到:Metadata头包含两个部分:Storage signature 和 Storage header。

     Storage signature结构为:

     1) 签名(Signature),类型为DWORD,值为0x424a5342,这个值存在哪儿呢?利用图3可以从图5找到Metadata头的起始地址为0x00001098,从0x00001098到0x0000109b可以找到这个值;

     2) 主版本(Major Version),类型为WORD;

     3) 次版本(Minor Version),类型为WORD;

     4) Extra Data Offset,保留字段,类型为DWORD,值为0;

     5) 版本字符串长度,类型为DWORD;

     6) 版本字符串,类型为BYTE数组,如当前版本为v2.0.50727。

     Storage header结构为:

     1) Flags,保留字段,类型为BYTE值为0;

     2) Stream的个数,类型为WORD,当前PE文件*有5个Stream。

     接下来就是Stream头,可以看到这个PE文件包含5个Stream:#~Stream、#Strings Stream、#US Stream、#GUID Stream、#Blob Stream:

表1 Stream头

Offset

Size

Name

0x0000006C (108)

0x00000150 (336)

#~

0x000001BC (444)

0x00000148 (328)

#Strings

0x00000304 (772)

0x00000074 (116)

#US

0x00000378 (888)

0x00000010 (16)

#GUID

0x00000388 (904)

0x0000006C (108)

#Blob

     1、#Strings Stream

     是一个String堆,从图5我们可看到Metadata起始地址为0x00001098,由表1可知#Strings流的偏移量为0x000001BC,所以我们到0x00001254地址去查看该Stream的内容,也可以直接通过ILDASM(图4)查看Stream内容,另外发现#Strings中存储的是元数据项的名字,并且以0开始以0结束,如表2:

表2 #Strings的内容

Offset

Data

0

 

1

<Module>

10

HelloWorld.exe

25

Hello

31

HelloWorld

42

mscorlib

51

System

58

Object

65

MessageBox

76

Main

81

.ctor

87

a

89

b

91

c

93

d

95

System.Diagnostics

114

DebuggableAttribute

134

DebuggingModes

149

System.Runtime.CompilerServices

181

CompilationRelaxationsAttribute

213

RuntimeCompatibilityAttribute

243

System.Runtime.InteropServices

274

DllImportAttribute

293

User32.dll

304

Console

312

Write

318

ReadLine

327

     2、#US Stream

     是一个Blob堆,可以存储用户自定义字符串或者二进制对象,在地址0x0000139C处看起,有表3内容:

表3 #US的内容

Offset

Byte Length

Data

0

0x00 (0)

1

0x03 (3)

2

5

0x03 (3)

3

9

0x31 (49)

Please enter your name:

59

0x35 (53)

Welcome to use IL Assembly

113

0x00 (0)

114

0x00 (0)

115

0x00 (0)

     3、#GUID

     是一个GUID堆,按序存储“全球唯一标识符”,在地址处查看起,有表4内容:

表4 #GUID的内容

Offset

Data

0

{ec04bb0c-8238-4d78-b80e-4415e508b3b5}

     4、#Blob Stream

     是一个Blob堆,存储Metadata中的内部二进制对象,例如,图1中定义对外部程序集mscorlib.dll的引用时,.publickeytoken的默认值为(B7 7A 5C 56 19 34 E0 89),这个默认值就存储在#Blob Stream中。内容如表5:

表5 #Blob的内容

Offset

Byte Length

Data

0

0x00 (0)

 

1

0x08 (8)

B7-7A-5C-56-19-34-E0-89

10

0x07 (7)

00-04-08-08-0E-0E-08

18

0x03 (3)

00-00-01

22

0x03 (3)

20-00-01

26

0x05 (5)

20-01-01-11-0D

32

0x04 (4)

20-01-01-08

37

0x04 (4)

20-01-01-0E

42

0x04 (4)

00-01-01-0E

47

0x03 (3)

00-00-0E

51

0x04 (4)

07-02-08-0E

56

0x08 (8)

01-00-07-01-00-00-00-00

65

0x08 (8)

01-00-08-00-00-00-00-00

74

0x1E (30)

01-00-01-00-54-02-16-57-72-61-70-4E-6F-6E-45-78-63-65-70-74-69-6F-6E-54-68-72-6F-77-73-01

105

0x00 (0)

106

0x00 (0)

107

0x00 (0)

     5、#~ Stream、

     可以划分为两个部分:头(Header)和Metadata Table。

     (1) 头(Header)

     从图3可以看到有这么一段:Metadata header: 2.0, heaps: 0x00, rid: 0x01, valid: 0x0000000914021547, sorted: 0x000016003301fa00,我们也可以到地址0x00001098 + 0x0000006c = 0x00001104处查看内容,如下:

Metadata探秘

 

图6 Header

     实际上Header由以下几个部分组成:

     1)、4字节大小的保留字段,值总为0;

     2)、1字节大小的主版本字段(Table Schema的版本,应该是跟随着CLR的版本);

     3)、1字节大小的次版本字段;

     4)、1字节大小的heap sizes,为0表示堆的索引大小为2字节;

     5)、1字节大小的保留字段,值总为1;

     6)、8字节大小的掩码串,相应位置为1,表示该Metadata Table有效;

     7)、8字节大小的掩码串,相应位置为1,表示该表为需要按照主键排序的表,说明如表6:

表6 Sorted Metadata Table

Table

Primary key

Secondary key

ClassLayout

Parent

Constant

Parent

CustomAttribute

Parent

DeclSecurity

Parent

FieldLayout

Field

FieldMarshal

Parent

FieldRVA

Field

GenericParam

Owner

Number column

GenericParamConstraint

Owner

ImplMap

MemberForwarded

InterfaceImpl

Class

Interface column

MethodImpl

Class

MethodSemantics

Association

NestedClass

NestedClass

     另外代码中,父类在TypeDef表中记录的索引号一定比子类在TypeDef表中记录的索引小。(父类定义在子类定义前面)

     8)、n个4字节大小的无符号整型(n为有效Metadata Table的个数),表示有效Metadata Table中的记录记录数分别是多少,上述内容反映到表7中:

表7 #~ Stream的Header的内容

Field

Value

Reserved

0x00000000 (0)

Major

0x02 (2)

Minor

0x00 (0)

HeapSizes

0x00 (0)

Reserved

0x01 (1)

MaskValid

0x0000000914021547 ( 0000 0000 0000 0000 0000 0000 0000 1001 0001 0100 0000 0010 0001 0101 0100 0111 )

Sorted

0x000016003301FA00 ( 0000 0000 0000 0000 0001 0110 0000 0000 0011 0011 0000 0001 1111 1010 0000 0000 )

Rows

1, 7, 2, 3, 4, 7, 3, 1, 1, 1, 1, 1

     紧接着Header的就是Metadata Table,2.0的Metadata Table一共有45个,按先后顺序反映在表8中,详细的说明可以参考Ecma-335:

表8 45个Metadata表

Token

Name

0x00

Module

0x01

TypeRef

0x02

TypeDef

0x03

FieldPtr

0x04

Field

0x05

MethodPtr

0x06

MethodDef

0x07

ParamPtr

0x08

Param

0x09

InterfaceImpl

0x0A

MemberRef

0x0B

Constant

0x0C

CustomAttribute

0x0D

FieldMarshal

0x0E

DeclSecurity

0x0F

ClassLayout

0x10

FieldLayout

0x11

StandAloneSig

0x12

EventMap

0x13

EventPtr

0x14

Event

0x15

PropertyMap

0x16

PropertyPtr

0x17

Property

0x18

MethodSemantics

0x19

MethodImpl

0x1A

ModuleRef

0x1B

TypeSpec

0x1C

ImplMap

0x1D

FieldRva

0x1E

EncLog

0x1F

EncMap

0x20

Assembly

0x21

AssemblyProcessor

0x22

AssemblyOS

0x23

AssemblyRef

0x24

AssemblyRefProcessor

0x25

AssemblyRefOS

0x26

File

0x27

ExportedType

0x28

ManifestResource

0x29

NestedClass

0x2A

GenericParam

0x2B

MethodSpec

0x2C

GenericParamConstraint

     6、以Hello类型为例,分析部分关键Metadata Table间的关系:

Metadata探秘

 

图7 Metadata Table

三、推荐资料

     1、ECMA-335:http://www.ecma-international.org/publications/standards/Ecma-355.htm

     2、《.NET IL Assembler》,作者:Serge Lidin;

     3、http://msdn.microsoft.com/zh-tw/library/dd229216.aspx,作者:蔡學鏞

转载于:https://www.cnblogs.com/vivounicorn/archive/2009/10/18/1585339.html

上一篇:彻底弄懂UTF-8、Unicode、宽字符、locale


下一篇:C#操作高低位