如何确保无法验证的 .NET 程序集有效?
How can I ensure an unverifiable .NET assembly is valid?
.NET 应用程序分布在名为 assemblies 的文件中,其中包含元数据和通用中间语言 (CIL) 代码。 .NET 符合的标准,
ECMA-335,II.3,指出两个发音相似的术语之间的区别:
一个组件如果符合标准就是有效。
Validation refers to the application of a set of tests on any file to check that the file’s format, metadata, and CIL are self-consistent. These tests are intended to ensure that the file conforms to the normative requirements of this specification.
如果程序集是有效的并且可以通过标准描述的静态分析算法证明程序集是类型-安全。
Verification refers to the checking of both CIL and its related metadata to ensure that the CIL code sequences do not permit any access to memory outside the program’s logical address space. In conjunction with the validation tests, verification ensures that the program cannot access memory or
other resources to which it is not granted access.
所有可验证的程序集都是有效的,但并非所有有效的程序集都是可验证的。此外,一些有效的程序集实际上可能是类型安全的,但验证算法无法证明它们是类型安全的,因此它们不可验证。要使用标准中的图表:
.NET SDK 提供了一种静态确定程序集是否可验证的工具:PEVerify。因为可验证程序集也必须有效,所以如果程序集无效,此工具也会报告错误。
但是,似乎没有等效的工具或程序来确定程序集是否只是 有效。例如,如果我已经知道程序集是无法验证的,而且我对此没有意见,我如何仍然确保 运行time 不会因程序无效而出错?
我的测试用例:
.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
}
}
我 assemble 使用 ilasm
,生成 MyAsm.exe
。
虽然我可以 运行 程序集,但 .NET 运行time 只会在调用 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
基于@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}");
}
}
}
}
将输出:
Program::invalid - Common Language Runtime detected an invalid program.
.NET 应用程序分布在名为 assemblies 的文件中,其中包含元数据和通用中间语言 (CIL) 代码。 .NET 符合的标准, ECMA-335,II.3,指出两个发音相似的术语之间的区别:
一个组件如果符合标准就是有效。
Validation refers to the application of a set of tests on any file to check that the file’s format, metadata, and CIL are self-consistent. These tests are intended to ensure that the file conforms to the normative requirements of this specification.
如果程序集是有效的并且可以通过标准描述的静态分析算法证明程序集是类型-安全。
Verification refers to the checking of both CIL and its related metadata to ensure that the CIL code sequences do not permit any access to memory outside the program’s logical address space. In conjunction with the validation tests, verification ensures that the program cannot access memory or other resources to which it is not granted access.
所有可验证的程序集都是有效的,但并非所有有效的程序集都是可验证的。此外,一些有效的程序集实际上可能是类型安全的,但验证算法无法证明它们是类型安全的,因此它们不可验证。要使用标准中的图表:
.NET SDK 提供了一种静态确定程序集是否可验证的工具:PEVerify。因为可验证程序集也必须有效,所以如果程序集无效,此工具也会报告错误。
但是,似乎没有等效的工具或程序来确定程序集是否只是 有效。例如,如果我已经知道程序集是无法验证的,而且我对此没有意见,我如何仍然确保 运行time 不会因程序无效而出错?
我的测试用例:
.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
}
}
我 assemble 使用 ilasm
,生成 MyAsm.exe
。
虽然我可以 运行 程序集,但 .NET 运行time 只会在调用 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
基于@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}");
}
}
}
}
将输出:
Program::invalid - Common Language Runtime detected an invalid program.