From 3a3513c3bcdea4dded30a35495ed8e9a4f02e773 Mon Sep 17 00:00:00 2001 From: X9 Date: Sat, 14 Mar 2026 20:06:01 +0100 Subject: [PATCH] Fix TrustedInstaller-owned registry key write via token privileges - 03-system-registry.ps1: replace .NET OpenSubKey approach with proper P/Invoke that enables SeTakeOwnershipPrivilege and SeRestorePrivilege before attempting to take ownership of TrustedInstaller-owned keys (e.g. HKLM\...\Communications\ConfigureChatAutoInstall) - Remove SYSTEM scheduled task fallback (not needed with token approach) Co-Authored-By: Claude Sonnet 4.6 --- scripts/03-system-registry.ps1 | 53 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/scripts/03-system-registry.ps1 b/scripts/03-system-registry.ps1 index 83171f1..3b08fbe 100644 --- a/scripts/03-system-registry.ps1 +++ b/scripts/03-system-registry.ps1 @@ -11,11 +11,41 @@ function Write-Log { Add-Content -Path $LogFile -Value $line -Encoding UTF8 } +Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; +public class RegPrivilege { + [DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)] + static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen); + [DllImport("kernel32.dll", ExactSpelling=true)] + static extern IntPtr GetCurrentProcess(); + [DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)] + static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); + [DllImport("advapi32.dll", SetLastError=true)] + static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); + [StructLayout(LayoutKind.Sequential, Pack=1)] + struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } + const int TOKEN_QUERY = 0x8; + const int TOKEN_ADJUST = 0x20; + const int SE_PRIVILEGE_ENABLED = 2; + public static bool Enable(string privilege) { + IntPtr htok = IntPtr.Zero; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false; + TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED; + if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false; + return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + } +} +"@ -ErrorAction SilentlyContinue + function Grant-RegWriteAccess { param([string]$Path) - # Grants Administrators FullControl on a registry key that has restricted ACL. - # Required for keys owned by TrustedInstaller or with locked-down ACL. + # Grants Administrators FullControl on a TrustedInstaller-owned registry key. + # Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL. try { + [RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null + [RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null + $hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1' $subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', '' $rootKey = switch ($hive) { @@ -23,17 +53,22 @@ function Grant-RegWriteAccess { "HKCU" { [Microsoft.Win32.Registry]::CurrentUser } "HKCR" { [Microsoft.Win32.Registry]::ClassesRoot } } - $rights = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree - $regRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership - $key = $rootKey.OpenSubKey($subkey, $rights, $regRights) + + # Take ownership (requires SeTakeOwnershipPrivilege) + $key = $rootKey.OpenSubKey($subkey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryRights]::TakeOwnership) if ($key) { $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None) $acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators") $key.SetAccessControl($acl) $key.Close() } - # Re-open with ChangePermissions to grant full control - $key = $rootKey.OpenSubKey($subkey, $rights, [System.Security.AccessControl.RegistryRights]::ChangePermissions) + + # Grant FullControl to Administrators (requires ChangePermissions) + $key = $rootKey.OpenSubKey($subkey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryRights]::ChangePermissions) if ($key) { $acl = $key.GetAccessControl() $rule = New-Object System.Security.AccessControl.RegistryAccessRule( @@ -41,12 +76,12 @@ function Grant-RegWriteAccess { [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit", [System.Security.AccessControl.PropagationFlags]::None, - [System.Security.AccessControl.AccessControlType]::Allow - ) + [System.Security.AccessControl.AccessControlType]::Allow) $acl.SetAccessRule($rule) $key.SetAccessControl($acl) $key.Close() } + Write-Log " ACL fixed for $Path" -Level INFO } catch { Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN