MSMQ QueueJumper( RCE 漏洞):深入技术分析

夜间在多台显示器上工作

Microsoft 于 2023 年 4 月 11 日发布的安全更新,修复了 90 余个独立漏洞。其中尤为值得关注的是 CVE-2023-21554(代号 QueueJumper)——这是一个影响 Microsoft 消息队列 (MSMQ) 服务的远程代码执行漏洞。MSMQ 是 Windows 系统的可选组件,其核心功能是让应用程序能够通过本地和远程均可访问的消息队列交换消息。本分析报告由 Randori 团队与 X-Force 对抗性服务团队联合完成,作者包括Valentina PalmiottiFabius WatsonAaron Portnoy

研究动机

CVE-2023-21554 之所以最初引发关注,主要源于以下特性:

  • 由于第三方应用对 MSMQ 队列的使用场景存在差异,难以全面界定该漏洞在不同环境及跨环境场景下的影响边界。
  • 早期报告显示,互联网上有 36 万个可访问主机暴露了 MSMQ 服务。而当时通过 Shodan 检索得出的结果仅为 250 个。这种数据差异进一步加剧了漏洞实际暴露范围的不确定性。
  • 该漏洞被归类为无需认证的远程代码执行,且影响广泛的 Windows 平台。
  • 唯一明显的要求是该服务可以通过 TCP 端口 1801 访问。

初始分类

识别 MSMQ 端点

为明确如何检测 MSMQ 的存在,我们在实验室环境中搭建了配置该组件的测试系统。当尝试连接 1801 端口上的 MSMQ 端点时,服务器未返回任何数据。由此可推测,若未接收到格式合规的数据,服务器不会作出响应。为掌握与该端点进行通信的方法,需进一步逆向分析其底层代码。

查找 MSMQ 代码

从 Microsoft 安全公告中,无法直接明确此次修复涉及哪个二进制文件。识别变更代码的第一步是下载对应更新文件。我们通过在 Microsoft 更新目录中检索 KB5025224,成功获取了该更新包。

查找 MSMQ 代码的屏幕截图

KB5025224 的 Microsoft 更新目录搜索结果

具体而言,我们获取到的是适用于 x86 架构系统的 Windows 11 累积更新文件 

windows10.0-kb5025224-x64_3522b1b562ed44bc949541dd1c7d08e1e967d925.msu

我们使用 7-Zip 解压工具对 MSU 文件及其包含的 CAB 文件进行解包处理。在解压后生成的所有文件夹中检索字符串“msmq”,最终匹配到容器索引文件 

Windows11.0-KB5025239-x64/express.psf.cix.xml,

该文件包含以下条目:

<File id="21296" name="amd64_microsoft-windows-msmq-queuemanager-core_31bf3856ad364e35_10.0.22621.1555_none_5f975f8fdb349517\f\mqqm.dll" length="34811" time="133245266510000000" attr="128">

该条目引用了 MQQM.dll ,此内容已被确定为需进一步分析的目标。

识别已修补代码

为识别 MQQM.dll 文件在补丁版本与未补丁版本之间的变更,使用了 Zynamics 的 BinDiff 工具。下图展示了相似度评分低于 1.00 的匹配函数:

已修补代码的屏幕截图

修复版与未修复版 MQQM.dll 文件的 BinDiff 对比结果

通过 Hex-Rays 反编译器对这些函数进行分析后发现,修复版文件的符号表中包含详细的函数名称,这些名称明确引用了 Microsoft 安全响应中心 (MSRC) 的漏洞标识,具体如下:

Microsoft 安全响应中心 (MSRC) 漏洞标识引用截图

MQQM.dll 中的功能标志符号

功能标志分析

经深入核查发现,包含 MSRC 案例编号的符号对应漏洞修复的功能标志。功能标志是 Windows 系统的核心组件,用于切换各类功能模块及实验性特性的启用状态。在此场景中,所发现的“功能”本质上是针对该漏洞的修复补丁:当功能标志处于启用状态时,系统会执行经过安全加固的修复版代码路径;若处于禁用状态,则会执行原始未修复的代码路径。

原始未修复代码路径执行截图

展示漏洞修复功能标志的反编译代码片段

针对构成 CVE-2023-21554 漏洞的每个子漏洞,其用户态组件与内核态组件中均存在对应的功能标志校验逻辑。对该安全更新包中其余二进制文件的分析显示,2023 年 4 月“补丁星期二”修复的绝大多数(甚至全部)漏洞,均引入了类似的功能标志机制。

根本原因分析

通过交叉比对这些标志,我们得以迅速定位补丁代码的具体位置。具体而言,CQmPacket::CQmPacket 中与 MSRC76146_MSMQ_OOBRWF ixes 标志相关的变更看起来最有价值。

该函数内部共包含八处针对此功能标志的校验逻辑。其中一处校验示例如下:

根本原因分析的屏幕截图

展示漏洞修复逻辑的反编译代码片段

在上述反编译代码片段中,已修补的代码路径通过调用 GetNextSectionPtrSafe. 获取 MSMQ 消息下一节的指针。而在未修补的路径中,指针仅通过简单增加节大小获得,并未进行任何检查。新增的 GetNextSectionPtrSafe 函数会验证计算出的数据包下一节指针,确保其未超出数据包自身的长度范围。

函数屏幕截图

GetNextSectionPtrSafe 函数的反编译片段

经核查该功能标志及其他相关功能标志的所有调用场景后,我们明确:CVE-2023-21554 漏洞由多个缺陷构成,这些缺陷均存在对各类 MSMQ 消息段落类型的大小校验不充分问题。此类漏洞允许攻击者通过任意 32 位偏移量篡改 MSMQ 消息末尾指针,使其超出原始分配缓冲区的边界范围。

在数据包处理完成后, CQmPacket::CQmPacket 的最后一个步骤会将一个 OnDiskExtensionHeader 附加到数据包缓冲区的末尾。由于上述漏洞的存在,经计算得出的消息末尾指针可能出现越界情况。因此,追加 OnDiskExtensionHeader 可能会导致多次越界写入。下方反编译代码展示了在发生越界写入前,对存在漏洞的消息结束指针所做的补丁检查:

越界写入执行前对存在漏洞的消息末尾指针的修复版校验截图

消息末尾的越界内存访问校验

内核态变体

.在评估漏洞可利用性时,于 MSMQ 服务对应的内核驱动程序 mqac.sys 中发现了额外的内核漏洞。这些漏洞再次由功能标志符号所揭示,如同在 MQQM.dll 中所见,同样关联到 MSRC 案例 76146

功能的屏幕截图

mqac.sys 中的功能标志符号

KernelVariants 功能标志的分析显示,对应的漏洞与用户空间组件中发现的漏洞如出一辙。核分量management for MSMQ packet buffers.内核组件mqac.sys  参与了 MSMQ 服务的多种操作,例如将消息转发至死信队列、发送确认消息,以及为 MSMQ 数据包缓冲区进行内存管理。

为执行上述相关操作,该内核态组件会对 MSMQ 数据包缓冲区的多个字段进行解析。以下为两处解析过程的示例截图:

MSMQ 数据包缓冲区多字段解析流程截图(内核态组件操作关联场景)

内核驱动程序 mqac.sys 中漏洞修复功能标志的反编译代码片段

如上方反编译代码片段所示,若该功能标志已启用,则会调用“safe”函数 CPacketBuffer::MsgDeadletterHeaderSafe CPacketBuffer::MsgOnDiskExtensionHeaderSafe  从数据包缓冲区中检索相应的标头。如果未启用该功能,则会调用“unsafe”函数,这些函数对节区的大小缺乏充分的检查。

触发漏洞

协议逆向工程

大部分与协议解析相关的代码位于 MQQM.dll 库中,该库由mqsvc.exe 进程加载。为了访问到 MSRC76146_MSMQ_OOBRWFixes 所引用的代码,必须从客户端发送一个有效的 MSMQ 请求。MSMQ 消息规范与协议体系庞大且复杂。最初,我们在 GitHubgist 中找到了扫描 MSMQ 服务器的公开代码,为更深入地探索数据包处理提供了基础。此外,Microsoft 官方文档对理解 MSMQ 消息格式提供了有益的帮助。

该 DLL 中的代码通过调用 WSARecv 函数在 1801 端口接收数据,相关调试过程如下所示:

0:020> bp ws2_32!wsarecv ; g

Breakpoint 0 hit

WS2_32!WSARecv:

00007ffd`651115c0 48895c2408      mov     qword ptr [rsp+8],rbx ss:000000aa`f07fef30=0000000000000006

 

0:019> k

# Child-SP          RetAddr               Call Site

00 000000aa`f07fef28 00007ffd`57ba9cb0     WS2_32!WSARecv

01 000000aa`f07fef30 00007ffd`57b9683d    

MQQM!NoReceivePartialBuffer+0x38

02 000000aa`f07fefb0 00007ffd`57b2078e     MQQM!CWinsockConnection::ReceivePartialBuffer+0x7d

03 000000aa`f07ff010 00007ffd`57b25b28    

MQQM!CSockTransport::BeginReceive+0xe6

04 000000aa`f07ff060 00007ffd`57b2d40b    

MQQM!CSockTransport::NewSession+0x194

05 000000aa`f07ff3f0 00007ffd`57b2d367     MQQM!CSessionMgr::AcceptSockSession+0x6f

06 000000aa`f07ff430 00007ffd`645e26bd     MQQM!AcceptIPThread+0x6a7

07 000000aa`f07ff7d0 00007ffd`6650a9f8    

KERNEL32!BaseThreadInitThunk+0x1d

08 000000aa`f07ff800 00000000`00000000     ntdll!RtlUserThreadStart+0x28

 

0:019> r @$t0=@rdx

0:019> pt

WS2_32!WSARecv+0x19f:

00007ffd`6511175f c3              ret

0:019> dc poi(@$t0+8) LC

000001b5`880ff490  62f06110 524f494c 00000070 5a5a5a5a  .a.bLIORp…ZZZZ

000001b5`880ff4a0  0073006e 00610074 0063006e 00200065  n.s.t.a.n.c.e. .

000001b5`880ff4b0  00440049 00000000 00000000 00000000  I.D………….

 

0:019> ba r1 poi(@$t0+8) ; g

Breakpoint 1 hit

MQQM!CBaseHeader::SectionIsValid+0x154:

00007ffd`57b53450 7462            je     

MQQM!CBaseHeader::SectionIsValid+0x1b8 (00007ffd`57b534b4) [br=1]
 

CBaseHeader::SectionIsValid 函数负责解析新消息的开头部分,该部分起始于以下形式的 BaseHeader 结构:

格式的 Baseheader 结构屏幕截图

BaseHeader 对象的结构

在服务器发送响应之前,传入消息字段由 SectionIsValid 进行验证:

消息字段屏幕截图

MQQM.dll 中 SectionIsValid 的反汇编代码

完成初始验证后,大部分协议解析将在CQmPacket:: CQmPacket 构造函数内进行。为触发 CVE-2023-21554 漏洞,可发送包含格式错误且未妥善处理的节点头部的 MSMQ 消息。以下示例详细说明了如何利用某个 EodHeader 来触发与 Eod(一次性送达)节区相关的漏洞。

使用 Eodheader 进行 Eod 触发漏洞的屏幕截图

反编译代码展示 EodHeader 结构处理逻辑中的漏洞

上述反编译代码显示,修复后的路径调用了名为CEodHeader::GetNextSectionSafe. T 的函数;而未修复的路径仅执行指针加法操作,利用头部内的值来计算下一个节区的起始位置。

EodHeader  的结构未见于官方文档;该UserHeader 中用于指示EodHeader 存在状态的标志位在微软 文档 中被标注为“保留”,其中明确指出此标志位在发送时不应被设置。通过分析反汇编代码,可以洞察分析该头部结构的具体特征:

头部结构的屏幕截图

计算 EodHeader 段落总长度的汇编代码

在上述反汇编代码中,指向  EodHeader 起始位置的指针被加载至  rdx  寄存器。可推测该头部的第一个和第二个字段用于描述整数型尺寸信息(可能是头部长度与数据长度,或类似的尺寸相关参数)。这两个大小加上 0xF 表示 Eod 字段的总大小(以字节为单位)。通过发送一条带有  EodHeader 的消息,并将对应的长度字段设置为较大数值,即可触发一次越界写入。这会导致在将 OnDiskExtensionHeader 写入到(程序所认为的)消息末尾时,引发一次访问冲突:

扩展头部的屏幕截图

WinDbg 调试会话展示向畸形 MSMQ 消息末尾写入数据时触发的内存访问冲突

可利用性

内存管理与操控

为评估该漏洞所触发的越界写入漏洞的利用强度,我们首先对底层内存的分配机制与管理逻辑展开了调研。

MSMQ 消息数据包被处理之前,其内容会被复制到函数 QmAcAllocatePacket 中所分配的缓冲区里。该函数会通过以下方式调用 mqac.sys 内核驱动程序NtDeviceIoControl.

调用 mqac.sys 的函数的屏幕截图

数据包缓冲区分配函数的反编译

该内核组件返回一个缓冲区,该缓冲区是从磁盘上某文件的映射视图中划分出来的,该文件存储在C:\Windows\System32\msmq\storage 目录下,扩展名为.mq  。每个文件最多可容纳4MB,并可包含来自多个队列的消息

内核态组件屏幕截图

ProcessHacker 工具显示的 MQSVC 进程内存布局,表明数据包缓冲区内存为文件映射内存

处理完内存分配请求后,会通过 IOCTL 接口将数据包缓冲区地址,以及一个随机化的数据包句柄一同返回至用户态。驱动程序在处理来自用户态的请求时,会通过该句柄计算出数据包缓冲区的地址。

内存堆整理

易受攻击缓冲区的后备内存位于映射文件内,因此不属于进程的堆空间。此外,受影响的内存不存储任何对象。该文件映射中仅包含 MSMQ 消息的内容,以及存储在 的元数据。OnDiskExtension.

此外,若未预先掌握内存布局,则无法知晓文件映射与进程堆的相对位置,进而无法覆盖对象指针。因此,要实现可行的远程代码执行,需通过某种方法将内存构建为可预测的布局。

在尝试向目标发送合法 MSMQ 消息后,我们发现通过密集发送大量消息,可以强制在与 .mq 文件映射相邻的位置进行新的堆分配。消息在被接收时会按顺序写入内存。利用 MSMQ 消息的 TimeToBeReceived 属性,可以精心安排释放特定消息的时机。

内核态组件屏幕截图

MQSVC 进程的内存布局示意图:文件映射相邻位置分配的新堆内存

然而,这要求攻击者能够向目标上的至少一个队列发送消息;否则,这些消息将被丢弃并覆盖,无法持久占用数据包内存。

在开展的研究测试中发现,默认情况下,非特权用户可创建一个允许任何人远程发送消息的队列。且该队列会永久保留在系统中。尽管现代环境中 MSMQ 多以可选组件和适配器的形式存在,但甲骨文、威睿和施乐等厂商提供的企业级软件,仍将 MSMQ 作为核心依赖项。因此,这种攻击场景并非不切实际。

漏洞利用原语

越界内存访问漏洞引入了一种利用原语,它允许恶意客户端在分配给数据包的内存边界之外写入一个OnDiskExtensionHeader

在评估允许向偏移量写入数据的漏洞时
,必须考虑以下特性:

  • 偏移量是否可控制
  • 写入的数据是否可控制

对于 QueueJumper 漏洞而言,分配的缓冲区相对于越界写入的偏移量是可控制的,因为该偏移量是通过攻击者可控的数据计算得出的。不过,越界写入的内容控制权限有限。

在此,Address 代表已损坏的指向数据包消息末尾的指针,OnDiskExtension 标头将被写入该位置。发生的一系列操作如下:

  • 0x000000000000000C 被写入至Address
  • 0x00000000 被写入至 Address+0x2
  • 0x0000000000000000 被写入至 Address+0x12
  • 0x0000000000000000 被写入至 Address+0x1A
  • 0x00000094 被写入至 Address+0xE
  • 0x0000 被写入至Address+0x22
  • 0x0000 被写入至 Address+0x62
  • memcpy(Address+0xA6, Source, Source->AddressLength+0x08)

上方调用memcpy 时所使用的Source 指向一个 TA_ADDRESS 对象。TA_ADDRESS 结构定义了特定类型(例如 NetBIOS)的单个传输地址。定义如下:

typedef struct _TA_ADDRESS {

    USHORT AddressLength;;

    USHORT AddressType; Info;

    UCHAR Address[1];;

} TA_ADDRESS, *PTA_ADDRESS;

AddressLength

指定 AddressType 对应的地址所占字节数。

AddressType

指定传输地址的类型。

Address

指定一个包含传输地址的变长数组。

在以下调试会话中,TA_ADDRESS 包含了攻击者可操控的数据,其形式为一个 IP 地址:

rax=0000014e1d110180 rbx=0000014e1d110180 rcx=0000014e1d110184

rdx=0000014e1ce86b10 rsi=0000014e1d110001 rdi=0000000545d7fa50

rip=00007ff843208074 rsp=0000000545d7f940 rbp=0000000545d7f980

r8=000000000000000c  r9=0000000000000002 r10=0000000000000000

r11=0000000545d7f938 r12=0000000000000002 r13=0000000000001000

r14=0000014e1ce86b10 r15=0000000000000000

iopl=0         nv up ei pl nz na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206

MQQM!CQmPacket::CQmPacket+0x89c:

00007ff8`43208074 e8a5780a00      call    MQQM!memcpy (00007ff8‘432af91e)

0:017> dc rcx

0000014e`1d110184  00010004 00000000 8620a8c0 00000000  ………. …..
 

在上方,TA_ADDRESS 对象的长度为 4 ,类型为1 ,值为 0x8620a8c0 。将每个字节转换为 IPv4 的八位组后,得到地址 192.168.32.134 ,这正是客户端的 IPv4 源地址。

该场景支持攻击者通过源地址,将可控数据写入内存中相对于数据包分配区域的指定偏移位置。尽管存在极强的限制性且可能不具备实操性,但理论上可通过以下方式精准控制写入内存的值:从不同源地址发起多次请求,并逐步递减数据包偏移量。

替代攻击

由于实现盲远程代码执行面临诸多限制,因此研究人员探究了信息泄露等替代攻击场景。

其中一种考虑的攻击方式是:篡改指向特定消息段的指针,使其指向越界内存,后续再通过读取该消息来获取相邻内存中的数据。

然而,每个消息段解析的起始阶段都会对当前段的位置进行校验,因此无法欺骗解析逻辑去解析越界的数据段。

另一种可行的攻击场景是:覆盖另一条消息的 OnDiskExtensionHeader,从而欺骗消息接收方,使其对接收消息的来源产生误判。同样,发送到队列的消息内容也有可能被覆盖,攻击者无法访问。若要覆盖消息的特定内容,攻击者需掌握该消息的相关信息(例如消息大小)。

存在一个名为 SoapHeader 的可选部分,该部分易受越界数据访问漏洞影响,其格式如下:

Soapheader 屏幕截图

SoapHeader 数据包格式

经观察发现,存储 MSMQ 数据包数据的内存在缓冲区释放时未被正确清零。因此,当发送的 MSMQ 数据包尺寸小于之前发送的数据包时,内存布局会呈现为当前数据包数据 + 此前处理过的 MSMQ 数据包遗留数据。

研究发现, SoapHeader 可被利用来滥用此特性,从而泄露先前处理过的数据包或相邻存储消息中的数据。攻击者可以任意定义 BodyDataLength,并完全省略正文部分。这将导致生成的 MSMQ 消息中,会将先前处理过的 MSMQ 数据包中可能包含的敏感数据,作为攻击者所构造的 SoapHeaderBody 部分。已修补的代码路径能够防止这种滥用情况,因为它验证了 Body 数据是否在 MSMQ 数据包数据的边界之内。

写入内容的控制权限有限,且额外的非受控写入操作会破坏相邻内存,加之对消息布局的了解不足,这些因素共同构成了漏洞利用过程中的重大障碍。

通过内核态攻击

在分析时,仅对 MSMQ 消息包初始处理过程中涉及的用户空间漏洞进行了可利用性评估。对内核变种漏洞的分析可能会揭示更多可利用的途径。例如,包含格式错误的标头可能会在 .mq 文件的内核内存映射中获得写入原语。在某些情况下,用户空间处理中的检查会阻止内核执行某些后处理操作。

此外,对于内核态变体漏洞而言,攻击者对消息内容的直接控制能力更弱,且操作的是与用户态共享的文件映射区域,而非内核池(堆)。同时,KASLR 等缓解措施,也为远程漏洞利用设置了难以突破的障碍。

检测存在漏洞的服务器

由于补丁代码的实现方式,远程客户端可以安全地判断系统是否已应用更新。这可以通过发送一个触发漏洞但不会导致越界访问的 MSMQ 消息来实现,从而避免服务进程崩溃。如果消息的 UserHeader 中设置了 HTTP 标志,则预期会出现一个 SRMPEnvelopeHeader。在补丁中,对 DataLength 字段乘以 2 的结果执行了整数溢出检查。该长度可以被设置,使得乘法运算的结果溢出为一个与实际数据大小相等的小数值。

反编译代码片段,展示对 SRMPEnvelopeHeader 结构体 DataLength 字段的整数溢出校验逻辑

反编译代码片段,展示对 SRMPEnvelopeHeader 结构体 DataLength 字段的整数溢出校验逻辑

若目标系统已安装补丁,该漏洞触发后会抛出异常,服务器将中止消息处理流程,因此不会向客户端返回任何响应。若未安装补丁,服务器会正常处理消息并返回响应。

总结

由于易受攻击的缓冲区所对应的内存位置特性,对于能够向目标上任何队列发送消息的攻击者而言,实现远程代码执行似乎更为可行。在这种情况下,可以进行梳理,以便分配相邻的堆。对写入内容的控制极其有限,这为远程开发带来了挑战。由于存在 ASLR 等缓解措施,攻击者还必须获取信息泄露才能实现利用。将常量(0xC, 0x0 - 通过原语获取)写入对象即可实现此目的,但前提是该对象必须包含有用字段,且相邻字段能够承受其他写入操作带来的损坏。此外,该对象必须能被攻击者可预测地分配,其生命周期必须覆盖漏洞利用的整个持续时间,并且对象的某些操作必须能被攻击者触发。在本研究过程中,尚未发现符合上述要求的适配对象。

综上,我们得出结论:尽管 QueueJumper 漏洞的远程利用并非完全不可能,但上述各项要求使其实施难度极大。本文仅对这一极为复杂的消息规范、服务架构及协议体系进行了初步探讨。针对其用户态与内核态组件的运行机制,仍有必要开展进一步的漏洞研究。

Mixture of Experts | 12 月 12 日,第 85 集

解码 AI:每周新闻摘要

加入我们世界级的专家小组——工程师、研究人员、产品负责人等将为您甄别 AI 领域的真知灼见,带来最新的 AI 资讯与深度解析。