You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:08:13