使用NtObjectManager发现运行中的RPC服务器信息
在进行安全研究时,我经常使用我的NtObjectManager PowerShell模块来发现和调用Windows上的RPC服务器。通常,我会使用Get-RpcServer
命令,传递DLL或EXE文件的名称来提取嵌入的RPC服务器。然后,我可以使用返回的服务器对象创建客户端来访问服务器并调用其方法。blueclearjar最近写了一篇很好的博客文章,介绍了其中一些工作原理。
使用Get-RpcServer
只能获得可能运行的RPC服务器列表,而不是它们是否正在运行以及运行在哪个进程中。这就是RpcView做得更好的地方,因为它解析进程内存中的RPC结构来查找注册的内容和位置。不幸的是,这是我尚未在NtObjectManager中实现的功能。
然而,事实证明,有多种方法可以获取运行中的RPC服务器信息,这些方法由操作系统和RPC运行时提供,我们可以使用它们来获得或多或少完整的运行服务器列表。通过最近对模块的一些更新,我暴露了所有我知道的方法。让我们来看看你可以如何拼凑这些信息。
注意:一些PowerShell代码示例需要最新版本的NtObjectManager模块。由于各种原因,我没有更新PS库的版本,所以请从github获取源代码并自行构建。
RPC端点映射器
如果你幸运的话,这是找出特定RPC服务器是否运行的最简单方法。当RPC服务器启动时,服务可以使用RpcEpRegister
函数向运行在RPCSS中的RPC端点映射器服务注册RPC接口,指定接口UUID和版本以及绑定信息。这会注册服务器当前监听的所有RPC端点,以RPC接口为键。
你可以使用RpcMgmtEpEltInqBegin
和RpcMgmtEpEltInqNext
API查询端点表。我通过Get-RpcEndpoint
命令暴露了这个功能。运行不带参数的Get-RpcEndpoint
会返回本地端点映射器知道的所有接口,如下所示。
|
|
注意,除了接口UUID和版本之外,输出还显示了端点的绑定信息,如协议序列和端点。还有一个自由格式的注释字段,但服务器在调用RpcEpRegister
时可以将其设置为任何内容。
这些API还允许你指定托管端点映射器的远程服务器。你可以使用这个来查询远程服务器上运行的RPC服务器,假设防火墙没有阻止你。为此,你需要为SearchBinding
参数指定一个绑定字符串,如下所示。
|
|
RPC端点映射器的一个大问题是它只包含明确注册到端点的RPC接口。服务器可能包含更多可访问的接口,但由于它们没有注册,它们不会从端点映射器返回。注册通常只在服务器使用临时名称作为端点时使用,例如随机TCP端口或自动生成的ALPC名称。
优点:
- 运行简单的命令即可获得良好的运行RPC服务器列表。
- 可以针对远程服务器运行,以查找可远程访问的RPC服务器。
缺点:
- 只返回有意注册的RPC服务器。
- 不直接给出托管进程,尽管可选的注释可能会给你一些线索。
- 不提供关于RPC服务器功能的任何信息,你需要找到它托管在哪个可执行文件中,并使用
Get-RpcServer
解析它。
服务可执行文件
如果你提取的RPC服务器在注册的系统服务可执行文件中,那么模块将尝试通过查询SCM来找出对应的服务。Get-RpcServer
命令的默认输出将显示为“Service”列,如下所示。
|
|
输出还显示appinfo.dll可执行文件是Appinfo服务的实现,这是UAC服务的通用名称。注意,这里还显示了服务是否正在运行,但这只是为了方便。如果服务正在运行,你可以使用此信息通过查询服务PID来找到可能托管RPC服务器的进程。
|
|
输出还显示每个接口都有一个针对接口UUID和版本注册的端点。这是从端点映射器中提取的,再次只是为了方便。然而,如果你选择一个不是服务实现的可执行文件,结果就不那么有用了:
|
|
efslsaext.dll实现了EFS的一个实现,所有这些都在LSASS中托管。然而,它不是注册的服务,因此输出不显示任何服务名称。而且它也没有在端点映射器中注册,因此不显示任何端点,但它正在运行。
优点:
- 如果可执行文件是服务,它可以很好地告诉你谁在托管RPC服务器以及它们当前是否正在运行。
- 你可以获得RPC服务器接口信息以及该信息。
缺点:
- 如果可执行文件不是服务,它没有直接帮助。
- 如果RPC服务器未在端点映射器中注册,它不能确保RPC服务器正在运行。
- 即使服务正在运行,它也可能没有启用RPC服务器。
枚举进程模块
从任意可执行文件中提取RPC服务器在离线时很好,但如果你想知道当前正在运行的RPC服务器怎么办?这类似于RpcView的进程列表GUI,你可以查看一个进程并找到其中运行的所有服务。
事实证明,有一种非常明显的方法可以获取进程中可能运行的服务的列表:使用诸如EnumerateLoadedModules
之类的API枚举加载的DLL,然后在每个DLL上运行Get-RpcServer
以提取可能的服务。要使用这些API,你至少需要对目标进程具有读取访问权限,这意味着你确实需要是管理员,但这与RpcView的限制没有什么不同。
大问题在于,仅仅因为一个模块被加载,并不意味着RPC服务器正在运行。例如,WinHTTP DLL有一个内置的RPC服务器,只有在运行WinHTTP代理服务时才会加载,但DLL可能被任何使用API的进程加载。
为了简化事情,我通过Get-RpcServer
函数的ProcessId
参数暴露了这种方法。你还可以使用ServiceName
参数来查找服务PID,如果你对特定服务感兴趣。
|
|
优点:
- 你可以确定任意进程中可能运行的所有RPC服务器。
缺点:
- 如果RPC服务器未在端点映射器中注册,它不能确保RPC服务器正在运行。
- 你无法直接从受保护的进程中枚举模块列表,除了主可执行文件(有多种技巧可以做到,但这里不在范围内)。
友好地询问RPC端点
最后一种方法是友好地询问RPC端点它支持哪些RPC服务器。我们不需要深入挖掘进程的内部来实现这一点,我们只需要要查询的端点的绑定字符串,然后调用RpcMgmtInqIfIds
API。
这只会返回可从端点访问的RPC服务器的UUID和版本,而不是RPC服务器信息。但它会给你一个所有支持的RPC服务器的确切列表,事实上,它非常详细,它会给你进程正在监听的所有COM接口。要查询此列表,你只需要访问端点传输,而不是进程本身。
但是,如何获取端点呢?一种方法是,如果你有权访问进程,你可以通过获取进程的句柄列表来枚举其服务器ALPC端口,找到名称中带有\RPC Control\
前缀的端口,然后使用它来形成绑定字符串。这种方法通过Get-RpcEndpoint
的ProcessId
参数暴露。同样,它也支持ServiceName
参数来简化查询服务。
|
|
如果你无权访问进程,你可以反向操作,通过枚举潜在端点并查询每个端点。例如,你可以枚举\RPC Control
对象目录并查询每个目录。自从Windows 10 19H1以来,ALPC客户端现在可以查询服务器的PID,因此你不仅可以找到暴露的RPC服务器,还可以找到它们运行在哪个进程中。要从ALPC端口的名称查询,请使用带有Get-RpcEndpoint
的AlpcPort
参数。
|
|
优点:
- 你可以准确确定进程中运行的RPC服务器。
缺点:
- 你无法直接确定RPC服务器的功能,因为列表没有提供关于哪个模块托管它的信息。
结合方法
显然,没有一种方法是完美的。然而,通过将模块枚举方法与友好地询问端点结合起来,你可以在很大程度上接近RpcView的进程列表。例如,你可以首先通过枚举模块和解析RPC服务器来获取潜在接口的列表,然后通过直接查询端点来过滤该列表,只保留正在运行的接口。这还会给你RPC服务器运行的ALPC服务器端口列表,这样你就可以用手动构建的客户端直接连接到它。一个执行此操作的示例脚本在github上。
我们仍然缺少一些RpcView可以访问的关键信息,例如任何方法中的接口注册标志。尽管如此,希望这能给你几种方法来分析本地系统的RPC攻击面并确定你可以调用的端点。