Skip to content

Commit d6c8e87

Browse files
committed
Added GetAlpcServer to query server from an ALPC port.
1 parent b40b77b commit d6c8e87

6 files changed

Lines changed: 175 additions & 51 deletions

File tree

NtApiDotNet/Win32/Rpc/Transport/RpcAlpcClientTransport.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ private static AlpcPortAttributes CreatePortAttributes(SecurityQualityOfService
4949
};
5050
}
5151

52-
private static NtAlpcClient ConnectPort(string path, SecurityQualityOfService sqos)
52+
private static NtAlpcClient ConnectPort(string path, SecurityQualityOfService sqos, NtWaitTimeout timeout)
5353
{
5454
AlpcReceiveMessageAttributes in_attr = new AlpcReceiveMessageAttributes();
5555
return NtAlpcClient.Connect(path, null,
56-
CreatePortAttributes(sqos), AlpcMessageFlags.SyncRequest, null, null, null, in_attr, NtWaitTimeout.FromSeconds(5));
56+
CreatePortAttributes(sqos), AlpcMessageFlags.SyncRequest, null, null, null, in_attr, timeout);
5757
}
5858

5959
private static void CheckForFault(SafeHGlobalBuffer buffer, LRPC_MESSAGE_TYPE message_type)
@@ -229,22 +229,39 @@ private RpcClientResponse SendAndReceiveImmediate(int proc_num, Guid objuuid, by
229229
/// </summary>
230230
/// <param name="path">The path to connect. The format depends on the transport.</param>
231231
/// <param name="security_quality_of_service">The security quality of service for the connection.</param>
232-
public RpcAlpcClientTransport(string path, SecurityQualityOfService security_quality_of_service)
232+
public RpcAlpcClientTransport(string path, SecurityQualityOfService security_quality_of_service)
233+
: this(path, security_quality_of_service, NtWaitTimeout.FromSeconds(5))
234+
{
235+
}
236+
237+
/// <summary>
238+
/// Constructor.
239+
/// </summary>
240+
/// <param name="path">The path to connect. The format depends on the transport.</param>
241+
/// <param name="security_quality_of_service">The security quality of service for the connection.</param>
242+
/// <param name="timeout">Timeout for connection.</param>
243+
public RpcAlpcClientTransport(string path, SecurityQualityOfService security_quality_of_service, NtWaitTimeout timeout)
233244
{
234245
if (string.IsNullOrEmpty(path))
235246
{
236247
throw new ArgumentException("Must specify a path to connect to");
237248
}
238249

250+
if (timeout is null)
251+
{
252+
throw new ArgumentNullException(nameof(timeout));
253+
}
254+
239255
if (!path.StartsWith(@"\"))
240256
{
241257
path = $@"\RPC Control\{path}";
242258
}
243259

244-
_client = ConnectPort(path, security_quality_of_service);
260+
_client = ConnectPort(path, security_quality_of_service, timeout);
245261
_sqos = security_quality_of_service;
246262
Endpoint = path;
247263
}
264+
248265
#endregion
249266

250267
#region Public Methods

NtApiDotNet/Win32/RpcAlpcServer.cs

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using NtApiDotNet.Win32.Rpc.Transport;
1516
using System;
1617
using System.Collections.Generic;
1718
using System.Linq;
@@ -34,7 +35,7 @@ public class RpcAlpcServer
3435
/// <summary>
3536
/// List of known endpoints potentially accessible via this RPC server.
3637
/// </summary>
37-
public IEnumerable<RpcEndpoint> Endpoints { get; }
38+
public IReadOnlyCollection<RpcEndpoint> Endpoints { get; }
3839
/// <summary>
3940
/// The number of endpoints.
4041
/// </summary>
@@ -48,65 +49,93 @@ public class RpcAlpcServer
4849
/// </summary>
4950
public SecurityDescriptor SecurityDescriptor { get; }
5051

51-
private RpcAlpcServer(NtHandle handle, List<RpcEndpoint> endpoints)
52+
private RpcAlpcServer(int process_id, string name, SecurityDescriptor sd, string process_name, IEnumerable<RpcEndpoint> endpoints)
53+
{
54+
ProcessId = process_id;
55+
ProcessName = process_name;
56+
Name = name;
57+
SecurityDescriptor = sd;
58+
Endpoints = new List<RpcEndpoint>(endpoints).AsReadOnly();
59+
EndpointCount = Endpoints.Count;
60+
}
61+
62+
private RpcAlpcServer(NtHandle handle, string process_name, IEnumerable<RpcEndpoint> endpoints)
63+
: this(handle.ProcessId, handle.Name, handle.SecurityDescriptor, process_name, endpoints)
5264
{
53-
ProcessId = handle.ProcessId;
54-
using (var proc = NtProcess.Open(handle.ProcessId, ProcessAccessRights.QueryLimitedInformation, false))
55-
{
56-
if (proc.IsSuccess)
57-
{
58-
ProcessName = proc.Result.Name;
59-
}
60-
else
61-
{
62-
ProcessName = string.Empty;
63-
}
64-
}
65-
Name = handle.Name;
66-
SecurityDescriptor = handle.SecurityDescriptor;
67-
Endpoints = endpoints.AsReadOnly();
68-
EndpointCount = endpoints.Count;
6965
}
7066

7167
/// <summary>
7268
/// Get RPC ALPC servers for a specific process.
7369
/// </summary>
7470
/// <param name="process_id">The ID of the process.</param>
7571
/// <returns>The list of RPC ALPC servers.</returns>
72+
/// <remarks>If the process is suspended or frozen this call can hang.</remarks>
7673
public static IEnumerable<RpcAlpcServer> GetAlpcServers(int process_id)
7774
{
78-
return GetAlpcServersInternal(NtSystemInfo.GetHandles(process_id, true)).ToCached();
75+
using (var proc = NtProcess.Open(process_id, ProcessAccessRights.QueryInformation | ProcessAccessRights.DupHandle))
76+
{
77+
List<RpcAlpcServer> ret = new List<RpcAlpcServer>();
78+
GetAlpcServersInternal(proc.Duplicate(), NtObjectUtils.IsWindows7OrLess ? NtSystemInfo.GetHandles(process_id, true) :
79+
proc.GetHandles(true), ret);
80+
if (ret.Count == 0)
81+
Win32Error.RPC_S_SERVER_UNAVAILABLE.ToNtException();
82+
return ret.AsReadOnly();
83+
}
7984
}
8085

8186
/// <summary>
8287
/// Get a list of all RPC ALPC servers.
8388
/// </summary>
84-
/// <remarks>This works by discovering any server ALPC ports owned by the process and querying for interfaces.</remarks>
89+
/// <remarks>This works by discovering any server ALPC ports owned by the process and querying for interfaces.
90+
/// This will ignore any frozen processes (primarily UWP) as they can't respond to the endpoint enumeration.</remarks>
8591
/// <returns>The list of RPC ALPC servers.</returns>
8692
public static IEnumerable<RpcAlpcServer> GetAlpcServers()
8793
{
88-
return GetAlpcServersInternal(NtSystemInfo.GetHandles()).ToCached();
94+
List<RpcAlpcServer> ret = new List<RpcAlpcServer>();
95+
96+
foreach (var group in NtSystemInfo.GetHandles().GroupBy(h => h.ProcessId))
97+
{
98+
using (var proc = NtProcess.Open(group.Key,
99+
ProcessAccessRights.QueryLimitedInformation | ProcessAccessRights.DupHandle, false))
100+
{
101+
if (!proc.IsSuccess)
102+
continue;
103+
if (proc.Result.Frozen)
104+
continue;
105+
GetAlpcServersInternal(proc.Result, group, ret);
106+
}
107+
}
108+
return ret.AsReadOnly();
109+
}
110+
111+
/// <summary>
112+
/// Get the RPC ALPC server for an ALPC port object path.
113+
/// </summary>
114+
/// <param name="path">The object manager path to the ALPC port.</param>
115+
/// <returns>The ALPC RPC server.</returns>
116+
/// <remarks>Needs an API which is only available from Windows 10 19H1.</remarks>
117+
[SupportedVersion(SupportedVersion.Windows10_19H1)]
118+
public static RpcAlpcServer GetAlpcServer(string path)
119+
{
120+
using (var transport = new RpcAlpcClientTransport(path, null))
121+
{
122+
var server = transport.ServerProcess;
123+
return new RpcAlpcServer(server.ProcessId, path, null, server.Name,
124+
RpcEndpointMapper.QueryEndpointsForAlpcPort(path));
125+
}
89126
}
90127

91-
private static IEnumerable<RpcAlpcServer> GetAlpcServersInternal(IEnumerable<NtHandle> handles)
128+
private static void GetAlpcServersInternal(NtProcess process, IEnumerable<NtHandle> handles, List<RpcAlpcServer> servers)
92129
{
93130
NtType alpc_type = NtType.GetTypeByType<NtAlpc>();
94-
131+
string process_name = process.Name;
95132
foreach (var handle in handles.Where(h => h.NtType == alpc_type
96-
&& h.Name.StartsWith(@"\RPC Control\", StringComparison.OrdinalIgnoreCase)))
133+
&& h.Name.IndexOf(@"\RPC Control\", StringComparison.OrdinalIgnoreCase) >= 0))
97134
{
98-
List<RpcEndpoint> endpoints = new List<RpcEndpoint>();
99-
try
100-
{
101-
endpoints.AddRange(RpcEndpointMapper.QueryEndpointsForAlpcPort(handle.Name));
102-
}
103-
catch (SafeWin32Exception)
104-
{
105-
}
106-
107-
if (endpoints.Count > 0)
135+
var eps = RpcEndpointMapper.QueryEndpointsForAlpcPort(handle.Name, false);
136+
if (eps.IsSuccess)
108137
{
109-
yield return new RpcAlpcServer(handle, endpoints);
138+
servers.Add(new RpcAlpcServer(handle, process_name, eps.Result));
110139
}
111140
}
112141
}

NtApiDotNet/Win32/RpcEndpointMapper.cs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,24 +118,25 @@ private static RpcEndpoint CreateEndpoint(SafeRpcBindingHandle binding_handle, R
118118

119119
private const string RPC_CONTROL_PATH = @"\RPC Control\";
120120

121-
private static IEnumerable<RpcEndpoint> QueryEndpointsForBinding(SafeRpcBindingHandle binding_handle)
121+
private static NtResult<RpcEndpoint[]> QueryEndpointsForBinding(SafeRpcBindingHandle binding_handle, bool throw_on_error)
122122
{
123123
using (binding_handle)
124124
{
125-
int status = Win32NativeMethods.RpcMgmtInqIfIds(binding_handle, out SafeRpcIfIdVectorHandle if_id_vector);
125+
Win32Error status = Win32NativeMethods.RpcMgmtInqIfIds(binding_handle, out SafeRpcIfIdVectorHandle if_id_vector);
126126
// If the RPC server doesn't exist return an empty list.
127-
if (status == 1722)
127+
if (status == Win32Error.RPC_S_SERVER_UNAVAILABLE)
128128
{
129-
return new RpcEndpoint[0];
129+
return new RpcEndpoint[0].CreateResult();
130130
}
131-
if (status != 0)
131+
if (status != Win32Error.SUCCESS)
132132
{
133-
throw new SafeWin32Exception(status);
133+
return status.CreateResultFromDosError<RpcEndpoint[]>(throw_on_error);
134134
}
135135

136136
using (if_id_vector)
137137
{
138-
return if_id_vector.GetIfIds().Select(if_id => CreateEndpoint(binding_handle, if_id)).ToArray();
138+
return if_id_vector.GetIfIds().Select(if_id =>
139+
CreateEndpoint(binding_handle, if_id)).ToArray().CreateResult();
139140
}
140141
}
141142
}
@@ -280,15 +281,38 @@ public static IEnumerable<RpcEndpoint> QueryAlpcEndpoints(NdrRpcServerInterface
280281
/// Query for endpoints for a RPC binding.
281282
/// </summary>
282283
/// <param name="alpc_port">The ALPC port to query. Can be a full path as long as it contains \RPC Control\ somewhere.</param>
284+
/// <param name="throw_on_error">True to throw on error.</param>
283285
/// <returns>The list of endpoints on the RPC binding.</returns>
284-
public static IEnumerable<RpcEndpoint> QueryEndpointsForAlpcPort(string alpc_port)
286+
public static NtResult<IEnumerable<RpcEndpoint>> QueryEndpointsForAlpcPort(string alpc_port, bool throw_on_error)
285287
{
286288
int index = alpc_port.IndexOf(@"\RPC Control\", StringComparison.OrdinalIgnoreCase);
287289
if (index >= 0)
288290
{
289291
alpc_port = alpc_port.Substring(0, index) + RPC_CONTROL_PATH + alpc_port.Substring(index + RPC_CONTROL_PATH.Length);
290292
}
291-
return QueryEndpointsForBinding(SafeRpcBindingHandle.Create(null, "ncalrpc", null, alpc_port, null));
293+
return QueryEndpointsForBinding(SafeRpcBindingHandle.Create(null, "ncalrpc",
294+
null, alpc_port, null), throw_on_error).Cast<IEnumerable<RpcEndpoint>>();
295+
}
296+
297+
/// <summary>
298+
/// Query for endpoints for a RPC binding.
299+
/// </summary>
300+
/// <param name="alpc_port">The ALPC port to query. Can be a full path as long as it contains \RPC Control\ somewhere.</param>
301+
/// <returns>The list of endpoints on the RPC binding.</returns>
302+
public static IEnumerable<RpcEndpoint> QueryEndpointsForAlpcPort(string alpc_port)
303+
{
304+
return QueryEndpointsForAlpcPort(alpc_port, true).Result;
305+
}
306+
307+
/// <summary>
308+
/// Query for endpoints for a RPC binding.
309+
/// </summary>
310+
/// <param name="string_binding">The RPC binding to query, e.g. ncalrpc:[PORT]</param>
311+
/// <param name="throw_on_error">True to throw on error.</param>
312+
/// <returns>The list of endpoints on the RPC binding.</returns>
313+
public static NtResult<IEnumerable<RpcEndpoint>> QueryEndpointsForBinding(string string_binding, bool throw_on_error)
314+
{
315+
return QueryEndpointsForBinding(SafeRpcBindingHandle.Create(string_binding), throw_on_error).Cast<IEnumerable<RpcEndpoint>>();
292316
}
293317

294318
/// <summary>
@@ -298,7 +322,7 @@ public static IEnumerable<RpcEndpoint> QueryEndpointsForAlpcPort(string alpc_por
298322
/// <returns>The list of endpoints on the RPC binding.</returns>
299323
public static IEnumerable<RpcEndpoint> QueryEndpointsForBinding(string string_binding)
300324
{
301-
return QueryEndpointsForBinding(SafeRpcBindingHandle.Create(string_binding));
325+
return QueryEndpointsForBinding(string_binding, true).Result;
302326
}
303327

304328
/// <summary>

NtApiDotNet/Win32/Win32NativeMethods.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ ref IntPtr InquiryContext
623623
);
624624

625625
[DllImport("rpcrt4.dll", CharSet = CharSet.Unicode)]
626-
internal static extern int RpcMgmtInqIfIds(
626+
internal static extern Win32Error RpcMgmtInqIfIds(
627627
SafeRpcBindingHandle Binding,
628628
out SafeRpcIfIdVectorHandle IfIdVector
629629
);

NtObjectManager/Formatters.ps1xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5554,5 +5554,49 @@
55545554
</TableRowEntries>
55555555
</TableControl>
55565556
</View>
5557+
<View>
5558+
<Name>RpcAlpcServer</Name>
5559+
<ViewSelectedBy>
5560+
<TypeName>NtApiDotNet.Win32.RpcAlpcServer</TypeName>
5561+
</ViewSelectedBy>
5562+
<TableControl>
5563+
<TableHeaders>
5564+
<TableColumnHeader>
5565+
<Label>PID</Label>
5566+
<Alignment>left</Alignment>
5567+
</TableColumnHeader>
5568+
<TableColumnHeader>
5569+
<Label>ProcessName</Label>
5570+
<Alignment>left</Alignment>
5571+
</TableColumnHeader>
5572+
<TableColumnHeader>
5573+
<Label>Endpoints</Label>
5574+
<Alignment>left</Alignment>
5575+
</TableColumnHeader>
5576+
<TableColumnHeader>
5577+
<Label>Name</Label>
5578+
<Alignment>left</Alignment>
5579+
</TableColumnHeader>
5580+
</TableHeaders>
5581+
<TableRowEntries>
5582+
<TableRowEntry>
5583+
<TableColumnItems>
5584+
<TableColumnItem>
5585+
<PropertyName>ProcessId</PropertyName>
5586+
</TableColumnItem>
5587+
<TableColumnItem>
5588+
<PropertyName>ProcessName</PropertyName>
5589+
</TableColumnItem>
5590+
<TableColumnItem>
5591+
<PropertyName>EndpointCount</PropertyName>
5592+
</TableColumnItem>
5593+
<TableColumnItem>
5594+
<PropertyName>Name</PropertyName>
5595+
</TableColumnItem>
5596+
</TableColumnItems>
5597+
</TableRowEntry>
5598+
</TableRowEntries>
5599+
</TableControl>
5600+
</View>
55575601
</ViewDefinitions>
55585602
</Configuration>

NtObjectManager/RpcFunctions.ps1

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ Gets a list of ALPC RPC servers.
415415
This cmdlet gets a list of ALPC RPC servers. This relies on being able to access the list of ALPC ports in side a process so might need elevated privileges.
416416
.PARAMETER ProcessId
417417
The ID of a process to query for ALPC servers.
418+
.PARAMETER AlpcPort
419+
The path to the ALPC port to query.
418420
.INPUTS
419421
None
420422
.OUTPUTS
@@ -425,12 +427,17 @@ Get all ALPC RPC servers.
425427
.EXAMPLE
426428
Get-RpcAlpcServer -ProcessId 1234
427429
Get all ALPC RPC servers in process ID 1234.
430+
.EXAMPLE
431+
Get-RpcAlpcServer -AlpcPort srvsvc
432+
Get the ALPC RPC servers for the srvsvc ALPC port. Needs Windows 10 19H1 and above to work.
428433
#>
429434
function Get-RpcAlpcServer {
430435
[CmdletBinding(DefaultParameterSetName = "All")]
431436
Param(
432437
[parameter(Mandatory, Position = 0, ParameterSetName = "FromProcessId")]
433-
[int]$ProcessId
438+
[int]$ProcessId,
439+
[parameter(Mandatory, ParameterSetName = "FromAlpc")]
440+
[string]$AlpcPort
434441
)
435442

436443
Set-NtTokenPrivilege SeDebugPrivilege | Out-Null
@@ -441,6 +448,9 @@ function Get-RpcAlpcServer {
441448
"FromProcessId" {
442449
[NtApiDotNet.Win32.RpcAlpcServer]::GetAlpcServers($ProcessId)
443450
}
451+
"FromAlpc" {
452+
[NtApiDotNet.Win32.RpcAlpcServer]::GetAlpcServer($AlpcPort)
453+
}
444454
}
445455
}
446456

0 commit comments

Comments
 (0)