Post

A Survey of Windows RPC Discovery Tools

A Survey of Windows RPC Discovery Tools

TL;DR A survey of Windows Remote Procedure Call discovery tools and an attempt to understand how open source tools discover RPC servers, interfaces, and procedures.

Windows RPC has been a black box for me for some time. This post is an attempt to leverage analysis of open source RPC tools to pry open that box. I started by reading MSDN, getting bored and then bouncing between several detailed security and research blog posts. Reading was my first step down the road of Windows RPC comprehension, and it helped me understand RPC at a high level.

Microsoft Remote Procedure Call (RPC) defines a powerful technology for creating distributed client/server programs. The RPC run-time stubs and libraries manage most of the processes relating to network protocols and communication. This enables you to focus on the details of the application rather than the details of the network. -MSDN

RPC is a way to standardize security and communication across either local or distributed clients and servers. Used in services to provide a separation of privileges (as it supports impersonation) or as means to provide secure communication across a network through available transports. It is prolific in Windows and therefore useful to understand for auditing or researching Windows.

When I’m trying to understand something, reading is hardly ever enough. I often use writing as a tool for understanding (hence this blog). I find I can’t feign understanding in writing. This post will examine existing RPC discovery and enumeration tools hoping to understand each of the tools’ means for RPC discovery.

Questions to consider:

  • Which tools enumerate RPC?
  • By what means can RPC servers (and clients) be found?
  • What are the advantages and disadvantages of dynamic vs static tools?
  • What approaches are used by the various RPC tooling?

Evidence of RPC

Here are some well-known ways to identify RPC within a binary.

  • Look for the import of rpcrt4.dll
    • Each binary that supports RPC will need to link against the RPC runtime (rpcrt4.dll) to support common RPC actions.
    • The import of rpcrt4.dll may not be found in the binary of the server or application running RPC, it could be in a dependency DLL loaded at runtime with a RPC runtime dependency. This needs to be considered for the tooling looking for RPC, whether you attempt to discover RPC statically (by examining the binary) or dynamically (looking at a process at runtime).
  • Query the RPC endpoint mapper - Windows runs a service known as the RPC Endpoint Mapper. If (and only if) a RPC server registers with the endpoint mapper via an Win32 API such as RpcEpRegister will the server be known to the RPC Endpoint Mapper.

These common ways, mentioned in 0xcarsten’s RPC post here, are also alluded to in MSDN under linking and registering endpoints.

Some of the less known ways include the walking of RPC data structures available both in the compiled RPC binary and in a RPC process memory at runtime. These methods, detailed below by tools like RpcView and NtObjectManager, provide the means to not only find RPC servers and clients but also derive the interfaces and procedures within the binary.

RPC Tools - Discovering RPC Servers, Interfaces and Procedures

To begin, we survey the landscape a bit to understand each tool’s heuristic for RPC discovery.

RPCView

rpcview-githubRpcView

RpcView discovers RPC servers already running on your host. It takes a dynamic (runtime) approach to discovery. RpcView starts by enumerating every running process and discovers the RPC interface, endpoint, and AuthInfo for each of the running RPC servers it detects. Finally, it displays the results in a nice GUI.

rpcview-guiRpcView GUI

Code

Source: https://github.com/silverf0x/RpcView

As far as I can tell by exploring the code related to enumerating interfaces is:

  1. EnumProcess - The EnumProcess function enumerates all the processes by iterating through all the PIDs within a current process snapshot (see CreateToolhelp32Snapshot). It is called when RpcView is initialized and again as the user clicks on the various widgets (interfaces, endpoints, processes) within the GUI to ensure that a fresh process listing is used.
  2. GetRpcServerAddressInProcess - For RpcView’s ability to enumerate RPC interfaces, endpoints, and all other RPC information, it first attempts to discover the global symbol GlobalRpcServer from the rpcrt4.dll (RPC runtime dll) loaded in the running processes address space. The GlobalRpcServer variable is a pointer to a root _RPC_SERVER_T data structure needed to unravel all related RPC information with a process. The GlobalRpcServer is found in the the .data section of the rpcrt4.dll, so for each running process, GetRpcServerAddressInProcess function searches through the entire .data section (brute force style) dereferencing one sizeof(void *) pointer at a time) until it finds the GlobalRpcServer data structure. It identifies the GlobalRpcServer symbol by leveraging RpcView’s heuristic to identify the symbol. Essentially, they are searching for a unique RPC GUID 8a885d04-1ceb-11c9-9fe8-08002b104860 known as the NDR Transfer Syntax Identifier. More details of this heuristic and RPC data structures are better explained by @_xpn_ in his analysis of RPCView. Once found, GlobalRpcServer is assigned and subsequently used as the starting point for the all functionality we care about (RpcCoreEnumProcessInterfaces, RpcCoreEnumProcessEndpoints, and RpcCoreEnumProcessAuthInfo). Each of these functions has a similar start. Open process for reading memory, populate the _RPC_SERVER_T data structure with the memory pointed to by GlobalRpcServer.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     BOOL __fastcall RpcCoreEnumProcessInterfaces(void* pRpcCoreCtxt,DWORD Pid,RpcCoreEnumProcessInterfacesCallbackFn_T RpcCoreEnumProcessInterfacesCallbackFn,void* pCallbackCtxt,ULONG InterfaceInfoMask)
     {
         HANDLE					hProcess;
         BOOL					bResult=FALSE;
         RPC_SERVER_T			RpcServer;
         UINT					i;
         UINT					Size;
         VOID PTR_T *			pTable=NULL;
         VOID PTR_T				pRpcServer;
         BOOL					bContinue=TRUE;
         RpcInterfaceInfo_T*		pRpcInterfaceInfo = NULL;
         RpcCoreInternalCtxt_T*	pRpcCoreInternalCtxt=(RpcCoreInternalCtxt_T*)pRpcCoreCtxt;
    
         hProcess=ProcexpOpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION,FALSE,Pid);
         if (hProcess==NULL) goto End;
    
         if (!ReadProcessMemory(hProcess,pRpcCoreInternalCtxt->pGlobalRpcServer,&pRpcServer,sizeof(VOID PTR_T),NULL)) goto End;
    

    GlobalRpcServer starting point within RpcCoreEnumProcessInterfaces assigned to RPC_SERVER_T data structure

  3. RpcCoreEnumProcessInterfaces - Enumerating RPC interfaces and procedures in a process. Starting with the GlobalRPCServer the basic objective is to dig into its _RPC_SERVER_T data structure to identify the RPC interface _RPC_INTERFACE_T data structures. RpcCoreEnumProcessInterfaces iterates through all the interfaces and pulls out detailed information via InternalGetInterfaceInfo. The InternalGetInterfaceInfo function copies data from process memory to populate a detailed RpcInterfaceInfo_T data structure used by RpcView to update the GUI. The RPC interface IDs and procedure address table are populated within this function. The procedure names for the interface are not available from process memory, but are later enriched by referencing the PDB symbols for the corresponding binary and the procedure address table to produce the procedure names (assuming symbols are configured).
  4. RpcCoreEnumProcessEndpoints - Enumerating RPC endpoints in a process. This function again relies on the base GlobalRPCServer data structure and iterates a simple array that holds the RPC endpoint information (specifically the name and protocol) for the endpoint. There can be more than one endpoint within a process.
  5. RpcCoreEnumProcessAuthInfo - Enumerate the AuthInfo in a RPC process. Just like the previous two, starting with GlobalRPCServer, iterating over the AuthInfo, and populating RpcView’s RpcAuthInfo_T data structure.

Thoughts

RpcView’s runtime approach to RPC discovery has both advantages and disadvantages. An immediate disadvantage that comes to mind is that perhaps a server isn’t running? It could be missed. Some RPC servers are activated by some distinct action or trigger. If an RPC server isn’t running, then RpcView is blind to it. The counter is that what you see is what you get. There is no mystery or time wasted trying to figure out how to trigger a RPC server, because it is already running.

Also, what about trying to open a handle to a process with a higher PPL than that of RpcView (even running as admin)? It wouldn’t be possible to open a handle to the process to analyze the runtime memory.

An advantage to reading some of the RPC data structures at runtime is that it has access to interface registration flags and authentication info for the server. Some of the RPC specific information is not directly available in the compiled binary, IDL, or ACF file. When the server registers an interface, it passes a flag to the Windows APIRpcServerRegisterIf2. The registration flags configure the RPC server at runtime.

rpcview-auth-regRpcView AuthInfo (top) Registration Flags (bottom) GUI

No other tool analyzed provides this information. This is helpful when trying to understand the connection requirements for a client binding to the server.

NtObjectManager

ntobjectmanager-githubNtObjectManager

NtObjectManager is the PowerShell module that exposes several RPC discovery methods (such as Get-RpcServer) (backed by its supporting .NET managed library NtApiDotNet). NtObjectManager goes about discovering RPC servers (and even clients) a bit differently. It does not look directly at running processes, but rather it will parse a list of PE files that you feed it to attempt to discover if the binary is an RPC server. It will then load each of those PE files and parse NDR data structures (think _RPC_SERVER_T and _RPC_INTERFACE_T from RpcView) found with the data section of RPC compiled binaries. From the data structures the RPC interfaces, endpoints, and procedures can also be discovered as in RpcView.

Code

Source: https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools

The starting point for RPC server enumeration is the Get-RpcServer powershell cmdlet that takes a list of binaries as input to parse as RPC server objects.

1
2
3
4
# Find all servers in SYSTEM32. 
PS C:\Users\user> $rpc = ls "C:\Windows\system32\*" -Include "*.dll","*.exe" `
  | Get-RpcServer

For NT Object Manager:

This command does a heuristic search in a DLL’s data sections for RPC servers and clients and parses the NDR structures. You can use this to generate RPC server definitions similar to RpcView (but in my own weird C# pseudo-code syntax) but for this scenario we only care about the clients.- Finding Windows RPC Client Implementations Through…

For each binary it calls out to the static method [NtApiDotNet.Win32.RpcServer]::ParsePeFile within NtApiDotNet. This is where all the magic happens, or at least where it begins. You might want to grab a coffee before getting into this next section, or skip it entirely and check out the summary. Otherwise, brace yourself.

The code path for RPC discovery via [NtApiDotNet.Win32.RpcServer]::ParsePeFile can be summarized as follows:

  1. LoadLibrary - The first thing is a call to LoadLibrary on the supplied binary path (such as C:\Windows\System32\lsass.exe). This call is essentially a .NET wrapper (or interop) around the Win32 Native LoadLibraryEx that returns a SafeLoadLibraryHandle class type (which is a class that holds a reference to the loaded module with several useful helper methods). An interesting (and likely necessary for stability) flag passed to LoadLibraryEx here is DONT_RESOLVE_DLL_REFERENCES, which will prevent the DllMain from being called and further dependencies being loaded. Interestingly, MSDN tells us not to use it, but it seems like it would serve the purpose of just loading the binary to get a handle to the module for data parsing no?
  2. GetImageSections - After the SafeLoadLibraryHandle type is created, GetImageSections is called to parse each section of the loaded module. This is done via a call to the private method SetupValues responsible for building the list of ImageSections assigned the private SafeLoadLibraryHandle class member <List> _image_sections here.
  3. FindRpcServerInterfaces - Each image section (.text,.data,etc.) is then passed into FindRpcServerInterfaces. This call has a similar goal as the GetRpcServerAddressInProcess call in RpcView, that of searching for the root of RPC_SERVER_T data structure (pointed to at runtime by the GlobalRpcServer symbol). For FindRpcServerInterfaces the data structure is RPC_SERVER_INTERFACE and it discovers the data quite differently. Rather than looking for the GlobalRpcServer symbol within the rpcrt4.dll .data section at runtime, it discovers the data structure within the read-only data section (.rdata) memory section of the image it just loaded in step 1. It seems as though the base RPC data structure leading to interface, MIDL information, and all things RPC are available within the of the image on disk as well (AuthInfo excepted). FindRpcServerInterfaces searches through all loaded image memory sections for the DCE_TransferSyntax GUID. Once it is found, it returns a IEnumerable<RpcOffset> used in the next step.

    Some other key differences from RpcView are that FindRpcServerInterfaces:

    • searches for an alternative NDR64_TransferSyntax GUID 71710533-BEBA-4937-8319-B5DBEF9CCC36. RpcView has code that will identify it in the InterfacesWidget::AddInterfaces enhancing the GUI, but will not find it when identifying RPC servers. I wonder if any interfaces come up with the NDR64 identifier?? I guess even if the NDR64 syntax ID was found, it can’t be fully parsed.
    • doesn’t seem to be limited to the .data section, it looks at all sections (not sure if it matters though?). It’s only requirement is that the image section be readable.
  4. SymbolResolver.Create - At this point, a new instance of a SymboleResolver is created to leverage the pdb symbols for the RPC server (if available). I won’t explain any details of this besides it depends on dbghelp.dll being installed and configured to work properly. It is used later to resolve or “fixup” procedure names once they are identified.
  5. ReadFromRpcServerInterface - Read out RPC_SERVER_INTERFACE from the .rdata image section in memory. For each of the found RpcOffsets from FindRpcServerInterfaces a NdrParser is instantiated and leveraged to parse out all of the interfaces and procedures. The NdrParser class calls ReadFromRpcServerInterface which in turn calls its private method ReadRpcServerInterface that performs the rest of the work. On success, it returns a NdrRpcServerInterface that is used to finally generate an RpcServer class to add to the list of RPC servers found.
  6. ReadRpcServerInterface - Now we are in the thick of it. Within this method several things happen that for brevity (and hopefully not for lack of understanding) I will summarize.
    • GetDispatchTable - This function reads the RPC_DISPATCH_TABLE struct referenced within RPC_SERVER_INTERFACE needed to get a count of the number of procedures for the interface (just like RpcView).
    • ReadProcs - This method resolves all of the procedures relative to the identified interface and the RPC_DISPATCH_TABLE that contains the info needed to find the procedure offsets. It is within this function that all the procedures get their names from the aforementioned SymbolResolver.
    • GetProtSeq Reads out all the endpoints pointers and transforms each one into a new NdrProtocolSequenceEndpoint class that assigns the protocol and endpoint.
    • new NdrRpcServerInterface - This call takes all of the parsed information and wraps it into a nice data structure NdrRpcServerInterface
1
2
3
4
5
6
7
8
9
private static NdrRpcServerInterface ReadRpcServerInterface(IMemoryReader reader, RPC_SERVER_INTERFACE server_interface, 
    NdrTypeCache type_cache, ISymbolResolver symbol_resolver, NdrParserFlags parser_flags, IntPtr base_address)
{
    RPC_DISPATCH_TABLE dispatch_table = server_interface.GetDispatchTable(reader);
    var procs = ReadProcs(reader, server_interface.GetServerInfo(reader), 0, 
        dispatch_table.DispatchTableCount, type_cache, symbol_resolver, null, parser_flags, base_address);
    return new NdrRpcServerInterface(server_interface.InterfaceId, server_interface.TransferSyntax, procs,
        server_interface.GetProtSeq(reader).Select(s => new NdrProtocolSequenceEndpoint(s, reader)));
}

Thoughts

Well, one thought it that my head hurts. My venture into RPC enumeration tools has led me down paths of C++ and C# that I didn’t know I could travel. But as for NtObjectManager the tool and its RPC enumeration capability, I like it. It is trivial to discover all the RPC on a machine, both servers and RPC clients alike. One current downside is that it doesn’t seem to parse out any of the AuthInfo or registation flags as RpcView reports. On the other hand, it doesn’t miss an RPC servers or clients if they can be found on disk.

RPCEnum

rpcenumRpcEnum

This tool is well described by @_xpn_. It is based on an RpcView runtime discovery and enumeration strategy.

Code

Source: https://github.com/xpn/RpcEnum

The RPC::huntForGlobalRPCServer function mimics RpcView’s search for GlobalRpcServer. The project it much easier to understand, more straight to the point and not littered with callbacks to QT like RpcView. In hind sight, I should have started here to better understand RpcView. Some nice features include the ability to dump JSON files related to interfaces and their procedures as pointed out in the corresponding article, the ability to graph all the things and find links between RPC calls and Win32 calls within RPC server binaries.

RPCDump

rpcdumpRPCDump

This is a dynamic tool by 0xcsandker that relies on an endpoint being registered by an RPC server via RpcEpRegister. During an RPC server initialization, the server would have had to call this function to make it known to the endpoint mapper.

Code

Source: https://github.com/csandker/RPCDump

The code follows a path iterating through each endpoint in the RPC endpoint mapper.

Another option is to query the Endpoint Manager directly by calling RpcMgmtEpEltInqBegin and iterating over the interfaces via RpcMgmtEpEltInqNext. 0xcsandker

One cool thing about the tool is that is adds known endpoints to its analysis to enrich the information related to the outputs. . The known endpoints seem to be a collection known by the author from various sources.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

PS C:\Users\user\source\repos\RPCDump\x64\Debug> .\CPP-RPCDump.exe localhost
## Testing protseq.: ncacn_ip_tcp

IfId: 51a227ae-825b-41f2-b4a9-1ac9557a1018 version 1.0
Known Endpoint: (C:\Windows\System32\keyiso.dll).
Annotation: Ngc Pop Key Service
UUID: 00000000-0000-0000-0000-000000000000
Binding: ncacn_ip_tcp:localhost[49664]

IfId: 367abb81-9844-35f1-ad32-98f038001003 version 2.0
Known Endpoint: [MS-SCMR](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/19168537-40b5-4d7a-99e0-d77f0f5e0241).
Annotation:
UUID: 00000000-0000-0000-0000-000000000000
Binding: ncacn_ip_tcp:localhost[49671]

IfId: 650a7e26-eab8-5533-ce43-9c1dfce11511 version 1.0
Known Endpoint: (C:\Windows\System32\rascustom.dll).
Annotation: Vpn APIs
UUID: 00000000-0000-0000-0000-000000000000
Binding: ncacn_np:localhost[\\PIPE\\ROUTER]

IfId: 2f5f6521-cb55-1059-b446-00df0bce31db version 1.0
Known Endpoint: (C:\Windows\System32\unimdm.tsp.
Annotation: Unimodem LRPC Endpoint
UUID: 00000000-0000-0000-0000-000000000000
Binding: ncacn_np:localhost[\\pipe\\tapsrv]

IfId: 12345678-1234-abcd-ef00-0123456789ab version 1.0
Known Endpoint: [MS-RPRN](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/e8f9dad8-d114-41cc-9a52-fc927e908cf4).
Annotation:
UUID: 00000000-0000-0000-0000-000000000000
Binding: ncacn_ip_tcp:localhost[49669]

Impacket - rpcdump.py

impacketimpacket - rpcdump.py

Nothing but good things to say about the impacket python library allowing fine grained control of packets for various network protocols. When learning how to use NtObjectManager exercising Petitpotam, I used @topotam77’s PetitPotam.py python implementation leveraging impacket as a control to make sure I understood the expected behavior for Petitpotam. This led to my blog post From NtObjectManager to PetitPotam.

Code

https://github.com/SecureAuthCorp/impacket/blob/master/examples/rpcdump.py

The script, rpcdump.py is another tool that relies on the endpoint mapper having a registered endpoint from an RPC server. It also has the benefit of combining known endpoints with the RPC enumeration results to provide more information.

Ghidra

ghidraGhidra

Ah Ghidra, the most cost effective SRE tool on the market. It doesn’t have any native RPC discovery or enumeration functionality that I am aware of, but it is often used in combination with the aforementioned tools to provide insight into RPC through reverse engineering or used as a part of suite of tools used to map Windows RPC calls to native Win32 APIs. @_xpn_ also provided a Ghidra python script that would leverage the JSON output from RpcEnum to discover the RPC procedures in binaries and then recursively dump the functions called by each procedure.

WinRpcFunctions

winrpcfunctions-githubWinRpcFunctions

Another article Extending the Exploration and Analysis of Windows RPC Methods Calling other Functions with Ghidra, Jupyter Notebooks and Graphframes by @Cyb3rWard0g built on xpn’s work.

Code

Source: https://github.com/Cyb3rWard0g/WinRpcFunctions

The researcher put both RPC enumeration and xpn’s recursive function discovery into a single Ghidra script. The script could then map all RPC functions to Windows API calls within various RPC servers on disk. Using the script, RPC enumeration and discovery can be performed in Ghidra with it’s ability to analyze binaries and enriched with PDB symbols. Due to the fact that each binary needs to be analyzed in Ghidra for the script to function, it can take quite a bit of time. To narrow down analysis to only RPC servers, Cyb3rWard0g leverages NtObjectManager initially to identify and generate the list of RPC servers to avoid analyzing more binaries than necessary. For further details on this cool idea check out the blog post.

Summary

It turns out there are several available tools to discover RPC on a Windows machine. I know I have not exhausted the list, but perhaps I came close. Each tool and technique has their own advantages and disadvantages. Here is a best effort on a summary.

ToolTypeProsConsRequirementsLanguage
RpcViewDynamicGUI, AuthInfoMisses RPC clients and dormant serversRPC Server RunningC++
NtObjectManagerStaticPS + Speed + Filtering + ClientsNo AuthInfoRPC Server Path KnownPS,C#
RpcEnumDynamicJSON OutputMisses RPC clients and dormant serversRPC Server RunningC++
RpcDumpDynamicWell-Known EndpointsBlind to unregistered endpoints C++
Impacket - rpcdumpDynamicWell-Known Endpoints  Python
WinRpcFunctionsStaticGhidra onlySlowRPC Server Path KnownJava

When to use which tool?

The answer to this question is “it depends”.

RPC PurposeToolsReason
Trying to figure out client connection requirementsRpcViewAvailability of AuthInfo
Dynamically looking at running RPC serversRpcViewEasy to navigate across interfaces and click around
Looking for RPC clientsNtObjectManagerOny tool that finds them?
Trying to find all RPC servers on a machineNtObjectManagerRPC server doesn’t have to be running
Testing an RPC interfaceNtObjectManagerBuilds RPC clients on the fly
Developing the next PetitPotamimpacketlow level control of RPC transport protocols

That’s all for now. The next post will be an in depth walkthrough of using NtObjectManager to discover the well known PetotPotam.

Please reach out @clearbluejar with questions or comments. Also appreciate any feedback or corrections you might have for the post.


Cover photo by Todd Quackenbush on Unsplash

This post is licensed under CC BY 4.0 by the author.