• 元数据与IL简介


     

    文/玄魂

    1.3.2       元数据

    元数据是描述数据的数据。在CLR的上下文中,元数据表示由描述符组成的一套体系,这些操作符包括了在一个模块中被声明或引用的所有项。由于CLR模型是面向对象的,因此在元数据中描述的项是类和它们的成员,以及它们伴随着的特性、属性和关联。本节简单地介绍元数据,与原数据安全相关的内容会在后续章节中继续讲解,元数据的详细内容不在本书的论述范围之内。

    元数据实际上是一块二进制数据,包含了三种表:定义表、引用表和清单表。

    元数据定义表主要是模块定义、类型定义、方法定义、字段定义、事件定义、参数定义、属性定义等一系列定义表的集合。当编译器编译代码时,所有定义的内容都会生成对应的定义表。

    元数据引用表用于记录编译器中源代码引用的类型、方法、字段、事件。常用的引用表如:AssemblyRef(程序集引用表)、ModuleRef(模块引用表)、TypeRef(类型引用表)等。

    元数据清单表包含了组成程序集所需要的所有信息,同时包含了对其他程序集的引用信息。它明确地指出了哪些条目可以对外开放,哪些条目只可以在程序集内部进行访问。

    下面通过经典的HelloWorld程序简要分析其中的元数据信息,如代码清单1-7所示。

    代码清单1-7 HelloWorld程序代码

    using System;
    
    using System.Collections.Generic;
    
    using System.Linq;
    
    using System.Text;
    
     
    
    namespace HelloWorld
    
    {
    
        class Program
    
        {
    
            static void Main(string[] args)
    
            {
    
                Console.WriteLine("Hello World!");
    
                Console.Read();
    
     
    
            }
    
        }
    
    }

    下面使用反汇编工具ILDasm打开HelloWorld.exe,双击MANIFEST,图1-7为查看清单信息的截图。ILDasm的使用方法和参数说明请读者参考MSDN文档。

     

    图1-7  查看程序清单信息

    详细的清单信息如代码清单1-8所示。

    代码清单1-8 HelloWorld.EXE的清单信息

    // Metadata version: v4.0.21006
    
    .assembly extern mscorlib
    
    {
    
      .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
    
      .ver 4:0:0:0
    
    }
    
    .assembly HelloWorld
    
    {
    
    ......
    
    }
    
      .hash algorithm 0x00008004
    
      .ver 1:0:0:0
    
    .module HelloWorld.exe
    
    // MVID: {B8EB35DD-5AD2-402C-B422-AA63B0AACCFA}
    
    .imagebase 0x00400000
    
    .file alignment 0x00000200
    
    
    .stackreserve 0x00100000
    
    .subsystem 0x0003       // WINDOWS_CUI
    
    .corflags 0x00000003    //  ILONLY 32BITREQUIRED
    
    // Image base: 0x05C40000

    程序比较简单,代码中包含了版本、外部引用等简单的信息。表1-2描述了示例中所使用的 HelloWord.exe 程序集的程序集清单中的各项指令。

    表1-2 HelloWorld.exe指令说明

    指令

    说明

    .assembly extern < assembly name >

    指定包含当前模块所引用项目的另一程序集(在此示例中为 mscorlib)

    .publickeytoken < token >

    指定所引用程序集的实际密钥的标记

    .ver < version number >

    指定引用程序集的版本号

    .assembly < assembly name >

    指定程序集名称

    .hash algorithm < int32 value >

    指定使用的哈希算法

    .module < file name >

    指定组成程序集的模块名称,在此示例中,程序集只包含一个文件

    .subsystem < value >

    指定程序要求的应用程序环境。在此示例中,值 3 表示该可执行文件从控制台运行

    .corflags

    当前是元数据中的一个保留字段

    根据程序集的内容,程序集清单可包含许多不同的指令。有关程序集清单中指令的完整列表请读者参考相关文档,本例旨在抛砖引玉。若要查看完整的元数据信息,可以使用快捷键“Ctlr+M”,如图1-8所示。

     

    图1-8 查看元数据信息

    1.3.3       IL常用指令

    为方便起见,还是以HelloWorld.exe为例讲解IL的相关内容。由于篇幅所限,关于IL的详细内容还请各位读者参考相关资料。图1-9为Main方法的IL代码。

     

    图1-9 HelloWorld.exe Main方法的IL代码

    在一个中间语言程序中,如果某一行以“.”开始,代表这是一个传输给汇编工具的指令;而没有以“.”开始的行是中间语言的代码。上图中.method是方法定义指令,定义了Main方法,参数在“()”中,IL代码在“{}”中。.entrypoint是入口指令,表明该方法是入口方法。.maxstack指定了最大栈的深度为8。下面的IL_n是代码标签,后面是IL代码。nop是空指令;ldstr指令向栈中压入字符串“Hello World!”;call指令调用静态方法Console.WriteLine(string)和Console.read();pop弹出栈顶的值;ret指令表示方法体的结束。IL支持“//”和“/* */”的注释方法。

    提示  在中间语言中,如果需要调用一个方法,需要指定方法的全名,包括它的名称域(namespace)、类名、返回值类型和参数的数据类型。

    表1-3列举了IL的其他一些常用指令,更多的指令可以查看IL指令表。

    表1-3 IL常用指令

    指令

    描述

    .assembly <程序集名称> {}

    设置程序集

    ldc.i4.n

    把一个 32位的常量(n从0到8)装入堆栈

    stloc.n

    把一个从堆栈中返回的值存入第n(n取0~8)个局部变量

    add

    2个值相加。命令的参数必须在调用前装入堆栈,该函数从堆栈中移除参数并把运算后的结果压入堆栈

    sub

    2个值相减

    mul

    2个值相乘

    newarr type

    生成一个元素类型为type 的数组。数组的大小必须在调用该命令前装入堆栈。该命令会把一个数组的引用装入堆栈

    stelem.i4

    给一个数组成员赋值。数组的引用、下标和值必须在调用该命令前装入堆栈

    ldelema type

    把数组元素的地址装入堆栈。数组的引用和下标必须在调用该命令前装入堆栈。地址用来调用非静态函数

    ldlen

    把数组的长度装入堆栈。数组的引用必须在调用该命令前装入堆栈

    ldloca.s variable

    把变量的地址装入堆栈

    ldc.i4.s value

    把一个Int32的常量装入堆栈(用于大于8位的数)

    conv.i4

    把堆栈中值转换成Int32类型

    call instance function(arguments)

    调用类的非静态函数

    bge.s label

    跳转至label 如果value1≥value 2. Values 1和 2 必须在调用本命令前装入堆栈

    br.s label

    跳转至label

    box value type

    把一个值类型转成一个Object,并把该Object的引用装入堆栈

    blt.s label

    跳转至label 。如果value 1小于 value 2. Values 1 和 2 必须在调用本命令之前装入堆栈

    ldelem.i4

    把一个数组元素装入堆栈。数组引用和下标必须在调用本命令之前装入堆栈

    ldarga.s argument

    把函数参数的地址装入堆栈

    dup

    在堆栈上复制一个值

    stind.i4

    存储值的地址。地址和值必须在调用本命令之前装入堆栈

    .field

    定义类成员。和关键字public、private、static等一起使用

    stsfld static field

    用堆栈中的值替换静态字段的值

    ldfld field

    把一个非静态字段装入堆栈。类实例的地址必须在调用本命令之前装入堆栈

    ldarg.n

    把第n个参数装入堆栈。在非静态函数中,第0个参数是一个隐含的参数,代表this

    newobj constructor

    用构造函数constructor生成一个类的实例。构造函数的参数必须在调用本函数之前先装入堆栈。一个类的实例会被生成并装入堆栈

    callvirt instance function

    调用一个对象的后期绑定方法

    1.3.4       IL与代码验证

    在将MSIL编译为本机代码的过程中,MSIL代码必须通过验证过程,除非管理员已经建立了允许代码跳过验证的安全策略。验证过程检查MSIL和元数据以确定代码是否是类型安全的,这意味着它仅访问已被授权访问的内存位置。类型安全帮助将对象彼此隔离,因而可以保护它们免遭无意或恶意的破坏。它还提供了对代码可以可靠地强制安全限制的保证。

    运行库使用下列条件来验证代码是否为类型安全:

    q  对类型的引用与被引用的类型严格兼容。

    q  在对象上只调用正确定义的操作。

    q  标识与声称的要求一致。

    验证过程中检查 MSIL 代码,尝试确认该代码只能通过正确定义的类型访问内存位置和调用方法。例如,代码不允许以超出内存范围的方式来访问对象。另外,验证过程检查代码以确定 MSIL 是否已正确生成,这是因为不正确的 MSIL 会导致违反类型安全规则。验证过程通过正确定义的类型安全代码集,并且它只通过类型安全的代码。然而,由于验证过程存在一些限制,某些类型安全代码可能无法通过验证,而某些语言在设计上并不产生可验证的类型安全代码。如果安全策略要求提供类型安全代码,而该代码不能通过验证,则在运行该代码时将引发异常。

    -------------------------注:本文摘抄自《.NET 安全揭秘》1.3节


    作者:玄魂
    出处:http://www.cnblogs.com/xuanhun/
    原文链接:http://www.cnblogs.com/xuanhun/ 更多内容,请访问我的个人站点 对编程,安全感兴趣的,加qq群:hacking-1群:303242737,hacking-2群:147098303,nw.js,electron交流群 313717550。
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    关注我:关注玄魂的微信公众号

  • 相关阅读:
    美国首位女计算机博士荣获今年图灵奖
    此人需要关注一下
    Microsoft的壮大与IBM对Sun的收购
    文章介绍:Sexy Lexing with Python
    程序员的门道
    闲谈:敏捷与否的区分方法、对组织内部人员的现实作用与长远利益
    聊聊最俗的工厂相关话题
    人之患在好为人师
    TIOBE的头头儿和“反Java”的教授
    敏捷的核心究竟是什么
  • 原文地址:https://www.cnblogs.com/xuanhun/p/2516346.html
Copyright © 2020-2023  润新知