When doing security research I regularly use my NtObjectManager PowerShell module to discover and call RPC servers on Windows. Typically I'll use the Get-RpcServer command, passing the name of a DLL or EXE file to extract the embedded RPC servers. I can then use the returned server objects to create a client to access the server and call its methods. A good blog post about how some of this works was written recently by blueclearjar.
Using Get-RpcServer only gives you a list of what RPC servers could possibly be running, not whether they are running and if so in what process. This is where the RpcView does better, as it parses a process' in-memory RPC structures to find what is registered and where. Unfortunately this is something that I'm yet to implement in NtObjectManager.
However, it turns out there's various ways to get the running RPC server information which are provided by OS and the RPC runtime which we can use to get a more or less complete list of running servers. I've exposed all the ones I know about with some recent updates to the module. Let's go through the various ways you can piece together this information.
NOTE some of the examples of PowerShell code will need a recent build of the NtObjectManager module. For various reasons I've not been updating the version of the PS gallery, so get the source code from github and build it yourself.
If you're lucky this is simplest way to find out if a particular RPC server is running. When an RPC server is started the service can register an RPC interface with the function RpcEpRegister specifying the interface UUID and version along with the binding information with the RPC endpoint mapper service running in RPCSS. This registers all current RPC endpoints the server is listening on keyed against the RPC interface.
You can query the endpoint table using the RpcMgmtEpEltInqBegin and RpcMgmtEpEltInqNext APIs. I expose this through the Get-RpcEndpoint command. Running Get-RpcEndpoint with no parameters returns all interfaces the local endpoint mapper knows about as shown below.
PS> Get-RpcEndpoint
UUID Version Protocol Endpoint Annotation
---- ------- -------- -------- ----------
51a227ae-825b-41f2-b4a9-1ac9557a1018 1.0 ncacn_ip_tcp 49669
0497b57d-2e66-424f-a0c6-157cd5d41700 1.0 ncalrpc LRPC-5f43... AppInfo
201ef99a-7fa0-444c-9399-19ba84f12a1a 1.0 ncalrpc LRPC-5f43... AppInfo
...
Note that in addition to the interface UUID and version the output shows the binding information for the endpoint, such as the protocol sequence and endpoint. There is also a free form annotation field, but that can be set to anything the server likes when it calls RpcEpRegister.
The APIs also allow you to specify a remote server hosting the endpoint mapper. You can use this to query what RPC servers are running on a remote server, assuming the firewall doesn't block you. To do this you'd need to specify a binding string for the SearchBinding parameter as shown.
PS> Get-RpcEndpoint -SearchBinding 'ncacn_ip_tcp:primarydc'
UUID Version Protocol Endpoint Annotation
---- ------- -------- -------- ----------
d95afe70-a6d5-4259-822e-2c84da1ddb0d 1.0 ncacn_ip_tcp 49664
5b821720-f63b-11d0-aad2-00c04fc324db 1.0 ncacn_ip_tcp 49688
650a7e26-eab8-5533-ce43-9c1dfce11511 1.0 ncacn_np \PIPE\ROUTER Vpn APIs
...
The big issue with the RPC endpoint mapper is it only contains RPC interfaces which were explicitly registered against an endpoint. The server could contain many more interfaces which could be accessible, but as they weren't registered they won't be returned from the endpoint mapper. Registration will typically only be used if the server is using an ephemeral name for the endpoint, such as a random TCP port or auto-generated ALPC name.
Pros:
Cons:
If the RPC servers you extract are in a registered system service executable then the module will try and work out what service that corresponds to by querying the SCM. The default output from the Get-RpcServer command will show this as the Service column shown below.
PS> Get-RpcServer C:\windows\system32\appinfo.dll
Name UUID Ver Procs EPs Service Running
---- ---- --- ----- --- ------- -------
appinfo.dll 0497b57d-2e66-424f-a0c6-157cd5d41700 1.0 7 1 Appinfo True
appinfo.dll 58e604e8-9adb-4d2e-a464-3b0683fb1480 1.0 1 1 Appinfo True
appinfo.dll fd7a0523-dc70-43dd-9b2e-9c5ed48225b1 1.0 1 1 Appinfo True
appinfo.dll 5f54ce7d-5b79-4175-8584-cb65313a0e98 1.0 1 1 Appinfo True
appinfo.dll 201ef99a-7fa0-444c-9399-19ba84f12a1a 1.0 7 1 Appinfo True
The output also shows the appinfo.dll executable is the implementation of the Appinfo service, which is the general name for the UAC service. Note here that is also shows whether the service is running, but that's just for convenience. You can use this information to find what process is likely to be hosting the RPC server by querying for the service PID if it's running.
PS> Get-Win32Service -Name Appinfo
Name Status ProcessId
---- ------ ---------
Appinfo Running 6020
The output also shows that each of the interfaces have an endpoint which is registered against the interface UUID and version. This is extracted from the endpoint mapper which makes it again only for convenience. However, if you pick an executable which isn't a service implementation the results are less useful:
PS> Get-RpcServer C:\windows\system32\efslsaext.dll
Name UUID Ver Procs EPs Service Running
---- ---- --- ----- --- ------- -------
efslsaext.dll c681d488-d850-11d0-... 1.0 21 0 False
The efslsaext.dll implements one of the EFS implementations, which are all hosted in LSASS. However, it's not a registered service so the output doesn't show any service name. And it's also not registered with the endpoint mapper so doesn't show any endpoints, but it is running.
Pros:
Cons:
Extracting the RPC servers from an arbitrary executable is fine offline, but what if you want to know what RPC servers are running right now? This is similar to RpcView's process list GUI, you can look at a process and find all all the services running within it.
It turns out there's a really obvious way of getting a list of the potential services running in a process, enumerate the loaded DLLs using an API such as EnumerateLoadedModules, and then run Get-RpcServer on each one to extract the potential services. To use the APIs you'd need to have at least read access to the target process, which means you'd really want to be an administrator, but that's no different to RpcView's limitations.
The big problem is just because a module is loaded it doesn't mean the RPC server is running. For example the WinHTTP DLL has a built-in RPC server which is only loaded when running the WinHTTP proxy service, but the DLL could be loaded in any process which uses the APIs.
To simplify things I expose this approach through the Get-RpcServer function with the ProcessId parameter. You can also use the ServiceName parameter to lookup a service PID if you're interested in a specific service.
PS> Get-RpcEndpoint -ServiceName Appinfo
Name UUID Ver Procs EPs Service Running ---- ---- --- ----- --- ------- -------
RPCRT4.dll afa8bd80-7d8a-11c9-bef4-... 1.0 5 0 False
combase.dll e1ac57d7-2eeb-4553-b980-... 0.0 0 0 False
combase.dll 00000143-0000-0000-c000-... 0.0 0 0 False
Pros:
Cons:
The final approach is just to ask an RPC endpoint nicely to tell you what RPC servers is supports. We don't need to go digging into the guts of a process to do this, all we need is the binding string for the endpoint we want to query and then call the RpcMgmtInqIfIds API.
This will only return the UUID and version of the RPC server that's accessible from the endpoint, not the RPC server information. But it will give you an exact list of all supported RPC servers, in fact it's so detailed it'll give you all the COM interfaces that the process is listening on as well. To query this list you only need to access to the endpoint transport, not the process itself.
How do you get the endpoints though? One approach is if you do have access to the process you can enumerate its server ALPC ports by getting a list of handles for the process, finding the ports with the \RPC Control\ prefix in their name and then using that to form the binding string. This approach is exposed through Get-RpcEndpoint's ProcessId parameter. Again it also supports a ServiceName parameter to simplify querying services.
PS> Get-RpcEndpoint -ServiceName AppInfo
UUID Version Protocol Endpoint
---- ------- -------- --------
0497b57d-2e66-... 1.0 ncalrpc \RPC Control\LRPC-0ee3...
201ef99a-7fa0-... 1.0 ncalrpc \RPC Control\LRPC-0ee3...
...
If you don't have access to the process you can do it in reverse by enumerating potential endpoints and querying each one. For example you could enumerate the \RPC Control object directory and query each one. Since Windows 10 19H1 ALPC clients can now query the server's PID, so you can not only find out the exposed RPC servers but also what process they're running in. To query from the name of an ALPC port use the AlpcPort parameter with Get-RpcEndpoint.
PS> Get-RpcEndpoint -AlpcPort LRPC-0ee3261d56342eb7ac
UUID Version Protocol Endpoint
---- ------- -------- --------
0497b57d-2e66-... 1.0 ncalrpc \RPC Control\LRPC-0ee3...
201ef99a-7fa0-... 1.0 ncalrpc \RPC Control\LRPC-0ee3...
...
Pros:
Cons:
Obviously no one approach is perfect. However, you can get most of the way towards RpcView process list by combining the module enumeration approach with asking the endpoint nicely. For example, you could first get a list of potential interfaces by enumerating the modules and parsing the RPC servers, then filter that list to only the ones which are running by querying the endpoint directly. This will also get you a list of the ALPC server ports that the RPC server is running on so you can directly connect to it with a manually built client. And example script for doing this is on github.
We are still missing some crucial information that RpcView can access such as the interface registration flags from any approach. Still, hopefully that gives you a few ways to approach analyzing the RPC attack surface of the local system and determining what endpoints you can call.