如何阻止.NET Framework从当前工作目录加载程序集?
解决.NET Framework下CWE-427系统DLL劫持问题
你的场景核心是:.NET Framework程序因Windows默认DLL搜索顺序优先检查EXE所在目录,导致恶意伪造的系统DLL(如propsys.dll)被加载,触发CWE-427漏洞。以下是针对无外部依赖、需兼容低版本.NET Framework的解决方案:
一、强制锁定系统DLL加载路径
Windows的LoadLibrary默认会先搜索EXE所在目录,这是劫持的根源。你可以通过P/Invoke调用SetDllDirectory API,清空额外搜索路径,让系统仅从System32/Windows目录加载DLL:
using System.Runtime.InteropServices; class Program { [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool SetDllDirectory(string lpPathName); static void Main() { // 清空自定义DLL搜索目录,仅保留系统默认路径 SetDllDirectory(""); // 启动程序主逻辑 // ... } }
这个操作要放在程序启动的最早期(Main方法第一行),确保在任何系统DLL加载前生效。由于你的程序无外部依赖,不会影响合法DLL的加载。
二、验证系统DLL的官方数字签名
通过检查加载的DLL是否带有微软官方有效签名,彻底阻断恶意DLL的执行。核心是调用Windows的WinVerifyTrust API实现签名验证:
using System; using System.Runtime.InteropServices; class DllSignatureValidator { private const uint WINTRUST_ACTION_GENERIC_VERIFY_V2 = 0x00000002; private const uint ERROR_SUCCESS = 0; [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 pFile; 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 = true)] private static extern uint WinVerifyTrust(IntPtr hwnd, ref Guid pgActionID, ref WINTRUST_DATA pWVTData); public static bool IsMicrosoftSignedDll(string dllPath) { var actionGuid = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"); var fileInfo = new WINTRUST_FILE_INFO { cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)), pcwszFilePath = dllPath }; var trustData = new WINTRUST_DATA { cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_DATA)), dwUIChoice = 2, // 不显示UI fdwRevocationChecks = 0, dwUnionChoice = 1, // 验证文件 pFile = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_FILE_INFO))), dwStateAction = 0, // 执行验证 dwProvFlags = 0x00000010 // 跳过吊销检查(可按需调整) }; Marshal.StructureToPtr(fileInfo, trustData.pFile, false); uint result = WinVerifyTrust(IntPtr.Zero, ref actionGuid, ref trustData); Marshal.FreeHGlobal(trustData.pFile); // 完整实现需额外检查证书链是否归属微软,此处简化为基础签名验证 return result == ERROR_SUCCESS; } }
使用时,在程序启动前预加载并验证关键系统DLL:
using System.IO; static void Main() { string propsysPath = Path.Combine(Environment.SystemDirectory, "propsys.dll"); if (!DllSignatureValidator.IsMicrosoftSignedDll(propsysPath)) { // 验证失败,终止程序 Environment.Exit(1); } // 后续程序逻辑 }
三、补充说明
- 以上方案均兼容低版本.NET Framework(如4.0、3.5),无需升级框架版本。
- 若攻击者已能修改EXE所在目录,虽然签名能保障EXE可信,但仍需通过上述方法阻断DLL劫持路径。
- .NET 8通过修改默认DLL搜索顺序解决了该问题,但低版本框架需手动干预。
内容的提问来源于stack exchange,提问作者Bryan Dam
相关产品推荐
相关产品推荐

