From: Max Vilimpoc Date: Sat, 29 Aug 2026 18:16:55 +0000 (+0200) Subject: dotfiles: keep the group membership, drop the grants that bought nothing X-Git-Url: https://vilimpoc.org/repos/dotfiles/commitdiff_plain/593189b7ff9cfdb6f644796728a2a1368637235e?ds=inline;hp=5c01c42c07a9003596daec75d22f779e743f8e55 dotfiles: keep the group membership, drop the grants that bought nothing The NT Kernel Logger is reserved for Administrators and LocalSystem, and no amount of permission granting reaches it. Measured on this box, all three in place at once and across a reboot -- the account in Performance Log Users, SeSystemProfilePrivilege granted to that group, and an explicit ACE giving the group TRACELOG_ACCESS_KERNEL_LOGGER on SystemTraceControlGuid, confirmed present in the descriptor afterwards: xperf -on base -> NT Kernel Logger: Access is denied. (0x5) wpr -start GeneralProfile -> Access is denied. (0x80070005) It is not a check an ACE overrides, and Microsoft documents Performance Log Users access as explicitly not extending to that session. So the privilege grant and the ACE go, along with the LSA and EventAccessControl interop that existed only to apply them -- roughly 280 lines, in a script that was getting long enough to notice. What stays is the part that works, and it does work: membership in Performance Log Users lets the account create and control ordinary ETW sessions, verified unelevated after the reboot -- xperf -start T -on Microsoft-Windows-Kernel-Process -f trace.etl -> exit 0 which was Access denied before. Enough to trace an application's own providers without a UAC prompt. Kernel and CPU-sampling traces are elevated work now, run deliberately with xperf, wpr or VTune from an Administrator prompt. The step and the README keep the negative result rather than quietly dropping it: it is a plausible-looking path that does not work, and the next person to try deserves to be told where it ends. The README also says how to undo the two grants on a box that ran the earlier revision -- they are still applied here. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YMh8i2QzkHNdE3MkKfcaT6 --- diff --git a/README.md b/README.md index 1bdadca..e9b2005 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ throwaway VM reachable from a Linux host. | --- | --- | | `setup-windows.bat` | Entry point. Runs the winget installs, then launches the elevated half and prints its log, then runs the non-elevated script. | | `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge and BinSkim on the user `PATH`, and the global git config (identity, plus `core.sshCommand`). Can also be run directly from an ordinary prompt. | -| `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio 2022 Community with the required components, the WDK, and the Windows Performance Toolkit, and finally grants one ordinary account the rights to collect ETW traces without elevation. Can also be run directly from an Administrator prompt — pass `-TraceUser DOMAIN\user` to say who gets those rights, and `-EtwRightsOnly` to do that step alone. | +| `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio 2022 Community with the required components, the WDK, and the Windows Performance Toolkit, and reports whether Intel VTune Profiler is present. It also puts one ordinary account into `Performance Log Users`, so it can run user-mode ETW sessions unelevated. Can also be run directly from an Administrator prompt — pass `-TraceUser DOMAIN\user` to name that account, and `-EtwRightsOnly` to do that step alone. | | `setup-windows-7-test-env.bat` | Prepares a **Windows 7 VM** as a test target driven from the host by `VBoxManage guestcontrol`. Copy it into the guest and run it there; it is idempotent, so re-run it after any snapshot restore. The per-user half needs no UAC (crash-dialog suppression, no screen blanking, a staging directory, the shared folder on `Z:`); the machine-wide half is skipped with a notice unless run elevated inside the guest. It then reports what the box can actually test: DWM composition, printers, audio capture devices. | ## Usage @@ -163,74 +163,59 @@ throwaway VM reachable from a Linux host. — the Linux driver can be handed to a `vtune` group, but on Windows the documented answer is to run as administrator. -- **Tracing without a UAC prompt.** `xperf` and `wpr` fail for a standard user in - two different ways, because two different things are missing: +- **User-mode ETW tracing without a UAC prompt — and the kernel logger's hard + limit.** Out of the box a standard user cannot start *any* event tracing + session, not even a user-mode one naming a single provider: ```text - xperf -on base -> NT Kernel Logger: Access is denied. (0x5) - wpr -start GeneralProfile -> Failed to enable the policy to profile system performance. + xperf -start T -on Microsoft-Windows-Kernel-Process -f trace.etl + -> Access is denied. (0x5) ``` - Three separate things are in the way, and all three have to be dealt with: - - 1. Creating or controlling *any* ETW session — even a user-mode one naming a - single provider — is checked against the security descriptor ETW keeps per - provider GUID under `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`, - whose **default** grants the session-control rights to SYSTEM, - Administrators, the service accounts and `BUILTIN\Performance Log Users`, - and to nobody else. - 2. Switching on the *kernel* provider needs the `SeSystemProfilePrivilege` user - right ("Profile system performance"), held by default only by - Administrators and `NT SERVICE\WdiServiceHost` — that is the one `wpr` - names. - 3. The kernel logger is not covered by that default descriptor. - `SystemTraceControlGuid` — the session both `xperf -on` and `wpr` drive — - carries an explicit one that does not mention Performance Log Users. With - 1 and 2 in place a user-mode session starts and the privilege is held, and - `xperf -on base` *still* answers "NT Kernel Logger: Access is denied" while - `wpr`'s error changes to a bare `0x80070005`; even reading that descriptor - comes back access-denied, which is the tell. So an ACE for the group is - added with `EventAccessControl`, `TRACELOG_ACCESS_KERNEL_LOGGER` included. - - The privilege and the ACE both go to the **group**, and the account then goes - into the group, so membership alone is the switch and enabling another account - afterwards is just `net localgroup "Performance Log Users" /add`. - `SeDebugPrivilege` is deliberately *not* granted: CPU sampling and stack walks - of your own processes do not need it, and it is equivalent to handing out - administrator. Worth being clear about the cost: a member of that group can - capture system-wide kernel traces — process, image, file and registry activity - across every account on the box, paths and command lines included. - - Two consequences worth knowing. A privilege and a group membership are both - read into the access token **at logon**, so the account must sign out and back - in for 1 and 2 — any new logon does it, and an `ssh` login into the box is the - quick way to check without dropping the desktop. The ACE in 3 is machine state - instead, and a logon does nothing for it: ETW reads these descriptors into a - cache, so it takes a **reboot** — with the ACE written and readable, `xperf -on - base` was still denied from a fresh shell on the running system. Plan on both - on a first run. And this only - helps a **non-admin** account: UAC hands an administrator a filtered token - carrying just five harmless privileges, so an admin's ordinary shell still - cannot trace however the policy reads. Verify from the target account, - unelevated: - - ```powershell - whoami /priv | findstr SeSystemProfilePrivilege - xperf -on base ; xperf -stop C:\Temp\trace.etl - ``` - - Analysis never needed any of this — `wpa.exe` opens an existing `.etl` as a - plain user. This is only about collection. - - The rights step runs **first** in the elevated half, and `-EtwRightsOnly` runs - it and nothing else. It is seconds of LSA and registry work where a full run is - dominated by the three Visual Studio passes, which take minutes even with - nothing to do: + Session control is checked against the security descriptor ETW keeps per + provider GUID under `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`, whose + default grants those rights to SYSTEM, Administrators, the service accounts and + `BUILTIN\Performance Log Users` — and to nobody else. So the elevated half puts + the account named by `-TraceUser` into that group, and the command above then + works unelevated; enabling another account later is just + `net localgroup "Performance Log Users" /add`. Membership is read into + the access token **at logon**, so sign out and back in first — any new logon + does it, and an `ssh` login into the box is the quick way to check without + dropping the desktop. + + **Kernel traces are not available this way, and cannot be made to be.** + `xperf -on base` and `wpr -start` drive the *NT Kernel Logger*, which is + reserved for Administrators and LocalSystem — Microsoft documents Performance + Log Users access as explicitly not extending to it. That was measured rather + than assumed, and the negative result is recorded here so nobody repeats the + experiment: with the account in the group, `SeSystemProfilePrivilege` ("Profile + system performance") granted to that group, and an explicit ACE giving the + group `TRACELOG_ACCESS_KERNEL_LOGGER` on `SystemTraceControlGuid` — all three + in place, across a reboot — `xperf -on base` still answered `NT Kernel Logger: + Access is denied. (0x5)`. It is not a check an ACE overrides. The privilege + grant and the ACE were dropped again rather than left on the box earning + nothing: CPU sampling and whole-system traces are elevated work, with `xperf`, + `wpr` or VTune from an Administrator prompt. + + Analysis was never affected — `wpa.exe` opens an existing `.etl` as a plain + user. This is only about collection. + + The step runs **first** in the elevated half, and `-EtwRightsOnly` runs it and + nothing else, which matters because a full run is dominated by the three Visual + Studio passes that take minutes even with nothing to do: ```powershell Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass', '-File','\setup-windows-with-uac.ps1','-TraceUser','DOMAIN\user','-EtwRightsOnly' ``` + + If you ran an earlier revision of this script, it left both of the grants above + on the box. Undo the privilege in `secpol.msc` > Local Policies > User Rights + Assignment > "Profile system performance" by removing Performance Log Users. + The ACEs sit in the `{9e814aad-3204-11d2-9a82-006008a86939}` value under + `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`: strip the `LU` entries + from that descriptor rather than deleting the value, which also carries entries + for SYSTEM, Administrators and two service accounts. - The scripts were extracted from a native Windows project, so the component selection is tuned for that: Spectre-mitigated runtimes, the v141/XP toolset, and driver-kit headers. Trim the component lists in the `.ps1` if you don't diff --git a/setup-windows-with-uac.ps1 b/setup-windows-with-uac.ps1 index 34bfa8b..17d02ee 100644 --- a/setup-windows-with-uac.ps1 +++ b/setup-windows-with-uac.ps1 @@ -17,9 +17,10 @@ - Windows Driver Kit 10.0.26100 - Windows Performance Toolkit - xperf, wpr and Windows Performance Analyzer (wpa.exe) - on the machine PATH - - ETW collection rights for one ordinary account: Performance Log Users - membership plus the "Profile system performance" user right, so xperf and - wpr run WITHOUT elevation + - Performance Log Users membership for one ordinary account, so it can run + user-mode ETW sessions (xperf -start ... -on ) without elevation. + Kernel traces are NOT covered - the NT Kernel Logger is admin-only; see the + step for what was measured. Change $VsInstallerUrl below to the Professional or Enterprise bootstrapper if needed: Professional : https://aka.ms/vs/17/release/vs_professional.exe @@ -27,22 +28,22 @@ #> param( - # Account to be granted non-elevated ETW collection rights (see the "ETW - # collection rights" step at the bottom). Defaults to the interactive - # console user, but setup-windows.bat passes it explicitly: with - # over-the-shoulder elevation THIS script runs as the administrator whose - # credentials went into the UAC prompt, not as the user who started the - # batch file, so $env:USERNAME here is the wrong answer. + # Account to put in Performance Log Users (see the "ETW session control" + # step, which runs first). Defaults to the interactive console user, but + # setup-windows.bat passes it explicitly: with over-the-shoulder elevation + # THIS script runs as the administrator whose credentials went into the UAC + # prompt, not as the user who started the batch file, so $env:USERNAME here + # is the wrong answer. # - # Pass an empty string to skip the group membership (the user right is still - # granted to the group, so adding an account later is one command). + # Pass an empty string to skip it; adding an account later is one + # `net localgroup` away. [string] $TraceUser = '', - # Do the ETW rights step and nothing else. That step is seconds of registry - # and LSA work with no downloads, where a full run is dominated by the three - # Visual Studio passes, which take minutes even when they have nothing to do. - # It is why the ETW step runs FIRST: -EtwRightsOnly is then just an early - # exit rather than a set of guards down the rest of the script. + # Do the ETW step and nothing else. It is a group membership and no + # downloads, where a full run is dominated by the three Visual Studio + # passes, which take minutes even when they have nothing to do. It is why + # that step runs FIRST: -EtwRightsOnly is then just an early exit rather + # than a set of guards down the rest of the script. [switch] $EtwRightsOnly ) @@ -62,220 +63,6 @@ function Assert-ExitCode([int]$Code, [string]$Step) { } } -# --------------------------------------------------------------------------- -# User rights assignment (LSA account rights) -# -# Windows has no built-in cmdlet for "grant this SID this privilege". The two -# ways to script it are secedit (export the whole USER_RIGHTS area to an INF, -# edit one line, re-import) and the LSA API. The API is used here because it is -# surgical: LsaAddAccountRights adds exactly one right to exactly one SID and is -# a no-op when it is already held, where a secedit round-trip re-applies every -# user right on the box to fix one of them. The GUI equivalent, for a human, is -# secpol.msc > Local Policies > User Rights Assignment -# -# The type is compiled on first use; C# 5 only, since Windows PowerShell 5.1's -# Add-Type compiles with the in-box CodeDom compiler. -# --------------------------------------------------------------------------- -function Initialize-LsaRightsType { - if ('LsaRights' -as [type]) { return } - Add-Type -TypeDefinition @' -using System; -using System.ComponentModel; -using System.Runtime.InteropServices; - -public static class LsaRights -{ - [StructLayout(LayoutKind.Sequential)] - private struct LSA_UNICODE_STRING - { - public ushort Length; - public ushort MaximumLength; - public IntPtr Buffer; - } - - [StructLayout(LayoutKind.Sequential)] - private struct LSA_OBJECT_ATTRIBUTES - { - public int Length; - public IntPtr RootDirectory; - public IntPtr ObjectName; - public uint Attributes; - public IntPtr SecurityDescriptor; - public IntPtr SecurityQualityOfService; - } - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern uint LsaOpenPolicy(IntPtr systemName, - ref LSA_OBJECT_ATTRIBUTES objectAttributes, uint desiredAccess, out IntPtr policyHandle); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern uint LsaAddAccountRights(IntPtr policyHandle, byte[] accountSid, - LSA_UNICODE_STRING[] userRights, uint countOfRights); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern uint LsaEnumerateAccountRights(IntPtr policyHandle, byte[] accountSid, - out IntPtr userRights, out uint countOfRights); - - [DllImport("advapi32.dll")] - private static extern uint LsaClose(IntPtr policyHandle); - - [DllImport("advapi32.dll")] - private static extern uint LsaFreeMemory(IntPtr buffer); - - [DllImport("advapi32.dll")] - private static extern int LsaNtStatusToWinError(uint status); - - private const uint POLICY_VIEW_LOCAL_INFORMATION = 0x00000001; - private const uint POLICY_CREATE_ACCOUNT = 0x00000010; - private const uint POLICY_LOOKUP_NAMES = 0x00000800; - - // Returned by LsaEnumerateAccountRights when the SID holds no rights at all, - // which is an empty list rather than an error. - private const uint STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034; - - private static IntPtr OpenPolicy() - { - LSA_OBJECT_ATTRIBUTES attrs = new LSA_OBJECT_ATTRIBUTES(); - attrs.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); - IntPtr handle; - uint status = LsaOpenPolicy(IntPtr.Zero, ref attrs, - POLICY_VIEW_LOCAL_INFORMATION | POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES, out handle); - if (status != 0) { throw new Win32Exception(LsaNtStatusToWinError(status)); } - return handle; - } - - public static string[] Get(byte[] sid) - { - IntPtr policy = OpenPolicy(); - try - { - IntPtr rights; - uint count; - uint status = LsaEnumerateAccountRights(policy, sid, out rights, out count); - if (status == STATUS_OBJECT_NAME_NOT_FOUND) { return new string[0]; } - if (status != 0) { throw new Win32Exception(LsaNtStatusToWinError(status)); } - try - { - string[] result = new string[count]; - int stride = Marshal.SizeOf(typeof(LSA_UNICODE_STRING)); - for (int i = 0; i < count; i++) - { - LSA_UNICODE_STRING s = (LSA_UNICODE_STRING)Marshal.PtrToStructure( - new IntPtr(rights.ToInt64() + (long)i * stride), typeof(LSA_UNICODE_STRING)); - result[i] = Marshal.PtrToStringUni(s.Buffer, s.Length / 2); - } - return result; - } - finally { LsaFreeMemory(rights); } - } - finally { LsaClose(policy); } - } - - public static void Add(byte[] sid, string right) - { - IntPtr policy = OpenPolicy(); - try - { - LSA_UNICODE_STRING[] rights = new LSA_UNICODE_STRING[1]; - rights[0].Buffer = Marshal.StringToHGlobalUni(right); - // Length counts BYTES and excludes the terminator; MaximumLength includes it. - rights[0].Length = (ushort)(right.Length * 2); - rights[0].MaximumLength = (ushort)(right.Length * 2 + 2); - try - { - uint status = LsaAddAccountRights(policy, sid, rights, 1); - if (status != 0) { throw new Win32Exception(LsaNtStatusToWinError(status)); } - } - finally { Marshal.FreeHGlobal(rights[0].Buffer); } - } - finally { LsaClose(policy); } - } -} -'@ -} - -function Get-SidBytes([string]$Sid) { - $s = New-Object System.Security.Principal.SecurityIdentifier($Sid) - $bytes = New-Object byte[] $s.BinaryLength - $s.GetBinaryForm($bytes, 0) - return ,$bytes -} - -function Get-AccountRight([string]$Sid) { - Initialize-LsaRightsType - return [LsaRights]::Get((Get-SidBytes $Sid)) -} - -function Grant-AccountRight([string]$Sid, [string]$Right) { - Initialize-LsaRightsType - [LsaRights]::Add((Get-SidBytes $Sid), $Right) -} - -# --------------------------------------------------------------------------- -# ETW provider-GUID access control -# -# ETW keeps a security descriptor per provider GUID under -# HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security, and EventAccessControl is -# the documented way to edit one. Editing the registry value directly would work -# too - it is a self-relative SD in a REG_BINARY - but the API takes the SID and -# the rights mask and leaves the descriptor's shape to Windows. -# --------------------------------------------------------------------------- -function Initialize-EtwAclType { - if ('EtwAcl' -as [type]) { return } - Add-Type -TypeDefinition @' -using System; -using System.Runtime.InteropServices; - -public static class EtwAcl -{ - // ULONG EventAccessControl(LPGUID, ULONG Operation, PSID, ULONG Rights, BOOLEAN AllowOrDeny) - [DllImport("advapi32.dll", SetLastError = true)] - public static extern uint EventAccessControl(ref Guid guid, uint operation, byte[] sid, - uint rights, [MarshalAs(UnmanagedType.U1)] bool allowOrDeny); - - // ULONG EventAccessQuery(LPGUID, PSECURITY_DESCRIPTOR, PULONG BufferSize) - [DllImport("advapi32.dll", SetLastError = true)] - public static extern uint EventAccessQuery(ref Guid guid, byte[] buffer, ref uint bufferSize); -} -'@ -} - -# The rights a session controller needs, from evntrace.h: -# 0x0001 WMIGUID_QUERY 0x0100 TRACELOG_ACCESS_KERNEL_LOGGER -# 0x0020 TRACELOG_CREATE_REALTIME 0x0200 TRACELOG_LOG_EVENT -# 0x0040 TRACELOG_CREATE_ONDISK 0x0400 TRACELOG_ACCESS_REALTIME -# 0x0080 TRACELOG_GUID_ENABLE 0x0800 TRACELOG_REGISTER_GUIDS -# TRACELOG_ACCESS_KERNEL_LOGGER is the one that names the NT Kernel Logger -# specifically; the rest are what any controller needs to create a session, -# write it to disk and enable providers on it. -# -# READ_CONTROL (0x20000) and SYNCHRONIZE (0x100000) go with them - the SYSTEM and -# Administrators entries on this GUID carry 0x120FFF. Without READ_CONTROL the -# group cannot read the descriptor back, which makes EventAccessQuery useless as -# a check on whether the grant landed: it answers "access denied" either way. -$EtwControllerRights = 0x120FE1 - -function Grant-EtwGuidAccess([string]$Guid, [string]$Sid, [uint32]$Rights) { - Initialize-EtwAclType - $g = [Guid]$Guid - # Operation 2 = EventSecurityAddDACL: add one ACE and leave every existing - # one in place. EventSecuritySetDACL (0) would REPLACE the descriptor, which - # on the kernel logger means removing the entries Windows itself relies on. - $rc = [EtwAcl]::EventAccessControl([ref]$g, 2, (Get-SidBytes $Sid), $Rights, $true) - if ($rc -ne 0) { throw (New-Object System.ComponentModel.Win32Exception([int]$rc)) } -} - -function Get-EtwGuidSddl([string]$Guid) { - Initialize-EtwAclType - $g = [Guid]$Guid - $size = [uint32]0 - [void][EtwAcl]::EventAccessQuery([ref]$g, $null, [ref]$size) - if ($size -eq 0) { return $null } - $buf = New-Object byte[] $size - if ([EtwAcl]::EventAccessQuery([ref]$g, $buf, [ref]$size) -ne 0) { return $null } - return (New-Object System.Security.AccessControl.RawSecurityDescriptor($buf, 0)).GetSddlForm('Access') -} - function Show-VsSetupLogs { # The VS Installer writes dd_*.log to the invoking user's %TEMP%. Because # this script runs elevated, that %TEMP% belongs to the elevated user and is @@ -346,109 +133,48 @@ try { Start-Transcript -Path $LogFile -Force | Out-Null } catch {} try { # --------------------------------------------------------------------------- -# ETW collection rights for an ordinary account +# ETW session control for an ordinary account # -# Out of the box, xperf and wpr only work elevated. THREE separate things stand -# in a standard user's way, and each has its own error: +# Creating or controlling an event tracing session - even a user-mode one naming +# a single provider - is checked against the security descriptor ETW keeps per +# provider GUID under HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security. The +# default grants the session-control rights (TRACELOG_CREATE_ONDISK, +# TRACELOG_CREATE_REALTIME, TRACELOG_GUID_ENABLE, TRACELOG_LOG_EVENT) to SYSTEM, +# Administrators, the service accounts and BUILTIN\Performance Log Users, and to +# nobody else. Its own description says members "may ... enable trace providers, +# and collect event traces", and that is what membership buys: # -# xperf -on base -> "NT Kernel Logger: Access is denied. (0x5)" -# wpr -start GeneralProfile -# -> "Failed to enable the policy to profile system -# performance." (0xc5585011) +# xperf -start MySession -on Microsoft-Windows-Kernel-Process -f trace.etl +# xperf -stop MySession # -# 1. Creating or controlling ANY event tracing session - even a user-mode one -# naming a single provider - is checked against the security descriptor ETW -# keeps per provider GUID under -# HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security. The DEFAULT descriptor -# grants the session-control rights (TRACELOG_CREATE_ONDISK, -# TRACELOG_CREATE_REALTIME, TRACELOG_GUID_ENABLE, TRACELOG_LOG_EVENT) to -# SYSTEM, Administrators, the service accounts, and BUILTIN\Performance Log -# Users - and to nobody else. That group is the supported hook; its own -# description says members "may ... enable trace providers, and collect event -# traces". +# runs unelevated for a member and is "Access is denied. (0x5)" for everyone +# else. Enough to trace your own application's providers without a UAC prompt. # -# 2. Switching on the kernel/system trace provider on top of that needs the -# SeSystemProfilePrivilege user right ("Profile system performance"), held by -# default only by Administrators and NT SERVICE\WdiServiceHost. That is the -# one wpr names in its error. +# Membership is read into the access token at LOGON, so the account has to sign +# out and back in. Any NEW logon does it - an ssh login into this box is one, +# which is the quick way to check without dropping the desktop. # -# 3. The kernel logger is not covered by that default descriptor. Its own GUID - -# SystemTraceControlGuid, the session both `xperf -on` and wpr drive - carries -# an explicit descriptor that does not mention Performance Log Users, so 1 and -# 2 are not enough by themselves. Measured on this box with both in place: a -# user-mode session starts (exit 0) and the account holds the privilege, and -# `xperf -on base` still answers "NT Kernel Logger: Access is denied" while -# wpr's error changes from the policy message above to a bare 0x80070005. -# Even READING that descriptor comes back access-denied, which is the tell. So -# add an ACE for the group with EventAccessControl; TRACELOG_ACCESS_KERNEL_LOGGER -# is the right that names this particular session. +# WHAT THIS DOES NOT BUY: system-wide kernel traces. `xperf -on base` and +# `wpr -start` drive the NT Kernel Logger, which is reserved for Administrators +# and LocalSystem - Microsoft documents Performance Log Users access as +# explicitly NOT extending to it. Measured here, so that nobody repeats it: with +# the account in the group, SeSystemProfilePrivilege ("Profile system +# performance") granted to that group, and an explicit ACE giving the group +# TRACELOG_ACCESS_KERNEL_LOGGER on SystemTraceControlGuid - all three in place, +# across a reboot - xperf still answered # -# The privilege and the ACE both go to the GROUP, and the account then goes into -# the group: membership alone becomes the switch, and enabling the next account -# is one `net localgroup` away with no policy or registry edit. +# xperf: error: NT Kernel Logger: Access is denied. (0x5). # -# What this costs, stated plainly: a member of that group can capture -# system-wide kernel traces - process, image, file and registry activity across -# every account on the box, paths and command lines included. That is what the -# group is for, and it is the price of collecting a trace without a UAC prompt. +# It is not a check an ACE overrides. Those two grants were dropped again rather +# than left on the box earning nothing, and CPU sampling and whole-system traces +# are elevated work: run xperf, wpr or VTune from an Administrator prompt. # -# Deliberately NOT granted: SeDebugPrivilege. xperf needs it for neither CPU -# sampling nor walking stacks in your own processes, and it is equivalent to -# handing out administrator. -# -# THIS ONLY HELPS A NON-ADMIN ACCOUNT. Both a privilege and a group membership -# are baked into the access token at LOGON, and UAC hands an administrator a -# filtered token that keeps just five harmless privileges - so an admin's -# ordinary shell still cannot trace, however the policy reads. Running as a -# standard user is what makes this work. -# -# For the same reason 1 and 2 do not take effect in an already-open session: the -# account has to sign out and back in. Any NEW logon does it - an ssh login into -# this box is one, which is the quick way to check without dropping the desktop. -# The ACE in 3 is machine state rather than token state, so a logon does nothing -# for it. ETW reads these descriptors into a cache, so a REBOOT is what is -# expected to put the change into effect: with the ACE written and readable in -# the descriptor, xperf -on base was still answering "Access is denied" from a -# fresh shell on the running system. So on a first run, plan on both - a new -# logon for 1 and 2, a reboot for 3. -# -# Analysis never needed any of this: wpa.exe opens an existing .etl as a plain -# user. This step is only about collection. +# Analysis needs none of this either way - wpa.exe opens an existing .etl as a +# plain user. # --------------------------------------------------------------------------- -Write-Step 'ETW collection rights (non-elevated xperf / wpr)' +Write-Step 'ETW session control (non-elevated user-mode tracing)' $PerfLogUsersSid = 'S-1-5-32-559' # BUILTIN\Performance Log Users -# SystemTraceControlGuid: the NT Kernel Logger / system session that xperf -on -# and wpr both drive. Fixed by contract, from evntrace.h. -$SystemTraceControlGuid = '9e814aad-3204-11d2-9a82-006008a86939' try { - # --- The user right, granted to the group --- - $existing = Get-AccountRight $PerfLogUsersSid - if ($existing -contains 'SeSystemProfilePrivilege') { - Write-Host ' OK: Performance Log Users already holds SeSystemProfilePrivilege' - } else { - Grant-AccountRight $PerfLogUsersSid 'SeSystemProfilePrivilege' - Write-Host ' Granted SeSystemProfilePrivilege ("Profile system performance") to Performance Log Users' - } - - # --- The kernel logger's own descriptor --- - # Safe to repeat: a second ACE for the same SID unions to the same access. - # Kept in its own try so that a failure here still leaves the group - # membership below to be done - user-mode sessions work without it. - # - # ETW reads these descriptors out of the registry into a cache, so a REBOOT - # is what puts a change here into effect - not a new logon, which is what the - # group membership and the privilege need. Both, on a first run. - try { - Grant-EtwGuidAccess $SystemTraceControlGuid $PerfLogUsersSid $EtwControllerRights - Write-Host (" Granted Performance Log Users the controller rights (0x{0:X4}, TRACELOG_ACCESS_KERNEL_LOGGER included) on SystemTraceControlGuid" -f $EtwControllerRights) - $sddl = Get-EtwGuidSddl $SystemTraceControlGuid - if ($sddl) { Write-Host " kernel logger DACL is now $sddl" -ForegroundColor DarkGray } - } catch { - Write-Warning "Could not add the ACE on SystemTraceControlGuid: $($_.Exception.Message)" - Write-Warning 'xperf -on will keep answering "NT Kernel Logger: Access is denied."' - } - - # --- The membership --- # Fall back to the console user when the caller did not name one: with # over-the-shoulder elevation that is the person who started # setup-windows.bat, which is who wants to trace. @@ -460,7 +186,7 @@ try { if (-not $target) { Write-Warning 'No account to add to Performance Log Users (pass -TraceUser DOMAIN\user).' - Write-Warning 'The user right is in place, so this is the only step left:' + Write-Warning 'To do it later:' Write-Warning ' net localgroup "Performance Log Users" DOMAIN\user /add' } else { # Resolve to a SID first: it validates the name, and it is what the @@ -502,16 +228,14 @@ try { } Write-Host '' - Write-Host " $target must sign out and back in for the group and the privilege," -ForegroundColor Yellow - Write-Host ' and the box must be REBOOTED for the kernel logger ACE (ETW caches it).' -ForegroundColor Yellow + Write-Host " $target must sign out and back in before this takes effect." -ForegroundColor Yellow Write-Host ' Then, from that account (NOT elevated):' -ForegroundColor Yellow - Write-Host ' whoami /priv | findstr SeSystemProfilePrivilege' -ForegroundColor Yellow - Write-Host ' xperf -on base ; xperf -stop C:\Temp\trace.etl' -ForegroundColor Yellow + Write-Host ' xperf -start T -on Microsoft-Windows-Kernel-Process -f trace.etl' -ForegroundColor Yellow + Write-Host ' xperf -stop T' -ForegroundColor Yellow } } catch { - Write-Warning "ETW rights setup failed: $($_.Exception.Message)" - Write-Warning 'Grant them by hand: secpol.msc > Local Policies > User Rights Assignment >' - Write-Warning '"Profile system performance" > add Performance Log Users, then' + Write-Warning "Performance Log Users membership failed: $($_.Exception.Message)" + Write-Warning 'Do it by hand with:' Write-Warning ' net localgroup "Performance Log Users" /add' }