开卷有益 · 不求甚解
去年,我写了一篇关于Investigating .NET CLR Usage Log Tampering Techniques For EDR Evasion的博客。在第 1 部分的帖子中,我们介绍了:
最近,我重新审视了研究课题,以结束一些杰出研究的循环,并想我会分享。在这篇文章中,我们将回顾 .NET 使用日志,重点介绍另外两种篡改技术,并回顾防御注意事项。
当执行 .NET 应用程序或将程序集 注入 另一个进程内存空间(由红队)时,加载 .NET 运行时以促进程序集代码的执行并处理各种不同的 .NET 管理任务。由 CLR (crl.dll) 启动的一项任务是, 一旦程序集在(用户)会话上下文中第一次完成执行,就创建一个以执行进程命名的使用日志文件。此日志文件包含 .NET 程序集模块数据,其目的是作为 .NET 本机映像自动生成 (auto-NGEN)的信息文件。
有几个目录专门用于创建使用日志,具体取决于 .NET 用户上下文、.NET 版本或其他特殊注意事项,例如特殊应用程序(例如 Office/Store/等)。一些例子包括:
在进程退出之前,如果目标目录中不存在日志文件,CLR 通常会写入上述文件路径之一。例如,我们可以看到 powershell.exe.log使用日志是在“优雅地”终止 powershell.exe 进程之前首次创建的:
监控使用日志文件创建事件为识别已加载 .NET CLR 的可疑和/或不太可能的进程提供了检测机会。
防止使用日志写入操作的一种省力但有效的方法是在目标UsageLogs
目录上设置访问控制列表 (ACL) 条目。例如,让我们以运行 64 位 .NET 应用程序的名为“user”的用户上下文为目标。
首先,让我们检查 \UsageLogs 目录中的现有 ACL。这可以通过 get-acl PowerShell cmdlet
获得:
get-acl c:\Users\user\AppData\Local\Microsoft\CLR_v4.0\UsageLogs\ |fl
正如预期的那样,“用户”对其各自的主目录结构中的 \UsageLogs 文件夹具有允许-完全控制权限。让我们使用以下来自 Microsoft Docs 的稍作修改的C# 代码,使用System.Security.AccessControl 命名空间中的AddAccessRule () 方法在 \UsageLogs 目录中为“用户”设置拒绝 ACL 条目:
using System;
using System.IO;
using System.Security.AccessControl;namespace FileSystemExample
{
class DirectoryExample
{
public static void Main()
{
try
{
Console.WriteLine("Hello World!");
//Set Deny ACL for "user"
DirectoryInfo dInfo = new DirectoryInfo(@"C:\Users\user\AppData\Local\Microsoft\CLR_v4.0\UsageLogs\");
DirectorySecurity dSecurity = dInfo.GetAccessControl();
dSecurity.AddAccessRule(new FileSystemAccessRule(@"WIN-FLARE\user", FileSystemRights.FullControl, AccessControlType.Deny));
dInfo.SetAccessControl(dSecurity);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}
应用程序运行后,拒绝条目被添加到 ACL:
尽管Allow-FullControl条目仍然存在,但Deny-FullControl条目具有优先权,并且在这种情况下会阻止使用日志的创建以及对 \UsageLogs 目录的访问:
注意:此技术在完成后可能需要进行带外清理。
拆除用户模式安全功能的一种有趣技术是挂钩目标函数并中断内存中的程序流。很好的例子包括通过破坏 Kernel32.dll 中的EtwEventWrite () 函数来规避 ETW (感谢@xpn)和通过修补 amsi.dll 中的AmsiScanBuffer ( ) 来规避 AMSI(感谢@xpn和@ _RastaMouse)。同样,我们是否可以使用一种技术来篡改 .NET 使用日志创建事件?如果我们启动Procmon并检查我们的 .NET 程序跟踪,我们可以深入查看为首次程序执行创建使用日志时发生的一系列事件:
Procmon 让我们深入了解所执行的操作,毫不奇怪,我们可以看到CreateFile操作(使用CreateFileW ())用于打开句柄以创建目标使用日志文件,如以下部分堆栈跟踪所示:
看起来简单地在 KernelBase.dll 中修补CreateFileW () 可能会阻止使用日志的创建,这就是我们应该尝试的,对吧?在探索这种可能性之前,让我们在继续之前考虑一些注意事项和权衡:
现在,假设我们希望继续进行修补,因为它满足用例要求,因此我们在x64dbg调试器中打开我们的目标 64 位 .NET 程序以查找可能帮助我们利用合适的(一组) 补丁说明。首先,我们在CreateFileW ()上设置断点并逐步执行,直到在反汇编程序中找到感兴趣的指令。在这种情况下,它是KernelBase:CreateFileW的 JMP :
此时,我们看到RCX寄存器保存了CreateFileW ()中第一个参数的内存地址,也就是目标Usage Log的文件路径指针:
接下来,我们进入函数并按照说明进行操作。强调一下,我们观察到KernelBase:CreateFileW ()中的几个操作,但没有似乎操纵 RCX 的指令。但是,我们确实看到了对内部KernelBase:CreateFileInternal () 函数的调用,这在我们之前的 Procmon 堆栈跟踪中很明显(如上所示)。
有趣的是,我们最终在进入KernelBase:CreateFileInternal () 后观察到 RCX 在反汇编器中被操纵:
快速的 Google 搜索没有显示有关KernelBase:CreateFileInternall () 的官方文档。它不是 Kernel32 或 KernelBase 的导出,所以它绝对是一个内部函数。找到的最佳信息是James Forshaw在这篇博文中的参考资料。然而,对于我们的用例,我们可能正处于众所周知的兔子洞中冒险的地步,因此分析随后使用CreateFileInternal () 执行的繁重工作,以处理 user 和内核模式超出了本文的范围(但仍然是一个很好的练习)。
所以,让我们回到正轨,跳出CreateFileInternal (),回到CreateFileW ()。在这里,我们观察到当我们接近返回 (RET) 指令时,我们已接近CreateFileW () 调用的结尾。方便的是,这似乎就是我们所需要的,因为它在CreateFileW () 完成后将我们带回 CLR:
毕竟,让我们在下面的 C# 代码示例中简单地使用返回操作码 (0xC3)修补CreateFileW() :
using System;
using System.Runtime.InteropServices;namespace MyVeryEvilTestAssembly
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hellow World");
//Placed at the end of our program for good measure
EvadeUsageLogDetections();
}
static void EvadeUsageLogDetections()
{
byte[] patch = new byte[] { 0xC3 }; //Patch with ret code
IntPtr kernel32 = LoadLibrary("KernelBase.dll"); //We should be able to use Kernel32.dll as well
IntPtr createFileAddr = GetProcAddress(kernel32, "CreateFileW");
VirtualProtect(createFileAddr, (UIntPtr)patch.Length, 0x40, out uint oldProtect);
Marshal.Copy(patch, 0, createFileAddr, patch.Length);
VirtualProtect(createFileAddr, (UIntPtr)patch.Length, oldProtect, out oldProtect);
}
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string plpProcName2);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint pflOldProtect);
}
}
在程序更改后在调试器中再次分析我们的程序后,我们可以看到应用了返回操作码补丁,并且 CreateFile 操作完全阻止了使用日志的创建:
这只是一个补丁选项。可能有更好的方法来处理最后一个错误😉
去年,我向 MSRC 报告了这个问题,他们得出结论,使用日志规避不是安全边界问题。但是,上一篇文章和这篇文章中的安全要点是相关的:
*监控 UsageLog 目录 ACL 更改(通过事件日志):确保在系统审计策略中启用“审计对象访问”设置(成功和失败)或在高级审计策略中启用“审计文件系统”设置配置。
对应监控的\UsageLog 目录设置审核,包括安全主体(例如每个人)。在高级设置中,选择“更改权限”,确保(至少)检查成功,然后应用。
监控事件 ID 4670 以检测 DACL 更改(根据此来源) 示例事件如下所示:
继续监视使用日志创建和删除事件:为加载 CLR 并且实际上没有业务的非托管进程创建日志文件应被视为可疑,尤其是那些讨厌的脚本主机。在使用Beacon 中的执行程序集等命令时,攻击性操作员在执行其 .NET 工具时不会总是考虑使用日志篡改。请记住,某些非托管进程会根据用例合法地加载 CLR,例如mmc.exe*。如果参与者试图清理而不是部署规避技术,则使用日志删除事件可能是妥协的指标。
*继续监视可疑的 .NET 运行时负载:由于监视日志文件创建事件以进行检测可能会命中或未命中,监视可疑的 .NET CLR 负载(例如 clr.dll、mscoree.dll 等)可能会在以下情况下产生有趣的结果调整正确。
继续寻找 CLR 配置旋钮的添加或修改:在HKCU\Software\Microsoft.NETFramework* 和 HKLM\Software\Microsoft.NETFramework注册表项中添加 NGenAssemblyUsageLog 字符串 可能是妥协的迹象。在永久用户/系统环境变量中 寻找 COMPlus_NGenAssemblyUsageLog的前置。启用审计对象访问策略并审计目标键的键写入/设置值事件时生成事件 ID 4657(感谢@Cyb3rWard0g):
*继续寻找可疑进程事件:识别早期进程终止和 DLL 卸载事件在检测使用日志规避技术的上下文中可能很有趣。
近期阅读文章
,质量尚可的,大部分较新,但也可能有老文章。开卷有益,不求甚解
,不需面面俱到,能学到一个小技巧就赚了。译文仅供参考
,具体内容表达以及含义, 以原文为准
(译文来自自动翻译)尽量阅读原文
。(点击原文跳转)每日早读
基本自动化发布(不定期删除),这是一项测试
最新动态: Follow Me
微信/微博:
red4blue
公众号/知乎:
blueteams