Dotnet文件格式解析

0x0、序

  解析过程并没有介绍对pe结构的相关解析过程,网上此类相关资料很多可自行查阅,本文只介绍了网上资料较少的从pe结构的可选头中的数据目录表中获取dotnet目录的rva和size,到完全解析dotnet文件格式特有数据结构的部分。

  了解dotnet文件格式你可能需要一款名为CFF Explorer的工具;你也可能在很多时候需要查阅书籍《Expert .NET 2.0 IL Assembler》,该书籍的中文版本名为《.NET探秘MSIL权威指南》。简要的文件格式图,可以参考下面:

   分析文件的md5为:79D7AF997C9224CFF7B82E539C71FCDB。

0x1IMAGE_COR20_HEADER结构

  通过可执行文件nt头下的可选头中数据目录中最后一项获取dotnet目录的rva和size。获取的数据为:rva=0x00002008;size=0x00000048,使用stud_pe将rva地址转化为raw。得到raw地址为:0x208。读取出其中数据,数据内容如下:

00000200                            48 00 00 00 02 00 05 00           H      

00000210   A0 23 00 00 58 10 00 00  03 00 02 00 06 00 00 06   ?  X          

00000220   30 22 00 00 70 01 00 00  00 00 00 00 00 00 00 00   0"  p           

00000230   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00                  

00000240   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00                  

  该长度为0x48的数据的数据结构如下:

typedef struct IMAGE_COR20_HEADER

{

    ULONG         cb;

    USHORT        MajorRuntimeVersion;

    USHORT        MinorRuntimeVersion;

    //符号表和开始信息

    IMAGE_DATA_DIRECTORY    MetaData;

    //信息都在这个元数据表中可以获取。

    ULONG         Flags;

    union{

        DWORD    EntryPointToken;

        DWORD    EntryPointRVA;

          };

     //绑定信息

      IMAGE_DATA_DIRECTORY    Resource;

      IMAGE_DATA_DIRECTORY    StrongNameSignature;

      //常规的定位和绑定信息

      IMAGE_DATA_DIRECTORY    CodeMagagerTable;

      IMAGE_DATA_DIRECTORY    VTableFixups;

      IMAGE_DATA_DIRECTORY    ExprotAddressTableJumps;

      IMAGE_DATA_DIRECTORY    MagageNativeHeader;

}IMAGE_COR20_HEADER

  通过对结构解析,读取结构中的元数据MetaData。该结构中可以得到一个元数据的rva和size。具体数据内容为:MetaData_rva=000023a0,MetaData_size= 00001058。MetaData_rva转换成MetaData_raw后为5a0。需要注意的是后面好多数据的定位都是以这个地址为基准的相对偏移。

0x2、元数据

  MetaData_rva指向的数据结构如下:

typedef struct STORAGE_SIGNATURE

{

     DWORD    lSignature;

     WORD     iMajorVersion;

     WORD     iMinorVersion;

     DWORD    iExtraData;

     DWORD    iLength;

     BYTE     iVersionString[];

     //字符串编译环境版本号的长度由iLength决定,包含尾部0,且按4字节对齐。

}STORAGE_SIGNATURE;

  结构的数据内容为:

000005A0   42 53 4A 42 01 00 01 00  00 00 00 00 0C 00 00 00   BSJB           

000005B0   76 34 2E 30 2E 33 30 33  31 39 00 00               v4.0.30319     

  紧跟存储标志结构后面的数据结构定义如下:

typedef struct STORAGE_HEADER

{

     BYTE      fFlags;

     BYTE      reserved;

     WORD      NumberOfStreams;

}STORAGE_HEADER;

  结构的数据内容为:

000005B0                                        00 00 05 00                  

0x3、流描述表

  存储头结构的最后一个成员是流的个数,在该结构后面将跟的是每一个流的描述结构,该成员有几个流个数,后面就有几个流描述结构,分析的文件流个数为5,后面将跟5个流描述结构。每个结构的定义如下:

typedef struct STREAM_HEADER

{

     DWORD    iOffset;

     //相对于MetaData_raw的偏移

     DWORD    iSize;

     BYTE     rcName[];

     //流名称字符串得长度由iSize决定,包含尾部0,且按4字节对齐。

}STREAM_HEADER;

  结构数据(不同的数据流之间已经用不同的颜色区分)为:

000005C0   6C 00 00 00 44 05 00 00  23 7E 00 00 B0 05 00 00   l   D   #~  ? 

000005D0   8C 07 00 00 23 53 74 72  69 6E 67 73 00 00 00 00   ?  #Strings   

000005E0   3C 0D 00 00 88 00 00 00  23 55 53 00 C4 0D 00 00   <   ?  #US ? 

000005F0   10 00 00 00 23 47 55 49  44 00 00 00 D4 0D 00 00       #GUID   ? 

00000600   84 02 00 00 23 42 6C 6F  62 00 00 00               ?  #Blob  

  流名称字符串中的名称是由微软定义好的字符串,不会出现非定义的字符串。已经定义的流名称有:

名称

含义

#~

存储压缩(优化)后的元数据信息,存在#~流后不会出现#-流。

#-

存储未压缩(优化)的元数据信息,存在#-流后不会出现#~流。

#Strings

存储元数据的各种字符串,比如类名称,方法名称,成员名称,参数名称等。字符串格式为UTF8格式。该流数据首部会存在一个空字符串;且此处定义的字符串最大长度不超过1024。

#Blob

存储程序中的非字符串信息,包括常量值,方法的签名,强名称等。每个数据的长度由数据的前1-3位决定:0表示1字节;10表示2字节;110表示4字节。

#GUID

存储所有GUID

#US

存储IL代码中使用的各种字符串,字符串格式为Unicode格式。

0x4、元数据信息流

  微软定义的六个数据流中,除去一个互斥的元数据信息流外,其他的流都是可能存在的。而其他的流信息都是通过元数据信息流中的结构对其进行引用的。并且紧跟流表结构的也是元数据信息流,该地址可以通过流表中的元数据信息流流表中的相对便移地址计算得到:该流数据内容的地址相对元数据起始地址偏移为0x6c。和metadata_raw相加得到的地址为:0x60c。该地址数据结构如下:

typedef struct METADATA_HEADER

{

     DWORD    Reserved;

     BYTE     Major;

     BYTE     Minor;

     BYTE     Heaps;

     BYTE     Rid;

     QWORD    MaskValid;

     QWORD    Sorted;

}METADATA_HEADER;

  结构下的数据内容为:

00000600                                        00 00 00 00                  

00000610   02 00 00 01 57 15 A2 15  09 01 00 00 00 FA 25 33       W ?     ?3

00000620   00 16 00 00                                           

  结构中有两个域需要说明,分别是Heaps域和MaskValid域。

  结构中MaskValid是一个位向量,每一个二进制位表示某一个特定的表存在。该域所占大小为64位。但对应的表个数为45个,所有只有低45位有意义,其他位没有含义。位和表的对应关系如下:

位数

表名

位数

表名

0

Module

23

Property

1

TypeRef

24

MethodSemantics

2

TypeDef

25

MethodImpl

3

FiledPtr

26

ModuleRef

4

Filed

27

TypeSpec

5

MethodPtr

28

ImplMap

6

MethodDef

29

FiledRVA

7

ParamPtr

30

ENCLog

8

Param

31

ENCMap

9

MethodImpl

32

AssemblyRef

10

MemberRef

33

AssemblyProcessor

11

Constant

34

AssemblyOS

12

CustomAttribute

35

Assembly

13

FieldMarshal

36

AssemblyRefProcessor

14

DeclSecurity

37

AssemblyRefOS

15

ClassLayout

38

File

16

FieldLayout

39

ExportedType

17

StandAloneSig

40

ManifestResource

18

EventMap

41

NestedClass

19

EventPtr

42

GenericParam

20

Event

43

MethodSpec

21

PropertyMap

44

GenericParamConstraint

22

PropertyPtr

 

 

  分析文件的MaskValid值转换成二进制数值如下:

MaskValid值(16进制)

MaskValid值(2进制)

0000010915A21557

10000100100010101101000100001010101010111

  分析MaskValid的二进制数值,可以发现共有17个二进制位被置1,所以该文件的元数据流中存在17个类型的表。根据位对应编号得到存在的各个类型的表的序号分别为:0、1、2、4、6、8、10、12、17、21、23、24、26、28、32、35、40。查找上面的序号名称关系表可以查到对应的表名称。再通过紧跟其后的4字节数组确定表中有多少个记录。记录的个数可以确定表的长度。4字节数组内容如下:

00000620               01 00 00 00  31 00 00 00 05 00 00 00           1      

00000630   05 00 00 00 0D 00 00 00  04 00 00 00 36 00 00 00               6  

00000640   16 00 00 00 01 00 00 00  02 00 00 00 03 00 00 00                  

00000650   04 00 00 00 01 00 00 00  01 00 00 00 01 00 00 00                  

00000660   04 00 00 00 02 00 00 00                                    

  4字节数组后将是按照数组各个元素对应的记录表,各个记录表的顺序和4字节数组的中顺序相同,也是按照结构编号从小到大排列。但每个表里每个元素的结构可能并不相同。以第一个表Module表为例,如果文件存在该结构,由于该结构序号为0,所以必然为第一个结构。其记录结构如下:

typedef struct Module_Struct

{

     WORD          Generation;

     WORD/DWORD    Name;         //(在#String流中的偏移量)大小可能是word也可能是dword

     WORD/DWORD    Mvid;         //(在#GUID流中的偏移量)大小可能是word也可能是dword

     WORD/DWORD    EncId;        //(在#GUID流中的偏移量)大小可能是word也可能是dword

     WORD/DWORD    EncBaseId;    //(在#GUID流中的偏移量)大小可能是word也可能是dword

}METADATA_HEADER;

  需要说明的是结构中后4个便宜量才用DWORD还是WORD由METADATA_HEADER结构中的Heaps域决定。

  结构中Heaps的含义为:元数据信息流中引用其他流中数据时,索引值的大小。

Heaps数值

含义

0

所有索引值均采用16位索引值

0x01

表示引用#String流时,索引值均为32位

0x02

表示引用#GUID流时,索引值均为32位

0x04

表示引用#Blob流时,索引值均为32位

  从上面的METADATA_HEADER结构中我们可以看到heaps的数值为0,索引均采用WORD类型大小,而通过4字节数组可以确定Module记录表的长度为1,所以具体的Module数据内容如下:

00000660                            00 00 0A 00 01 00 00 00                  

00000670   00 00                                               

  其他记录数据同样通过该方式进行查询。

  其他表中元素结构相关信息可以查阅相关资料文档《.NET探秘MSIL权威指南》或《Expert .NET 2.0 IL Assembler》附录B。

 

文章pdf版本、分析文件、简要文件格式图 打包下载

posted @ 2016-12-05 17:12  米哈伊尔  阅读(1694)  评论(0编辑  收藏  举报