今年早些时候,我们对 Windows 远程桌面服务进行了深入分析,发现了多个漏洞,所有相关漏洞(56 例)都已报告给微软。其中包括远程桌面授权服务中的多个 Preauth RCE 漏洞(未经身份验证的非沙盒 0-click RCE)。这些漏洞可用于构建针对 Windows 远程桌面授权服务的多个 Preauth RCE 漏洞。是的,它们是多年来在 Windows 中未见过的 0-click preauth RCE。我们将它们称为Mad、Bad 和 Dead Licenses 漏洞。本文是关于这些漏洞的系列文章中的第一篇。
在本文中,我们介绍了漏洞 CVE-2024-38077(我们将其命名为 MadLicense【狂躁许可】),并在启用了完整和新缓解措施的 Windows Server 2025 上演示了该漏洞的利用。我们之所以选择 Windows Server 2025,是因为微软声称 Windows Server 2025 提供了下一代安全改进。并且该漏洞适用于 Windows Server 2000到2025(所有 Windows Server )。我们现在不会给出详细的技术解释,也不会提供完整的POC 。但是这里的伪代码足以了解此漏洞。为了防止滥用,此处的 python 代码实际上是伪代码。你甚至无法使用此伪代码触发漏洞,更不用说利用它了。这足以证明其严重性,并为防御者在真正弄清楚如何利用它之前采取行动提供足够的时间。我们在一个月前就通知微软这个漏洞可以被利用,但微软仍将其标记为不太可能被利用。因此我们在此进行了负责任的披露。我们的目的是提高人们对该漏洞风险的认识,并鼓励用户及时更新系统以解决这些问题。Defender 还可以使用本博客中的信息来检测和阻止可能的攻击。
2024年7月,我们报告的以下7个与RDP相关的漏洞已被Microsoft修复:
其中,Windows 远程桌面授权服务中的 3 个 CVSS 评分为 9.8 的 RCE 漏洞值得关注。在微软的公告中,他们认为这些漏洞不太可能被利用。但事实并非如此。事实上,我们在补丁发布之前就告知了微软这些漏洞的可利用性。
在本博客中,我们将演示如何利用 Windows Server 202 5上的 CVE-2024-38077 进行预认证 RCE 攻击,绕过所有现代缓解措施,在最新的 Windows Server 上实现零点击 RCE。是的,你没听错,只需利用一个漏洞,你无需任何用户交互即可实现此目的。
远程桌面许可服务是 Windows Server 的一个组件,用于管理和颁发远程桌面服务的许可证,确保对远程应用程序和桌面的安全且合规的访问。
RDL 服务广泛部署在启用了远程桌面服务的机器上。默认情况下,远程桌面服务仅允许同时使用两个会话。要启用多个同时会话,您需要购买许可证。RDL 服务负责管理这些许可证。RDL 被广泛安装的另一个原因是,在Windows 服务器上安装远程桌面服务 (3389) 时,管理员通常会勾选安装 RDL 的选项。这导致许多启用了 3389 的服务器也启用了 RDL 服务。
在审计RDL服务之前,我们进行了网络扫描,以确定RDL服务在互联网上的部署情况。我们发现至少有17万个活跃的RDL服务直接暴露在公共互联网上,而内部网络中的数量无疑要大得多。此外,RDL服务通常部署在关键业务系统和远程桌面集群中,因此RDL服务中的预认证RCE漏洞对网络世界构成了重大威胁。
终端服务器授权程序旨在管理将任何用户或设备连接到服务器所需的终端服务 CAL。
在CDataCoding::DecodeData过程中,会分配一个固定大小的缓冲区(21 字节),然后使用该缓冲区计算并填充用户控制的长度缓冲区,从而导致堆溢出。
这是调用堆栈和伪代码。
10:012> k 2 # Child-SP RetAddr Call Site 300 000000b9`d2ffbd30 00007fff`67a76fec lserver!CDataCoding::DecodeData 401 000000b9`d2ffbd70 00007fff`67a5c793 lserver!LKPLiteVerifyLKP+0x38 502 000000b9`d2ffbdc0 00007fff`67a343eb lserver!TLSDBTelephoneRegisterLicenseKeyPack+0x163 603 000000b9`d2ffd7d0 00007fff`867052a3 lserver!TLSRpcTelephoneRegisterLKP+0x15b 704 000000b9`d2fff0c0 00007fff`8664854d RPCRT4!Invoke+0x73 805 000000b9`d2fff120 00007fff`86647fda RPCRT4!NdrStubCall2+0x30d 906 000000b9`d2fff3d0 00007fff`866b7967 RPCRT4!NdrServerCall2+0x1a 1007 000000b9`d2fff400 00007fff`86673824 RPCRT4!DispatchToStubInCNoAvrf+0x17 1108 000000b9`d2fff450 00007fff`866729e4 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x194 1209 000000b9`d2fff520 00007fff`86688d4a RPCRT4!RPC_INTERFACE::DispatchToStub+0x1f4 130a 000000b9`d2fff7c0 00007fff`86688af1 RPCRT4!OSF_SCALL::DispatchHelper+0x13a 140b 000000b9`d2fff8e0 00007fff`86687809 RPCRT4!OSF_SCALL::DispatchRPCCall+0x89 150c 000000b9`d2fff910 00007fff`86686398 RPCRT4!OSF_SCALL::ProcessReceivedPDU+0xe1 160d 000000b9`d2fff9b0 00007fff`86697f4c RPCRT4!OSF_SCONNECTION::ProcessReceiveComplete+0x34c 170e 000000b9`d2fffab0 00007fff`840377f1 RPCRT4!CO_ConnectionThreadPoolCallback+0xbc 180f 000000b9`d2fffb30 00007fff`867f7794 KERNELBASE!BasepTpIoCallback+0x51 1910 000000b9`d2fffb80 00007fff`867f7e37 ntdll!TppIopExecuteCallback+0x1b4 2011 000000b9`d2fffc00 00007fff`85b11fd7 ntdll!TppWorkerThread+0x547 2112 000000b9`d2ffff60 00007fff`8683d9c0 KERNEL32!BaseThreadInitThunk+0x17 2213 000000b9`d2ffff90 00000000`00000000 ntdll!RtlUserThreadStart+0x20
1void __fastcall CDataCoding::SetInputEncDataLen(CDataCoding *this)
2{
3 // ...
4
5
6 dword_1800D61D0 = 35;
7 v1 = log10_0((double)dword_1800D61C8) * 35.0;
8 v2 = v1 / log10_0(2.0);
9 v3 = (int)v2 + 1;
10 v4 = 0;
11 if ( v2 <= (double)(int)v2 )
12 v3 = (int)v2;
13 LOBYTE(v4) = (v3 & 7) != 0;
14 LODWORD(dwBytes) = (v3 >> 3) + v4; // dwBytes is a fixed value 21
15}
16__int64 __fastcall CDataCoding::DecodeData(
17 CDataCoding *this,
18 const unsigned __int16 *a2,
19 unsigned __int8 **a3,
20 unsigned int *a4)
21
22
23{
24 // ...
25 v4 = 0;
26 v8 = 0;
27 if ( a3 )
28 {
29 // dwBytes is a global variable with value 21
30 v9 = dwBytes;
31 *a3 = 0i64;
32 *a4 = 0;
33 ProcessHeap = GetProcessHeap();
34 v11 = (unsigned __int8 *)HeapAlloc(ProcessHeap, 8u, v9);
35 v12 = v11;
36 if ( v11 )
37 {
38 memset_0(v11, 0, (unsigned int)dwBytes);
39 while ( *a2 )
40 {
41 // Str is BCDFGHJKMPQRTVWXY2346789
42 // a2 is user-controlled buffer
43 v13 = wcschr_0(Str, *a2);
44 if ( !v13 )
45 {
46 v4 = 13;
47 v18 = GetProcessHeap();
48 HeapFree(v18, 0, v12);
49 return v4;
50 }
51 // here change the integer a2 from base 24 to base 10
52 // but does not check the length of a2
53 v14 = v13 - Str;
54 v15 = v12;
55 v16 = (unsigned int)(v8 + 1);
56 do
57 {
58 v17 = dword_1800D61C8 * *v15 + v14;
59 *v15++ = v17;
60 LODWORD(v14) = v17 >> 8;
61 --v16;
62 }
63 while ( v16 );
64 if ( (_DWORD)v14 )
65 v12[++v8] = v14;
66 ++a2;
67 }
68 *a4 = dwBytes;
69 *a3 = v12;
70 }
71 else
72 {
73 return 8;
74 }
75 }
76 else
77 {
78 return 87;
79 }
80 return v4;
81}
82}
这里我们只是演示了漏洞利用。详细的技术解释将在本系列的后续博客文章中。这里的 Python 代码实际上是伪代码。你甚至无法用这个伪代码触发漏洞,更不用说利用它了。这足以证明漏洞的严重性,也为防御者提供了足够的时间,让他们能够在有人真正弄清楚如何利用漏洞之前采取行动。
适用于:
1Windows Server 2025 标准版本 24H2 (26236.5000.amd64fre.ge_prerelease.240607-1502 )lserver.dll( 10.0.26235.5000 )
该漏洞利用的 POC 在 Windows Server 2025 上的成功率超过 95%。考虑到服务崩溃后会重新启动,不需要两次泄露模块基址,最终的成功率可以更高(接近 100%)。
此 POC 将在 Windows Server 2025 上在 2 分钟内完成。但是我们这里的堆整理技术是使用 Windows Server 2025 中引入的新 LFH 缓解措施的未优化版本。我们很懒,实际上并没有完全逆转 Windows Server 2025 中的段堆机制,所以我们的堆整理只是一种启发式解决方案。它一点也不优雅。当然,你必须可以对其进行优化,以使漏洞在 Windows Server 2025 上运行得更快。
对于 Windows Server 2000 到 Windows Server 2022,利用此漏洞会更快,因为缓解措施较少。为简单起见,POC 将加载远程 DLL。但您可以让它在 RDL 进程中运行任意 shellcode。这将使其更加隐蔽。
在 Windows Server 2025 之前的版本中利用此漏洞应该更容易和更有效,但当然,你需要调整代码和偏移量。漏洞可以在 Windows Server 2000到 Windows Server 2025上构建。这里我们只在 2025 年进行演示,因为 Windows Server 2025 是最新和最安全的Windows Server 。而且它仍处于预览阶段,因此 POC 不会对世界造成危害。如果要避免偏移量问题以使漏洞利用更加通用,动态搜索是可能的,但你需要将其替换为更高效的内存读取原语以使漏洞利用更加高效。
这里我们做了一个负责任的披露。为了进一步防止这个 POC 被滥用,这里发布的 POC 只是伪代码,并且是未优化的版本,其中的一些关键部分被隐藏了。但伪代码中的信息 足以让研究人员检测和阻止利用。
在本文中,我们将演示如何利用单个漏洞绕过所有缓解措施,并在被认为是最安全的 Windows Server 2025 上实现预身份验证远程代码执行 (RCE) 攻击。这在 2024 年似乎很荒诞,但这是事实。尽管微软几十年来对 Windows 进行了各种防御,并且多年来我们都没有在 Windows 中看到预身份验证 0-click RCE,但我们仍然可以利用单个内存损坏漏洞来完成整个攻击。看起来,具有“下一代安全改进”的系统这次无法阻止 30 年前同样的旧内存利用。
本文的目的是提醒用户尽快更新系统以修复漏洞。这个组件中其实还有更多的利用,记住我们已经报告了 56 个案例(虽然微软 SRC 合并了我们的许多案例,这很烦人)。有兴趣的研究人员可以尝试弄清楚。
这是本系列的第一篇博文。有关更多漏洞、更多漏洞、与 Microsoft SRC 合作的痛苦与收获等,我们可能会在本系列的后续博文中讨论。
博客文章中的观点仅代表我们自己的观点,不反映我们雇主的观点。
1https://sites.google.com/site/zhiniangpeng/blogs/MadLicense