.NET应用程序分布在名为 assemblies 的文件中,其中包含元数据和公共中间语言(CIL)代码。 .NET符合的标准, ECMA-335,II.3,注意到两个相似的术语之间的区别:
如果程序集符合标准,则程序集有效。
验证是指对任何文件应用一组测试,以检查文件的格式,元数据和CIL是否自洽。这些测试旨在确保文件符合本规范的规范要求。
如果程序集有效,并且可以通过标准描述的静态分析算法证明程序集是类型安全的,则程序集可验证。
验证是指检查CIL及其相关元数据,以确保CIL代码序列不允许对程序逻辑地址空间外的内存进行任何访问。结合验证测试,验证可确保程序无法访问内存或 未被授予访问权限的其他资源。
所有可验证的程序集都有效,但并非所有有效的程序集都是可验证的。另外,一些有效的程序集实际上可能是类型安全的,但验证算法不能证明它们是这样的,所以它们是不可验证的。要使用标准中的图表:
.NET SDK提供了一个静态确定程序集是否可验证的工具:PEVerify。由于可验证程序集也必须有效,因此如果程序集无效,此工具也会报告错误。
但是,似乎并不是用于确定程序集 有效的等效工具或程序。例如,如果我已经知道程序集是无法验证的,并且我没关系,我如何仍然确保运行时因程序无效而错误?< / p>
我的测试用例:
.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
答案 0 :(得分:4)
基于@Damien_The_Unbelievers评论我写了这个小片段,它使用RuntimeHelpers.PrepareMethod Method来编译每个方法。它不会处理所有情况(嵌套类型,泛型,参考分辨率......),但作为起点它起作用:
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}");
}
}
}
}
将输出:
程序::无效 - 公共语言运行时检测到无效程序。