从原理到实战:深度拆解CobaltStrike的execute-assembly无文件攻击技术
如果你曾经在红队行动中,面对层层防护的目标系统,为如何执行一个.NET工具而绞尽脑汁,那么execute-assembly这个功能很可能已经进入了你的视野。它不像传统的进程注入那样留下明显的磁盘痕迹,也不像PowerShell脚本那样容易被日志记录。它更像是一个隐形的传送门,将完整的.NET程序集直接送入目标进程的内存空间,悄无声息地执行。对于追求隐蔽性和操作安全(OPSEC)的渗透测试人员和安全研究人员来说,这不仅仅是一个功能,更是一种艺术。
在真实的攻防对抗中,杀毒软件和终端检测响应(EDR)系统对磁盘文件的监控越来越严格。一个可执行文件落地,往往意味着几分钟内就会被静态扫描检出。而execute-assembly提供的内存加载能力,恰恰绕过了这一层防御。它允许攻击者将编译好的.NET程序集(DLL或EXE)直接加载到目标进程的地址空间中执行,整个过程无需向磁盘写入任何字节。这种“无文件”的特性,使其成为后渗透阶段权限维持、横向移动和数据窃取等操作的理想选择。
本文将带你深入execute-assembly的技术腹地。我们不会停留在简单的命令使用层面,而是从.NET公共语言运行时(CLR)的底层机制开始,一步步拆解内存加载的全过程。接着,我们会剖析CobaltStrike是如何实现这一功能的,并探讨在实战中如何最大化其效能,同时规避常见的检测手段。无论你是刚开始接触红队工具的安全爱好者,还是经验丰富的渗透测试专家,相信都能从接下来的内容中获得新的启发。
1. 理解基石:.NET CLR内存加载的核心原理
要真正掌握execute-assembly,就不能绕过对.NET公共语言运行时(CLR)托管机制的理解。CLR是.NET框架的执行引擎,负责管理代码的编译、执行、内存分配和垃圾回收等一系列任务。当我们双击一个.NET程序时,实际上是操作系统加载器唤醒了CLR,再由CLR来托管我们的程序集。而execute-assembly所做的,就是在一个已经运行的(可能是非托管的)进程中,手动启动并操控这个CLR引擎,让它为我们加载并执行指定的.NET代码。
1.1 CLR托管接口的演进与选择
微软为CLR托管提供了多种编程接口,其演变历程直接影响了我们实现内存加载的技术路径。早期,我们主要依赖ICorRuntimeHost接口,它兼容从.NET Framework 1.0到4.0的多个版本。但随着.NET 2.0的发布,微软引入了更现代的ICLRRuntimeHost接口。到了.NET 4.0,又进一步推出了ICLRMetaHost接口,提供了更精细的运行时控制和信息查询能力。
在实现内存加载时,我们通常面临一个选择:是追求最大兼容性,还是采用更现代、功能更清晰的接口?许多开源项目和早期的execute-assembly实现选择了ICorRuntimeHost,因为它能覆盖更旧的系统环境。其关键步骤通常包括:
- 初始化COM库:因为CLR托管接口基于COM技术。
- 创建运行时宿主实例:通过
CorBindToRuntimeEx或CLRCreateInstance等函数获取接口指针。 - 启动CLR:调用
Start()方法,在当前进程中初始化CLR环境。 - 获取默认应用程序域:CLR中,程序集需要加载到一个应用程序域(AppDomain)中执行。
- 加载程序集:这是核心步骤,通过
Load_3方法,传入一个包含程序集字节码的SAFEARRAY,实现从内存加载。 - 调用入口点:获取程序集的入口方法(如Main)并调用它,传递所需的参数。
下面是一个高度简化的C++代码片段,展示了使用ICorRuntimeHost进行内存加载的关键逻辑轮廓:
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
HRESULT LoadAssemblyFromMemory(LPBYTE assemblyBytes, DWORD assemblySize) {
ICLRMetaHost* pMetaHost = NULL;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pRuntimeHost = NULL;
IUnknown* pAppDomain = NULL;
_AppDomain* pDefaultAppDomain = NULL;
_Assembly* pAssembly = NULL;
// 1. 创建元数据宿主
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&pMetaHost);
// 2. 获取特定版本的运行时信息(例如v4.0.30319)
pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (void**)&pRuntimeInfo);
// 3. 获取ICorRuntimeHost接口
pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**)&pRuntimeHost);
// 4. 启动CLR
pRuntimeHost->Start();
// 5. 获取默认应用程序域
pRuntimeHost->


518

被折叠的 条评论
为什么被折叠?



