2

.NET 应用程序分布在称为程序集的文件中,其中包含元数据和通用中间语言 (CIL) 代码。.NET 遵循的标准 ECMA-335 II.3 指出了两个听起来相似的术语之间的区别:

  • 符合标准的程序集是有效的。

    验证是指对任何文件进行一组测试,以检查文件的格式、元数据和 CIL 是否一致。这些测试旨在确保文件符合本规范的规范要求。

  • 如果程序集有效并且可以通过标准描述的静态分析算法证明程序集是类型安全的,则程序集是可验证的。

    验证是指检查 CIL 及其相关元数据,以确保 CIL 代码序列不允许对程序逻辑地址空间之外的内存进行任何访问。结合验证测试,验证确保程序无法访问内存或其他未被授予访问权限的资源。

所有可验证程序集都是有效的,但并非所有有效程序集都是可验证的。此外,一些有效的程序集实际上可能是类型安全的,但验证算法无法证明它们是类型安全的,因此它们是不可验证的。要使用标准中的图表:

ECMA-335,II.3,图 1:正确和可验证的 CIL 之间的关系

.NET SDK 提供了一个静态确定程序集是否可验证的工具:PEVerify。因为可验证程序集也必须有效,所以如果程序集无效,此工具也会报告错误。

但是,似乎没有等效的工具或程序来确定程序集是否有效。例如,如果我已经知道程序集是无法验证的,并且我对此感到满意,那么我如何仍然确保运行时不会由于无效程序而出错

我的测试用例:

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}

.assembly MyAsm { }
.module MyAsm.exe
.corflags 0x00020003    //  ILONLY 32BITPREFERRED

.class public Program
{ 
  .method public static int32 EntryPoint(string[] args) cil managed
  {
    .maxstack 2
    .entrypoint

    call string [MyAsm]Program::normal()
    call void [mscorlib]System.Console::WriteLine(string)

    call string [MyAsm]Program::unverifiable_init()
    call void [mscorlib]System.Console::WriteLine(string)

    call string [MyAsm]Program::unverifiable_jmp()
    call void [mscorlib]System.Console::WriteLine(string)

    call string [MyAsm]Program::invalid()
    call void [mscorlib]System.Console::WriteLine(string)

    ldc.i4.0
    ret
  }

  .method public static string normal() cil managed
  {
    .maxstack 2
    .locals init ([0] int32 initialized)

    ldstr  "From normal: "
    ldloca initialized
    call instance string [mscorlib]System.Int32::ToString()
    call string [mscorlib]System.String::Concat(string, string)

    ret
  }

  .method public static string unverifiable_jmp() cil managed
  {
    .maxstack 1

    ldstr "Printing from unverifiable_jmp!"
    call void [mscorlib]System.Console::WriteLine(string)

    jmp string [MyAsm]Program::normal() // jmp is always unverifiable
  }

  .method public static string unverifiable_init() cil managed
  {
    .maxstack 2
    .locals ([0] int32 hasGarbage) // uninitialized locals are unverifiable

    ldstr  "From unverifiable_init: "
    ldloca hasGarbage
    call instance string [mscorlib]System.Int32::ToString()
    call string [mscorlib]System.String::Concat(string, string)

    ret
  }

  .method public static string invalid() cil managed
  {
    .maxstack 1

    ldstr "Printing from invalid!"
    call void [mscorlib]System.Console::WriteLine(string)

    ldstr "From invalid"
    // method fall-through (no ret) is invalid
  }
}

我组装这个使用ilasm,生产MyAsm.exe

虽然我可以运行程序集,但 .NET 运行时只会在invalid()调用方法时出错,而不是在加载程序集时出错。如果我删除调用,则程序运行完成且没有错误,因此仅加载和运行程序集并不能保证它完全有效。

在程序集上运行 PEVerify 会产生三个错误。虽然在这种情况下,人眼很容易看出前两个错误是验证错误,最后一个是验证错误,但似乎没有一种简单的方法可以自动区分这种情况(例如,检查每一行verifi似乎太宽泛了)。

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.0
Copyright (c) Microsoft Corporation.  All rights reserved.

[IL]: Error: [C:\...\MyAsm.exe : Program::unverifiable_jmp][offset 0x0000000A] Instruction cannot be verified.
[IL]: Error: [C:\...\MyAsm.exe : Program::unverifiable_init][offset 0x00000005] initlocals must be set for verifiable methods with one or more local variables.
[IL]: Error: [C:\...\MyAsm.exe : Program::invalid][offset 0x0000000A] fall through end of the method without returning
3 Error(s) Verifying MyAsm.exe
4

1 回答 1

5

基于@Damien_The_Unbelievers 评论,我编写了这个小片段,它使用RuntimeHelpers.PrepareMethod 方法来编译每个方法。它不会处理所有情况(嵌套类型、泛型、引用解析等),但它可以作为一个起点:

var b = File.ReadAllBytes("MyAsm.exe");
var asm = Assembly.Load(b);

foreach(var m in asm.GetModules())
{
    foreach(var t in m.GetTypes())
    {
        foreach(var mb in t.GetMethods((BindingFlags)62).Cast<MethodBase>().Union(t.GetConstructors((BindingFlags)62)))
        {
            try
            {
                RuntimeHelpers.PrepareMethod(mb.MethodHandle);
            }
            catch (InvalidProgramException ex)
            {
                Console.WriteLine($"{mb.DeclaringType}::{mb.Name} - {ex.Message}");
            }

        }
    }
}

将输出:

Program::invalid - 公共语言运行时检测到无效程序。

于 2017-12-29T12:49:20.907 回答