Cobalt Strike内存加载.NET程序集功能原理分析并重构
字数 1593 2025-10-01 14:05:44
Cobalt Strike execute-assembly 功能原理分析与实现指南
概述
Cobalt Strike 的 execute-assembly 功能允许在非托管进程(如 Beacon)中直接内存加载并执行 .NET 程序集,无需将文件写入磁盘。本文深入分析其技术原理,并提供重构实现的关键步骤。
0x01 技术背景
托管代码与非托管环境
- .NET 程序集属于托管代码,依赖公共语言运行时(CLR)环境执行。
- Beacon 等传统 payload 是非托管程序,需主动加载 CLR 才能运行 .NET 代码。
- 关键技术:通过 COM 接口动态初始化 CLR 并加载程序集。
0x02 内存加载 .NET 程序集的核心步骤
1. 初始化 CLR 环境
需按顺序调用以下 COM 接口:
// 1. 获取 CLR MetaHost
ICLRMetaHost* iMetaHost;
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&iMetaHost);
// 2. 获取指定版本运行时信息(如 v4.0.30319)
ICLRRuntimeInfo* iRuntimeInfo;
iMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&iRuntimeInfo);
// 3. 加载 CLR 到当前进程并启动
ICorRuntimeHost* iRuntimeHost;
iRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&iRuntimeHost);
iRuntimeHost->Start();
2. 加载应用程序域(AppDomain)
- AppDomain 提供代码执行的隔离环境。
- 默认使用当前进程的默认域,也可创建新域。
3. 装载程序集并执行
- 将程序集(二进制数据)加载到 AppDomain。
- 通过反射获取入口点(如
Main方法)。 - 使用
MethodInfo.Invoke()调用并传递参数。
4. 示例代码结构
// 编译后的 .NET 程序集字节数据
byte[] assemblyData = { /* HEX 数据 */ };
// 加载并执行流程:
// 1. 初始化 CLR → 2. 获取默认域 → 3. 加载程序集 → 4. 调用入口点
0x03 Cobalt Strike 的实际实现机制
关键设计:反射式注入(RDI)
- Beacon 不直接加载 CLR,而是将 CLR 初始化代码嵌入到目标 .NET 程序集中。
- 修改程序集,添加
ReflectiveLoader导出函数,支持反射加载。
进程注入流程
-
选择目标进程
- 默认使用
rundll32.exe(可配置其他进程)。 - 以
CREATE_SUSPENDED方式创建进程,挂起主线程。
- 默认使用
-
注入与执行
- 将修改后的程序集注入目标进程。
- 使用
SetThreadContext+ResumeThread触发执行(避免CreateRemoteThread被监控)。
-
输出捕获
- 通过管道(Pipe)重定向目标进程的 stdout/stderr。
- Beacon 轮询读取管道数据并回传至服务端。
复杂处理逻辑(完整版支持)
- 进程选择:根据 profile 配置动态决定。
- BlockDlls 防护:可选阻止非微软 DLL 加载。
- Token 模拟:支持凭据窃取后的上下文执行。
- 智能注入(SmartInject):规避内存扫描。
0x04 简化版 execute-assembly 实现
功能阉割说明
- 仅支持 x64 环境。
- 使用固定目标进程(
rundll32.exe)。 - 无 Token 模拟/BlockDlls 等高级功能。
- 输出捕获仅等待 2 秒,适合短时任务。
关键代码步骤
1. 启动挂起进程
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessA("C:\\Windows\\System32\\rundll32.exe", ..., CREATE_SUSPENDED, ...);
2. 注入程序集
- 使用
VirtualAllocEx在目标进程分配内存。 - 写入嵌入 CLR 初始化代码的程序集数据。
- 通过
SetThreadContext修改线程上下文并执行。
3. 读取输出
// 等待管道数据(最多 2 秒)
PipeWaitForExec(hReadPipe, 2000);
// 读取数据并返回
ReadFile(hReadPipe, buffer, ...);
0x05 总结与注意事项
技术要点
- 核心是通过 COM 接口动态加载 CLR,无需安装 .NET 环境。
- Cobalt Strike 通过 RDI 将初始化代码融入程序集,增强隐蔽性。
- 完整实现需处理进程创建、注入策略、输出捕获等复杂逻辑。
局限性
- 简化版无法处理长时间运行的任务(如 keylogger)。
- 需完整实现 Job 管理和异步管道读取才能支持持续输出。
扩展建议
- 参考开源 CoffLoader 项目实现 Beacon API。
- 集成进程保护(BlockDlls)、Token 模拟等企业级功能。
参考文献
- 逆向分析 Cobalt Strike 4.4 Beacon。
- Microsoft DOCS: CLR Hosting Interfaces。