Pass-the-Challenge 击败 Windows Defender Credential Guard

admin 2025年6月18日21:33:50评论0 views字数 8991阅读29分58秒阅读模式

【翻译】Pass-the-Challenge Defeating Windows Defender Credential Guard 

在本篇博客中,我们介绍了从受 Windows Defender Credential Guard 保护的加密凭证中恢复 NTLM 哈希的新技术。虽然之前绕过 Credential Guard 的技术主要针对登录到已入侵服务器的新受害者,但这些新技术也可以应用于服务器被入侵前已登录的受害者。

Credential Guard 旨在保护 NTLM 哈希和 Kerberos 票据,但在本文中,我们将重点讨论 NTLM 哈希。

我们将提供技术细节的概述,并为有兴趣深入了解底层机制的读者提供进一步探索的选项。

需要注意的是,当我们提到 "NTLM 哈希" 时,实际上指的是 NT 哈希,因为 LM 哈希已经过时。

Credential Guard 基础

关于 Credential Guard 的博客文章很多,其中一些比其他更准确。在本节中,我们将概述 Credential Guard,并解释为什么攻击者和防御者都需要理解它。

引用 Microsoft 关于 Credential Guard、Pass-the-Hash 和 Pass-The-Ticket 的描述:

Windows Defender Credential Guard 通过保护 NTLM 密码哈希、Kerberos 票据授予票据(Ticket Granting Tickets)以及应用程序存储的域凭证来防止这些攻击 [Pass-the-Hash 和 Pass-The-Ticket]。

LSASS(本地安全机构子系统服务)进程负责管理和执行 Windows 系统的安全策略。它处理诸如用户认证、资源访问授权和执行安全策略等任务。

LSASS 进程在内存中存储凭证,包括哈希密码。Windows Defender Credential Guard 旨在通过将这些凭证与 LSASS 进程内存隔离来保护它们。我们将在稍后详细介绍其实现方式。

为什么这很重要?

攻击者经常尝试从已入侵机器的 LSASS 进程内存中提取凭证,以便在网络中进行横向移动,使用像 Mimikatz 这样的工具可以提取各种凭证,包括明文密码、NTLM 哈希和 Kerberos 票据。

在 Pass-the-Hash(PtH)攻击中,攻击者可以使用被泄露的 NTLM 哈希来验证系统或服务,而无需知道实际密码。这是可能的,因为 NTLM 哈希是从用户密码派生的,并在许多协议中用于身份验证。

Credential Guard 似乎通过将 NTLM 哈希(和 Kerberos 票据)隔离在 LSASS 进程内存中来防止这些类型的攻击,从而保护用户的 NTLM 哈希不被初始泄露。

现在,如果我们入侵了一个启用了 Credential Guard 的系统,并尝试使用 Mimikatz 从 LSASS 进程内存中提取凭证,我们会观察到什么?

Pass-the-Challenge 击败 Windows Defender Credential Guard

如上所示,我们无法从 LSASS 内存中提取 NTLM 哈希,而是看到 "LSA Isolated Data: NtlmHash"。作为比较,这是在未受 Credential Guard 保护的系统上的输出。

Pass-the-Challenge 击败 Windows Defender Credential Guard

这个被泄露的 NTLM 哈希随后可以用于验证系统或服务。

在 IFCR,我们在红队演练中注意到越来越多的系统受到 Credential Guard 的保护。此外,从 Windows 11 企业版 22H2 和 Windows 11 教育版 22H2 开始,兼容 Credential Guard 的系统默认启用该功能。

工作原理

Windows Defender Credential Guard 使用基于虚拟化的安全(VBS)来隔离机密。VBS 利用硬件虚拟化功能创建一个与正常操作系统分离的安全内存区域。

要理解攻击者在处理 Credential Guard 时面临的挑战,可以将正常操作系统视为运行在一个虚拟机(VM)中,而安全进程则运行在具有独立内核的另一个 VM 中。这些 VM 由 "Hypervisor" 管理。

Pass-the-Challenge 击败 Windows Defender Credential Guard

基于虚拟化的安全(VBS)和 Hypervisor 强制代码完整性(HVCI)—— Microsoft 社区中心

即使攻击者在正常操作系统中获得了内核代码执行权限,他们仍然需要通过攻击 Hypervisor 或安全 VM 来逃逸 VM。这种情况与基于虚拟化的安全类似。

那么,Credential Guard 是如何工作的?当启用 Credential Guard 时,一个名为 LSAIso(LSA 隔离)的进程在安全 VM 中运行。LSASS 和 LSAIso 可以通过高级本地过程调用(ALPCs)进行通信。

Pass-the-Challenge 击败 Windows Defender Credential Guard

Windows Defender Credential Guard 工作原理 | Microsoft Learn

当 LSASS 进程想要保护一个机密时,它可以调用 LSAIso 来加密它。加密后的机密随后返回给 LSASS。理想情况下,只有 LSAIso 应该能够解密该机密。一旦 NTLM 哈希受到保护,LSASS 进程只持有隔离的机密(加密的 blob)。

如上图所示,LSAIso 进程具有 "NTLM 支持"。当 LSASS 进程想要对加密的机密执行 NTLM 操作时,它可以调用 LSAIso 进程中的各种方法来执行该操作。值得注意的是,LSAIso 没有网络访问权限。因此,尽管 LSAIso 可以执行 NTLM 操作,但 LSASS 进程仍然负责执行操作前后的任何操作。例如,虽然 LSAIso 可以计算 NTLM Challenge/Response 对,但 LSASS 负责接收和发送该对。

之前击败 Credential Guard 的状态

到目前为止,唯一公开的针对 Credential Guard 的攻击涉及在机密受到保护之前攻击 LSASS 的认证管道。例如,这可能涉及在 LSASS 中挂钩一个函数,以便当新受害者登录到已入侵的服务器时,他们的凭证可以在发送到 LSAIso 之前被泄露。

新击败 Credential Guard 的状态

尽管需要一些额外的步骤,但仍然可以恢复 NTLM 隔离机密的 NTLM 哈希。换句话说,如果我们成功入侵一个系统并在 LSASS 进程的内存中遇到加密的 NTLM 凭证,我们仍然可以提取 NTLM 哈希。

通过利用 LSAIso 进程的暴露功能以及加密的凭证,我们可以执行所需的操作,最终获得 NTLM 哈希。

恢复 NTLM 哈希

在本节中,我们将演示两种获取 NTLM 哈希的技术。

首先,我们必须在 LSASS 进程中获得代码执行权限,以便通过已建立的 ALPC 通信通道与 LSAIso 进程通信。对于对底层机制感兴趣的读者,这一步在 "技术细节" 部分有更详细的描述。

其次,我们必须获取 LSASS 进程的内存转储以提取加密的凭证。我修改了 Pypykatz 代码来实现这一点,详细信息也将在 "技术细节" 部分提供。

在这个例子中,我创建了一个域管理员账户,其长随机密码对应的 NTLM 哈希为 65A13AB2FAEB5B700DE1A938AE5621CA

Pass-the-Challenge 击败 Windows Defender Credential Guard

在这个场景中,我们获取了 LSASS 进程的内存转储,并使用修改后的 Pypykatz 版本提取了加密的凭证,如下图所示。

Pass-the-Challenge 击败 Windows Defender Credential Guard

在接下来的两节中,我们将演示如何从 "加密 blob"(隔离机密)中提取 NTLM 哈希 65A13AB2FAEB5B700DE1A938AE5621CA

我还创建了一个名为 "PassTheChallenge" 的工具,它通过将库加载到 LSASS 进程中来调用 LSAIso 方法。该工具将随本博客文章一起提供,技术细节和所需信息将在 "技术细节" 部分提供。

PtCv1

该技术使我们能够在相对较短的时间内恢复 NTLM 哈希。

LSAIso 进程暴露了一个名为 NtlmIumCalculateNtResponse 的方法,它允许我们使用加密的凭证为任意服务器挑战计算 NTLMv1 响应。

通过利用内存转储中的"Context Handle"、"Proxy Info"和"Encrypted blob",我们可以使用 nthash 命令将这些参数传递给 PassTheChallenge.exe。默认情况下,该工具使用静态挑战 1122334455667788。如果一切顺利,它将生成一个 NTHASH。尽管格式如此,但这并不是 NTLM 哈希,而是一个 NTLMv1 响应。

Pass-the-Challenge 击败 Windows Defender Credential Guard

现在来到有趣的部分。有了 NTLMv1 响应和静态挑战 1122334455667788,我们可以将其提交到 crack.sh 并免费破解。在你认为这需要数小时破解之前,请继续阅读。我们并不是将 NTLMv1 响应破解为密码,而是破解为 NTLM 哈希。

如果你需要复习 NTLMv1 响应的生成方式,可以参考以下代码:

Pass-the-Challenge 击败 Windows Defender Credential Guard

NTLMv1 响应由三个 8 字节块组成。每个块都是通过使用 DES 加密挑战和 7 字节密钥块创建的。你可能已经注意到,三个 7 字节密钥块加起来是 21 字节,这比 16 字节的 NTLM 哈希要大。为了解决这个问题,密钥的最后一个块由 NTLM 哈希的前 2 个字节和空字节组成。

总结一下,我们需要使用三个不同的 7 字节密钥破解三个 DES 加密,其中一个密钥只有 2 个未知字节,以恢复 NTLM 哈希。这个过程比破解单个 16 字节密钥要简单得多。幸运的是,crack.sh 已经构建了最大的公开可用的 DES 密钥空间彩虹表。他们声称平均破解时间为 25 秒,成功率为 99.5%。如果系统无法立即破解密钥,它将把任务传递给暴力破解设备,该设备应该能够在几天内找到密钥。

让我们看看它的实际效果。首先,我们可以将 NTHASH 格式免费提交给 crack.sh。

Pass-the-Challenge 击败 Windows Defender Credential Guard

不到一分钟,我就收到了 crack.sh 的邮件,称 NTLM 哈希在 30 秒内成功恢复:65A13AB2FAEB5B700DE1A938AE5621CA

Pass-the-Challenge 击败 Windows Defender Credential Guard

虽然这不是对 crack.sh 的广告,但它确实说明了将 NTLMv1 哈希破解回 NTLM 哈希是多么简单。

这个特定的哈希之前没有提交给 crack.sh,使用长随机密码表明破解不是密码复杂性的问题,而是 NTLMv1 哈希的固有弱点。

PtCv2

现在,假设由于某种原因 NTLMv1 选项不可用——也许微软决定删除它。另一个有趣的选择是使用 LSAIso 方法 NtlmIumLm20GetNtlm3ChallengeResponse 计算 NTLMv2 响应。

为了演示这一点,我修改了 Impacket 函数computeResponseNTLMv2,以交互方式打印挑战并提示响应。这个单一修改将适用于所有使用 Impacket 进行身份验证的脚本和工具,例如 Certipy。

当密码设置为 CHALLENGE 时,Impacket 中的 computeResponseNTLMv2 函数将打印出挑战并等待响应。让我们看看它的实际效果。对于第一个演示,我们将使用 Impacket 的 psexec.py 在域控制器 DC 上获得代码执行。

我们将像往常一样运行脚本,但为了使我的修改生效,我们需要指定密码 CHALLENGE。Impacket 将打印出自定义格式的挑战并等待响应,如下所示。

Pass-the-Challenge 击败 Windows Defender Credential Guard

然后我们可以像之前一样使用 PassTheChallenge.exe,但这次我们使用 challenge 命令并将挑战附加到之前使用的参数中。

Pass-the-Challenge 击败 Windows Defender Credential Guard

该工具将输出一个响应,我们将提供给 psexec.py

Pass-the-Challenge 击败 Windows Defender Credential Guard

如上所示,psexec.py 执行多次身份验证,因此我们需要重复这些步骤几次。然而,我们最终获得了访问权限并实现了代码执行。

虽然这并没有让我们恢复 NTLM 哈希,但它确实提出了使用我们的 NTLMv2 原语的其他可能性。

如果你读过我的一些其他文章,你可能已经注意到我对 Active Directory Certificate Services (AD CS) 的喜爱。如果你不熟悉 AD CS,我建议你查看我之前关于该主题的文章。

现在,让我们利用我们的 NTLMv2 原语和 Certipy 通过 Active Directory Certificate Services 为受害者用户 "Administrator" 请求证书。

Pass-the-Challenge 击败 Windows Defender Credential Guard

如前所述,修改后的 Impacket 函数甚至会导致 Certipy 在密码设置为 CHALLENGE 时打印挑战并提示响应。如上所示,证书请求成功,颁发的证书和私钥保存到 administrator.pfx

最后,我们可以使用证书和 Certipy 以管理员用户身份进行身份验证并检索 NTLM 哈希,如下所示。

Pass-the-Challenge 击败 Windows Defender Credential Guard

值得一提的是,为了请求证书并使用它们进行身份验证,必须在环境中安装 AD CS。一旦启用了 AD CS,默认情况下任何用户都可以成为目标。

额外说明

根据我自己的测试,加密的凭证似乎可以在重启后使用,这与一些现有信息相反。似乎 LSAIso 并没有使用每次启动的密钥来加密凭证。因此,可以在以后的时间返回被入侵的机器来执行这些操作。唯一需要注意的是,"Context Handle" 和 "Proxy Info" 是从内存转储中提取的内存地址,这些需要通过新的内存转储或类似方法重新计算。"Context Handle" 和 "Proxy Info" 地址并不与特定的凭证集绑定,所有加密的 NTLM 凭证在内存转储中都有相同的 "Context Handle" 和 "Proxy Info" 地址。更多信息请参考"技术细节"部分。

结论

在这篇博客文章中,我们特别关注了 NTLM。然而,值得考虑的是,Kerberos 可能也提供了一些有趣的功能,可以帮助我们实现目标。

在文章的前面,我们引用了微软关于 Credential Guard 页面的一句话。以下是该页面的结论:

虽然 Windows Defender Credential Guard 是一个强大的缓解措施,但持续性威胁攻击很可能会转向新的攻击技术 (…)

我们确实这样做了。

技术细节

如果您已经读到这里,无论您是攻击者还是防御者,您可能对技术细节感兴趣。

LSASS 与 LSAIso

如前所述,LSASS 和 LSAIso 通过 ALPC(高级本地过程调用)和 RPC(远程过程调用)进行通信。

安全支持提供程序(SSP)“MSV”(msv1_0.dll)负责 NTLM 和 Kerberos 身份验证。当 msv1_0.dll 模块加载到 LSASS 中并调用 SpInitialize 函数初始化 SSP 时,如果启用了 Credential Guard,该模块将创建一个新的 NtlmCredIsoIum 对象,并将该对象的地址保存到全局变量 LocalhostNtLmCredIsoObj::IsoObj 中。该对象作为 NtlmCredIsoApi 接口的实现接口。如果未启用 Credential Guard,LocalhostNtLmCredIsoObj::IsoObj 变量将存储 NtLmCredIsoInProc 对象,该对象实现的方法不与 LSAIso 通信,从而导致凭据不被加密。

当创建新的 NtlmCredIsoIum 对象时,它会建立到 LSA_ISO_RPC_SERVER 的 ALPC 绑定,LSAIso 已经在此监听过程调用。绑定建立后,MSV 将调用 LSAIso 的 NtlmIumGetContext 方法以获取唯一的上下文句柄(Context Handle)。

Pass-the-Challenge 击败 Windows Defender Credential Guard

上下文句柄在初始化期间传递给 NtlmCredIsoIum 对象,并存储在偏移量+0x10 处。

Pass-the-Challenge 击败 Windows Defender Credential Guard

在 LSAIso 内部,NtlmIumGetContext 方法检查是否已经分发了“auth cookie”。如果已经分发,该方法将中止。如果没有,它将“auth cookie”存储在提供的上下文句柄中。

Pass-the-Challenge 击败 Windows Defender Credential Guard

根据我的测试,LSAIso 进程将唯一的“auth cookie”值分配给自己的内存,并将其与 LSASS 进程提供的上下文句柄关联。这意味着 LSASS 进程无法直接访问“auth cookie”值,但当它使用上下文句柄与 LSAIso 通信时,LSAIso 可以识别“auth cookie”值与该特定上下文句柄相关联。

为了利用 LSASS 和 LSAIso 进程之间建立的绑定,我实现了一个将模块加载到 LSASS 中的工具。最初,我尝试复制或提取绑定,但 RPC 绑定的内部机制似乎比进程中的单个指针或值更复杂。

关于上下文句柄的使用,通过检查 LSAIso 可执行文件,我们可以看到 NTLM 接口支持的方法。

Pass-the-Challenge 击败 Windows Defender Credential Guard

如果我们检查 LSAIso 中的 NtlmIumProtectCredential 方法,可以看到它检查提供的上下文句柄是否与全局“auth cookie”值匹配。如果不匹配,该方法将暂停 5 毫秒,然后返回错误代码(0xc0000022)。

Pass-the-Challenge 击败 Windows Defender Credential Guard

“auth cookie”看似是函数的第一个参数,但实际上是通过 RPC 编组和上下文处理将上下文句柄转换为“auth cookie”。如果我们尝试使用 64 位值而不是上下文句柄调用该函数,LSAIso 中的 RPC 服务器将返回“bad RPC stub”或“invalid handle”错误,因为我们的客户端编组与服务器的解组不匹配。

也许有人可以想出更智能的实现来跨进程复制绑定和上下文句柄。

现在我们已经了解了 LSASS 和 LSAIso 如何通信,让我们看一些示例。前面提到的 NtlmIumProtectCredential 方法用于保护未受保护的凭据集(如 NTLM)。

总结一下,IumpProtectCredential 函数将加密 MSV1_0_SECRETS_WRAPPER 结构中的凭据并将其返回给 LSASS。这意味着 LSAIso 不维护加密凭据的记录;这是 LSASS 的责任。LSAIso 只知道如何解密凭据并对其执行操作。

让我们检查 LSAIso 中的 NtlmIumCalculateNtResponse 方法。

Pass-the-Challenge 击败 Windows Defender Credential Guard

如前所述,该方法首先验证上下文句柄是否有效。然后检查 MSV1_0_SECRETS_WRAPPER 结构的 IsEncrypted 字段和 NtPasswordPresent 字段是否设置为 true。该结构未记录,但我已对其进行逆向工程,可以在发布的工具中找到。

如果两个字段都为 true,该方法随后调用 IumpUnprotectCredential 解密凭据。值得注意的是,RPC 接口中该方法的声明不会将解密的 MSV1_0_SECRETS_WRAPPER 返回给 LSASS。解密凭据后,该方法调用 CRYPTSP.DLL 中的 SystemFunction009。该函数的 Wine 实现可以在这里找到。

现在让我们看看该方法在 LSASS 中是如何调用的。

Pass-the-Challenge 击败 Windows Defender Credential Guard

可以看到,NdrClientCall3 调用使用“Proxy Info”对象(_MIDL_STUBLESS_PROXY_INFO)作为其第一个参数。NtlmIumCalculateNtResponse 的过程编号 3 作为第二个参数传递,上下文句柄(this + 0x10)作为第四个参数,服务器挑战作为第五个参数,MSV1_0_SECRETS_WRAPPER(加密凭据)作为第六个参数,指向响应的指针作为第七个参数。

LSASS 和 LSAIso 之间共享的 RPC 接口指定了这两个进程之间交换的数据。因此,不能假设 LSAIso 中对参数的操作会对 LSASS 中的相应参数产生任何影响。

这些只是 LSASS 和 LSAIso 如何通信的示例。

Pypykatz

我在自己的版本中对 Pypykatz 做了一些修改,以打印出更多信息。具体来说,我添加了一个功能,在凭据加密的情况下读取并打印出加密的 blob,而不是将加密的 blob 读取为 NT、LM 和 SHA 值。

Pass-the-Challenge 击败 Windows Defender Credential Guard

此外,如果凭据受 Credential Guard 保护,变量 pNtlmCredIsoInProc 实际上保存了上一节中提到的 NtlmCredIsoIum 对象的指针。因此,“Context Handle”可以在该地址的偏移量 +0x10 处找到。

我们还将动态提取用于 NdrClientCall3 调用的“Proxy Info”对象的地址。

输出将如下所示:

Pass-the-Challenge 击败 Windows Defender Credential Guard

修改后的版本可以在我的 Github 上找到。

PassTheChallenge

最后,我创建了一个由可执行文件和 SSP(Security Support Provider,安全支持提供程序)提供程序(DLL)组成的工具。

计划是使用 [AddSecurityPackage](https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-addsecuritypackagea) 将新的 SSP 提供程序加载到 LSASS 进程中。我们的自定义 SSP 随后将启动一个本地 RPC 服务器,我们可以从可执行文件与之通信。

这使得 PassTheChallenge 工具能够连接到我们的安全包启动的新本地 RPC 服务器,从而实现双向通信并调用各种函数。

PassTheChallenge 可执行文件包含多个不同的命令。其中之一是模块注入,可以按如下方式完成:

Pass-the-Challenge 击败 Windows Defender Credential Guard

模块加载后,我们可以发出简单的"ping"命令来检查其是否响应。

Pass-the-Challenge 击败 Windows Defender Credential Guard

以下是该工具的使用方法:

Pass-the-Challenge 击败 Windows Defender Credential Guard

例如,LSAIso 使我们能够比较两个加密的 blob。通过将其与保护我们自己的凭据的方法相结合,我们可以将一个加密的 blob 与另一个加密的 blob 或 NT 哈希进行比较。

Pass-the-Challenge 击败 Windows Defender Credential Guard

<addresses> 参数从 Pypykatz 的输出中获取,表示 <context handle>:<proxy info>。其后跟一个加密的 blob,然后可以将其与另一个加密的 blob 或 NT 哈希进行比较。

从操作安全的角度来看,重要的是要意识到该工具可能不太安全,可能存在错误。在使用此工具时必须小心,因为崩溃 LSASS 进程可能会影响操作系统的稳定性。因此,建议谨慎操作,避免引入任何错误的地址或类似问题。模块加载后,它将保留在 LSASS 内存中,直到下次系统重启。

Impacket

我还在 Impacket 源代码的 ntlm.py 文件中的 computeResponseNTLMv2 函数中添加了六行代码。

Pass-the-Challenge 击败 Windows Defender Credential Guard

这个补丁相对简单。如果密码是 CHALLENGE,它将以一种特定格式打印出域、用户、服务器名称和服务器挑战。然后可以将此挑战与 PassTheChallenge 工具一起使用。PassTheChallenge 的输出可以传递回 Impacket,它将使用计算出的挑战响应和会话密钥。

修补后的版本可以在我的 Github 页面上找到。

进一步研究

在本文中,我们尚未讨论 Kerberos,这将是未来研究的重点。

原文始发于微信公众号(securitainment):Pass-the-Challenge 击败 Windows Defender Credential Guard

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月18日21:33:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Pass-the-Challenge 击败 Windows Defender Credential Guardhttp://cn-sec.com/archives/4176524.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息