Published On: December 2024
Remote Procedure Calling (RPC) is a protocol that allows software on different systems to communicate efficiently. It enables a program to execute a function on a remote server as if it were local, making it an essential component in distributed computing environments. In practical terms, RPC helps systems and applications interact seamlessly, whether they’re on the same machine or across a network.
For cybersecurity professionals researching Windows, understanding RPC is crucial, especially in Windows environments where Microsoft’s implementation (MSRPC) is widely used. MSRPC is integrated into various Windows components like COM/DCOM and Event Viewer, making it a common target for attacks. Misconfigurations or vulnerabilities in RPC can open up systems to unauthorized access or privilege escalation, highlighting the need for proper security measures.
This blog will walk through the fundamentals of RPC, focusing on MSRPC. We’ll explore key concepts, practical implementations with code examples, and the security challenges associated with it. By the end, you should have a solid understanding of how RPC works and how to manage its risks effectively.
Remote Procedure Calling (RPC) is fundamentally about enabling communication between a client and server, whether they're running on the same system or across a network. Microsoft’s implementation, MSRPC, is a key focus for this discussion, as it’s integral to various Windows components and services.
MSRPC supports interactions within the same process space or between different hosts entirely, allowing it to be used for tasks such as remote printing, accessing the Event Viewer remotely, and managing COM/DCOM operations. Understanding how MSRPC is structured and implemented is essential for anyone managing or securing Windows environments.
MSRPC isn’t just about one type of service; it’s versatile. It’s implemented in various Windows components to enable remote management and interaction between different systems. For example:
Let’s break down some of the essential terms:
ncacn_ip_tcp
: Uses IP addresses and ports for communication.ncacn_np
: Uses named pipes, often in local environments.ncacn_http
: Routes through HTTP proxies for web-based interactions.The process of RPC communication involves several key steps on both the server and client sides. Let’s walk through the general steps involved in setting up and using MSRPC:
Understanding this flow is critical for setting up and troubleshooting RPC connections in real-world scenarios. It also highlights where potential security risks might arise, such as misconfigured protocol sequences or unregistered interfaces.
Microsoft Interface Definition Language (MIDL) is a cornerstone of developing RPC applications. It provides a structured way to define the interfaces that facilitate communication between the client and server. When defining an RPC interface, the MIDL file serves as the blueprint for generating critical components:
A typical MIDL file might look like this:
[
// The uuid uniquely identifies the interface.
uuid(73efcd65 - 810d - 47c8 - 8eab - 88fed2805866),
version(1.0)] interface SimpleRPC {
void MultiplyByTwo(
// Input ([in]) and output ([out]) parameters define how data flows
// between the client and server.
[in] handle_t hBinding, [in] int number,
// The handle_t binding handle establishes the connection parameters.
[ out, retval ] int* result);
}
Upon compilation, the MIDL file generates:
In some scenarios, clients may not know the exact endpoint that is listening on the server. This is where the Endpoint Mapper comes into play. The Endpoint Mapper is a service that provides context for the client on connectivity details to the endpoint. The RPC server registers itself with the mapper using the RpcEpRegisterW function, enabling clients to query the mapper for endpoint details dynamically.
To further illustrate and examine RPC communication, we can examine network captures using Wireshark while running the example code in the Github repository. We can use the “DCEPRC” filter to gather all relevant packets.
The following screenshot shows a packet capture of the bind request:
Following this, we see the client's request to the server. Notice the stub data which contains the actual payload for communication. In this case, we used MSRPC to achieve command execution, and thus, the cmd.exe /C whoami
payload.
Then, we check the server's response to the client in the stub data with the machine name.
RPC's complexity makes it a frequent target for attackers. Securing RPC implementations, especially Microsoft’s implementation (MSRPC), requires a multi-layered approach which can be daunting. This section outlines key security considerations and best practices.
https://github.com/akamai/akamai-security-research/tree/main/rpc_toolkit
One of the primary methods to secure RPC communication is through authentication. Microsoft’s RPC implementation supports multiple authentication protocols, including Kerberos and NTLM, which are implemented through the RPC_C_AUTHN_GSS_NEGOTIATE
and RPC_C_AUTHN_GSS_KERBEROS
services, respectively. These protocols enable mutual authentication, ensuring that both the client and server validate each other's identities before communication is established.
To configure authentication on the server, developers can use the RpcServerRegisterAuthInfo
function. This function allows the server to specify the authentication service, the principal name, and optionally, a callback function for retrieving security keys. Below is an example of how this can be implemented:
RPC_STATUS RpcServerRegisterAuthInfo(
RPC_CSTR ServerPrincName, // Server principal name (e.g., SPN for Kerberos)
unsigned long
AuthnSvc, // Authentication service (e.g., RPC_C_AUTHN_GSS_NEGOTIATE)
RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, // Optional key retrieval callback
void *Arg // Callback context
);
The ServerPrincName identifies the server within the domain, while the AuthnSvc parameter defines the authentication mechanism, such as NTLM or Kerberos.
Not all protocol sequences support authentication. For example, ncacn_ip_tcp protocol does not support authentication. This limitation makes it unsuitable for scenarios where authenticated communication is mandatory. Instead, protocols like ncacn_http (which routes through an HTTP proxy) or ncacn_np (which uses named pipes) are preferred for their compatibility with authentication mechanisms.
Security can be configured at both the endpoint and the interface level. While these configurations provide flexibility, improper implementation or reliance on a single layer of security can lead to vulnerabilities which will be explained momentarily.
RPC interfaces can be secured using a Security Descriptor, which defines access control rules for the interface. A common approach involves creating a Security Descriptor using the function ConvertStringSecurityDescriptorToSecurityDescriptor
. This descriptor is then applied to the interface using RpcServerUseProtseqEpEx
. For example:
LPCWSTR sddl = L"D:(A;;GA;;;BA)";
ConvertStringSecurityDescriptorToSecurityDescriptor(…);
RpcServerUseProtseqEpEx(…, sddl, …);
However, relying solely on this approach is not recommended. A Security Descriptor applied at the interface level may not provide sufficient protection when other endpoints or interfaces on the same process are misconfigured.
Utilizing endpoint security alone effectiveness diminishes when multiple endpoints share the same process. If one endpoint is secured but another is loosely configured, clients may bypass restrictions by accessing the unsecured endpoint. For example, A client restricted by the Security Descriptor on one endpoint might connect to another, less secure endpoint within the same process, gaining access to interfaces indirectly.
Securing an RPC interface goes beyond endpoint configurations by applying protections directly to the interface itself. This ensures that the interface remains secure regardless of which endpoint is used to access it. A Security Descriptor can be assigned to the interface to control access explicitly.
The function RpcServerRegisterIf3 provides the capability to secure an interface. Along with standard configuration parameters, it includes options for assigning flags and a security descriptor.
RPC_STATUS RpcServerRegisterIf3(
[in] RPC_IF_HANDLE IfSpec,
[in, optional] UUID *MgrTypeUuid,
[in, optional] RPC_MGR_EPV *MgrEpv,
[in] unsigned int Flags,
[in] unsigned int MaxCalls,
[in] unsigned int MaxRpcSize,
[in, optional] RPC_IF_CALLBACK_FN *IfCallback,
[in, optional] void *SecurityDescriptor
);
The PetitPotam attack highlights how vulnerabilities in RPC services can be exploited to gain unauthorized access. Specifically, this attack abuses the Encrypted File System RPC (EFSRPC) service, which manages encryption of data objects over RPC.
Overview of the Attack:
Given that much of RPC's internals are undocumented, reverse engineering provides valuable insights into how RPC servers and clients operate. We will use the PetitPotam attack as an example to show how it might’ve been found through reverse engineering. Here are some insights to consider when reverse engineering RPC:
We are going to dive deeper into the efsslsaext DLL and try to identify the root cause of the attack.
The code inside efssvc.dll shows the use of the function ConvertStringSecurityDescriptorToSecurityDescriptorW, which converts a string representation of a Security Descriptor into a usable format for defining access control. This function is worth investigating as it relates to security configuration on the RPC service. We can copy the security descriptor string to analyze. We can do that either manually or using Powershell's ConvertFrom-SddlString
utility. Below is the function call:
ConvertStringSecurityDescriptorToSecurityDescriptor(
"D:(A;;GRGWGX;;;WD)(A;;GRGWGX;;;RC)(A;;GA;;;BA)(A;;GA;;;OW)…", lu,
&SecurityDescriptor, &hMem);
By manually analyzing the SDDL string, we can conclude the following:
The resulting Security Descriptor is applied to the RPC service, defining permissions for different user groups and levels. This essentially means that the function grants broad access (read, write, execute) to the Everyone group and Ensures full control for Administrators and the resource owner. This creates the entry point for the attack surface as no access restriction is imposed on who can launch the RPC service.
Moving on to the RpcServerRegisterAuthInfoW, we see that the following parameters are passed:
v9 = RpcServerRegisterAuthInfoW(0i64, 9u, 0i64, 0i64);
By referring to MSDN, we find that the second parameter passed is the authentication service to use when the server receives a request for a remote procedure call. Although this is not mentioned on the MSDN page, the full definition the service constants can be found in this page. The number 9 corresponds to RPC_C_AUTHN_GSS_NEGOTIATE which is NTLM authentication, and thus, the NTLM relay attack surface.
Moving on with the code in with the code in the DLL, we find calls to the functions RpcServerInterfaceGroupCreateW
and RpcServerInterfaceGroupActivate
. Those are particularly useful for managing multiple RPC interfaces in an organized way, especially in scenarios involving complex services like the EFS RPC interface.
This function is used to create a group of RPC interfaces. The first parameter to this function is a pointer to an array of interfaces, defined as RPC_INTERFACE_TEMPLATE structures. These structures specify the interfaces to be grouped and exposed. We can see some interesting variables passed to this function.
v30 = L"EFS RPC Interface"
and v39 = L"EFSK RPC Interface":
v13 = "ncalrpc"
:v18 = "ncacn_np"
:v19 = "\\pipe\\efsrpc"
:After defining the group of interfaces using RpcServerInterfaceGroupCreateW, the RpcServerInterfaceGroupActivate function is called to activate the group. This step makes the interfaces available for clients to use.
Now going back to EfsRpcEncryptFileSrv, we can see the Opnum 4, which dictates the server function to be executed. Most of the times, this will be in the same order in the dispatch table.
RPC (Remote Procedure Call) is not exclusive to Microsoft; many third-party developers implement their own RPC interfaces for various purposes. These implementations are used in diverse applications, from enterprise software to embedded systems.
RPC interfaces, particularly those created by third parties, often present a rich attack surface due to:
These characteristics make RPC interfaces a fruitful target for attackers seeking to exploit vulnerabilities such as:
By analyzing third-party RPC implementations, researchers can uncover logic flaws or insecure configurations that lead to privilege escalation or data breaches.
The RPC protocol is vast, encompassing numerous features, protocols, and use cases. While it can be daunting to dive into all its details, understanding the fundamentals provides a strong foundation:
ncacn_np
, ncacn_ip_tcp
).With this knowledge, one can effectively explore RPC vulnerabilities, analyze traffic, or even develop basic RPC-based applications.
MSRPC's complexity arises from its extensive features, such as:
This complexity increases the likelihood of bugs, particularly in:
For attackers, these bugs can lead to significant vulnerabilities such as remote code execution or privilege escalation. For developers, it underscores the need for thorough testing and adherence to best practices.
Building custom RPC applications provides a hands-on understanding of how the protocol operates. This includes:
RPC is not limited to a single context—it underpins a wide variety of systems and applications: