言
随着目前的防御机制不断加强对于PowerShell的检测功能,攻击者也不断改变他们所使用的战术,并逐渐改用到很少会被监测到的技术,.NET就是其中的一种。随着时间的推移,很多攻击者已经习惯了可以用于后漏洞利用的大量.NET Payload。诸如GhostPack和SharpHound这样的工具套件,已经成为攻击者武器库中的一部分,负责为其提供“动力”的框架,通常会是Cobalt Strike的execute-assembly。
这样的功能,有效减少了红队的工作量。在我看来,这也正是.NET工具持续流行的原因之一,它使得恶意活动运营人员可以在后漏洞利用的剧本(Playbook)中从非托管的进程中运行程序集。
如同PowerShell一样,随着时间的推移,Microsoft和终端安全厂商已经逐渐引入了防御功能,以帮助缓解在.NET Payload执行这方面的盲区(例如:在.NET 4.8中引入的AMSI)。对于攻击者来说,一大挑战就是如何继续保证在不产生告警的情况下继续利用这种技术。当然,目前AMSI对攻击者来说并不是一个太大的问题,奇热但是我们担心防御方所使用的其他技术没有得到足够的审查。
因此,在这几篇文章中,我们希望探讨蓝队能如何检测恶意的.NET,如何通过execute-assembly这样的方法来利用,以及红队的攻击者如何能绕过针对这类攻击的检测和限制。
在这一系列的第一篇文章中,我们将重点讨论Windows事件线程(ETW),以及如何使用它来表示正在从非托管进程执行的.NET程序集。
Execute-assembly的工作原理
为了了解防御方的侦查能力,我们首先需要研究execute-assembly技术的实际工作原理。
该方法的魔力在于,其背后隐藏着三个接口——ICLRMetaHost、ICLRRuntimeInfo和ICLRRuntimeHost。要开始将CLR加载到“非托管”进程中(也称为没有启动CLR的Windows进程),我们需要调用CLRCreateInstance方法。使用该函数,将会提供一个ICLRMetaHost接口,该接口在.NET Frameworks列表中公开了一些对我们有帮助的信息:
ICLRMetaHost *metaHost = NULL; IEnumUnknown *runtime = NULL; if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost) != S_OK) { printf("[x] Error: CLRCreateInstance(..)\n"); return 2; } if (metaHost->EnumerateInstalledRuntimes(&runtime) != S_OK) { printf("[x] Error: EnumerateInstalledRuntimes(..)\n"); return 2; }
一旦选择了运行时,接下来就要实例化我们的ICLRRuntimeInfo接口,该接口又用于创建我们的ICLRRuntimeHost接口。
frameworkName = (LPWSTR)LocalAlloc(LPTR, 2048); if (frameworkName == NULL) { printf("[x] Error: malloc could not allocate\n"); return 2; } // Enumerate through runtimes and show supported frameworks while (runtime->Next(1, &enumRuntime, 0) == S_OK) { if (enumRuntime->QueryInterface(&runtimeInfo) == S_OK) { if (runtimeInfo != NULL) { runtimeInfo->GetVersionString(frameworkName, &bytes); wprintf(L"[*] Supported Framework: %s\n", frameworkName); } } } // For demo, we just use the last supported runtime if (runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost) != S_OK) { printf("[x] ..GetInterface(CLSID_CLRRuntimeHost...) failed\n"); return 2; }
在创建后,所有内容都会通过两个方法调用合并在一起。ICLRRuntimeHost::Start将CLR加载到我们的进程中,而ICLRRuntimeH