dinghao

记录成长点滴

 

元数据和CLR(一)

分析工具:元数据用UtralEdit,内存布局用Sos调试扩展和vs2005的内存,寄存器和反汇编窗口的信息。

步骤:用UE打开任意一个.net dll或者Exe文件,分析静态的元数据。

转到调试状态,结合SOS和调试器信息分析CLR的执行(2.0中的MethodTable布局和1.1变化较大,看不懂,哪有相关资料?)

通过IL和元数据可以看到.net语句的内部实现,而IL的实现只能通过反汇编信息

元数据和元数据表

一般为描述其他对象而建立的对象都会加上“元”,元数据就是描述其他数据的数据,在.net中,其他数据指.net对象,引用到的对象及他们的关系。

元-元数据是描述元数据的数据,他们描述了元数据的构成,有了他们我们才能定位元数据表中每一条记录。

元数据的逻辑结构类似于数据库中的表,所以被称为元数据表。元数据表有列和行,每一行都是不重复的,都有唯一的RID(表索引),RID就像是数据库表的主建。数据库有Schema,如每一列的类型,大小等,元数据也有类似的东西,他们被称为“元-元数据”,包含表中记录的大小,列的大小,偏移等。

PE中比较固定的部分(32位系统)

IMAGE_DOS_HEADER 40h byte 00h---39h。其中从0x3c开始的最后四个字节为e_lfanew,为指向pe signature的文件指针。pe signature00 00 45 50,占四个字节,e_lfanewpe signature之间是实模式残余程,从40h79h8083为四字节pe signature

后面是IMAGE_FILE_HEADERcoff header)从84h97h,占14h

Peheader98h178hE0h224 bytes

Pe header中:从98h开始偏移96字节(32pe头)是_IMAGE_DATA_DIRECTORY 。共16个,每个表占八字节。

之后是区域头,每个头占28h40),共三个(个数是有Coff头的Numberofsections定义):从178h1a0h.text

1a0-1c8 rsrc

1c8-1f0 .rsloc

 

文件指针和RVA

在文件被加载到内存之前,项在文件中的偏移量,RvaVa是加载到内存之后的相对地址(偏移量)和地址。

RIDToken

RID是元数据表的行索引,只能在元数据表之间引用,如TypeDef表中通过RID定位此类型包含的第一个字段在Filed表中的RID

通过列类型代码(元-元数据有定义)可以确定列所引用的表(隐式),因此RID也是可以定位表的,只是在元数据外不能被访问。

Token是表索引加上RID,他们显式的确定了是哪个表中的RID,因此可以被外部引用。在IL中变量,常量等都是通过token引用的,他们会被编译为Token。有token类型的表共有24个,另外20个表没有token他们不能被外部引用到,只能在元数据间引用。

其中有个特别的token type 0x70000000,他的TokenRID部分并不是真正的RID,因为他们存在于#US流中,此流包含用户定义的数据,没有任何元数据信息,元数据也不会引用到#Us。他的RID部分是用户字符串在#US中的偏移量,通过他可以定位用户定义的字符串。而普通的Token,却要先定位RID,再根据元数据表的列类型等信息定位元数据信息。

流和堆

此处的堆和数据结构中的堆没有关系,是两个概念。分为三种StringGUIDblob

元数据提供了六种命名堆:

String:元数据引用到的内容,如类名,方法名,变量名等

US:用户定义的blob堆(不是String堆),包含字符串常量等,可以被ldstr指令直接寻址。不能被元数据直接引用,但可以被IL以及外部api引用。如string s =”dd”; dd存储在#uss存储在#string

Blob:元数据引用的二进制对象,不能包含用户定义的对象。

GUID:各种唯一标志如Modle元数据表的Mvid等。

#~和#-(镜像文件中只能包含一个):元数据流,包含元数据头,元数据表等,最复杂的堆。他会引用到(#String,#blob,#GUID流)

FlagSignature

Flag包含可见性,布局(Layout),类型语义,实现,字符串格式化等Flag,通过Flag我们可以确认这几类信息。

Signature:(blob流中)

定位Token

以用户定义的字符串为例子(#US):

用户字符串定义在#US流,计算方法是:流的首地址(即元数据头地址,因为流头在元数据头中)+#US流的偏移量(offset然后)然后加上字符串的偏移(tokenRID部分)就是字符串在#US流中的偏移。

定位元数据表

首先要找到元数据头(metaDataHeader,方法和Cor2.0Header一样,只是Rva换成Cor2.0HeaderMetaDataDir表的Rva。元数据头由STORAGESIGNATURESTORAGEHEADE、流头构成。后面为STORAGESTREAM,依次包含,#String,  #Blob, #Guid,  #US(User String)#~

元数据表被存储在#~流中,找到元数据表头,加上偏移量和元-元数据信息就可以定位。具体参见定位Cor2.0Header

定位区域头(Section Header

区域头共有三个,每个头占28h40)(头个数是有Coff头的Numberofsections定义):从178h1a0h.text

1a0-1c8 rsrc

1c8-1f0 .rsloc

定位PE头(数据目录表)

数据目录表位于PE头,从98h开始偏移96字节(32pe头)是_IMAGE_DATA_DIRECTORY 。共16个,每个表占八字节。

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 

数据目录表指向的数据在上面的三个Section中,可以根据 _IMAGE_DATA_DIRECTORYVirtualAddressrva)判断,其落在那个Section中:VirtualAddress<rva< VirtualAddress+ SizeOfRawData

十六个表中和CLI关系最密切的是第十五个表Cli header,通过上面的方法可以算出他在哪个区中。

 

定位Cor2.0Header

知道为于哪个Section,就可以定位Cor2.0Header了,根据计算可知,Section.text,地址每次编译都会有区别。根据CliHeaderRva和此sectionvirtualAddress PointerToRawData  可以计算出COR20Header的地址:(rva-virtualAddress) +  PointerToRawData 

Cor头含有七个表,最后一个为保留表(2.0只发现了前六个,可那2.0仍然没有用到此表),其中重要的是MetaDataDir表,地址可以根据同样方法计算得到。

 

posted on 2006-10-17 18:08  思无邪  阅读(2425)  评论(1编辑  收藏  举报

导航