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


     概述  
       
              本系列文章,将从系统层角度,通过对MS.Net   CLR架构对PE映像结构的扩展的分析,  
      解析MS.Net   CLR架构的底层部分运行机制,帮助读者从更深层次理解CLR中某些重要概念  
      本文读者应具备基本的Win32编程经验,了解.Net中常见概念意义,并对Win32之PE映像  
      结构有一定了解,具体结构请参看Matt   Pietrek于1994.3发表在MSJ的经典文章  
      《Peering   Inside   the   PE:   A   Tour   of   the   Win32   Portable   Executable  
      File   Format》,与之重复的部分我一概跳过。  
              本系列文章,将分为几个大部分,首先是最重要的MetaData,其次是IL代码结构,  
      然后……我还没想好,呵呵。此外会根据需要穿插一下CLR核心概念、思想、技术的介绍。  
              至于CLR几个核心部件之间的关系与交互等问题,我热切期待TBSoft的大作,  
      我这里就不去抢他的话题了,呵呵。  
       
      前言  
       
              对一个优秀Win32程序员来说,对PE结构的了解是对Win32架构了解的必经之路,  
      而从Chicago(Win95的开发代号,Win95正式发布以前的文档对Win95的称呼)以来,  
      PE结构就相对稳定,直到MS.Net的出现,才发生了一些不大不小的变化。  
              之所以说是不大不小,是因为CLR基本上没有对PE结构进行改变,只是利用现有PE  
      结构的优良可扩展性,将其所需的信息扩展到PE映像中。具体一点说,就是利用了PE结构  
      中的IMAGE_OPTIONAL_HEADER.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR]  
      来保存服务于CLR的IMAGE_COR20_HEADER结构。此外的PE结构一律不变。  
      IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR此节,原本是设计用于COM,但不知为何  
      一直没有被使用,现在用于保存.Net信息的最高级信息结构。  
            我们的分析也将集中在此结构以及相关信息的分析上。  
            IMAGE_COR20_HEADER结构的定义,可以在FrameworkSDK\include\CorHdr.h  
      文件中找到,如下:  
       
      //   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;  
       
            而详细的说明,则可以在FrameworkSDK\Tool   Developers   Guide\docs  
      目录中找到。因为要将CLR变为标准,MS这次一反常态,公开大量有价值的文档,  
      避免我等浪费时间去逆向过程,呵呵  
            此结构虽然字段较多,但实际上其核心在于MetaData,其他信息都是围绕着  
      MetaData服务。之间的关系,等会再慢慢道来。  
            cb是结构大小,MajorRuntimeVersion.MinorRuntimeVersion是版本号  
      指执行此程序所需的最低CLR版本号,目前一般设置为1.1。而现在发布的.Net  
      Framework的CLR版本一般为2.0。  
            Flags是Runtime   Image描述标志,描述此映像的执行属性。如设置位  
      COMIMAGE_FLAGS_32BITREQUIRED=0x02,则此映像只能在32位系统上执行  
      对以后的64位CLR无效(MS.Net很大的一个功能就是为以后平滑过渡到64位  
      平台做准备,想想以前16位平台到32位平台过渡时的混乱,以及现在比以前翻了  
      n倍的代码量就恐怖,MS真是未雨绸缪啊,呵呵)。如果设置  
      COMIMAGE_FLAGS_STRONGNAMESIGNED=0x04,则此映像有strong   name  
      signature(这个东东不知道怎么翻译好)。这个strong   name   signature  
      在CLR架构里起到了非常重要的作用。为什么这么说呢?因为这个strong   name  
      signature起到Assembly的身份证的作用,它关系到CLR中一大堆概念的实现,  
      以后我会专门用一章篇幅来介绍他,这里暂且放下。  
            EntryPointToken则是指向IL程序的入口,类似于以前的  
      IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint的作用,只是以前的  
      AddressOfEntryPoint是一个RVA直接指向程序入口代码所在地址,  
      (不要告诉我你不知道RVA是什么啊,呵呵,赶快去看Peering   Inside   the   PE)  
      而现在EntryPointToken指向一个Token。注意,是Token,因为IL代码是  
      JIT编译的,存在于映像中的都是IL形式的P-code(pseudo   code),在需要时  
      才由CLR动态读取,在内存中编译展开为本机代码(Native   Code),进而执行。  
      因此这里的程序入口执行的只是一个MethodDef或File表的入口,一个Token而已。  
            这里的MethodDef是一个MetaData表,每行定义一个方法;而File表则是  
      每行有一个File定义的表,每行包含一个外部文件的信息。也就是说,在执行程序时  
      可以直接编译执行此映像中的一个方法的IL代码,也可能是重定向到另一个文件,  
      这就是Assembly作为一个逻辑代码单元,与传统DLL之类相比一个很大的不同。  
      Assembly的概念也非常重要,我不想这里一下说完,以后专门拿一章出来讲好了。  
          剩下几个字段都是IMAGE_DATA_DIRECTORY类型,这个类型是一个数据块  
      定义结构,在Winnt.h中有定义  
      typedef   struct   _IMAGE_DATA_DIRECTORY   {  
          DWORD       VirtualAddress;  
          DWORD       Size;  
      }   IMAGE_DATA_DIRECTORY,   *PIMAGE_DATA_DIRECTORY;  
          呵呵,知道RVA的意思了吧   RVA   =   Relative   Virtual   Address  
          Resources定义CLI资源;StrongNameSignature定义刚刚提到的  
      strong   name   signature;此外CodeManagerTable,  
      ExportAddressTableJumps,MangedNativeHeader都没用到。  
      VTableFixups暂且略过,以后用上时再详细解释。  
          这样一来,就剩下一个MetaData字段没有介绍了,不过这个重中之重的东东,  
      这次只言片语是无法介绍了,因为下面会有专门的一整篇——MetaData篇,  
      用n章的篇幅来详细剖析,呵呵  
       
      btw:因为自己以前不是搞Win32底层开发的,实在不知这种文章怎么写,  
              希望读者多多提意见,有没有解释清楚或者错误的地方尽管提出来。  
       
              分析.Net的CLR   PE映像其实并不是什么困难的事情,有现成的  
              代码(mono)现成的文档(Tool   Developers   Guide)可以看,  
              只是代码比较难看(不习惯unix代码风格),文档比较长  
              (一共20几M,poor)而已。我是实在耐不住好奇心才动手分析的,  
              希望能够把自己分析的一些收获和体会写出来,节省其他朋友的时间。  
       
              希望能够有充足的时间、精力和耐心完成这个系列文章……

    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,下次讲二进制实现时再说。
  • 相关阅读:
    bootstrap-table 数据表格行内修改
    java文件上传(单文件 多文件)与删除
    bootstrap-table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)
    bootstrap-table 大量字段整体表单上传之时间处理
    Java实习问题记录
    Playbook剧本初识
    自动化运维工具-Ansible基础
    性能优化概述
    Rewrite基本概述
    Nginx常见问题
  • 原文地址:https://www.cnblogs.com/cxd4321/p/1213305.html
Copyright © 2020-2023  润新知