如何在C#中绕过限制访问受保护的Windows注册表USBSTOR项?
解决Windows注册表USBSTOR项访问权限限制问题
问题概述
- 开发C#程序访问Windows注册表
SYSTEM\ControlSet001\Enum\USBSTOR下的子项(尤其是Properties子项),即使以管理员权限运行,仍遇到访问限制 - 使用
Microsoft.Win32.Registry.OpenSubKey并指定RegistryRights.TakeOwnership可部分访问,但无法读取LastWriteTime这类注册表项元数据 - 调用Win32 API
RegOpenKeyEx并传入KEY_ALL_ACCESS时,返回错误码5(ERROR_ACCESS_DENIED)
最佳解决方案
注册表Enum分支下的USB相关项默认权限极为严格,直接高权限打开会被拒绝。正确流程是先以最低必要权限打开项,获取所有权并修改权限控制列表(DACL),再重新打开以获取完整访问权限,最后通过Win32 API读取元数据。
步骤1:声明所需Win32 API及结构体(P/Invoke)
using System; using System.Runtime.InteropServices; using System.Security.Principal; public class RegistryHelper { // Windows API常量 private const int ERROR_SUCCESS = 0; private const int KEY_READ = 0x20019; private const int KEY_ALL_ACCESS = 0xF003F; private const int OWNER_SECURITY_INFORMATION = 0x00000001; private const int DACL_SECURITY_INFORMATION = 0x00000004; private const int SE_PRIVILEGE_ENABLED = 0x00000002; private const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"; private const string SE_RESTORE_NAME = "SeRestorePrivilege"; // 结构体 [StructLayout(LayoutKind.Sequential)] private struct LUID { public uint LowPart; public int HighPart; } [StructLayout(LayoutKind.Sequential)] private struct LUID_AND_ATTRIBUTES { public LUID Luid; public uint Attributes; } [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct TOKEN_PRIVILEGES { public uint PrivilegeCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public LUID_AND_ATTRIBUTES[] Privileges; } [StructLayout(LayoutKind.Sequential)] private struct SECURITY_DESCRIPTOR { public byte Revision; public byte Sbz1; public ushort Control; public IntPtr Owner; public IntPtr Group; public IntPtr Sacl; public IntPtr Dacl; } // P/Invoke声明 [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint ulOptions, int samDesired, out IntPtr hKeyResult); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegGetKeySecurity(IntPtr hKey, int securityInformation, ref SECURITY_DESCRIPTOR pSecurityDescriptor, ref uint lpcbSecurityDescriptor); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegSetKeySecurity(IntPtr hKey, int securityInformation, ref SECURITY_DESCRIPTOR pSecurityDescriptor); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegQueryInfoKey(IntPtr hKey, IntPtr lpClass, ref uint lpcbClass, IntPtr lpReserved, IntPtr lpcSubKeys, IntPtr lpcbMaxSubKeyLen, IntPtr lpcbMaxClassLen, IntPtr lpcValues, IntPtr lpcbMaxValueNameLen, IntPtr lpcbMaxValueLen, IntPtr lpcbSecurityDescriptor, ref long lpftLastWriteTime); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetCurrentProcess(); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetSecurityDescriptorOwner(ref SECURITY_DESCRIPTOR pSecurityDescriptor, IntPtr pOwner, bool bOwnerDefaulted); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool ConvertStringSidToSid(string StringSid, out IntPtr Sid); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool GetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR pSecurityDescriptor, out bool lpbDaclPresent, out IntPtr pDacl, out bool lpbDaclDefaulted); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR pSecurityDescriptor, bool bDaclPresent, IntPtr pDacl, bool bDaclDefaulted); [DllImport("advapi32.dll", SetLastError = true)] private static extern int AddAccessAllowedAce(IntPtr pAcl, uint dwAceRevision, int AccessMask, IntPtr pSid); }
步骤2:启用必要的系统权限
在操作注册表前,需要启用SeTakeOwnershipPrivilege和SeRestorePrivilege权限,这是获取注册表项所有权的前提:
private static bool EnablePrivilege(string privilegeName) { if (!OpenProcessToken(GetCurrentProcess(), 0x0020, out IntPtr tokenHandle)) return false; try { if (!LookupPrivilegeValue(null, privilegeName, out LUID luid)) return false; TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES { PrivilegeCount = 1, Privileges = new LUID_AND_ATTRIBUTES[1] }; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; return AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); } finally { if (tokenHandle != IntPtr.Zero) Marshal.Close(tokenHandle); } }
步骤3:获取注册表项所有权并修改权限
先以KEY_READ权限打开项,然后修改所有者为当前用户,再添加完全访问权限:
private static bool TakeOwnershipAndSetPermissions(IntPtr hKey, string subKey) { // 启用必要权限 if (!EnablePrivilege(SE_TAKE_OWNERSHIP_NAME) || !EnablePrivilege(SE_RESTORE_NAME)) return false; // 以只读权限打开项 int result = RegOpenKeyEx(hKey, subKey, 0, KEY_READ, out IntPtr regKey); if (result != ERROR_SUCCESS) return false; try { // 获取当前安全描述符 SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR(); uint sdSize = (uint)Marshal.SizeOf(typeof(SECURITY_DESCRIPTOR)); result = RegGetKeySecurity(regKey, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, ref sd, ref sdSize); if (result != ERROR_SUCCESS) return false; // 设置所有者为当前用户 string currentUserSid = WindowsIdentity.GetCurrent().User.Value; if (!ConvertStringSidToSid(currentUserSid, out IntPtr userSid)) return false; try { if (!SetSecurityDescriptorOwner(ref sd, userSid, false)) return false; } finally { Marshal.FreeHGlobal(userSid); } // 添加当前用户的完全访问权限 if (!GetSecurityDescriptorDacl(ref sd, out bool daclPresent, out IntPtr dacl, out bool daclDefaulted)) return false; if (AddAccessAllowedAce(dacl, 0x02, KEY_ALL_ACCESS, WindowsIdentity.GetCurrent().User.AccountSid) != ERROR_SUCCESS) return false; if (!SetSecurityDescriptorDacl(ref sd, true, dacl, daclDefaulted)) return false; // 应用修改后的安全描述符 result = RegSetKeySecurity(regKey, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, ref sd); return result == ERROR_SUCCESS; } finally { if (regKey != IntPtr.Zero) Marshal.Close(regKey); } }
步骤4:读取注册表项元数据(包括LastWriteTime)
权限修改完成后,重新打开项并调用RegQueryInfoKey获取LastWriteTime:
public static DateTime? GetRegistryKeyLastWriteTime(IntPtr hKeyRoot, string subKey) { // 先获取所有权和权限 if (!TakeOwnershipAndSetPermissions(hKeyRoot, subKey)) return null; // 重新以完全权限打开项 int result = RegOpenKeyEx(hKeyRoot, subKey, 0, KEY_ALL_ACCESS, out IntPtr regKey); if (result != ERROR_SUCCESS) return null; try { long lastWriteTimeTicks = 0; result = RegQueryInfoKey(regKey, IntPtr.Zero, ref uint.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref lastWriteTimeTicks); if (result != ERROR_SUCCESS) return null; return DateTime.FromFileTime(lastWriteTimeTicks); } finally { if (regKey != IntPtr.Zero) Marshal.Close(regKey); } }
使用示例
// 调用示例:获取USBSTOR下指定项的LastWriteTime IntPtr hklm = (IntPtr)0x80000002; // HKEY_LOCAL_MACHINE string targetSubKey = @"SYSTEM\ControlSet001\Enum\USBSTOR\CdRom&Ven_TS8XDVDR&Prod_Transcend&Rev_TW00\112233445568&0\Properties"; DateTime? lastWriteTime = RegistryHelper.GetRegistryKeyLastWriteTime(hklm, targetSubKey); if (lastWriteTime.HasValue) Console.WriteLine($"Last Write Time: {lastWriteTime.Value}"); else Console.WriteLine("Failed to retrieve last write time");
关键注意事项
- 必须以管理员权限运行程序,否则无法启用系统特权和修改注册表权限
ControlSet001可能不是当前活跃的控制集,建议先读取SYSTEM\CurrentControlSet的指向,再使用对应控制集路径- 修改注册表权限可能影响系统稳定性,操作完成后建议恢复原有权限(需额外代码实现)
内容的提问来源于stack exchange,提问作者Junaid Arshad
相关产品推荐
相关产品推荐

