介绍

近年来,已经发布了许多用于规避端点安全解决方案和来源的技术,例如 A/V、EDR 和日志记录设施。为实现预期结果而部署的方法通常在复杂性和实施方面有所不同,但是,有效性通常是最终目标(当然,要考虑到潜在的权衡)。防御者可以利用操作系统的原生设施和支持框架来构建质量检测。检测潜在有趣的 .NET 行为的一种方法是监控 .NET 执行事件的公共语言运行时 (CLR) 使用日志(“UsageLogs”)。

这篇文章中,我们将确定防御者如何(可能)利用 .NET 使用日志进行检测和取证响应,调查规避检测日志监控的方法,并讨论捕获使用日志篡改行为的潜在监控机会。

使用 .NET CLR 使用日志检测可疑活动

当执行 .NET 应用程序或将程序集注入另一个进程内存空间(由红队)时,加载 .NET 运行时以促进程序集代码的执行并处理各种不同的 .NET 管理任务。由 CLR (crl.dll) 启动的一项任务是,一旦程序集在(用户)会话上下文中第一次完成执行,就创建一个以执行进程命名的使用日志文件。此日志文件包含 .NET 程序集模块数据及其为 .NET本机映像自动生成 (auto-NGEN)提供信息文件的目的。

在进程退出之前,CLR 通常会写入以下文件路径之一(尽管可能还有其他路径):

<系统驱动器>:\Users\<用户>\AppData\Local\Microsoft\CLR_<版本>_(arch)\UsageLogs<系统驱动器>:\Windows\<System32|SysWOW6$=4>\config\systemprofile\AppData\Local\Microsoft\CLR_<版本>_(arch)\UsageLogs

例如,我们可以看到powershell.exe.log使用日志是在“优雅地”终止 powershell.exe 进程之前首次创建的:

从DFIR 和威胁搜寻的角度来看,分析使用日志对于调查目的非常有机会,正如MENASEC 应用研究团队在这篇出色的博客文章中所概述的那样。从端点监控的角度来看,端点检测和响应解决方案 ('EDR') 可能会监控使用日志文件创建事件,以识别已加载 .NET CLR 的可疑或不太可能的进程。例如,Olaf Hartong ( @olafhartong ) 维护着令人难以置信的Sysmon-Modular项目,并慷慨地提供了一个监视使用日志的规则配置.NET 2.0 活动和有风险的 LOLBIN 的活动。红队人员当然可以预期许多商业供应商正在以类似的方式监控使用日志(例如,捕获 Cobalt Strike 的execute-assembly)。

在深入探讨规避技术之前,让我们简要讨论一下.NET 中的配置

.NET CLR 配置旋钮快速入门,在为 .NET Framework 维护大量有价值的文档并随后发布开源 .NET Core 的同时,Microsoft 提供了对 .NET 生态系统功能组件内部运作的宝贵(显式和隐式)洞察。一般来说,.NET 是一个非常强大且功能强大的开发平台和运行时框架,用于构建和运行 .NET 托管应用程序。.NET 的一个强大功能(特别是在 Windows 上)是能够调整 .NET 公共语言运行时 (CLR) 的配置和行为以用于开发和/或调试目的。这可以通过由CLRConfig检索的环境变量、注册表设置和/或配置文件/属性设置控制的.NET CLR配置旋钮来实现。

滥用配置旋钮并不是一个新概念。其他研究人员已经探索了利用旋钮设置执行任意代码和/或逃避防御控制的各种技术。最近的一些示例包括 Adam Chester ( @_xpn_ ) 使用ETWEnabled CLR 配置旋钮来禁用Windows 事件跟踪 (ETW)和 Paul Laîné ( @am0nsec ) 使用GCName CLR 配置旋钮来指定自定义垃圾收集器 (DLL)用于加载任意代码和绕过应用程序控制解决方案。当然,Casey Smith (@subTee) 用于探索 .NET 的所有内容,包括COR_PROFILER用于防御规避/UAC 绕过的非托管代码加载和Ghost Loader AppDomainManager注入技术(如@netbiosX进一步描述)。

调整 .NET 配置旋钮注册表设置以绕过 CLR 使用日志文件的创建

有趣的是,可以通过在注册表中设置NGenAssemblyUsageLog CLR 配置旋钮或通过配置环境变量来控制 .NET 使用日志的输出位置(如下一节所述)。通过简化为期望值指定任意值(例如假输出位置或垃圾数据),不会创建 .NET 执行上下文的使用日志文件。NGenAssemblyUsageLog CLR 配置旋钮字符串值可以在以下注册表项中设置:

HKCU\SOFTWARE\Microsoft\.NETFrameworkHKLM\SOFTWARE\Microsoft\.NETFramework

在 HKCU 配置单元中配置值将应用于活动用户上下文并影响日志输出,否则日志输出将记录到:

<SystemDrive>:\Users\<user>\AppData\Local\Microsoft\CLR_<version>_(arch)\ UsageLogs 目录和/或 Microsoft Office Hub 路径。在 HKLM 配置单元中配置值将应用于系统上下文并影响日志输出,

否则日志输出将记录到:

<SystemDrive>:\Windows\<System32|SysWOW64>\config\systemprofile\AppData\Local\Microsoft\CLR_<version> _(arch)\UsageLogs 目录路径。让我们通过一个简单的示例来演示预期和被篡改的行为

以下源代码编译为名为“test.exe”的 64 位 NET 应用程序:

在执行应用程序之前,请注意此测试机器上的UsageLogs目录是空的。该目录可以很好地填充在生产或测试机器上。

执行后,会出现一个简单的消息框:

检查UsageLogs目录后,会创建一个名为test.exe.log的文件,其中包含程序集模块信息:

接下来,让我们从UsageLogs目录中删除 test.exe.log 文件以演示篡改行为:

在重新执行 .NET 应用程序之前,让我们使用以下命令验证HKCU中是否存在 .NETFramework 注册表(子)键:

reg query "HKCU\SOFTWARE\Microsoft\.NETFramework"

在这种情况下,注册表项存在并且不包含其他值或子项。(注意:如果.NETFramework键不存在,可以创建)。接下来,将NGenAssemblyUsageLog配置旋钮字符串值添加到 .NETFramework 键并验证更改:

reg.exe add "HKCU\SOFTWARE\Microsoft\.NETFramework" /f /t REG_SZ /v "NGenAssemblyUsageLog" /d "NothingToSeeHere"

程序再次执行:

和预期的一样,在查看目标UsageLogs目录的内容后, text.exe.log 文件并没有出现:

所以您可能会问——当您为NGenAssemblyUsageLog名称提供任意值时,CLR 会做什么?好吧,它实际上只是将任意字符串插入到“正确”构造的路径中。例如,如果我们将路径数据设置为“eeeee”并执行一个 .NET 应用程序,CLR 会将字符串值插入到构造的路径中:

由于找不到路径,使用日志不会写入磁盘。如以下屏幕截图所示,部分UsageLogs路径后缀是硬编码并从clr.dll中提取的:

调整 .NET 配置旋钮环境变量以规避 CLR 使用日志文件的创建

CLR 配置旋钮也可以通过设置带有COMPlus_前缀的环境变量来配置。在以下示例中,COMPlus_NGenAssemblyUsageLog在命令提示符中设置为任意值(例如“zzzz”)。调用 PowerShell(一个 .NET 应用程序)时,COMPlus_NGenAssemblyUsageLog环境变量从父 cmd.exe 进程继承:

退出 PowerShell 后,我们注意到使用日志文件 (powershell.exe.log) 从未在UsageLogs目录中创建:

以在启动子进程时注入COMPlus_ETWEnabled环境变量。修改程序中的一些变量后,可以使用相同的欺骗技术来禁用使用日志输出,如以下代码片段所示:

编译并执行程序后,PowerShell.exe 将启动,

并将COMPlus_NGenAssemblyUsageLog环境变量设置为任意值“zz”:

正如预期的那样,退出 PowerShell 会话后永远不会创建使用日志:

注意:修改后的环境变量欺骗 POC 可以在这里找到。

通过强制进程终止中断 CLR 使用日志输出操作

.NET 配置旋钮提供了一种优雅的方式来影响日志流。但是,有一些方法可以中断使用日志创建过程,而无需进行配置更改。这些方法对中断流程和程序工作流程构成更大的风险。当进程“优雅”退出时会生成使用日志。当程序集完成执行过程时会发生这种情况,例如使用隐式或显式返回语句或在 (C#) 托管代码中使用Environment.Exit()方法时:

但是,如果进程被强制终止,Usage Log 进程将被中断并且永远不会写入磁盘。例如,Process.Kill()方法可用于实现所需的结果(有丢失数据或 shell的风险):

通过模块卸载中断 CLR 使用日志输出操作

在另一个有趣但有风险的测试场景中,篡改加载的模块 (DLL) 可通过破坏进程的稳定性并导致其过早退出来破坏 CLR 使用日志的创建。为此,我们利用了 .NET 委托函数指针和由 The Wover ( @TheRealWover)和 b33f ( @FuzzySec ) 编写的强大的DInvoke库。对于测试用例,为FreeLibrary( )声明了一个委托函数指针Win32 API 函数被调用以从正在运行的 .NET 托管进程中卸载模块。删除单个模块或较少的模块组合可能会达到相同的效果,但是,我们将卸载几个 .NET 模块以增加使进程不稳定以强制终止和中断使用日志创建的机会(注意:我们正在挑选.NET 模块,但也可以卸载其他 DLL)

要成功卸载模块,我们必须首先使用 DInvoke 的GetLibraryAddress()获取指向FreeLibrary()函数的库地址的指针。然后,我们使用来自 .NET 'Interop' 服务的GetDelegateForFunctionPointer()方法将函数指针转换为FreeLibrary() API 方法的可调用委托。接下来,我们通过使用 DInvoke 的GetPebLdrModuleEntry()方法在 .NET 进程的进程执行块 (PEB)中搜索每个模块的基地址引用来获取每个已加载模块 (DLL)的句柄。最后,我们称FreeLibrary使用每个模块的句柄委托函数以将其从内存中卸载。此测试用例中的 POC 代码如下所示:

编译并执行代码后,Usage Log 文件创建过程中断(如预期的那样):

防御考虑

继续监控使用日志文件和目录。为使用日志的创建和修改实施分析/签名/检测。尽管这里展示了可疑的攻击技术,但这种检测仍然非常有价值。攻击性操作员在执行他们的 .NET 工具时不会总是考虑使用日志篡改。

查找通常不会加载 CLR 以创建使用日志的(不规则的)非托管二进制文件和脚本主机的日志实例。利用 Olaf Hartong ( @olafhartong ) 的 Sysmon-Modular规则配置和/或此 Elastic Security规则查询作为开始使用规则集的基准。此外,Samir ( @SBousseaden ) 为使用 .NET 工具监控 WinRM 横向移动提供了出色的检测提示。

此外,审计和监视删除使用日志文件的尝试,因为攻击性操作员可能会从磁盘中删除使用日志文件以掩盖他们的踪迹。注意:这是 MENASEC博客文章中提到的 tradecraft 。

监视可疑的 .NET 运行时负载。如果部署了使用日志规避,则识别可疑的 .NET CLR 运行时负载可能是一种有趣的补偿检测机制。加载 CLR 的非托管进程(例如MS Office)。可能是妥协的指标。

监控 CLR 配置旋钮的添加或修改。Roberto Rodriguez ( @Cyb3rWard0g ) 撰写了一篇精彩的文章,用于检测 [COMPLUS_]ETWEnabled 配置旋钮调整行为,其中包括 SACL 审计建议、Sysmon 配置设置、Sigma 规则和 Yara 规则。可以应用相同的方法来检测 [COMPLUS_]NGenAssemblyUsageLog 配置旋钮修改。(复制的)建议摘要包括以下内容:

寻找在

HKCU\Software\Microsoft\.NETFramework

HKLM\Software\Microsoft\.NETFramework

注册表项中添加NGenAssemblyUsageLog字符串。正如 Roberto 所指出的,当启用审计对象访问策略并且审计目标键的键写入/设置值事件时,会生成事件 ID 4657:

在系统环境变量中寻找 COMPlus_的前缀,以及适用的临时环境变量——例如进程命令行、转录日志等。

监控过程模块篡改。在大多数组织中,监视“可疑”进程终止事件可能并不实用。但是,从正在运行的进程中卸载 DLL 可能是一个有趣的检测机会。正如 spotheplanet ( @spotheplanet ) 在这篇文章中所描述的,可以使用 ETW Microsoft-Windows-Kernel-Process提供程序来跟踪模块卸载。