Set Registry key owner to SYSTEM user

1

I have to add an exclusions path to Windows Defender registry key. I am aware that there are cmdlets provided from Windows Defender to use them directly for these kinds of purposes. But unfortunately, in Windows 7 and PowerShell v2, they are not available. So I am trying to build a script that will manually enter the value to the registry key. By researching online, I have put together a script which will first change the owner to Administrators (Since only the SYSTEM, WinDefend and TrustedInstaller users have access to this key), then add the value and finally set the owner to the initial owner (in this case SYSTEM) again. My code is given below:

Code to enable the required privilege:

Param([string]$targetPath)

function enable-privilege {
  Param(
    ## The privilege to adjust. This set is taken from
    ## http://msdn.microsoft.com/en-us/library/bb530716(VS.85).aspx
    [ValidateSet(
      "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege",
      "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege",
      "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege",
      "SeDebugPrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege",
      "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege",
      "SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege",
      "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege",
      "SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege",
      "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege", "SeSystemtimePrivilege",
      "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege",
      "SeUndockPrivilege", "SeUnsolicitedInputPrivilege")]
    $Privilege,
    ## The process on which to adjust the privilege. Defaults to the current process.
    $ProcessId = $pid,
    ## Switch to disable the privilege, rather than enable it.
    [Switch] $Disable
  )

  ## Taken from P/Invoke.NET with minor adjustments.
  $definition = @'
  using System;
  using System.Runtime.InteropServices;

  public class AdjPriv {
    [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", ExactSpelling = true, SetLastError = true)]
    internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid {
      public int Count;
      public long Luid;
      public int Attr;
    }

    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    public static bool EnablePrivilege(long processHandle, string privilege, bool disable) {
      bool retVal;
      TokPriv1Luid tp;
      IntPtr hproc = new IntPtr(processHandle);
      IntPtr htok = IntPtr.Zero;
      retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
      tp.Count = 1;
      tp.Luid = 0;
      if(disable) {
        tp.Attr = SE_PRIVILEGE_DISABLED;
      } else {
        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;
    }
  }
  '@

  $processHandle = (Get-Process -id $ProcessId).Handle
  $type = Add-Type $definition -PassThru
  $type[0]::EnablePrivilege($processHandle, $Privilege, $Disable)
}

The code part where I perform the changes:

function getRegKeyOwner([string]$keyPath){
    $regRights=[System.Security.AccessControl.RegistryRights]::ReadPermissions
    $permCheck=[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree

    $Key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath, $permCheck, $regRights)
    $acl = $Key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Owner)
    $owner = $acl.GetOwner([type]::GetType([System.Security.Principal.NTAccount]))
    $key.Close()
    return $owner
}

function setValueToKey([string]$keyPath, [string]$name, [System.Object]$value, [Microsoft.Win32.RegistryValueKind]$regValueKind){
    $regRights=[System.Security.AccessControl.RegistryRights]::SetValue
    $permCheck=[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree

    $Key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath, $permCheck, $regRights)

    "Setting value with properties [name:$name, value:$value, value type:$regValueKind]"
    $Key.SetValue($name, $value, $regValueKind)
    $key.Close()
}

function changeRegKeyOwner([string]$keyPath, [System.Security.Principal.NTAccount]$user){
    try {
        $regRights=[System.Security.AccessControl.RegistryRights]::TakeOwnership
        $permCheck=[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree
        $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath, $permCheck, $regRights)
        # You must get a blank acl for the key b/c you do not currently have access
        $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)

        if([string]::IsNullOrEmpty($user)){
            $user = [System.Security.Principal.NTAccount]"$env:userdomain\$env:username"
        }

        "Changing owner of Registry key: HKEY_LOCAL_MACHINE\$keyPath to `"$user`""
        $acl.SetOwner($user)
        $key.SetAccessControl($acl)
    } catch {
        $_.Exception.toString()
        $key.Close()
        return
    }
    giveFullControlToUser -userName "$user" -key $key

    $key.Close()
}

function giveFullControlToUser([String]$userName,[Microsoft.Win32.RegistryKey] $key){
    "giving full access to $userName for key $key"
    # After you have set owner you need to get the acl with the perms so you can modify it.
    $acl = $key.GetAccessControl()
    $rule = New-Object System.Security.AccessControl.RegistryAccessRule ($userName, "FullControl", @("ObjectInherit", "ContainerInherit"), "None", "Allow")
    $acl.SetAccessRule($rule)
    $key.SetAccessControl($acl)
}

function getAdminUser {
    $windowsKey = "SOFTWARE\Microsoft\Windows"
    return getRegKeyOwner -keyPath $windowsKey
}

enable-privilege SeTakeOwnershipPrivilege

$exclussionsPathsKey = "SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths"
$adminGroupName = gwmi win32_group -filter "LocalAccount = $TRUE And SID = 'S-1-5-32-544'" |
                  select -expand name

$originalOwner = getRegKeyOwner -keyPath $exclussionsPathsKey

"original Owner to the key `"$exclussionsPathsKey`" is: `"$originalOwner`""
changeRegKeyOwner -keyPath $exclussionsPathsKey -user ([System.Security.Principal.NTAccount]"$adminGroupName")

if (!([string]::IsNullOrEmpty($targetPath))) {
    $valueName = $targetPath
    $vaue = 0
    $regValueKind = [Microsoft.Win32.RegistryValueKind]::DWord
    setValueToKey -keyPath $exclussionsPathsKey -name $valueName -value $vaue -regValueKind $regValueKind
}

changeRegKeyOwner -keyPath $exclussionsPathsKey -user $originalOwner

But until the value setting part, everything works fine. I can see the value in the registry key, the owner is changed to "Administrators" with full rights. Only when I try to set the original owner "SYSTEM" again, I get the following exception. This is my first time scripting with PowerShell. And I am totally unable to understand/solve the problem. By the way, it works if the original user was any other user other than "SYSTEM". Maybe I am missing some required privilages here.

Output:

True
original Owner to the key "SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths" is: "NT AUTHORITY\SYSTEM"
Changing owner of Registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths to "Administrators"
giving full access to Administrators for key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths
Changing owner of Registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths to "NT AUTHORITY\SYSTEM"

System.Management.Automation.MethodInvocationException: Exception calling "SetAccessControl" with "1" argument(s): "The security identifier is not allowed to be the owner of this object." ---> System.InvalidOperationException: The security identifier is not allowed to be the owner of this object.
   at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
   at System.Security.AccessControl.NativeObjectSecurity.Persist(SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
   at System.Security.AccessControl.NativeObjectSecurity.Persist(SafeHandle handle, AccessControlSections includeSections)
   at System.Security.AccessControl.RegistrySecurity.Persist(SafeRegistryHandle hKey, String keyName)
   at Microsoft.Win32.RegistryKey.SetAccessControl(RegistrySecurity registrySecurity)
   at SetAccessControl(Object , Object[] )
   at System.Management.Automation.MethodInformation.Invoke(Object target, Object[] arguments)
   at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[] originalArguments)
   --- End of inner exception stack trace ---
   at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[] originalArguments)
   at System.Management.Automation.ParserOps.CallMethod(Token token, Object target, String methodName, Object[] paramArray, Boolean callStatic, Object valueToSet)
   at System.Management.Automation.MethodCallNode.InvokeMethod(Object target, Object[] arguments, Object value)
   at System.Management.Automation.MethodCallNode.Execute(Array input, Pipe outputPipe, ExecutionContext context)
   at System.Management.Automation.ParseTreeNode.Execute(Array input, Pipe outputPipe, ArrayList& resultList, ExecutionContext context)
   at System.Management.Automation.StatementListNode.ExecuteStatement(ParseTreeNode statement, Array input, Pipe outputPipe, ArrayList& resultList, ExecutionContext context)
powershell
registry
access-rights
windows-security
asked on Stack Overflow Jul 10, 2017 by ShaDooW • edited Jul 11, 2017 by ShaDooW

1 Answer

2

Finally, I have found what was missing in the script. The Administrator user needed extra privilege for restoring the privilege. Just a simple call to the function enable-privilege SeRestorePrivilege before setting the original owner back, gives the current process the required permission.

answered on Stack Overflow Jul 11, 2017 by ShaDooW

User contributions licensed under CC BY-SA 3.0