How can I set audit controls on files owned by TrustedInstaller using Powershell?

I am trying to set audit controls on a number of files (listed in ACLsWin.txt) located in \%Windows%\System32 (for example, aaclient.dll) using the following Powershell script:

$FileList = Get-Content ".\ACLsWin.txt"
$ACL = New-Object System.Security.AccessControl.FileSecurity

$AccessRule = New-Object System.Security.AccessControl.FileSystemAuditRule("Everyone", "Delete", "Failure")
$ACL.AddAuditRule($AccessRule)
foreach($File in $FileList)
{
    Write-Host "Changing audit on $File"
    $ACL | Set-Acl $File
}

Whenever I run the script, I get the error PermissionDenied [Set-Acl] UnauthorizedAccessException.

This seems to come from the fact that the owner of these files is TrustedInstaller. I am running these scripts as Administrator (even though I'm on the the built-in Administrator account) and it's still failing. I can set these audit controls by hand using the Security tab, but there are at least 200 files for which doing by hand may lead to human errors.

How can I get around TrustedInstaller and set these audit controls using Powershell?


Solution 1:

The only supported way to modify files that are protected by Windows Resource Protection (which the TrustedInstaller account is a part of WRP,) is to use the Windows Module Installer service, which is really just a way of saying "you cannot modify these files in a supported way yourself; only through installing patches and service packs can these files be modified in a supported way."

The sfc.exe utility is also part of Windows Resource Protection. Using sfc.exe you can that the file has been modified, and it will replace it with a copy from the WinSxS store.

You would have to take ownership of these files yourself before you can modify them. The easiest way to do that is with takeown.exe.

But the fact of the matter is you're not supposed to be modifying these files.

Application developers can use the SfcIsFileProtected or SfcIsKeyProtected APIs to check whether a file is or isn't under the protection of WRP.

The reason why I wouldn't write a script for someone to do this is because it's not supported to modify these files, and as a professional, I couldn't in good conscience help someone get their system into an unsupported state. ;) Edit: Damnit, I just did...

Edit: If you still insist on hacking it, then technically one option is to launch Powershell.exe using the security token of TrustedInstaller.exe.

Or, the easier thing to do would be to write a script with a Try/Catch block, and if the catch block is triggered by an [UnauthorizedAccessException], then take ownership of the file and try again.

C:\Windows\system32>takeown /A /F C:\Windows\System32\aaclient.dll

SUCCESS: The file (or folder): "C:\Windows\System32\aaclient.dll" now owned by the administrators group.

You can also monitor modifications to these files using a filter driver. This is how things such as antivirus products accomplish it. But uh... that's probably more work than you want to do at the moment...

Edit again!: Oh, you want to set the owner back to TrustedInstaller afterwards? You need the SeRestorePrivilege privilege for that.

Copy paste this to .\Enable-Privilege.ps1:

Function Enable-Privilege 
{
 param([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,
  $ProcessId = $pid,
  [Switch]$Disable)

   $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)

}

In Powershell, dot-source Enable-Privilege.ps1 like so:

PS C:\> . .\Enable-Privilege.ps1

PS C:\> [System.Security.Principal.NTAccount]$TrustedInstaller = "NT SERVICE\TrustedInstaller"

Save the current ACL:

PS C:\> $ACL = Get-Acl C:\Windows\System32\aaclient.dll

Change the owner to TrustedInstaller:

PS C:\> $ACL.SetOwner($TrustedInstaller)

Enable the SeRestorePrivilege:

PS C:\> Enable-Privilege SeRestorePrivilege

Re-apply the modified ACL. This is the part that will fail, even for the Local System account, if you do not explicitly set the SeRestorePrivilege privilege:

PS C:\> Set-Acl -Path C:\Windows\System32\aaclient.dll -AclObject $ACL

I shameless stole the Enable-Privilege function from this TechNet Forums thread.