需要云计算吗? 即刻开始体验

Windows 中的 RPC防御功能

Stiv Kupchik

寫於

Stiv Kupchik

August 10, 2023

Stiv Kupchik

寫於

Stiv Kupchik

Stiv Kupchik 是 Akamai 安全研究员团队的负责人。他的研究项目主要涉及操作系统内核、漏洞研究和恶意软件分析等领域。他曾在 Black Hat、Hexacon 和 44CON 等会议上展示过他的研究成果。除了是一名网络安全专业人士外,Stiv 还拥有物理学学士学位。

在本博文中,我们将了解如何使用和解析来自 RPC ETW 提供程序的事件,以及如何扫描事件来查找潜在的恶意活动。

目录

本博文中的内容最初是在 BlackHat USA 2023 上呈现给公众的。

MS-RPC:解构协议以检测攻击

尽管在另一篇博文中 进行了介绍,我们仍将继续深入了解 MS-RPC。不过,这次我们的关注点不再是进攻方和寻找漏洞,而是探讨 Windows 的一些内置防御功能。我们将介绍如何利用它们大致了解 RPC 底层发生了什么,希望能借此检测出整个过程中的恶意活动。

MS-RPC 是 Windows 操作系统中不可或缺的一部分,因此这意味着很多横向移动路径都会经过它。诸如 PSExec远程计划任务、 DCSyncPetitPotam 之类的攻击都通过 MS-RPC 协议执行,并且在只利用传统网络元数据(源端口和 IP 地址及目标端口和 IP 地址;有时也包括进程信息)的情况下,难以区分良性网络流量和恶意网络流量。

Windows 事件跟踪 (ETW) 是 Windows 操作系统中的一种内置跟踪和监控机制,我们将利用它增强监测能力。ETW 为我们提供了一个信息宝库,其中包含专门用于 RPC 的信息,尤其适合用在与传统网络元数据进行比较时。

使用其 ETW 提供程序来跟踪 RPC 事件的概念不是什么新鲜事,而且已经有很多优秀的资源和工具可用于跟踪这些事件(我们在 参考资料 部分提供了其中一些资源和工具的链接;如果您知道任何其他资源和工具,请告知我们)。 大多数现有研究都专注于客户端事件,攻击者可以篡改这些事件或修改程序来彻底绕过日志记录。我们将目光转向使用攻击者无法触及的服务器端事件来检测攻击,并查看解析它们所需的独特注意事项。

在本博文中,我们将了解如何使用和解析来自 RPC ETW 提供程序的事件,以及如何扫描事件来查找潜在的恶意活动。通过使用 ETW 提供程序,我们还可以查看所请求的确切操作,这让我们能从更精细的角度来检测攻击。很快我们将确切地了解它的工作原理。

回顾:什么是 RPC?

RPC 是远程过程调用的缩写,它是一种进程间通信 (IPC) 形式。具体来说,此协议旨在允许不同进程之间进行远程函数调用。在我们的示例中,我们将专注于 Microsoft 的实现,即 MS-RPC

MS-RPC 设计为客户端-服务器模型。服务器使用接口定义语言 (IDL) 定义了它将公开的接口。在 IDL 内,我们有一个用于该接口的全局唯一标识符 (UUID), 以及该接口所公开的函数的函数定义(图 1)。

  [
    uuid(12345678-4000-2006-0000-20000000001a)
]

interface Test
{
    void Foo([in] int number,  [in] char *message);
    void Bar([out] int * result);
}

图 1:IDL 接口定义示例

设备之间通过一些传输协议进行通信,并且每个传输协议都有它自己的端点类型(图 2)。解释此内容的最佳方式是使用以下示例:RPC 通信可以通过 TCP 进行,在这种情况下,传输协议是 TCP,而端点是由端口号标识的 TCP 套接字。

传输协议

端点

TCP

指定的管道

UDP

ALPC

HTTP

Hyper-V 套接字

<端口号>

<管道名称>

<端口号>

<ALPC 端口>

<主机名>

<UUID>

图 2:常用传输协议及其各自的端点类型

必须注意的是,虽然函数在 IDL 文件中具有可读的名称,但它们在网络中以不同的方式进行标识。它们以整数而不是字符串名称进行标识,这些整数称为 opnum(操作号的简写)。通常,操作号按照函数在 IDL 接口定义中的出现顺序进行分配(因此,在图 1 的示例中,Foo 将使用 opnum 0 进行标识,而 Bar 将使用 opnum 1 进行标识)。稍后,在我们需要知道相关函数的 opnum 才能在将看到的数据中识别它们时,此信息非常重要。

有关 MS-RPC 更深入、详细的概述,您可以查看我们的 上一篇博文,也可以查看我们在 HexaConDEF CON 大会上关于该主题的会议演讲。

定义 ETW

Windows 事件跟踪 (ETW) 是在 Windows 内核中实施的一种内置跟踪和日志记录机制。它在提供程序-使用者模式下工作;提供程序将事件发送给内核,而后者将这些事件重新路由给任何使用者程序。提供程序和使用者都必须提前在内核中注册(图 3)。

此外,由于事件路由是由内核处理的,因此如果事件由提供程序发送,但这些事件没有使用者,那么系统会直接将这些事件丢弃并发送到 void

ETW 事件报告流程 图 3:ETW 事件报告流程示意图

Microsoft-Windows-RPC

RPC ETW 提供程序 在 RPC 运行时 rpcrt4.dll 内实现。它有 13 个不同的事件,但我们主要对其中四种感兴趣:事件 5 和 7 分别用于客户端调用开始和停止,而事件 6 和 8 分别用于服务器调用开始和停止。我们将专注于调用开始事件(图 4),因为它们提供的信息最多。(调用停止事件仅指明 RPC 返回状态。)客户端和服务器事件采用相同的格式。

  <template tid="RpcServerCallStartArgs_V1">
     <data name="InterfaceUuid" inType="win:GUID"/>
     <data name="ProcNum" inType="win:UInt32"/>
     <data name="Protocol" inType="win:UInt32"/>
     <data name="NetworkAddress" inType="win:UnicodeString"/>
     <data name="Endpoint" inType="win:UnicodeString"/>
     <data name="Options" inType="win:UnicodeString"/>
     <data name="AuthenticationLevel" inType="win:UInt32"/>
     <data name="AuthenticationService" inType="win:UInt32"/>
     <data name="ImpersonationLevel" inType="win:UInt32"/>
  </template>

图 4:RPC ETW 调用开始事件模式

理论上,事件中的数据应该为我们提供深入了解 RPC 流量所需的全部元数据,我们现在通过接口 UUID 和 opnum 知道了所请求的对象,并且还通过 NetworkAddress 字段知道了源或目标地址(取决于我们查看的是客户端事件还是服务器事件)。事情不可能这么简单,不是吗?

确实如此。事实证明,RPC 运行时在处理服务器调用事件时未填写 NetworkAddress 字段,因此如果我们想获取连接元数据,则需要通过其他方法来找到这些数据。对于客户端事件,此字段已填写。

RPC 运行时的屏幕截图。在用于检查全局变量 Microsoft_Windows_RPCEnableBits 的 if check 中,有一个对函数 EtwEventWriteTransfer 的调用。在该函数收到的很多参数中,网络地址参数按 0 发送。 图 5:RPC 运行时的屏幕截图;突出显示的部分为网络地址字段,可以看到其为 0

这会带来以下问题:我们是否可以忽略服务器事件并仅依靠客户端事件?答案是 。由于我们尝试寻找的恶意行为来自于受攻击者控制的机器, 因此既无法确定它们不会篡改客户端 ETW 提供程序(并将其关闭,或拦截其事件),也无法确定它们将通过 RPC 运行时

热门 python 库 Impacket通常被用在攻击概念验证 (PoC) 和工具中(并包含 PSExec等网络攻击的实现),它可以实现所包含的 RPC 流量,因此任何使用它的攻击者将绕过 RPC 运行时并且不会在 ETW 提供程序中注册。 由于服务器事件不受攻击者控制,因此更谨慎的做法是依靠这些事件,这也是我们现在专注于如何从其他地方获取网络数据并将其与 RPC 事件进行匹配的原因。

将 RPC 事件与网络流进行匹配

我们先休息一下,将注意力从 RPC ETW 提供程序转移到其他的 ETW 提供程序上,即 TCPSMB 提供程序。这两种协议是用于 RPC 流量的常见传输协议。我们说过,RPC 端点类型取决于传输协议。因此在从 RPC 提供程序接收到端点(端口号、管道名称等)后,我们可以将它与传输 ETW 提供程序中各自的值进行匹配。

对于 TCP 来说,这非常简单。我们来看 ID 为 1017 的事件,它称为 TcpAcceptListenerComplete,会在 TCP 三方握手完成时触发。

它主要包含两个字段: LocalAddressRemoteAddress (但在我们的示例中,由于我们查看的是服务器事件,因此“本地”指的是服务器,而“远程”指的是客户端)。地址字段的值是二进制值,包含地址族、IP 地址和端口号(图 6)。

0

1

2

3

4

5

6

7

地址族

端口号

地址值(如果是 AF_INET,则为 IP)

图 6:地址二进制字段格式

我们需要做的是从 RemoteAddress 字段中提取(客户端的)IP 地址,然后使用 LocalAddress 字段中的(目标)端口跟踪该地址。现在,当我们在服务器的同一 TCP 端口号上收到一个 RPC 事件后,我们可以根据从 TCP 提供程序中提取的 port-to-ip 匹配说出它来自哪里(图 7)。

说明我们如何匹配 RPC 和 TCP 事件的信息图。以匹配的拼图块的形式描述了这两种事件。RPC 事件包含 Endpoint 字段,其值是本地 TCP 端口。TCP 事件包含 LocalAddress 字段,其值也是本地 TCP 端口。它还包含 RemoteAddress 字段,其值是客户端 IP 图 7:匹配 TCP 和 RPC 事件

对于 SMB,情况更复杂一些,因为不同的 ETW 事件中出现不同的信息。该端点是指定的管道(与文件相同),但多个文件可以通过同一 SMB 会话进行访问。因此,在此示例中,为了将该端点与其网络来源进行匹配,我们必须跟踪两个事件:一个是实际连接事件,而另一个是文件请求事件(图 8)。

对于连接事件,我们有 ID 为 500 的事件 Smb2ConnectionAcceptStart,它是在 SMB 连接建立时触发的。我们可以从它那里获得源 IP 和连接 UUID。然后,查找 ID 为 8 的事件 Smb2RequestCreate_V2,它包含所请求的文件名和同一个连接 UUID 字段。现在,我们只需要通过连接 UUID 对两个事件进行交叉引用,将管道名称与请求它的 IP 进行匹配(稍后,我们需要将该管道名称与 RPC 调用端点进行匹配)。

将 SMB 与 RPC 事件进行匹配的过程示意图。我们从 Smb2ConnectionAcceptStart 事件中收集了 IP 地址,并使用连接 GUID 与随后的 Smb2RequestCreate_V2 进行匹配。在 Smb2RequestCreate_V2 内,我们可以获取管道名称,然后将该名称与 RpcServerCallStart_V1 事件内的 Endpoint 字段进行匹配,该字段是从 RPC 提供程序中获得的。我们保留了其中的 Opnum 和 Interface 字段,这两个字段和 IP 字段是先前收集的,用于告诉我们全部相关信息 图 8:使用 SMB ETW 事件将源 IP 与 RPC ETW 事件进行匹配

为了帮您省去自己实现所有这些匹配的麻烦,我们已实现此算法,您可以在我们的 GitHub 存储库中找到 RPC Visibility 工具。该工具使用 Python 编写,可减少流入 Neo4j 数据库的已记录 RPC 网络流量,从而能够轻松实现可视化。

检测攻击

现在,我们获得了需要的全部信息,最后可以将注意力转到检测恶意 RPC 流量上。常规流程很简单:我们发现通过 RPC 执行的攻击,运行它的 PoC,然后查看它使用的 RPC 接口以及所请求的操作。接下来,我们只需要创建与该操作匹配的签名。我们已在相应版本中包含了可以与 Neo4j 数据库实现结合使用的签名查询,不过您可继续阅读以了解一般逻辑和条件。

PSExec

PSExec 既是一种攻击技术的通用名称,也是一个 Sysinternals 工具(该技术以此工具命名)。该工具主要将服务二进制文件复制到远程目标的 ADMIN$ 共享文件夹(Windows 的安装文件夹)中,然后通过其 RPC 接口 (MS-SCMR) 与服务管理器进行通信,为该二进制文件创建并运行服务(图 9)。

psexec 攻击流。左侧是一个黑客,右侧是一台电脑,两者之间是两个箭头。第一个箭头标记为“1.SMB”并包含文本“cp psexecsvc.exe \\VICTIM\ADMIN$\psexesvc.exe”,显示用于将服务二进制文件复制到受害者机器的命令。第二个箭头标记为“2.RPC, MS-SCMR”并包含文本“RCreateServiceW(\\Victim, C:\Windows\psexesvc.exe)”,显示将被调用以远程启动服务的 SCMR 函数 图 9:PSExec 攻击流

通过 SCMR 联系远程机器的合法原因有很多,例如查询远程服务状态的监视程序。而远程创建新服务的原因要少得多。因此,我们不想在所有通过 SCMR 的连接上触发警报(甚至在不使用 RPC ETW 的情况下也可以进行检测,只需将传入网络连接与服务管理器进程 services.exe进行匹配),而是只在创建远程服务的连接上触发警报。

因此,我们的签名应当为(从大体上说,并非特定于我们的 RPC 可见性实现):

interface_uuid ==  “367ABB81-9844-35F1-AD32-98F038001003” AND (opnum == 0xC OR opnum == 0x18)

其中 0xC 是 RCreateServiceW 的 opnum,而 0x18 是 RCreateServiceA的 opnum。

远程任务计划程序

类似于 PSExec,该任务计划程序可用于启动远程二进制文件并通过此方式实现横向移动。它甚至不必启动新的二进制文件,因为它同样可以启动 cmd 或 PowerShell 控制台,并下载在线托管的二进制文件。

另外,类似于 PSExec,我们不想检测对任务计划程序服务的所有访问,而主要感兴趣的是对 SchRpcRegisterTask的 RPC 调用。

interface_uuid ==  “86D35949-83C9-4044-B424-DB363231FD0C” AND opnum == 0x1

DCSync

DCSync 是另一种基于 RPC 的攻击,但其目标是域控制器。在这种情况下,攻击者会连接到真正的域控制器,并伪装成新的域控制器。然后,他们会要求复制该域控制器的凭据数据库,从而获得对 KRBTGT 密码散列的访问权限。

该复制请求会在 MS-DRSR RPC 接口上进行,并使用特定的函数 DRSGetNCChanges (opnum 3)。

interface_uuid ==  “e3514235-4b06-11d1-ab04-00c04fc2dcd2” AND opnum == 0x3

PetitPotam

PetitPotam 是一种对加密文件系统 (EFS) 服务的强迫身份验证攻击。攻击者基本上会连接到 EFS RPC 接口 (MS-EFSR),并告诉它打开 UNC 路径指定的远程文件。然后,它会触发带身份验证的出站 SMB 连接,而攻击者随即就会中继该连接。

在攻击 PoC 被发出后,它使用了 EfsRpcOpenFileRaw (opnum 0),该函数已被修补。 Topotam是发现该漏洞的研究人员,他还发现 EfsRpcEncryptFileSrv (opnum 4) 也有相同的缺陷。

interface_uuid ==  “c681d488-d850-11d0-8c52-00c04fd90f7e” AND (opnum == 0x0 OR opnum == 0x4)

缺点

即便可以从 RPC ETW 提供程序中收集很多信息,它也不是能够满足我们全部安全需求的一站式资源中心。虽然知道每个网络流中所请求的操作是很重要,但最关键的信息(即,每个请求中传递了什么数据)未被记录。这意味着,通过 ETW 提供程序进行横向移动检测仍然只是一种启发式方法,尽管它包含的背景信息比传统网络四元组多得多。

另外,它也是一种纯检测方法,无法用于阻止攻击或借助抵御建议响应攻击。Microsoft 给我们提供了另一种用于 RPC 的内置防御机制,即 Windows 防火墙中的 RPC 过滤器。您可以在我们的 “Definitive Guide to the Remote Procedure Call (RPC) Filter”博文中详细了解该过滤机制并了解如何使用它。

总结

RPC ETW 提供程序并不是 Windows 中的新增功能,但人们在考虑网络防御措施时往往会忽略它。还有一些工具可以通过该提供程序使用事件和与事件进行交互,但它们主要面向安全研究人员,并且对事物的网络端关注得比较少。

在本博文中,我们讨论了如何使用 RPC ETW 提供程序和 TCP 及 SMB 提供程序,以深入了解来自网络的所请求的 RPC 操作。这为我们提供了一种启发式方法,可以使用此方法来检测可用于实现横向移动的可能的恶意请求。

参考资料



Stiv Kupchik

寫於

Stiv Kupchik

August 10, 2023

Stiv Kupchik

寫於

Stiv Kupchik

Stiv Kupchik 是 Akamai 安全研究员团队的负责人。他的研究项目主要涉及操作系统内核、漏洞研究和恶意软件分析等领域。他曾在 Black Hat、Hexacon 和 44CON 等会议上展示过他的研究成果。除了是一名网络安全专业人士外,Stiv 还拥有物理学学士学位。