b.0x449
@0x44449

관리자 권한으로 레지스트리를 조작하다 특정 레지스트리 키에 대해서 AccessDenied가 발생하며 열기에 실패하는 경우가 발생하였습니다.

using (RegistryKey regKey = parent.OpenSubkey(path, true))
{
    // todo
}

위와 같은 경우 OpenSubkey의 파라미터를 false로 변경하라고 알려주지만 해당 키에 대한 Writable 기능이 필요한 경우 해결책이 되지 않습니다.
또한 false로 변경해도 동일하게 실패하는 경우가 있습니다.

UAC가 켜져있고, 사용자에게 관리자 권한이 없는 경우에 이런 특이 케이스가 발견되었습니다.
이는 해당 레지스트리 키값이 다음과 같은 경우에 발생합니다.

  • 레지스트리 키의 소유자가 관리자가 아님
  • 관리자는 해당 레지스트리 키에 대한 권한이 존재하지 않음

이런 경우 해당 키를 열기 위해서는 해당 레지스트리 키에 대하여 관리자에게 읽기 권한이 필요합니다.
하지만 권한 할당은 소유자만이 가능하므로 먼저 해당 키의 소유자를 관리자로 변경해야 합니다.

public class TokenManipulator
{
    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool AdjustTokenPrivileges(IntPtr htok,
                                                      bool disall,
                                                      ref TokPriv1Luid newst,
                                                      int len,
                                                      IntPtr prev,
                                                      IntPtr relen);

    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string host,
                                                     string name,
                                                     ref long pluid);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

    [DllImport("kernel32.dll", ExactSpelling = true)]
    internal static extern IntPtr GetCurrentProcess();

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid
    {
        public int Count;
        public long Luid;
        public int Attr;
    }

    internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    public const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege";
    public const string SE_AUDIT_NAME = "SeAuditPrivilege";
    public const string SE_BACKUP_NAME = "SeBackupPrivilege";
    public const string SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege";
    public const string SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege";
    public const string SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege";
    public const string SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege";
    public const string SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege";
    public const string SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege";
    public const string SE_DEBUG_NAME = "SeDebugPrivilege";
    public const string SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege";
    public const string SE_IMPERSONATE_NAME = "SeImpersonatePrivilege";
    public const string SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege";
    public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
    public const string SE_INC_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege";
    public const string SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege";
    public const string SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege";
    public const string SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege";
    public const string SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege";
    public const string SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege";
    public const string SE_RELABEL_NAME = "SeRelabelPrivilege";
    public const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege";
    public const string SE_RESTORE_NAME = "SeRestorePrivilege";
    public const string SE_SECURITY_NAME = "SeSecurityPrivilege";
    public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
    public const string SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege";
    public const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege";
    public const string SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege";
    public const string SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege";
    public const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege";
    public const string SE_TCB_NAME = "SeTcbPrivilege";
    public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege";
    public const string SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege";
    public const string SE_UNDOCK_NAME = "SeUndockPrivilege";
    public const string SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege";

    public static bool AddPrivilege(string privilege)
    {
        try
        {
            bool retVal;
            TokPriv1Luid tp;
            IntPtr hproc = GetCurrentProcess();
            IntPtr htok = IntPtr.Zero;
            retVal = OpenProcessToken(hproc,
                                      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                                      ref htok);
            tp.Count = 1;
            tp.Luid = 0;
            tp.Attr = SE_PRIVILEGE_ENABLED;
            retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
            retVal = AdjustTokenPrivileges(htok,
                                           false,
                                           ref tp,
                                           0,
                                           IntPtr.Zero,
                                           IntPtr.Zero);
            return retVal;
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }
    public static bool RemovePrivilege(string privilege)
    {
        try
        {
            bool retVal;
            TokPriv1Luid tp;
            IntPtr hproc = GetCurrentProcess();
            IntPtr htok = IntPtr.Zero;
            retVal = OpenProcessToken(hproc,
                                      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                                      ref htok);
            tp.Count = 1;
            tp.Luid = 0;
            tp.Attr = SE_PRIVILEGE_DISABLED;
            retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
            retVal = AdjustTokenPrivileges(htok,
                                           false,
                                           ref tp,
                                           0,
                                           IntPtr.Zero,
                                           IntPtr.Zero);
            return retVal;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

다음과 같이 소유자 변경 이후 계정에 대한 권한 할당이 필요합니다.

try
{
    TokenManipulator.AddPrivilege("SeRestorePrivilege");
    TokenManipulator.AddPrivilege("SeBackupPrivilege");
    TokenManipulator.AddPrivilege("SeTakeOwnershipPrivilege");

        // 소유자 변경
        using (RegistryKey regKey = parent.OpenSubKey(path, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.TakeOwnership))
        {
                RegistrySecurity security = new RegistrySecurity();
                security.SetOwner(new NTAccount(domainName, userName));
                regKey.SetAccessControl(security);
        }
        // 권한 할당
        using (RegistryKey regKey = parent.OpenSubKey(path, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.ChangePermissions))
        {
                RegistrySecurity security = new RegistrySecurity();
                security.AddAccessRule(new RegistryAccessRule(domainName + "\\" + userName,
                                                      RegistryRights.FullControl,
                                                      InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                                                      PropagationFlags.None,
                                                      AccessControlType.Allow));
                regKey.SetAccessControl(security);
        }
}
finally
{
    TokenManipulator.RemovePrivilege("SeRestorePrivilege");
    TokenManipulator.RemovePrivilege("SeBackupPrivilege");
    TokenManipulator.RemovePrivilege("SeTakeOwnershipPrivilege");
}

하지만 위와 같은 방법에서는 이전 소유자에 대한 정보를 알 수 있는 방법이 없습니다.
이점을 고려하여 사용해야 합니다.