The Lazy Guide To Reverse RPC

This post is not going to cover in depth details of RPC implementation,for that read the resources mentioned in the Further Reading section. Also RPC programming is not in the scope of this post. We will only discuss concepts needed to reverse RPC client and a server.

You might encounter RPC calls in malware code or when doing security research, therefore it is essential to know the basic working of RPC to understand the code. Enjoy reading.

RPC Overview

  • Remote Procedure Call /RPC serves as backbone to Windows technology, various features and internal APIs are built on top of RPC.
  • RPC is same as conventional function calls only difference is caller and callee code reside on separate machines in a network. It could be on the same machine as well. Simply this means RPC lets us call functions over the network.
  • RPC follows a client-server model, where the code that calls RPC routine is the client and the code that serves the call is the server. The server code is a standalone PE file (exe or dll). The RPC call has to reach the server despite the nature of the call – network or local call.
  • Below image shows the RPC communication. As the call from the client needs to reach the server, the client makes use of endpoint information to invoke the call.
  • Client can start communicating with the server over network (ncan_ip_tcp) or using named pipes (ncacn_np) and locally via Advanced Local Remote Procedure aka ALRPC(ncalrpc). These serve as a set protocols for RPC to establish a communication channel between client and server. When we say RPC over network that means the client and server code are on separate machines in a networked environment, similarly in local RPC communication both client and server are present on same machine. The RPC is implemented by rpcrt4.dll which serves as a runtime.
  • There are hundreds of RPC servers running on a system, how does the client identify the correct one? – RPC Interface. Servers need to inform the system about its existence and functions it is serving for the client. This information is consolidated in the form of an Interface. By using these interfaces clients can easily identify the RPC server locally/remotely.
  • When the call reaches the server interface, the RPC runtime present on the machine where server runs can handover the data and control to the target RPC function implemented by the server(exe/dll), more on this in coming sections.

Dissecting RPC Client : PrintSpoofer

  • In this section we are going to use PrintSpoofer as the RPC client to further our discussion. You can find the detailed blog from itm4n on PrintSpoofer.
  • In a nut shell, PrintSpoofer abuses MS-RPRN PrinterBug to escalate privilege by impersonating the named pipe. The printerbug abuses RpcRemoteFindFirstPrinterChangeNotificationEx() to coerce authentication, more details here.
  • Below image shows the code from PrintSpoofer, there are three RPC calls RpcOpenPrinter, RpcRemoteFindFirstPrinterChangeNotificationEx and RpcClosePrinter (RpcExcept is a macro fyi). This is our RPC client .
  • When we reverse binaries, we don’t get to see the source code, below image shows the above code in the debugger. Interestingly above RPC functions are no where to be found in the assembly code. You can see an unusual function call to NdrClientCallX (X = 1,2,3 and 4), this is an indicator of an RPC call. When you compile an RPC program , all the calls will get converted to NdrClientCallX, this special function calls appropriate RPC apis implemented by a server.
  • Lets take a look at the function prototype for NdrClientCall3, function parameters are shown below.
  • pProxyInfo and nProcNum are two important parameters that help NdrClientCall3 api to invoke correct RPC routine implemented by a server.
  • Lets start with nProcNum, this is simply a positive integer value that is used to call a specific RPC function implemented by a server. As far as the reversing is concerned this parameter is super important in identifying the RPC routine the client is invoking. FYI without getting interface information, this data is of no use for us. For example nProcNum for RpcOpen is 0x01(1) and for RpcRemoteFindFirstPrinterChangeNotificationEx is 0x041(65)
  • As you can see pProxyInfo is pointer of type MIDL_STUBLESS_PROXY_INFO type. Sure it looks a little intimidating but it is not that bad. This structure simply holds all the valuable information required for the rpc runtime to invoke correct RPC function at the server site.(eg. Interface information)
  • The definition of MIDL_STUBLESS_PROXY_INFO is shown below. The first member is a pointer to another super important structure MIDL_STUB_DESC.
  • MIDL_STUB_DESC structure holds the interface information. Runtime fetches the interface information from here and establishes the communication channel with associated RPC server. Below image shows the definition of MIDL_STUB_DESC.
  • We are only interested in the first member in MIDL_STUB_DESC structure which is a pointer to RpcInterfaceInfomration of type void. This very deceiving, what is this RpcInterfaceInformation ? If you read the documentation over here, you will see – on the client side it points to client rpc information structure and on server side it points to server rpc information. A quick google search shows msdn page for RPC_CLIENT_INTERFACE structure. I am sharing a screenshot for both RPC_CLIENT_INTERFACE and RPC_SERVER_INTERFACE from WIndows SDK headers.
  • Despite the name, RPC_CLIENT_INTERFACE and RPC_SERVER_INTERFACE structures are identical. You can clearly see second member in both structures is InterfaceId. We want to know the value stored here to identify the server. Long story short this member holds a GUID which is used by the RPC to identify the server.
  • Now using this InterfaceId value and nProcNum we can easily find out the server and the RPC function the client is calling.

Finding InterfaceId Using Debugger

  • Lets look at the arguments values passed to NdrClientCall3 function, right of the bat lets make a note of the nProcNum, which is the second argument. In our case the value is 0x01. First argument is pointer to MIDL_STUBLESS_PROXY_INFO pProxyInfo. Lets follow the pointer in the memory dump.
  • The below memory shows the MIDL_STUB_DESC, the first member of pProxyInfo which is pointer to structure RPC_CLIENT_INTERFACE (as we are reversing client and on server side this changes to RPC_SERVER_INFERFACE) as highlighted in yellow below. Lets follow the pointer in the same memory dump.
  • Finally we have arrived at RPC_CLIENT_INTERFACE. The highlighted data is the interface GUID which is stored in the second member of RPC_CLIENT_INTERFACE that is InterfaceId. The GUID value is 12345678-1234-abcd-ef00-0123456789ab
  • Now we have following information – the interface GUID and nProcNum. Lets fire up RpcView application find the server using interface GUID. We can look up for the GUID value in the Interface window. Voila! The target RPC server is “spoolsv.exe”
  • We can see more details in the Interface Properties windows as shown below
  • Most important is Endpoints Window, you can see the all the information related to the communication protocol as we discussed in the beginning of this post. Inorder to talk to spoolsv rpc server over tcp we need to use the port 55149 or named pipe “\pipe\spoolss”. Locally we can make use of LRPC-XXX..X endpoint as shown below.
  • Now we have identified the RPC server from its GUID that we have fetched from the structures passed to NdrClientCall3 call. What about nProcNum value? what is that value? Simply it is an index number. RPC server has a dispatch table, this table stores all the functions implemented by the server. The nProcNum serves as an index value into dispatch table. You can see this table in the RpcView as shown in the image below. For some reason the names are not displayed.
  • There is this git hub repo where you can find all functions exposed by the RPC server. The function names are shown against its index values as shown below
  • Earlier we saw nProcNum with value 0x01, from the above image we can easily identify the function – RpcOpenPrinter.You can also refer to msdn, as shown below, Opnum is nProcNum.

Dissecting RPC Server : Spoolsv.exe

The goal of reversing RPC client is to identify RPC server using interface information and the function it invokes, in this section we are going to debug RPC function implemented in the server. From the previous section we have following information:

  • Interface GUID : 12345678-1234-abcd-ef00-0123456789ab
  • Server : C:\Windows\System32\spoolsv.exe

I am using IDA Pro 7.5 to work on the server code, you can use your favourite tool.

RPC Dispatch Table

  • RPC server has a special data structure called dispatch table to hold all the function it supports, we need to take look at the table to fetch the address of RPC routine we want analyse.
  • Similar to how we have analysed the client, we will have to process a few data structures to finally get to the dispatch table.
  • First we need to search for the GUID value in the server binary. Interface information are stored in rdata section. In IDA there is “Sequence of bytes..” search option as shown below. Lets search for first four bytes of the GUID.
  • I get two hits, lets check the first one
  • You can see the GUID stored at 14008D2F4, but this seems more like a structure that starts at unk_14008D2F0. Lets do a cross reference to this address by pressing x by clicking on unk_14008D2F0. A small window pops up as shown below. Click on address that points to rdata (14008D5E0)
  • Interesting, off_14008D5E0 points to unk_14008D2F0 where GUID is present, MIDL_user_allocate and MIDL_user_free indicate the structure we are staring at is definitely MIDL_STUB_DESC, yes go and check the structure definition again 🙂 . As we have discussed in the client section, first member of MIDL_STUB_DESC in server holds RPC_SERVER_INTERFACE structure, that means unk_14008D2F0 points to RPC_SERVER_INTERFACE
  • So go to unk_14008D2F0, thats our server interface structure, both RPC-CLIENT_INTERFACE and RPC_SERVER_INTERFACE are identical, there is one member InterpreterInfo, in one of the fortinet RPC related blogs, it is mentioned that InterpreterInfo points to MIDL_SERVER_INFO which holds DispatchTable. You can see MIDL_SERVER_INFO below in RPC_SERVER_INTERFACE at unk_14008D2F0.
  • Before we examine the this address lets look at the definition of MIDL_SERVER_INFO. The second member is our DispatchTable
  • With the help of definition shown above we can easily identify each member, the off_14008D690 is pointer to DispatchTable.
  • By examining off_14008D690, we can see list of routines implemented by spoolsv.exe server.

Debugging RPC Function

  • Lets say we want to analyse RpcRemoteFindFirstPrinterChangeNotificationEx function in debugger when PrintSpoofer makes the RPC but we dont know the address of the function to place a breakpoint.
  • Dont worry lets go back to our Dispatch table in IDA, not the address in rdata where the pointer to function is stored. As shown below, that address is 14008D898
  • Next attach a debugger (elevated process) to spoolsv.exe and find out the base address of the program as shown below. The address is 00007FF787EF0000
  • Now we need to find the offset where pointer to RpcRemoteFindFirstPrinterChangeNotificationEx is stored in rdata section. This is very simple it is the difference between base address of spoolsv (in IDA) and address we got from dispatch table (in IDA) (0000000140000000 – 14008D898) which gives us 8D898
  • Finally add this value to base address of the spoosv.exe in the debugger 00007FF787EF0000 + 8D898 = 7FF7 87F7 D898 . Lets examine this address in the debugger. Below image shows the Dispatch Table in the memory, lets follow the highlighted address in the disassembler.
  • VOILA! RpcRemoteFindFirstPrinterChangeNotificationEx code in the disassembler
  • If you are unsure you can go back check IDA’s pseudocode as shown below to verify above code. Now you can place a breakpoint to break spoolsv.exe when PrintSpoofer invokes RpcRemoteFindFirstPrinterChangeNotificationEx.

Credits & Further Reading

https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html

https://www.fortinet.com/blog/threat-research/the-case-studies-of-microsoft-windows-remote-procedure-call-serv

https://posts.specterops.io/uncovering-rpc-servers-through-windows-api-analysis-5d23c0459db6

Leave a Reply

Your email address will not be published. Required fields are marked *