Call and Register — Relay Attack on WinReg RPC Client
Executive summary
Akamai researcher Stiv Kupchik found a new elevation of privilege (EoP) vulnerability in Microsoft’s Remote Registry client, CVE-2024-43532, with a CVSS score of 8.8.
The vulnerability abuses a fallback mechanism in the WinReg client implementation that uses obsolete transport protocols insecurely if the SMB transport is unavailable.
By exploiting this vulnerability, an attacker can relay the client’s NTLM authentication details to the Active Directory Certificate Services (ADCS), and request a user certificate to leverage for further authentication in the domain. We provide a proof of concept in our GitHub repository.
The vulnerability was responsibly disclosed to the Microsoft Security Resource Center in February 2024, and was patched as part of October’s Patch Tuesday 2024.
The vulnerability affects all unpatched Windows versions.
Our findings were also presented at the No Hat computer security conference.
Introduction
MS-RPC is Microsoft’s implementation of the Remote Procedure Call (RPC) protocol and standard. RPC is a form of interprocess communication mechanism that allows processes to expose functionalities that other processes can call. It is a core component of the Windows operating system, and multiple services rely on it — from the service manager to shutdowns in wininit.
Our team has done a lot of research on MS-RPC — both offensive research in which we uncovered a large attack vector in the form of caching attacks, and defensive research in which we analyzed security mechanisms for it.
This time, we wanted to look at RPC from a different perspective. Any protocol that allows for communication and operations between different computers has to be involved with user authentication, and indeed the RPC protocol supports passing credentials and authenticating as part of its binding process. However, where there is authentication, there is authentication relay potentiality, so we set out to find relay opportunities.
RPC, authentication, and what’s in between
RPC sessions are handled by bindings. A client application connects to a server application, binds to the required RPC interface, and requests to run a specific function (Figure 1).
The bind request and response can carry over multiple fields of data, as required for the connection. Normally, if there’s no authentication involved, the RPC binding is simply used to decide on the transfer syntax that will be used to encapsulate function parameters on subsequent calls.
What’s missing from this interaction? Authentication, of course! How can the server know the client’s identity and whether that client is allowed to perform the requested action? The answer is: It doesn’t, unless the client specifically added security context (authentication) to the binding. By default, all RPC connections are unauthenticated, and not all RPC servers actually require authentication.
To authenticate (or, in RPC terms, “add security context”), the client has to add additional data to the binding request, negotiate authentication protocol (for example, NTLM or Kerberos), and insert additional metadata required by the authentication protocol (like username, domain, etc.; Figure 2).
Finding research targets
The first order of business is understanding how authentication should look from an API standpoint. This is done by a call to one of the RpcBindingSetAuthInfo* functions from the client after creating a binding handle. If you look at the documentation of these functions, you’ll notice a field called AuthenticationLevel; it indicates the level of security the authentication provides.
Security with authentication is more than just verifying that the user exists and is authorized, it can also be used to prevent tampering. The authentication levels vary from simply verifying the connection succeeded (RPC_C_AUTHN_LEVEL_CONNECT) to fully encrypting and signing all the traffic to ensure nothing is tampered (RPC_C_AUTHN_LEVEL_PKT_PRIVACY). Of course, attackers would be interested in the former level, no defenses on the traffic, which means it’s tamperable.
Yet the story isn’t so simple. RPC relay attacks are not a new concept, so a lot of the RPC clients and servers in Windows were already patched to use the highest level of authentication to ensure relay attacks can’t succeed. We’ll need to scour the operating system in hopes of finding some older nuggets of code that are still insecure for some reason (Figure 3).
WinReg — A promising candidate
As suspected, the list of potential targets that we found included less than 5% of the total RPC servers and clients; most simply do not use insecure authentication anymore. But we found a promising candidate in advapi32.dll.
advapi32.dll is a core component of Windows API, and implements a lot of the “advanced” logic in Windows (as its name suggests). It exports more than 800 functions from various fields: event logging, encryption, WMI, and more.
For our purposes, we’ve found that, on some occasions, the internal function BaseBindToMachine calls RpcBindingSetAuthInfoA with an authentication level of RPC_C_AUTHN_LEVEL_CONNECT, which is what we want. BaseBindToMachine is called by the exported (though undocumented) function RegConnectRegistryExW (Figure 4), if it receives a UNC path for a machine name.
By looking at BaseBindToMachine, we can see that it actually contains calls to both RpcBindingSetAuthInfoW and RpcBindingSetAuthInfoA. The former is used securely, with authentication level RPC_C_AUTHN_LEVEL_PKT_PRIVACY, while the latter uses authentication level RPC_C_AUTHN_LEVEL_CONNECT, which can be relayed as it doesn’t verify authenticity or integrity of the connection (Figure 5).
We just need to figure out why there are two conflicting calls. Looking at the function logic, we can see that it has a function pointer variable and an array of functions (Figure 6). Those functions set the RPC binding info to use a specific RPC transport; by default it attempts to use SMB and named pipes — but, if it fails, it attempts to bind over SPX, TCP/IP, and others.
For some reason, once it falls back to any other protocol besides SMB, it uses RpcBindingSetAuthInfoA to set the authentication level to Connect, which is insecure.
The fallback to TCP/IP is quite promising, as it means we can use the insecure authentication method to relay the traffic using a machine-in-the-middle attack without the client noticing. While it is possible with the other transport protocols as well, they are quite obsolete, so it might be unrealistic to find them in a modern network (and using them might trigger some alarm bells). TCP/IP is a lot more common as an RPC transport, so it should be okay even in a red team setting.
It is important to know that both BaseBindToMachine and RegConnectRegistryExW accept a flag as an argument that prevents the fallback behavior, but the basic function RegConnectRegistryW calls RegConnectRegistryExW without that flag present.
The relay process
Relaying is quite simple since NTLM relay is a common technique. Most of the logic we need is already implemented in Impacket’s ntlmrelayx, which is what we’ll use the most.
Building the RPC relay server
What ntlmrelayx lacks is a TCP/IP RPC server, as it implemented only an SMB server. As such, we’ll need to build our own relay server, which rejects the winreg named pipe to trigger a fallback to the TCP/IP binding function.
There are three critical points we need to implement:
The RPC endpoint mapper
The RPC bind request with NTLM
The NTLM challenge
The RPC endpoint mapper is responsible for translating RPC interface UUIDs into their respective endpoints, which in the case of the TCP/IP transport, would be a port number. Unlike SMB, where the endpoints are named pipes that are unique and can be known in advance, the TCP endpoints use ephemeral ports, so another layer of translation is necessary.
The critical part is the NTLM-related issues. The way RPC binding works with NTLM is that an NTLM negotiation message is sent during the bind request, then an NTLM challenge is sent with the bind response. Finally, the client has to send another message, called AUTH3, with the challenge response (Figure 7).
To relay, we just have to parse the corresponding messages in our RPC server. Once we see an NTLM negotiate message in a bind message, we open our own connection to our target authentication server and also request to authenticate via NTLM. Then, we just have to capture the challenge the server sends us, relay it back to the victim, and relay the response back to the server to get our own authenticated session.
All we need to consider is which server we want to relay the authentication.
RPC to RPC relay
The immediate thought would be to relay to another RPC server on a different machine, like the service manager or the task scheduler, and achieve a remote code execution that way. The issue with this, however, is that no one uses insecure authentication via RPC anymore, so we can’t relay to any high-profile RPC server. (This lack of insecure authentication via RPC is the same reason we had limited research targets.)
Both the service manager and the task scheduler RPC servers require RPC_C_AUTHN_LEVEL_PKT_PRIVACY, which encrypts the entirety of the traffic with the client's NTLMv2 hash, which we don’t know even with the relay. Instead, we have to look at a different angle.
RPC to ADCS
Luckily, the guys at SpecterOps have done a lot of work on ADCS; specifically on NTLM relay to ADCS. This is also implemented in Impacket by default, so all we have to do is pass our authenticated session to Impacket’s HTTPAttack module and let the magic happen by itself.
ADCS’s HTTP web server doesn’t require any security and is vulnerable to relay attacks. By abusing that, once we authenticate to it, we can request a user certificate, which we can then use by itself to authenticate, without having to go through the trouble of relaying the authentication again (Figure 8).
We used this certificate to authenticate to the LDAP service on the domain controller and create a persistent new domain admin in the compromised domain (Figure 9).
Potential impact
A function in advapi isn’t interesting by itself, it is only impactful if something else is using it. A quick lookup of imports of RegConnectRegistryExW or RegConnectRegistryExA doesn’t show anything on an up-to-date domain controller, but a search for RegConnectRegistryW uncovers a lot of potential candidates, like certutil and certsrv (AD CS), EFS, DFS, and more.
Detection
The Remote Registry service isn’t enabled by default on all Windows machines. It is possible to detect it’s status using the following osquery:
SELECT display_name, status, start_type, pid FROM services WHERE name='RemoteRegistry'
This, however, doesn’t protect from CVE-2024-43532, as it is a client issue. The results of the query should raise actual use cases for the Remote Registry in the organization that you might need to account for when hardening your systems.
To detect clients that use any of the vulnerable WinAPI, you can use the following YARA rule:
import "pe"
rule winreg_client_import {
meta:
description = "Detect binaries that rely on RegConnectRegistry"
author = "Stiv Kupchik with Akamai Technologies"
condition:
pe.is_pe and (
pe.imports(pe.IMPORT_ANY, "advapi32.dll", "RegConnectRegistryA")
or pe.imports(pe.IMPORT_ANY, "advapi32.dll", "RegConnectRegistryW")
or pe.imports(pe.IMPORT_ANY, "advapi32.dll", "RegConnectRegistryExA")
or pe.imports(pe.IMPORT_ANY, "advapi32.dll", "RegConnectRegistryExW")
)
}
Akamai Guardicore Segmentation users can also create policy rules for traffic coming into the RemoteRegistry service (Figure 10).
It is also possible to use Event Tracing for Windows (ETW) to monitor RPC traffic on both client and server sides of the communication. We’ve elaborated on this topic in our presentation at Black Hat 2023 and its accompanying blog post. Users can use our RPC visibility open-source tool to track RPC calls and filter for the WinReg RPC interface UUID {338cd001-2244-31f1-aaaa-900038001003}.
Conclusion
Although the RPC protocol — and MS-RPC — were built with security in mind, we can clearly see the evolution of security principles over time by analyzing various RPC interface implementations. While most RPC servers and clients are secure nowadays, it is possible, from time to time, to uncover relics of insecure implementation to varying degrees.
In this case, we managed to achieve NTLM relay, which is a class of attacks that better belongs to the past. This just proves that network defenses must be as thorough as possible because you never know which ancient interface is still exposed or running.
Disclosure timeline
02/01/2024 — Vulnerability disclosed to MSRC
04/25/2024 — Report closed as documentation issue
06/17/2024 — Report re-opened with better PoC and explanation
07/08/2024 — Vulnerability confirmed
10/08/2024 — Vulnerability patched
10/19/2024 — Blog post published