奇妙的 RPC 接口以及查找这些接口的方法
编辑和内容补充:Tricia Howard
前言
在过去的几个月里,我们的团队在 MS-RPC 研究方面投入了大量精力,因为它十分复杂,而且相关研究相当不充分。您可能已经看过无数篇关于我们在这项工作中发现的漏洞的帖子,内容涉及 srvsvc 和 Wininit.exe等。在整个研究过程中,我们积累了大量的数据和工具,而这些资源只有统合起来才有价值,我们应此推出了 RPC 工具包。
这个开源存储库包含您开始研究或进一步研究 RPC 所需要的任何资源:工具、文章、博客文章和会议讲座,以及 RPC 漏洞信息和漏洞攻击概念证明。这个存储库的建立是为了让防御者和研究人员更容易获得 RPC 知识;毕竟,协力同心才能全面提高安全性。对于任何了解过我们的工作、使用过我们的工具或分享过我们的一些工作的人,我们借此表示衷心的谢意!很高兴我们的工作能帮到您。
该工具包中的一个工具是我们的 RPC 接口分析器,它可以协助您这样的安全专家快速、轻松地寻找线索,了解可能有漏洞的接口。这篇博文是为了引导您了解它的预期目的以及发现的结果,并简要介绍 RPC 和它的一些安全机制,供那些可能还不熟悉的人参考。
什么是 RPC?它的安全机制有哪些?
远程过程调用 (RPC) 是进程间通信 (IPC) 的一种形式,它允许客户端调用一个由 RPC 服务器公开的过程。客户端可以像执行普通过程调用一样来调用此函数,几乎不需要为远程交互的细节编码。服务器可以托管在同一台机器的不同进程中,也可以托管在远程机器上。
Windows 将 MS-RPC(Microsoft 的 RPC 实施)广泛用于许多不同的服务,比如任务调度、服务创建、打印机和共享设置,以及远程存储的加密数据的管理。这种广泛的使用范围和 RPC 作为媒介的远程性质,正是我们今天讨论这个主题的原因,也是我们投入这么多资源研究 RPC 的原因。它拥有大量的功能,因此,从安全的角度来看,它吸引了很多人的注意。
在下面的部分中,我们将深入探讨 RPC 安全回调 ,以及您如何使用自动化来分析它们,并从中探究新的安全性和漏洞研究线索。
什么是 RPC 安全回调?它的工作原理是怎样的?
简而言之,安全回调是确保 RPC 接口安全的几种方法之一。它是一个由 RPC 服务器开发人员实施的自定义回调。它的逻辑由开发人员决定,它允许开发人员强制执行基于用户的访问控制、身份验证、传输类型,或阻止对服务器公开的特定函数的访问。
最终,回调返回 RPC_S_OK 以允许客户端与服务器通信,或者返回 RPC 错误代码之一,比如 RPC_S_ACCESS_DENIED,以拒绝通信。
接受或拒绝客户端请求的决策通常依赖于一个属性(或不止一个),我们将在下面探讨这些属性。
协议序列
客户端可以通过几种传输方式与服务器通信:TCP、命名管道、ALPC 等。安全回调可以检查此属性,以便只过滤本地连接请求、只过滤远程请求和 TCP 通信,等等。
虽然文档中未提供协议序列的值,但我们将 RpcCallAttributes 结构中传递给安全回调的协议序列映射为以下值:
#define ncacn_ip_tcp 1
#define ncacn_np 2
#define ncalrpc 3
#define ncacn_http 4
其他协议序列(比如 ncacn_hvsocket)可以由安全回调通过解析字符串绑定 来进行测试。
身份验证级别
检查客户端的 身份验证级别 在安全回调中是很常见的做法。通过这种方式,服务器可以定义它预计从客户端那里获得的最小身份验证级别。
有多个身份验证级别,每个级别都是对前一个级别的扩展:
#define RPC_C_AUTHN_LEVEL_DEFAULT 0
#define RPC_C_AUTHN_LEVEL_NONE 1
#define RPC_C_AUTHN_LEVEL_CONNECT 2
#define RPC_C_AUTHN_LEVEL_CALL 3
#define RPC_C_AUTHN_LEVEL_PKT 4
#define RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5
#define RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6
例如,服务器预计身份验证级别为 RPC_C_AUTHN_LEVEL_PKT_PRIVACY ,以确保通信数据只对客户端和服务器可见,或者预计身份验证级别为 RPC_C_AUTHN_LEVEL_NONE ,以表示没有身份验证。
身份验证服务
身份验证 服务 指定了负责验证身份验证策略(由身份验证级别提供)的服务。
身份验证服务常量值是:
#define RPC_C_AUTHN_NONE 0
#define RPC_C_AUTHN_DCE_PRIVATE 1
#define RPC_C_AUTHN_DCE_PUBLIC 2
#define RPC_C_AUTHN_DEC_PUBLIC 4
#define RPC_C_AUTHN_GSS_NEGOTIATE 9
#define RPC_C_AUTHN_WINNT 10
#define RPC_C_AUTHN_GSS_SCHANNEL 14
#define RPC_C_AUTHN_GSS_KERBEROS 16
#define RPC_C_AUTHN_DPA 17
#define RPC_C_AUTHN_MSN 18
#define RPC_C_AUTHN_DIGEST 21
#define RPC_C_AUTHN_NEGO_EXTENDER 30
#define RPC_C_NETLOGON 68(文档中未提供相关信息)
#define RPC_C_AUTHN_MQ 100
#define RPC_C_AUTHN_DEFAULT 0xffffffff
例如,RPC_C_AUTHN_NONE将关闭身份验证,而 RPC_C_AUTHN_WINNT 将使用 NTLM 协议。
身份验证服务的完整列表和它们的值可以在这个 GitHub 页面上找到。
NULL 会话
NULL 会话是一个匿名连接。在这种情况下,客户端与服务器通信时没有身份验证;也就是说,不使用用户名或密码。
在大多数情况下,如果注册了安全回调,那么,除非在服务器注册时提供了 RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH 标志,否则 NULL 会话将被默认阻止(在 此处了解其他情况)。安全回调也可以检查 NULL 会话。
通过阻止对这些会话的访问,安全回调可以保护 RPC 接口,避免未经身份验证的用户访问。
操作号 (opnum)
操作号代表客户端请求运行的接口函数。更准确地说,操作号是 RPC 服务器函数表的索引。
通过检查操作号值,服务器可以限制或阻止客户端对特定函数的访问,例如为远程客户端处理敏感数据的函数、为用户模式客户端访问内核内存和/或功能的函数,等等。
Akamai 安全研究团队发表了一篇博文, 介绍了一个有趣的安全回调示例, 那个回调依靠的就是此检查。
其他安全回调检查可以是:
调用者来源 ——检查调用者是来自用户模式还是内核模式
客户端 PID ——只允许特定的进程
字符串绑定 ——验证 RPC 连接属性,比如协议序列、网络地址、端点信息等
仿冒 ——确保服务器可以在客户端的安全上下文中运行代码
还有一些复杂的检查。例如,在 lsasrv.dll 的 LsaCapRpcIfCallbackFn 回调中,如果身份验证服务是 netlogon,身份验证级别应该小于 RPC_C_AUTHN_LEVEL_PKT_INTEGRITY。
RPC 接口分析器——实操导览
RPC 接口分析器是一个用于研究 RPC 接口的自动化工具。它允许研究人员从两个不同的来源(接口定义 (IDL) 文件或 PE 文件)查找并分析 RPC 接口。
IDL 文件
IDL 文件 是定义 RPC 接口及其函数的文件。通过分析公开的 IDL 文件,我们可以得到关于 RPC 接口和其服务器公开的函数的信息,以及它们的参数和返回值类型。借助这些信息,研究人员可以寻找可能有漏洞的函数;例如 PetitPotam等漏洞中接收路径参数的函数。
为了运行我们的 IDL 分析器,请运行下列命令:
1.使用 idl_scraper 脚本,从 Microsoft 网站将所有 IDL 文件下载到您的机器上:
idl_scraper.py [-h] [-o OUTPUT] [-p PROTOCOL]
2.然后运行 idl_parser,以便分析这些 IDL 文件:
idl_parser.py [-h] [-r] input_path [output_path]
您将获得的输出是一个 CSV 文件,包含 RPC 接口名称、通用唯一标识符 (UUID)、公开的函数名称和签名。
PE 文件
分析 IDL 文件可能很有用,但我们只能访问公开的 IDL 文件,所以可能会遗漏一些 RPC 接口。另一种方法是在本地文件系统中寻找 RPC 接口,也就是在 PE 文件(.exe 或 .dll 文件)中寻找。我们发现这种方法比检查实时进程更好,因为这样一来,我们就不会遗漏那些并非实时的 RPC 服务器,或在受保护进程中运行的 RPC 服务器。
RPC PE 分析器 将寻找由 RpcServerRegisterIf 函数(及其变体)注册的 RPC 接口,并对传递给它的参数执行分析(假设提供了反汇编程序)。在没有它的情况下运行时,在默认模式下,分析器会使用正则表达式查找 RPC 接口。此 讲座 详细介绍了搜索过程。
为了在默认模式下使用 RPC PE 分析器,请运行以下命令:
pe_rpc_scraper.py <scrape_path> <output_path>
此命令会给您提供基本输出,包括接口的 UUID、它的角色(客户端/服务器)以及它的函数名称和地址。
如果您想获得更详细的信息,可以向脚本提供一个反汇编程序及其路径(如果不是默认设置):
pe_rpc_scraper.py [-d {idapro,radare}] [-P DISASSEMBLER_PATH] <scrape_path> <output_path>
使用反汇编程序选项将同时添加接口注册信息,包括:
在服务器注册中提供的标志
接口的安全回调名称和地址
RPC 服务器的安全描述符(如有)
是否 为安全回调 启用了全局缓存
与这篇博文同时发布的新功能还支持分析安全回调本身。
使用示例
假设我们想扫描我们机器上所有可用的 RPC 接口。我们可以运行 RPC PE 分析器,提供一个 C:\Windows\System32 的副本作为我们的 scrape_path,并查看输出。
pe_rpc_scraper.py -d idapro “C:\Users\User\Documents\System32_Copy”
输出为 JSON 格式,因此,很容易对其进行迭代并寻找具体信息。例如:
查找 DLL 文件中的所有 RPC 客户端/服务器
查找一台机器上的所有 RPC 客户端/服务器
查找某个特定 RPC 接口的客户端
查找某个 RPC 服务器的 RPC 安全回调
查找哪些 RPC 接口使用了接口级缓存(阅读这篇 博文 ,详细了解这种类型的缓存为什么会存在问题)
这些用途只是该输出的众多用途中的一部分。我们很乐意了解更多的使用方法和想法。
新功能——安全回调信息
RpcCallAttributes 结构
RPC_CALL_ATTRIBUTES 是一个结构,其中有关于客户端请求的数据。服务器端的接口安全回调可以通过调用 RpcServerInqCallAttributes 函数获得这些信息。
typedef struct tagRPC_CALL_ATTRIBUTES_V3_W
{
unsigned int Version;
unsigned long Flags;
unsigned long ServerPrincipalNameBufferLength;
unsigned short *ServerPrincipalName;
unsigned long ClientPrincipalNameBufferLength;
unsigned short *ClientPrincipalName;
unsigned long AuthenticationLevel;
unsigned long AuthenticationService;
BOOL NullSession;
BOOL KernelModeCaller;
unsigned long ProtocolSequence;
RpcCallClientLocality IsClientLocal;
HANDLE ClientPID;
unsigned long CallStatus;
RpcCallType CallType;
RPC_CALL_LOCAL_ADDRESS_V1 *CallLocalAddress;
unsigned short OpNum;
UUID InterfaceUuid;
unsigned long ClientIdentifierBufferLength;
unsigned char *ClientIdentifier;
} RPC_CALL_ATTRIBUTES_V3_W;
我们已经提到了安全回调运行的测试。它们可以单独查询其中的一些值(例如, RpcStringBindingParseW 用于接收协议序列, RpcBindingInqAuthClient 用于身份验证信息,等等),也可以使用这个包含所有信息的结构,此时仅需要一次函数调用。事实上,在我们分析的大多数安全回调中,它们都调用 RpcServerInqCallAttributes 并使用 RPC_CALL_ATTRIBUTES 结构来同时查询所有属性。所以这个结构非常值得关注,可以帮您了解安全回调的逻辑。
该结构目前有三个不同的版本——1、2 和 3,每个版本都在其之前版本的基础上进行了扩展,并且有 ANSI 和 Unicode 版本。您可以在这个 GitHub 页面上找到不同的版本和它们的成员。
安全回调信息
我们的 RPC 工具包新添加了 安全回调信息,它是 RPC PE 分析器的一部分。可以通过它来了解安全回调在批准/拒绝客户端请求之前所做的检查和验证。
通过分析 RPC 接口的安全回调(特别是它对 RPC_CALL_ATTRIBUTES 结构的访问),可以对该接口有所了解。这样,如果您想过滤使用(或不使用)特定 身份验证 协议的 RPC 接口,就可以查找检查身份验证服务属性的安全回调。您也可以查询那些在允许客户端请求之前(以及在其服务器注册标志允许 NULL 会话 之前),不检查 NULL 会话的 RPC 接口,以发现可能有漏洞的 RPC 接口。
工作原理
对于每个安全回调,分析器将:
确定正在使用的 RPC_CALL_ATTRIBUTES 结构版本,并在 IDA 的本地类型中定义相关结构
查找 RpcCallAttribute 本地变量并应用 RPC_CALL_ATTRIBUTES 结构作为其类型
解析安全回调使用该结构执行的检查,并输出正在测试的成员、与之比较的值以及使用的运算符(== / != / > / < / 等)。
我如何使用它?
使用方法并没有改变:每次您运行带有 IDA 反汇编程序标志的 RPC PE 分析器时,每个 RPC 接口的输出现在都会包括其安全回调信息——它是否访问 RpcCallAttributes 结构成员,以及它测试什么。
注意:此新增功能目前只适用于 IDA,运行带有 Radare 选项的分析器时,不会包括安全回调信息。
在您获得输出后,可用它来查找可能有漏洞的 RPC 接口,并根据您的需求进行过滤。一些示例包括:
获取一个只使用 RPC_C_AUTHN_LEVEL_PKT_PRIVACY 身份验证级别的 RPC 接口
获取预计进行本地连接的 RPC 接口
获取预计只接受内核模式调用者的 RPC 接口
获取检查操作号的 RPC 接口(在存在缓存漏洞的情况下)
总结
很明显,RPC 是一个成熟的研究领域,特别是考虑到它有着如此长久的发展历程,并集成到如此之多的关键进程中。近一年来,我们一直在我们的存储库中汇集资源,但要充分认识 RPC 的潜在威胁,还有很多东西需要涉猎。它的研究仍然相当不充分,尽管它最近获得了更多的关注,但仍有许多信息尚待发现,以便了解攻击者可能滥用它的无数方式。
RPC 的内在性质要求我们对每个潜在的危险角度进行研究。希望我们的持续研究和存储库中的工具能够给您带来更多启示,帮您进一步了解这种媒介,也希望这些资源能激发其他研究人员深入探究。无论您是负责保护 RPC 的防御者,还是寻找下一个目标的研究人员,RPC 都有很多知识和见解值得挖掘。