签名

当一个程序引用另一个dll时,如果该dll已经被签名过,那么这个dll是不能被假冒的。这里我们做几个测试来验证一下。

强名称的机制。

首先,通过SN.exe获得一个公钥和一个密钥。然后,使用公钥对程序集的可执行文件(不包括DOS头、PE头等)进行哈希算法,得到一个文件散列值。最后,使用密钥对文件散列值进行加密,得到一个密文。这样,最后的强名称程序集里面要三样东西:

  1. 公钥标识(公钥的散列值的最后八个字节)
  2. 公钥
  3. 密文

使用公钥和程序集的可执行文件(不包括DOS头、PE头等)进行哈希算法可以得到一个文件散列值,使用公钥和密文也可以得到一个文件散列值,如果这两个散列值完全一致,OK,验证通过。最后,说说公钥标识(公钥的散列值的最后八个字节)的作用:

  1. 区分程序集,上面提到过它是程序集区分彼此的四大属性之一
  2. 验证公钥
  3. 避免程序集被恶意更改

pfk数字证书

证书文件是二进制格式,同时包含证书和私钥,且一般有密码保护。私钥需要安全存储,如果黑客获取了私钥,那么就可以使用该私钥替换强签名的dll,达到dll劫持的目的。 密钥如果有密码保护,则生成pfx文件,没有密码生成snk文件,pfx比snk文件较大些;

程序

  1. 新建一个解决方案,包含两个工程,一个是控制台主程序ConsoleApp1,一个是类库ClassLibrary1。
  2. 我们给类库创建两个自签名证书:Michael1.pfx和Michael2.pfx。
  3. 分别用两个签名证书编译类库,生成两个同名的类库ClassLibrary1.dll,另存到不同的文件夹。
  4. 使用反编译工具ILSpy查看这两个不同签名的ClassLibrary1.dll,可以看到虽然类库名称一样,但是PublicKeyToken不相同。
  5. 主控制台程序先引用Michael1.pfx签名的类库,然后在程序中创建一个ClassLibrary1.dll中的类,实例它就可以了。
  6. 编译程序,后面测试过程中直接在主程序的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文件中找到三处代码:publickkeytokenpublickeyhash,把对应的内容都删除,再重新使用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值相同,即通过验证。