几乎所有现代攻击都会使用 Mimikatz、SharpHound、SeatBelt、Rubeus、GhostPack 和其他社区可用的工具集。这种所谓的 githubification 正在降低攻击者的成本,并将重点从恶意软件开发转向逃避安全机制。当攻击者可以通过简单地重用现有工具并学习如何使用它们执行攻击来获得更多收益时,创建一个可以被 EPP 解决方案检测到的工具有什么意义?
无文件和无恶意软件攻击、LOLBAS 列表的大量使用、运行时加密、下载程序、打包程序,以及旧的、重新利用的和全新的技术来逃避各种安全工具和控制,所有这些都被攻击者积极使用。没有人会对 InstallUtil.exe 中嵌入的 Mimikatz 感到惊讶。我们将在本文中描述一种可用于在内存中隐藏攻击性活动的逃避技术,即如何从内存中删除指标。然后,我们将为你提供一些可能对检测此技术有用的工具和方法。我们将回顾运行在或使用CLR(公共语言运行时)环境中的应用程序,如PowerShell、许多LOLBAS工具和多个c#实用程序。
如果你已经熟悉 CLR,则可以直接转到 CLR 中的检测逃避。
CLR
CLR一般指公共语言运行库。公共语言运行库 (common language runtime,CLR) 是托管代码执行核心中的引擎。运行库为托管代码提供各种服务,如跨语言集成、代码访问安全性、对象生存期管理、调试和分析支持。
当你编译用 C# 编写的源代码时,编译器不会给你一个准备运行的 PE 文件,而是一个程序集。这主要是一组语句(CIL 代码),供运行时环境在此程序集执行期间生成本机代码(依次执行)。在运行时从程序集创建本机代码的过程称为 JIT 编译。
编译应用程序生成的程序集将包含以下数据:
程序集中的类、接口、类型、方法和字段的元信息,CLR 需要这些数据来处理编写的代码:加载它、引用它、从另一个代码运行一个代码以及传递输入和输出数据,读取和应用这些数据的过程称为反射。
代码本身,在模块中定义,如果没有在CLR中处理,它就无法启动。
包含有关安全性、版本、依赖项和程序集元素的数据的程序集清单。清单定义了执行代码所需的内容。例如,如果你的代码需要启动 https://github.com/JamesNK/Newtonsoft.Json,它将在清单中定义。
所有类型的文件和数据,它们可以包含在程序集本身中,也可以作为单独的文件存储。
程序集的加载和执行是一个复杂的过程,接下来就让我们仔细看看它是如何工作的。
进程启动
ETW CLR Runtime Provider (GUID e13c0d23-ccbc-4e12-931b-d9cc2eee27e4)提供了一个使用托管代码启动进程的指示。
CLR 启动
Microsoft 将 CLR 实现为 DLL 内的 COM 服务器,这意味着标准的 COM 接口用于 CLR 环境,并为该接口和 COM 服务器分配了一个 GUID。当你安装 .NET Framework 时,代表 CLR 的 COM 服务器就像任何其他 COM 服务器一样在 Windows 注册表中注册。任何 Windows 应用程序都可以托管 CLR 环境。这种托管会生成带有CLR激活信息的事件187,并包含COM激活数据:StartupMode、包含有关如何加载 CLR 的有用信息的 ComObjectGUID 字段,这在 COM 激活的情况下尤其有趣。
如果你需要有关此主题的额外信息,请参阅随 .NET Framework SDK 提供的 MetaHost.h C++ 标头文件。此标头文件指定 GUID 标识符和非托管 ICLRMetaHost 接口的定义。你将学习如何使用任何语言运行 CLR:C++、Python 等。
应用程序域加载
事件156出现。将应用程序域加载到CLR中。当CLR COM服务器初始化时,它创建一个应用程序域。AppDomain 表示一组通常实现应用程序的程序集的逻辑容器。此外,应用程序域是在 CLR 中实现的一种机制,它允许你将一组应用程序作为单个进程运行,以确保它们的相对隔离,同时允许它们更快地相互交互。在一个进程中可以有多个应用程序域。第一个应用程序域将在 CLR 环境初始化时创建。它被称为默认应用程序域,只有在 Windows 进程终止时才会被销毁。
在一个应用程序域中创建的对象不能被另一个应用程序域中的代码直接访问,当应用程序域代码创建一个对象时,该对象“属于”此应用程序域。此外,对象(包括工件)的存在时间不得超过其代码创建它的应用程序域的生命周期。其他应用程序域中的代码只能通过编组(数据传输)、引用或值访问其他应用程序域中的对象。这确保了清晰的分离和边界,因为一个应用程序域中的代码不能直接引用由其他应用程序域中的代码创建的对象。这种隔离使得从进程中卸载应用程序域变得容易,而不会影响在其他应用程序域中运行的代码。这正是允许卸载应用程序域的原因。 CLR 不支持从 AppDomain 卸载单个程序集的功能。但是,你可以命令 CLR 卸载整个 AppDomain,这将卸载它当前包含的所有程序集。
每个在自己的地址空间中运行的应用程序都是一个很棒的功能,它确保了一个应用程序的代码不能访问另一个应用程序使用的代码或数据。进程隔离可防止安全漏洞、数据损坏和其他不可预测的操作,使 Windows 及其上运行的应用程序可靠。不幸的是,在 Windows 中创建进程非常耗时。Win32 CreateProcess 函数非常慢,Windows 需要大量内存来虚拟化进程地址空间。
但是,如果应用程序完全由可靠安全的托管代码组成,并且不调用非托管代码,那么在同一个Windows进程中运行多个托管应用程序就没有问题。应用程序域提供了保护、配置和终止每个应用程序所必需的隔离。CLR中代码的隔离单元是应用程序域,而不是进程。我们可以说,根据一些假设,在WinAPI语义中启动的进程等同于应用程序域的创建。对于SOC分析师来说,最好将应用程序域载荷和进程启动事件视为功能相同。
对于可以在单个Windows进程中运行的应用程序域的数量没有硬编码限制,与IIS服务器站点一样,每个站点都是一个单独的应用程序域,具有自己的隔离性,可以从服务器卸载而不影响其他站点。
Assembly load
接下来,如果要在应用程序域之间共享程序集,则需要将程序集加载到应用程序域或共享域中,我们不会在本文中讨论它们。程序集为它所包含的代码确定一组规则,为 CLR(和其他代码)提供有关程序集中定义的类型和类的信息。
模块加载
带有CIL代码的模块被加载到程序集中,CIL代码进入JIT编译,根据清单生成可执行的本地代码。注意,我们需要卸载整个应用程序域,以便删除模块中定义或出现在所描述的进程中的工件。
该图显示了运行单个 CLR COM 服务器的单个 Windows 进程。此 CLR 环境当前管理着两个 AppDomain。两个 AppDomain 都有自己的加载器堆,每个堆都维护着自 AppDomain 创建以来可用的类型的记录。每个加载器堆都有一个方法表,如果该方法至少执行了一次,则方法表中的每个条目都指向 JIT 编译的本机代码。
通过c# J. Rihter实现CLR
CLR 中的检测逃避
首先,让我们看看何时以及如何检测到攻击。为此,我们将使用 Covenant 框架分析攻击。
在单个应用程序域中运行 Covenant
首先让我们看看 Covenant 框架是如何工作的,通过启动 Grunt 代理并执行典型的攻击活动,我们可以收集有关当前用户、AutoStart 和 AutoRun 条目、加载到当前用户会话中的 Kerberos 票据以及浏览器历史记录的信息。因此,我们可以看到多个程序集加载到我们的应用程序域中:Seatbelt AutoRuns、Seatbelt ChromeHistory、Rubeus klist 等。
Rubeus 和Seatbelt的加载组件
一组具有不同功能的程序集被加载到同一个应用程序域和一个进程中。这些程序集可以很容易地被带有签名分析的经典安全工具检测到,因为代码和执行结果中存在大量指标。而卸载它们也是不可能的,因为它们与实现与 C2 交互的代码链接到相同的应用程序域。
进程启动和注入
攻击者如何逃避呢?他们可能会使用经典的代码拆分方法,即代码注入或出于恶意目的启动新进程。然而,这并不总是可能的:事实上,在某些情况下,注入和启动新进程对于安全监控工具来说都太显眼了。此外,并非总是可以关闭包含指示器的进程,例如,如果攻击者使用了系统进程。为了说明这一点,我们可以创建 Mimikatz shellcode (Donut) 并使用从 Covenant 的 Grunt 启动的进程注入将其注入进程(我选择了 PowerShell)。此外,我们可以看到注入器进程的启动和注入。我们可以使用 Sysmon 和 SwiftOnSecurity 的“默认”配置来监控这些活动。
上传注入器应用程序
启动注入器并注入shellcode
Grunt 在受害者主机上使用以下命令行(glist 中的 b64 编码的 shellcode)执行 ProcessInjection.exe:
Sysmon 显示了许多可疑活动,如下所示。
使用 AV/EPP/EDR 是无法完成执行链的,因为它是众所周知的攻击者活动模式,所以运行/生成/注入代码的旧方法非常引人注目。
基于 COM 的 CLR 代理
接下来,我将要描述一种通过在 Explorer 进程中激活 COM 服务器来使远程机器下载和运行代码的方法。
让我们用以下输入将COM服务器注册为MSCOREE库(在Windows中实现CLR功能):程序集名称和具有服务器实现的类。因此,我们指示 CLR 在激活时从指定的类加载实现服务器的代码。
可以仔细观察一下 CodeBase 项,它允许我们使用 COM 服务器而无需在全局程序集缓存中注册我们的程序集,并代表用户定义 COM 服务器(GAC 注册需要管理权限)。这个参数需要一个URI,有点不寻常。主机进程从网络下载包含 COM 服务器的程序集并启动它。 COM 服务器注册也可以通过网络进行,我们只需要更改系统注册表来定义它。
CLR的配置方法和参数有多种,配置文件和全局环境变量。此外,还有一个特殊参数允许或禁止(默认情况下禁止)从远程源加载程序集。但是,默认情况下允许在 Explorer 主机进程中使用 COM 服务器激活 CLR(可以从远程源下载程序集)。通过分析这不是漏洞,而是某种特别的功能。
演示攻击:使检测复杂化
虽然可以检测到单个应用程序域(在单个应用程序域中运行 Covenant)和项目创建/注入(进程启动和注入)可以检测到不同程度的难度,但主要是高调、可见的活动。我们还展示了如何设置远程代码加载到 CLR。现在让我们看看在一个基于com的CLR代理的演示中,检测任务是如何变得复杂的。我们将在远程主机上的Explorer进程上下文中运行普通的Mimikatz,并在Mimikatz执行后清理工件。这次演示攻击是在我们已经访问过的主机上进行的。具体视频可以点击这里。
我们有来自 Mimikatz 存储库的 Yara 扫描仪和 Yara 规则作为我们扫描内存的首选 EPP。 Inveigh 和 Mimikatz 已经安装在受害者的主机上。首先,让我们检查 Yara 规则是否匹配。
现在让我们看一下 explorer.exe 进程(PID 3896)并确认里面没有 Mimikatz 的迹象。接下来,我们重新启动 explorer.exe 以再次显示它是干净的并且不包含任何 CLR 程序集。
接下来,我们移动到攻击者的主机(01:40)。 Explorer 处理程序被添加到受害者主机的注册表中。当受害者启动 explorer.exe 时,将加载来自远程(攻击者)主机的程序集以供执行。
返回到受害者的主机(02:30),我们通过重新启动 explorer.exe 来模拟用户登录。
现在 explorer.exe 已经加载了 .NET 程序集,并且进程中仍然没有可疑的工件。在加载并执行 KatzAssembly 之前不会有任何内容。发现在我们的目标进程中产生的空的(目前)应用程序域。
在03:50我们执行Mimikatz,它在内存中创建可检测的组件。
在Mimikatz操作完成之后,我们卸载(04:18)为这个Mimikatz会话生成的应用程序域。
这个想法很容易理论上很容易实现,但由于内存扫描的性能,卸载应用程序的扫描等原因很难实践。
CLR内存清除检测
你如何检测 CLR 内存清除?你需要密切关注卸载应用程序域的频率。
该图显示了 ETW 事件的顺序:应用程序域创建、程序集加载以及程序集和应用程序域卸载。你可以使用不同的工具(例如 SilkETW)对此进行记录。
你可以聚合应用程序域加载和卸载的事件,并确定最常加载和卸载应用程序域的进程。
AMSI 接口在加载期间扫描程序集,但在卸载期间扫描程序集内存和资源也很有用,尽管显然不是出于预防目的。当然,这种额外的扫描也会对性能产生负面影响。
检测 CLR 环境的 COM 激活和远程程序集加载
如果你在事件 187 中监控激活参数(startupMode 和 COMObjectGUID),则可以检测到通过 COM 服务器在 Explorer 进程中激活下载远程代码的技巧。
此外,应监控具有包含 URL 地址的 [HKEY_CLASSES_ROOT\CLSID\{GUID}\InprocServer32\CodeBase] 值的任何新 COM 服务器的注册事件(在系统注册表中)以及来自%AppData%\Local\assembly\dl3\([0-9A-Z]{8}.[0-9A-Z]{3}\\\){2}.*\Assemb.dll的 Explorer 进程。
本文翻译自:https://securelist.com/detection-evasion-in-clr-and-tips-on-how-to-detect-such-attacks/104226/如若转载,请注明原文地址