ThievingFox 是一组后漏洞利用工具,用于在渗透测试和类似活动中从工作站和服务器收集凭据。它的工作原理是让目标应用程序加载恶意库,该库执行内存挂钩以收集凭据。
在渗透测试期间,我使用KeeThief和 mimikatz 的ts::mstsc 模块在受感染的管理工作站上从 KeePass 和 Windows 上的默认 RDP 客户端收集额外凭据,并取得了巨大成功。
使用这些工具时,我发现了一些缺点,ThievingFox旨在解决,即:
并非所有管理员都会使用这些特定应用程序,这需要识别潜在的受害者
管理员可能不会一直使用 KeePass 或默认 RDP 客户端,因此需要主动等待他们打开它们
在这样做的同时,我添加了除了 KeePass 和默认 RDP 客户端之外的其他目标。
该工具可以在Github上找到。
https://github.com/Slowerzs/ThievingFox
设计考虑
从高层次来看,ThievingFox 的工作原理是将代码注入目标应用程序,然后挂接敏感功能,从而捕获凭据。在设计 ThievingFox 时,我们定义了几个目标。
首先,挂钩过程应该对最终用户透明 - 因为 ThievingFox 的目标是协助渗透测试,所以它不应该影响应用程序的使用。
此外,对于给定的目标应用程序,挂钩过程应该在一定程度上是稳定的。与具有类似目标的其他工具和概念验证不同,ThievingFox 不使用字节模式签名来识别目标函数,因为这些签名在环境和版本之间可能非常脆弱。这使得找到有趣的挂钩位置更具挑战性。没有签名,我们就无法直接挂钩负责处理凭据的函数,因此我尝试找到具有导出符号的库,这些库以某种方式用于处理凭据,以挂钩它们。这使得挂钩更加健壮,因为使用的其他库中的外部函数不太可能在不同版本和环境中发生变化。
最后,进行多次健全性检查以降低破坏合法应用程序任何功能的风险。
作为使用的前提条件ThievingFox,必须获得本地管理员权限,并且不得过滤 SMB 访问。
主动注入与被动注入
无论目标应用程序是什么,为了捕获凭据,必须将该应用程序挂钩。为了能够挂钩任何函数,我们必须能够在目标进程中运行一些代码。为此,必须执行某种形式的进程注入。
第一个选择是执行某种形式的“主动”注入 - 某种OpenProcess// WriteProcessMemoryAPICreateRemoteThread调用的组合。
这有几个缺点:
这些 Windows API 和相关的系统调用受到 EDR/AV 的严格监控。
这需要某种形式的轮询来确定受害进程是否正在运行,但出于性能原因,这并不理想。
要实际执行轮询,最常见的选项是使用 WMI 或创建远程进程,这两种方式都可以作为横向移动的一种形式被检测和阻止。
所有这些原因使得“被动”注入更可取。我们不是每次检测到受害者进程时就主动将代码注入其中,而是诱使受害者应用程序在启动时加载我们的附加代码。这解决了所有主要问题:无需轮询,无需横向移动即可在远程主机上主动创建进程,而且“被动”注入不需要调用受监控的 Windows API。
这些“被动”注射采用了不同的技术,详见后面的章节。
加密
当从任何应用程序捕获任何凭据时,它们必须由渗透测试人员的机器检索才能使用。起初,考虑了一种通过 HTTP 进行渗透的方法,但这需要永久监听器,而且由于防火墙的存在,这种方法也不切实际。最终选择的解决方案是将捕获的凭据存储在目标受害者文件系统上,并等待稍后通过 SMB 从磁盘收集凭据。
为了安全地将凭据存储在磁盘上,使用非对称加密 - 目标主机上的每个 DLL 都嵌入一个公钥,而私钥保留在ThievingFox运行的机器上。
我选择使用libsodium,因为它使用简单,并且具有多种语言(Python、Rust、.NET)的绑定 - 带有密封盒。
本机应用程序
DLL 代理
DLL 侧载可能是在目标应用程序中注入额外代码的最知名技术。其主要思想是通过滥用 Windows 库加载器的默认配置来劫持主可执行文件导入的某个 DLL,该默认配置会在可执行文件所在的文件夹中搜索相应的库名称。
对于我们所针对的应用程序,DLL 侧加载对于 Windows 二进制文件来说是不切实际的 - 我们不想覆盖C:\Windows\System32由许多其他应用程序使用的 DLL,这可能会导致我们的恶意 DLL 在意外的过程中被加载。
因此,DLL 侧载仅用于一个应用程序:KeePassXC,它附带多个 DLL,它们是侧载的良好目标。KeePassXC 使用的加密库之一是目标,argon2.dll并被我们的挂钩 DLL 替换:
为了确保我们替换的 DLL 提供的原始功能仍然有效,我们重新导出符号并将其转发到原始 DLL。为了自动化此过程,这是在编译期间使用 Rust 构建脚本完成的(示例可在此处找到)。
这会导致挂钩 DLL 导出以下符号:
从 的第一个版本开始ThievingFox,KeePassXC它是唯一使用 DLL 代理的应用程序。
连接 KeePassXC
要检索用于解锁数据库的主密钥,我们必须挂接一些外部函数,该函数将主密钥的引用传递给该函数。在 中KeePassXC,主密钥的寿命非常短,并且会尽快清零。
我能找到的唯一符合此条件的函数是Botan::Buffered_Computation::update,它是用于更新内部表示以计算主密钥哈希值的方法。这也可以在此处的源代码中看到。
https://github.com/keepassxreboot/keepassxc/blob/develop/src/crypto/CryptoHash.cpp#L74
这个方法被调用多次,使用主密钥,但也使用我们不感兴趣的数据。但通过简单的启发式方法(确保字符序列是有效的 UTF-8,并确保所有字符不是空字节和其他不可打印字符),可以从其他类型的数据中过滤掉主密钥。
现在我们知道了要针对哪个函数,我们必须执行实际的挂钩。我选择执行简单的内存字节修补。为此,我使用了minhook库,因为它简单、稳健,并且易于与 Rust 进行交叉编译(使用minhook-sys crate)。
所有这些使得每当 KeePassXC 应用程序解锁数据库时都可以检索 maskerkey(和潜在的密钥文件):
我们使用类似的方法来钩住其他本机应用程序。为了确定要使用哪些导出符号,我们使用了API 监视器和手动逆向工程的组合。
COM劫持
那么其他本机应用程序呢?正如我们之前所讨论的,替换 DLLC:\Windows\System32并不是理想的选择。幸运的是,我们感兴趣的所有 Microsoft 应用程序都以某种形式使用了 COM。
COM 劫持使攻击者能够通过简单地修改注册表值在进程中加载自己的库。注册表值描述了应用程序尝试实例化相应的 COM 类时加载的 DLL。这非常适合我们的场景!
并非所有类都可以被劫持,实现类功能的可执行文件必须在进程内实现。要确定某个类是否合适,所需的信息是与类 ID (CLSID) 关联的注册表项。
使用ProcMon可以轻松识别可能被目标进程劫持的潜在 COM 类:
对所有目标应用程序重复此过程。对于每个应用程序,都会选择一个很少被其他应用程序使用的 CLSID。
挂钩 RDCMan
RDCMan 使用 CLSID 4EB89FF4-7F78-4A0F-8B8D-2BF02E94E4B2,它与 MsRdpClient5 类相关联,该类包含接口IMsRdpClient5。此接口实现了创建 RDP 客户端所需的所有功能,包括身份验证。所有这些功能都在mstscax.dllDLL 内部实现。具体来说,用于交出用于身份验证的帐户密码的函数是put_ClearTextPassword
不幸的是,这些函数的符号不能直接导出:
但是,由于mstscax.dll实现 COM 方法的 DLL 是进程内服务器,因此没有编组来调用 COM 方法。这意味着我们可以实例化我们自己的对象,查看其内部vtable以直接找到我们感兴趣的函数(与身份验证相关的函数)在内存中的地址,并将它们挂钩,就像我们之前对 KeePassXC 所做的那样!
RDCMan这是和所采用的方法MobaXTerm,它们都使用不同版本的IMsRdpClient接口。
挂钩 LogonUI
挂钩LogonUI.exe与挂钩其他本机应用程序没有什么不同(在 ThievingFox 中,COM 劫持和库挂钩的组合用于LogonUI.exe)。然而,我想强调其影响。
LogonUI.exe是负责在计算机上实际存在的用户解锁其会话时处理凭据的进程。此外,LogonUI.exe还处理用于 RDP 连接的凭据(至少在未使用受限管理员时)。
这意味着如果服务器被毒害,并且管理员使用 RDP 连接到该服务器,则会捕获其凭据:
连接 KeePass
与本机应用程序不同,挂钩主二进制文件本身是可能的:反射使我们能够在接收凭据的主程序集内找到函数,而无需任何字节模式来识别函数。
实际的挂钩也不同:我们将要挂钩的函数是用.NET 编写的函数,因此如果我们的挂钩也在托管代码中,那么操纵其参数将容易得多。
为了执行修补,我们检索与Method钩子和目标函数关联的对象相关联的本机函数指针,然后简单地切换指针。
在切换指针之前,我们要求 CLR 对钩子和目标函数进行 JIT 编译,以确保它们是用本机代码实现的,并且不会被 CLR VM 模拟,也不会在内存中移动。我还确保钩子和原始函数具有相同的原型,以便能够操纵参数。
这有一个缺点:以前版本的 CLR 运行时不一定以相同的方式工作,因此,像这样切换函数指针可能不起作用。幸运的是,大多数最新版本的 .NET 框架都以这种方式运行。已添加健全性检查以确保如果我们无法检索正确的函数指针,则不会发生任何事情。
硬化
那么我们能做些什么呢?
至少对于KeePass和KeePassXC来说,总体立场是:如果恶意应用程序在计算机上运行,就无法采取任何措施。
KeePassXC 采取了针对“主动”注入的措施:它修改其自身进程句柄的 ACL,以拒绝所有人访问,包括当前用户相关源代码。
虽然这并不能阻止特权用户通过使用来获取 KeePassXC 的句柄SeDebugPrivilege,但如果低特权用户正在使用 KeePassXC,而同一用户也在运行恶意应用程序,这是一个很好的措施。
Configuration/Security/ProtectProcessWithDaclKeePass 具有相同的功能,尽管必须手动配置配置选项,如文档中所述。
某些应用程序(例如 Chrome)会强制要求所有 DLL 都经过 Microsoft 签名,从而防止被动注入PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY。这将要求应用程序使用的所有依赖项都经过签名,并会阻止使用第三方插件。但是,还有其他几种 技术可以强制 KeePass 导出未加密的数据库,而无需注入。
不幸的是,除了 PPL 反恶意软件(据我所知)之外,Windows 不提供任何其他机制来保护敏感的第三方用户模式应用程序。因此,凭据管理器无法从中受益。VirtualBox 尝试模拟这种保护机制,但这需要设备驱动程序和复杂的用户模式代码。
下一步
截至本文发表时,以下应用程序已成为攻击目标ThievingFox:
对于 的未来版本ThievingFox,我打算针对其他应用程序。特别是使用 Electron 框架的凭证管理器 ;)
相关工作
KeeThief介绍如何从正在运行的 KeePass 进程中检索凭据 - 作者:Lee Chagolla-Christensen和Will Schroeder
https://github.com/GhostPack/KeeThief
Mimikatz 的ts::mstsc 模块用于从正在运行的 mstsc 进程中检索凭据 - 作者:Benjamin Delpy
https://twitter.com/gentilkiwi
RDPThief挂钩 mstsc.exe 来捕获凭据 - 作者Rio Sherri
https://www.mdsec.co.uk/2019/11/rdpthief-extracting-clear-text-credentials-from-remote-desktop-clients/
KeePassHax在内存中检索 KeePass 主密钥 - 作者:holly-hacker
https://github.com/holly-hacker/KeePassHax
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里