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.