首页 > 编程 > .NET > 正文

MS.Net CLR 扩展PE结构分析2

2024-07-10 13:02:14
字体:
来源:转载
供稿:网友
  • 本文来源于网页设计爱好者web开发社区http://www.html.org.cn收集整理,欢迎访问。
  • flier lu <[email protected]>
      
    注意:本系列文章在水木清华bbs(smth.org)之.net版首发,
         转载请保留以上信息,发表请与作者联系
      
    metadata 篇
      
    第一章 metadata 概述
      
    1.1 什么是 metadata
      
        metadata翻译成中文是“元数据”,可以理解为type of type,
    说白了就是描述类型的类型数据。从最初级的语言层面支持的rtti
    (“近代”的编程语言基本上都提供了足够的支持,如c++,delphi等,
    部分较“落伍”的语言也通过不同形式如扩展库提供了模拟支持,
    “现代”的编程语言则提供了强力的支持,如java和c#<本质是clr>),
    到后来二进制级的com的idl和类型库(类型库是idl编译后的二进制形式),
    到现在的metadata,其实是遵循着相同的设计思路。只是出于不同的需求
    设计、实现,有这各自的优点缺点罢了。但随着语言的发展,更多的需求集中在
    灵活性方面,因而语言发展的趋势是元数据的使用越来越多、支持越来越强。
        举个最简单的例子,在ide中,动态显示当前对象的方法、属性名列表的功能
    (ms叫intellisense,borland叫codeinsight),就得宜于类型信息
    以前在vc里实现,比较麻烦,得生成专门的符号库;在vb里强一点,可以通过
    com的idispatch,itypeinfo,itypelib等接口动态获取,但编程麻烦要死;
    到clr,库一级直接提供支持,可以通过system.reflection完全控制
    甚至比com类型库更高一级地支持动态创建。
       对用户来说,可以完全了解当前程序接口,有哪些module,哪些class,
    哪些method等等,这给开发者提供了巨大的创造空间。如dunit(dotnet下
    的xunit单元测试平台)就大量使用reflection机制,我们等会谈使用时再说。
      
    1.2 metadata在clr中的作用
      
        对于clr架构来说,metadata可以算是核心操作对象,几乎绝大多数功能
    都需要参考其数据。从静态的il代码构成(二进制编码中直接使用metadata里的token)
    到动态jit编译器(使用metadata定位il代码及其关系);从简单的代码载入执行
    (class loader通过metadata定位代码入口、编译执行)到复杂的不同语言互操作
    (如vb.net继承c#的类,实际上是直接继承clr中metadata中的类);等等……
    几乎所有地方都能看到metadata的身影。
      
        因为本文的主要目的是介绍底层结构,这里就不再罗嗦metadata的好处了,
    反正以后文章中大家会次次看到他,各种优点自己慢慢体会吧 :)
      
    1.3 如何访问和使用 metadata
      
       做了一通广告,大家一定很关心如何使用metadata,听我慢慢道来
       在clr里使用metadata,可以在三个层面进行操作。
       最简单的方法是直接通过类库提供的system.reflection命名空间中的
    若干类进行访问,例如
      
    using system.reflection;
    using system;
      
    public class simple
    {
        public static void main ()
        {
             module mod = assembly.getexecutingassembly().getmodules () [0];
             console.writeline ("module name is " + mod.name);
             console.writeline ("module fullyqualifiedname is " +
    mod.fullyqualifiedname);
             console.writeline ("module scopename is " + mod.scopename);
        }
    }
      
       这种访问方式使用起来最简单,功能也足够强大,能够完成我们绝大多数的需要,
    特别是在system.reflection.emit命名空间中,更提供了动态生成、修改的支持
    功能强大得我都想不出能有什么改进了 :) (写.net病毒就靠他了,hoho)
       不过这种方式必须有clr环境的支持,受到库功能的限制(后面我们会看到很多
    在reflection一级里不提供的信息:),因此ms为工具软件开发商提供了另一套
    较底层的开发库,metadata unmanaged api。这套库通过一系列com接口,
    提供了直接访问metadata的强大支持,system.reflection应该就是使用它实现的。
    有兴趣的朋友可以参看frameworksdk/tool developers guide/docs
    目录下的metadata unmanaged api.doc文档,里面有详细的说明。
    如同其名字所示,它必须用unmanaged代码来使用,如传统的vc,delphi等。
       可以说99%的工作,都可以通过上面两套库来完成,不过总有些象我这样的人,
    喜欢对技术追根究底,想把隐藏在美好面纱下的底层架构纠出来暴露一把,呵呵
    因此有了第三个层面,二进制层面的逆向工程分析。
       好在ms为了让其cli(clr的子集)标准化,公开了大量文档,总算没要我用上
    softice之类的牛刀,partition ii metadata.doc文档中对metadata的
    二进制格式实现给出了比较详尽的说明,加上gnome的mono项目已经做了很多工作
    因而对metadata的二进制层面分析不是那么困难。
       接下去的文章中,我会逐渐将metadata在pe中的组织结构逐渐剥离开来,
    让大家能够了解这个神秘的clr核心到底是什么,里面隐藏了些什么,我们能够通过
    他做什么,为什么要这样设计,等等……
      
    1.4 metadata在pe中的组织结构
      
       说了一通废话后,回到正体上来,谈谈metadata在pe中的组织结构。
      
    注意:这一章里面我只把metadata结构的大概情况介绍一下,下一章会专门
    针对二进制模式分析进行详细讲解。如果你只想了解底层结构,可以跳过
    下一章。以后的文章也会遵循这种方式组织,讲一些结构、原理,跟着讲
    一些实际数据分析方法。
      
       上次我们提到clr的头信息里面专门有一个字段指向metadata数据块,
    实际上这个数据块只是metadata的一个头结构,保存有metadata的信息,
    而metadata的实际数据,是通过若干不同的heap或者说stream保存的。
    这里我统一使用stream“流“作为他的名字,但很多文档中以heap”堆“作为
    其称呼,我们可以理解他是一个二进制流,其中数据以堆的结构进行组织。
       metadata里最常见的有五种流,#string, #blob, #guid,
    #us(user string)和#~流("#"是流名字的前缀)
       string流就是一个字符串堆,metadata内部用到的所有字符串如类或方法
    的名字等等都以utf8编码保存在此堆内。而用户的字符串如字符串常量,
    则以unicode编码保存在us(user string)堆内。值得注意的是,
    us流和string流在二进制结构组织上不同,我们后面将分析时会详细提及。
    guid流是保存程序中使用到的guid的数组,如assembly中module的mvid。
    blob流是一个通用存储空间,除了guid和字符串以外基本上所有
    乱七八糟的东西都放在里面,呵呵,如publickey,常量的值等等。
       最重要的是#~流,这是metadata实际信息存放的地方。#~流结构上以
    若干张表(table)的形式组织,每张表存储某一方面的metadata信息,
    如methoddef表存储所有方法的信息。每张表又由若干的行(row)组成
    每行有n个列(column),每列代表一种信息,如methoddef表中每一行
    都有一个方法的rva,类型标志,名字,signature等等信息。在其中通过
    各种索引来相互关联,整个组织结构和关系数据库很相似。
       比较特殊的是,这里所有的表通过一个64bit的有效位图来表示其存在与否
    每种类型的表有一个编号,如methoddef表的编号是6,则第(1<<(6-1))位置1
    因而每个表的每一行,可以使用一个唯一的token表示。此token是一个32bit
    无符号整型数,最高一个字节表示表的编号,低三个字节表示表中的索引号。
    如0x06000003表示0x06表(methoddef)中第3行(如myapp::add)
    这个token概念在clr中频繁使用,如il代码调用函数、使用变量都是使用token。
       与之类似的还有coded index,下次讲二进制实现时再说。 
    发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表