Signing程序签名
签名
当一个程序引用另一个dll时,如果该dll已经被签名过,那么这个dll是不能被假冒的。这里我们做几个测试来验证一下。
强名称的机制。
首先,通过SN.exe获得一个公钥和一个密钥。然后,使用公钥对程序集的可执行文件(不包括DOS头、PE头等)进行哈希算法,得到一个文件散列值。最后,使用密钥对文件散列值进行加密,得到一个密文。这样,最后的强名称程序集里面要三样东西:
- 公钥标识(公钥的散列值的最后八个字节)
- 公钥
- 密文
使用公钥和程序集的可执行文件(不包括DOS头、PE头等)进行哈希算法可以得到一个文件散列值,使用公钥和密文也可以得到一个文件散列值,如果这两个散列值完全一致,OK,验证通过。最后,说说公钥标识(公钥的散列值的最后八个字节)的作用:
- 区分程序集,上面提到过它是程序集区分彼此的四大属性之一
- 验证公钥
- 避免程序集被恶意更改
pfk数字证书
证书文件是二进制格式,同时包含证书和私钥,且一般有密码保护。私钥需要安全存储,如果黑客获取了私钥,那么就可以使用该私钥替换强签名的dll,达到dll劫持的目的。 密钥如果有密码保护,则生成pfx文件,没有密码生成snk文件,pfx比snk文件较大些;
程序
- 新建一个解决方案,包含两个工程,一个是控制台主程序ConsoleApp1,一个是类库ClassLibrary1。
- 我们给类库创建两个自签名证书:Michael1.pfx和Michael2.pfx。
- 分别用两个签名证书编译类库,生成两个同名的类库ClassLibrary1.dll,另存到不同的文件夹。
- 使用反编译工具ILSpy查看这两个不同签名的ClassLibrary1.dll,可以看到虽然类库名称一样,但是PublicKeyToken不相同。
- 主控制台程序先引用Michael1.pfx签名的类库,然后在程序中创建一个ClassLibrary1.dll中的类,实例它就可以了。
- 编译程序,后面测试过程中直接在主程序的Debug文件夹运行程序,不能重新编译,否则测试环境会因为编译而改变。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClassLibrary1;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Class1 class1 = new Class1();
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
测试一:
使用Michael1.pfx给类库签名,然后运行程序,程序运行OK。因为程序名和PublicKeyToken都相同,不会报错。
测试二:
打开主控制台程序的Debug文件夹,使用Michael2.pfx给类库签名的dll库替换Michael1.pfx签名的类库,然后运行程序,程序运行报错。虽然程序名相同,但是PublicKeyToken不相同,报错。
测试三:
我们删除类库的数字签名,然后编译类库,此时通过反编译软件可以看到PublicKeyToken=null,测试我们使用这个没有签名的dll替换Michael1.pfx签名的类库,然后运行程序,程序运行报错。
测试四:
删除类库的数字签名,然后重新编译类库和主程序,此时主程序引用的是没有签名的类库。然后我们在运行程序时,替换没有签名的类库为Michael1.pfx签名的类库,此时运行程序,程序还是会报错。因为此时主程序需要引用的是PublicKeyToken=null的ClassLibrary1.dll类库,而当前能找到的却是有数字签名的类库,dll不匹配,导致报错。
测试结论:
引用dll时,如果数字签名不正确,会导致报错。因为数字签名具有唯一性,所以使用数字签名软件可以防止恶意DLL劫持漏洞攻击。
测试五:
哪些设置会导致PublicKeyToken变化,测试结果发现PublicKeyToken其实就是公钥的一部分,只是用于区分标识程序集,只要使用同样的证书进行签名,就会生产相同的PublicKeyToken,那么程序是如何验证的呢,我推测程序中应该还有一个地方保存着公钥和密文。
删除PublicKeyToken
通过C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe把dll转存为IL文件:
在.il文件中找到三处代码:publickkeytoken、publickey和hash,把对应的内容都删除,再重新使用ILAsm(Ilasm "ClassLibrary1\bin\Debug\testIL.il" /dll
)编译,这时该程序集的强名称就被成功的去除。如果源程序引用的是没有签名的dll,此时就可以直接使用刚刚Ilasm编译的类库替换原始的类库而不报错。
// Microsoft (R) .NET Framework IL Disassembler. Version 4.8.3928.0
// Copyright (c) Microsoft Corporation. All rights reserved.
// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly ClassLibrary1
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0D 43 6C 61 73 73 4C 69 62 72 61 72 79 31 // ...ClassLibrary1
00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0D 43 6C 61 73 73 4C 69 62 72 61 72 79 31 // ...ClassLibrary1
00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..
20 32 30 32 31 00 00 ) // 2021..
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 64 64 38 39 36 39 38 36 2D 37 36 35 31 // ..$dd896986-7651
2D 34 37 34 39 2D 61 63 39 63 2D 37 33 66 33 65 // -4749-ac9c-73f3e
61 38 35 39 65 63 30 00 00 ) // a859ec0..
.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1A 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework
2C 56 65 72 73 69 6F 6E 3D 76 34 2E 38 01 00 54 // ,Version=v4.8..T
0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C // ..FrameworkDispl
61 79 4E 61 6D 65 12 2E 4E 45 54 20 46 72 61 6D // ayName..NET Fram
65 77 6F 72 6B 20 34 2E 38 ) // ework 4.8
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........
89 64 42 ED CF 38 82 7A 25 DC 7A A1 33 55 FD DA // .dB..8.z%.z.3U..
DE B5 56 C8 A6 36 22 0B 4B 82 ED 22 FF 78 C0 F9 // ..V..6".K..".x..
63 8E C6 12 C7 F4 4F 53 C5 D8 46 67 BD 7B 2D 5E // c.....OS..Fg.{-^
82 AD AE F8 CD B2 8D 7A 6A 1E BB 37 7C 40 E2 4D // .......zj..7|@.M
D6 61 3A C5 ED CE 95 FB 5C 8C C0 E9 58 36 8C 86 // .a:.....\...X6..
0C D2 C7 77 06 D4 75 26 09 F3 5D 66 AB A0 2F 61 // ...w..u&..]f../a
FD E0 89 47 EB BB D2 CB 23 B7 C8 8C 41 AB BE F3 // ...G....#...A...
55 96 EF 37 B1 DE 5F 8C 6D B2 A1 D3 1B DC 73 D1 ) // U..7.._.m.....s.
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module ClassLibrary1.dll
// MVID: {02EEE682-D96B-4DF8-81E7-30637FF55EB8}
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000009 // ILONLY
// Image base: 0x09F80000
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi beforefieldinit ClassLibrary1.Class1
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Class1::.ctor
} // end of class ClassLibrary1.Class1
// =============================================================
// *********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file C:\Users\CNMIZHU7\source\repos\ConsoleApp1\ClassLibrary1\bin\Debug\testIL.res
动态加载DLL,并判断数字签名代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ClassLibrary1;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Class1 class1 = new Class1();
Console.WriteLine("Hello World");
string dir = @"C:\Users\CNMIZHU7\source\repos\ConsoleApp1\ClassLibrary2\bin\Debug\";
string assemblyName = "ClassLibrary2";
Assembly assembly = Assembly.LoadFrom(dir + assemblyName + ".dll");
byte[] publicKeyToken = null;
byte[] publicKey = null;
int hashCode = 0;
publicKeyToken = assembly.GetName().GetPublicKeyToken();
publicKey = assembly.GetName().GetPublicKey();
hashCode = assembly.GetName().GetHashCode();
string strPublicKeyToken = "";
foreach (var item in publicKeyToken)
{
strPublicKeyToken += Convert.ToString(item, 16);
}
Console.WriteLine(strPublicKeyToken);
foreach (var item in publicKey)
{
Console.Write(Convert.ToString(item, 16));
}
Console.WriteLine();
Console.WriteLine(Convert.ToString(hashCode, 16));
Console.WriteLine("SHA1Managed:");
SHA1Managed sha = new SHA1Managed();
byte[] hash = sha.ComputeHash(publicKey);
byte[] pkt = new byte[8];
Array.Copy(hash, hash.Length - 8, pkt, 0, 8);
Array.Reverse(pkt);
foreach (byte b in pkt)
{
Console.Write(Convert.ToString(b, 16).PadLeft(2, '0'));
}
Console.WriteLine("");
if (strPublicKeyToken!= "fba4ba41742f1dd3")
{
throw new Exception("Wrong PublicKeyToken");
}
Type type = assembly.GetType(assemblyName + ".Class1");
var instance = assembly.CreateInstance(assemblyName + ".Class1");
type.GetProperty("Name").SetValue(instance, "Michael",null);
var method = type.GetMethod("print");
method.Invoke(instance, null);
Console.ReadKey();
}
}
}
强名称签名过程
在进行强名称签名的时候,首先对程序集(不包括DOS头和PE头)进行Hash运算,得到文件的散列值;然后使用私钥对散列值进行加密,得到密文。将公钥、公钥标识(对公钥进行SHA-1散列运算后得到的密文的最后8个字节)和密文三个信息保存在程序集中。在加载该程序集时,首先对该程序集进行Hash运算得到一个Hash值(称为“新Hash值”),然后从程序集中提取公钥,对密文解密得到原始的Hash值,如果两个Hash值相同,即通过验证。