在之前的博文中,我们分享了我们追踪传统内存攻击的方法,以及对许多注入技术的深入分析。作为我 DerbyCon 演讲的后续,本文将探讨攻击者使用基于 .NET 的内存技术规避检测这一新兴趋势。我将讨论针对这些 .NET 技术的基于事件(实时)和按需检测策略。在 Endgame,我们明白这些不同的检测和预防方法是互补的,并且共同构成了抵御内存攻击的最强大的防御。
.NET 的吸引力
使用 .NET 内存技术,甚至标准的 .NET 应用程序,对攻击者来说很有吸引力,原因有很多。首先也是最重要的是,.NET 框架在所有 Windows 版本中都预安装。这一点很重要,因为它使攻击者的恶意软件能够在所有受害者之间具有最大的兼容性。接下来,.NET PE 元数据格式本身相当复杂。由于资源限制,许多端点安全供应商对这些应用程序的托管(.NET)结构的了解有限,超出了与普通、非托管(非 .NET)应用程序共享的内容。换句话说,大多数杀毒软件和安全产品无法很好地防御恶意 .NET 代码,而攻击者也知道这一点。最后,.NET 框架具有内置功能,可以通过Assembly.Load(byte[])函数(及其各种重载)动态加载仅内存模块。此函数允许攻击者轻松地创建加密程序/加载程序,将他们的有效负载保存在磁盘之外,甚至绕过应用程序白名单解决方案,如Device Guard。本文重点关注 Assembly.Load 函数,因为它支持攻击者的一系列强大功能。
.NET 攻击者技术
利用 .NET 内存技术的攻击者并非完全新鲜事物。但是,在过去六个月中,攻击技术明显增多,我将简要讨论一下以说明其危险性。例如,在 2014 年,据信在中国运营的威胁组织 DEEP PANDA 被观察到使用用 .NET 编写的多阶段 MadHatter 植入程序。更有趣的是,此植入程序在经过多阶段 Assembly.Load 引导过程后仅存在于内存中,该过程以 PowerShell 开始。PowerShell 可以直接调用 .NET 方法,Assembly.Load 函数也不例外。就像调用 [System.Reflection.Assembly]::Load($bin) 一样简单。最近,OilRig APT 组织使用了一个名为 ISMInjector 的打包 .NET 恶意软件样本以规避基于签名的检测。在解包过程中,该样本使用 Assembly.Load 函数访问嵌入的下一阶段恶意软件,称为ISMAgent。
第三个例子,对红队来说更为熟悉,就是ReflectivePick,由Justin Warner和Lee Christensen开发。ReflectivePick 允许 PowerShell Empire 将 PowerShell 注入并引导到任何正在运行的进程中。它利用 Assembly.Load() 方法加载他们的 PowerShell 运行程序 DLL,而无需将其放到磁盘上。下图显示了其工具的相关源代码。
需要指出的是,Assembly.Load 作为 .NET 框架的核心功能,通常用于合法程序中。这包括内置的 Microsoft 应用程序,这导致了一系列有趣的防御规避和应用程序白名单绕过。例如,Matt Graeber发现了一个 Device Guard 绕过,该绕过针对竞争条件以劫持对 Assembly.Load 的合法调用,允许攻击者在受 Device Guard 保护的主机上执行任何未签名的 .NET 代码。由于修复此类技术的难度,Microsoft 目前已决定不为该问题提供服务,从而为攻击者提供了一种方便的“永久性漏洞”,针对使用应用程序白名单加固的主机。
Casey Smith也发布了大量关于绕过应用程序白名单解决方案的研究。许多这些技术的核心目标是签名的 Microsoft 应用程序,这些应用程序使用攻击者提供的代码调用 Assembly.Load 方法。一个例子是 MSBuild,它预装在 Windows 上,并允许攻击者在合法且签名的 Microsoft 进程中执行未签名的 .NET 代码。这些技术不仅对针对应用程序白名单保护环境的攻击者有用。由于它们允许攻击者代码以非常规方式加载到合法的签名进程中,因此大多数防病毒和 EDR 产品对攻击者活动视而不见,并且可以绕过。
最后,James Forshaw开发了DotNetToJScript技术。其核心是,此技术利用 BinaryFormatter 反序列化方法仅使用 JScript 加载 .NET 应用程序。有趣的是,此技术在底层将调用 Assembly.Load 方法。DotNetToJscript 为许多新的巧妙技术打开了大门,这些技术可以以隐蔽的方式执行未签名的 .NET 代码。例如,James演示了如何将 DotNetToJScript 与COM 劫持和 Casey 的 squiblydoo 技术相结合,将代码注入受保护的进程。在另一个示例中,Casey 在 universal.js 中将 DotNetToJScript 武器化,以执行任意 shellcode 或 PowerShell 命令。
可以滥用来以隐蔽方式执行攻击者代码的 Microsoft 签名应用程序数量令人眼花缭乱。幸运的是,社区已迅速在许多地方公开记录和跟踪它们。一个很好的参考是Oddvar Moe的UltimateAppLockerByPassList,另一个是 Microsoft 自身的参考。
检测 .NET 攻击
如这些示例所示,攻击者正在以各种方式利用 .NET 来击败和规避端点检测。现在,让我们探讨两种检测这些攻击的方法:按需和基于实时的方法。
按需检测
按需检测利用快照进行时间类型数据收集。当攻击发生时,您不需要持续运行代理并收集数据,但您确实需要在搜寻/收集期间恶意代码正在运行。诀窍是关注可以捕获与攻击者无关的技术且信噪比高的有价值数据。一个例子是用于检测传统无管理内存注入技术的Get-InjectedThread脚本。为了演示检测.NET恶意软件使用Assembly.Load函数的情况,我利用了Will Schroeder等人开发的PowerShell Empire。Empire允许您通过远程引导PowerShell将代理注入任何进程。如下所示,注入后,calc.exe加载了PowerShell核心库System.Management.Automation.ni.dll。
仅此一项事实就可能很有趣,但令人惊讶的是,大量合法应用程序都加载了PowerShell。将此与进程网络活动结合起来,并查找所有数据中的异常值,可能会让您获得更好的效果。经过更深入的检查,我们发现了一些更有趣的东西。如下所示,内存区域0x2710000包含一个完整的.NET模块(存在PE头)。内存区域的特征有点不寻常。类型为MEM_MAPPED,尽管没有关联的文件映射对象(请注意,ProcessHacker中的“Use”字段为空)。最后,该区域的保护属性为PAGE_READWRITE,这令人惊讶的是不可执行的。这些内存特性是使用Assembly.Load(byte[])方法加载仅内存模块的副作用。
为了自动化此类搜寻,我编写了一个名为Get-ClrReflection的PowerShell函数,它查找内存特征的这种组合,并将任何匹配项保存以供进一步分析。以下是将其运行在感染了Empire的工作站上的示例输出。
同样,您会看到利用Assembly.Load函数的合法应用程序的匹配项。一个常见的误报是针对XmlSerializer生成的程序集。标准的搜寻实践适用。按进程名称或更优地使用模糊哈希匹配对匹配项进行分组。例如,ClrGuard(下一节中详述)将使用“-f”开关为您提供TypeRef哈希。以下是Empire的示例。
基于事件的检测
基于事件的检测非常棒,因为您无需依赖攻击者在您搜寻时处于活动状态的运气。它还为您提供了实时阻止攻击者技术的机会。为了向运行.NET的CLR提供信号,我们开发并发布了ClrGuard。ClrGuard将挂钩系统上的所有.NET进程。然后,它对本机LoadImage()函数执行内联挂钩。这是Assembly.Load()在CLR内部调用的函数。当观察到事件时,它们会通过命名管道发送到监视进程,以便进一步检查和缓解决策。例如,Empire的psinject函数可以像下图所示那样立即检测到并实时阻止。
类似地,OilRig的ISMInjector可以快速检测到并阻止。
下面的另一个示例显示了ClrGuard对抗Casey Smith的universal.js工具的实际应用。
虽然我们不建议您在整个企业中运行ClrGuard(它处于概念验证级别),但我们希望它能激发社区针对这些类型的.NET攻击进行讨论和创新。这些类型的防御技术为Endgame产品提供了保护能力,并且企业级的ClrGuard类功能即将推出。
结论
重要的是要感谢那些进行出色的进攻性安全研究并愿意为了社区的更大利益发布其能力和技能的人。.NET内存攻击的最新进展表明,现在是防御者提高警惕,与更高级别的红队和对手正面交锋的时候了。我们希望ClrGuard和Get-ClrReflection有助于平衡局势。这些工具可以增强防御者对.NET恶意软件活动的了解,并提高对攻击者技术最新演变的可见性。