For our exploit, we needed to reverse engineer the essential fields of the registry_ops structure:
typedef struct {
DWORD hive;
DWORD unknown;
wchar_t key[100];
wchar_t value[100];
DWORD regtype;
wchar_t value2[522];
DWORD value_len_qq;
} registry_ops_t;
One key parameter, DWORD hive, specifies the registry hive where the registry key would be written. To target HKEY_LOCAL_MACHINE, the key’s value had to be set to 0x80000002, as documented in some Windows internals documentation.
The final exploit call was constructed using the dynamically created registry client object and the crafted registry_ops structure. A simplified implementation of this exploit is provided below:
typedef DWORD_PTR(WINAPI* SaveRegvalue_Orig_t)(__int64 this_ptr, registry_ops_t* a2);
// [...]
registry_ops_t ops = { 0 };
ops.hive = 0x80000002;
ops.unknown = 0xFFFFFFFF;
wcscpy_s(ops.key, 0x64ui64, L"SYSTEM\\CurrentControlSet\\Services\\NetSetupSvc");
wcscpy_s(ops.value, 0x64ui64, L"ImagePath");
wcscpy_s(ops.value2, 0x64ui64, L"C:\\poc\\SpawnSystemShell.exe");
ops.regtype = REG_SZ;
ops.value_len_qq = strlen("C:\\poc\\SpawnSystemShell.exe") * 2 + 1;
SaveRegvalue_Orig_t saveRegValue = (SaveRegvalue_Orig_t)((uint64_t)seccenter + 0x10DCF0);
uint64_t global_reg_handler = (uint64_t)((uint64_t)seccenter + 0x198DC0);
saveRegValue(global_reg_handler, &ops);
Escalating our privileges
After understanding the back-end communication, we could write a registry key without restrictions on its path. We aimed to exploit this capability without requiring an admin login or system restart.
We decided to modify the binary path of a service to point to an executable we controlled. Although many services could be used for this purpose, most required a system restart. We ultimately chose NetSetupService because it was not started by default, ran as SYSTEM, and could be started by a low-privileged user.
We created a COM DLL that used the above-mentioned functionality to instruct the back end to change this service’s ImagePath to a controlled path. Once the modification was made, we could start the service, which then executed our binary as SYSTEM. Our binary then spawned a cmd.exe process with SYSTEM privileges on our desktop.
Put together, our exploit worked as follows:
- We hijacked the COM interface for dataexchange.dll.
- We started the front-end UI to trigger the COM interface and load our DLL into the front end.
- Our DLL would communicate with the back end and instruct the back end to write to the registry.
- The back-end would change the ImagePath for the NetSetupService service.
- We place our executable under the new ImagePath and start the service.
- Our executable is started as SYSTEM and spawns a cmd.exe process running as SYSTEM on our desktop.
Preventing COM hijacking vulnerabilities
To mitigate COM hijacking as an attack vector for privilege escalation, vendors should refrain from giving special privileges to applications running in low-privileged user contexts. Microsoft does not enforce a security boundary between processes running in the same user context, so the vendor is likely to always play catch-up to new techniques being used to inject into other processes. Products requiring administrator privileges for all critical actions, such as setting exclusions, were found to be more resistant to similar exploits.
Vendors may allow users to trigger certain actions without admin privileges for usability. However, they should implement measures to prevent code injection into these processes. The most effective way to do this would be to make the front-end process a Protected Process Light (PPL). Unfortunately, Microsoft does not support PPL for processes providing a UI, according to Microsoft’s documentation.
Therefore, if special permissions are given to the front-end process, the vendor must try to prevent code injection into this process. Most vendors currently do this by using a filter driver. Another measure is hooking the functions involved in DLL loading to enforce signature verification and an allow list. If implemented correctly, this would help against COM hijacking, as described in our posts. However, these methods require ongoing updates to counter new injection techniques that might allow an attacker to execute code in the trusted front-end process.
Denial of Service via COM Hijacking
During this research, we stumbled upon another interesting question:
We can inject a DLL into a front-end process of an Endpoint Detection and Response (EDR) via COM. Could we do the same for the back end and, thus, potentially create a Denial of Service (DoS) attack against security solutions?
We often struggle with permanently deactivating the security solution in place on a system so that it does not interfere with our tooling.
Note: You do not want to do this on production systems. However, we often use dedicated systems set up just for a project and get wiped afterwards. Deactivating security solutions on specific systems in penetration testing scenarios can be justified, as it increases the efficiency of the pentesters’ work and allows testing of blue team responses.
COM hijacking enabled us to inject a DLL into a central back-end process of the security solution. The most direct approach was to terminate the process upon DLL load, effectively disabling the security software. A more advanced method could involve suspending specific process functions to make the solution appear functional while neutralizing its effectiveness.
So, do the back-end processes even use COM? Some of our targeted products did. We found two EDR products whose back-end processes utilized COM interfaces, which we were able to hijack. For our proof of concept, we terminated the process every time our DLL was loaded by one of the security vendors’ processes. This effectively disabled the security solution and allowed us to execute tools like mimikatz without interference.
As this requires SYSTEM privileges and is only a DoS issue rather than a privilege escalation issue, both vendors we reported this to declined to issue a fix. Thus, this might remain a viable technique for the next time you want to get rid of an EDR and do not need to be too stealthy.
Conclusion
In this final part of the series, we detailed the reverse engineering of a custom IPC protocol and demonstrated how we combined it with COM hijacking to gain SYSTEM privileges. We also explored mitigation techniques to prevent similar vulnerabilities and discussed how COM hijacking can facilitate DoS attacks against security products.
This concludes our series of blog posts on COM hijacking. We hope that you enjoyed reading it and gained some insights from it.