C#中如何编程验证调用DLL的EXE文件数字签名
最优实现方案
要实现和SignTool Verify、文件资源管理器数字签名属性页完全一致的校验效果,唯一靠谱的方案是直接P/Invoke调用Windows系统内置的wintrust.dll提供的WinVerifyTrustAPI,不要自己解析PE结构、不要用.NET自带的X509Certificate相关读签名的方法——后者只能提取签名证书,完全不做完整性、信任链、吊销状态校验,结果完全不可信。
这个方案的优势:
- 和系统原生校验逻辑100%对齐,不存在校验结果和系统显示不一致的问题
- 无第三方依赖,兼容Windows 7及以上所有主流Windows版本
- 自动完成签名哈希校验、证书链信任验证、证书吊销检查、时间戳有效性校验所有流程,不需要自己实现复杂的签名逻辑
实现步骤
- 首先获取当前加载DLL的宿主EXE的完整路径,优先用
Process.GetCurrentProcess().MainModule.FileName,比Assembly.GetEntryAssembly().Location兼容性更好,能覆盖非托管宿主加载托管DLL的场景。 - 定义WinTrust API对应的P/Invoke签名、常量、结构体,注意参数要匹配32/64位系统的结构体内存布局。
- 调用
WinVerifyTrust传入EXE路径,使用系统默认的用户态PE文件签名校验策略,静默执行校验不弹出系统提示框。 - 根据返回值判断校验结果:返回
0代表签名校验通过,非0返回值对应不同的校验失败原因(签名缺失、文件被篡改、证书不受信任、证书被吊销等)。
可直接复用的代码示例
using System; using System.Diagnostics; using System.Runtime.InteropServices; public static class ExeSignatureValidator { // WinTrust 常量定义 private const int WTD_UI_NONE = 2; private const int WTD_REVOKE_NONE = 0; private const int WTD_CHOICE_FILE = 1; private const int WTD_STATEACTION_VERIFY = 0x00000001; private const int WTD_STATEACTION_CLOSE = 0x00000002; private const int WTD_REVOCATION_CHECK_CHAIN = 0x00000040; private static readonly Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid(0xaac56b, 0xcd44, 0x11d0, 0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WINTRUST_FILE_INFO { public uint cbStruct; public string pcwszFilePath; public IntPtr hFile; public IntPtr pgKnownSubject; } [StructLayout(LayoutKind.Sequential)] private struct WINTRUST_DATA { public uint cbStruct; public IntPtr pPolicyCallbackData; public IntPtr pSIPClientData; public uint dwUIChoice; public uint fdwRevocationChecks; public uint dwUnionChoice; public IntPtr pUnionData; public uint dwStateAction; public IntPtr hWVTStateData; public string pwszURLReference; public uint dwProvFlags; public uint dwUIContext; public IntPtr pSignatureSettings; } [DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = false)] private static extern int WinVerifyTrust(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID, ref WINTRUST_DATA pWVTData); /// <summary> /// 校验当前进程宿主EXE的数字签名,效果和SignTool Verify、资源管理器数字签名页校验一致 /// </summary> /// <returns>true=签名校验通过,false=校验失败</returns> public static bool VerifyCurrentHostExeSignature() { string exePath = Process.GetCurrentProcess().MainModule.FileName; return VerifyFileSignature(exePath); } /// <summary> /// 校验指定文件的数字签名 /// </summary> public static bool VerifyFileSignature(string filePath) { if (!System.IO.File.Exists(filePath)) return false; WINTRUST_FILE_INFO fileInfo = new WINTRUST_FILE_INFO(); fileInfo.cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)); fileInfo.pcwszFilePath = filePath; fileInfo.hFile = IntPtr.Zero; fileInfo.pgKnownSubject = IntPtr.Zero; IntPtr pFileInfo = IntPtr.Zero; WINTRUST_DATA trustData = new WINTRUST_DATA(); try { pFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf(fileInfo)); Marshal.StructureToPtr(fileInfo, pFileInfo, false); trustData.cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_DATA)); trustData.pPolicyCallbackData = IntPtr.Zero; trustData.pSIPClientData = IntPtr.Zero; trustData.dwUIChoice = WTD_UI_NONE; // 静默校验,不弹任何系统提示 trustData.fdwRevocationChecks = WTD_REVOKE_NONE; trustData.dwUnionChoice = WTD_CHOICE_FILE; trustData.pUnionData = pFileInfo; trustData.dwStateAction = WTD_STATEACTION_VERIFY; trustData.hWVTStateData = IntPtr.Zero; trustData.pwszURLReference = null; trustData.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN; // 开启证书链吊销检查,和SignTool默认行为一致 trustData.dwUIContext = 0; trustData.pSignatureSettings = IntPtr.Zero; int lStatus = WinVerifyTrust(IntPtr.Zero, WINTRUST_ACTION_GENERIC_VERIFY_V2, ref trustData); // 校验完成释放状态资源 trustData.dwStateAction = WTD_STATEACTION_CLOSE; trustData.hWVTStateData = (IntPtr)lStatus; WinVerifyTrust(IntPtr.Zero, WINTRUST_ACTION_GENERIC_VERIFY_V2, ref trustData); return lStatus == 0; } finally { if (pFileInfo != IntPtr.Zero) { Marshal.DestroyStructure(pFileInfo, typeof(WINTRUST_FILE_INFO)); Marshal.FreeHGlobal(pFileInfo); } } } }
避坑说明
- 不要用
System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile做校验:这个方法只会读取文件中嵌入的签名证书原始数据,完全不会校验文件哈希是否匹配、证书是否受信任、证书是否被吊销,只要文件里带了签名块哪怕是伪造的也会返回证书对象,完全没有安全校验能力。 - 不要手动解析PE文件的签名目录项:不同Windows版本对签名格式的支持有差异(比如嵌套签名、ECC签名、ED签名的兼容),自己解析很容易出现校验逻辑遗漏,导致签名被绕过,直接用系统API是最安全的选择。
- 如果需要和
SignTool Verify /pa的行为完全对齐,保持代码里用WINTRUST_ACTION_GENERIC_VERIFY_V2这个GUID即可,不要换成驱动签名校验的GUID,否则普通用户态EXE的签名会校验失败。
内容的提问来源于stack exchange,提问作者StackOverly89
相关产品推荐
相关产品推荐

