CobaltStrike逆向学习系列(3):Beacon C2Profile 解析
字数 1519 2025-08-07 08:22:12
CobaltStrike Beacon C2Profile 解析技术文档
0x00 概述
本文档详细解析CobaltStrike中Beacon的C2Profile配置机制,涵盖Controller端生成和Beacon端解析的全过程。通过深入理解C2Profile的存储和解析原理,可以更好地进行工具定制和检测规避。
0x01 Controller端生成机制
1.1 核心代码位置
- 主要逻辑位于
beacon/BeaconPayload.java的exportBeaconStage方法 - 最终将settings转换为byte数组,经过混淆后Patch到Beacon中
1.2 数据结构构建
Controller使用三种方法构建C2Profile数据结构:
-
addShort方法
- 添加短整型数据(2字节)
- 结构:
[索引(2字节)][类型标记1(1字节)][长度2(2字节)][数据(2字节)]
-
addInt方法
- 添加整型数据(4字节)
- 结构:
[索引(2字节)][类型标记2(1字节)][长度4(4字节)][数据(4字节)]
-
addData/addString方法
- 添加字符串/二进制数据
- 结构:
[索引(2字节)][类型标记3(1字节)][长度N(4字节)][数据(N字节)]
1.3 类型标记常量
Controller定义了三个关键常量标识数据类型:
private static final short TYPE_SHORT = 1;
private static final short TYPE_INT = 2;
private static final short TYPE_PTR = 3;
1.4 完整数据结构
Controller生成的C2Profile采用以下结构:
[索引(2字节)][类型(1字节)][长度(2/4字节)][数据(...)]
0x02 Beacon端解析机制
2.1 加载流程
-
Loader阶段:
- 通过ReflectiveLoader加载Beacon DLL
- 首次调用DllMain时fdwReason=1(解析C2Profile)
- 第二次调用fdwReason=4(执行核心功能)
-
内存准备:
- 申请0x800大小的内存区域
- 使用0x2E异或解密C2Profile数据
- 设置解析结构体,存储C2Profile地址和大小
2.2 解析过程
-
索引解析:
- 检查剩余大小是否≥2字节
- 读取2字节索引值(小端序)
- 偏移位置+2,总大小-2
-
类型和长度解析:
- 读取1字节类型标记
- 根据类型读取长度(2或4字节)
- 类型标记与Controller端定义一致
-
内存对齐处理:
- 索引值×16得到存储偏移(内存对齐)
- 将类型标记存入对应位置
-
数据存储:
- Short/Int类型:直接读取对应字节数
- Ptr类型:
- 根据长度申请内存
- 检查剩余数据是否足够
- 内存拷贝数据到申请的空间
2.3 关键数据结构
Beacon端使用以下结构存储解析后的C2Profile:
struct C2ProfileEntry {
short index; // 索引×16后的偏移
char type; // 数据类型标记
union {
short s_val;
int i_val;
void* p_val; // 指向动态分配的数据
} data;
};
0x03 内存布局对比
3.1 Controller端布局
+--------+--------+--------+----------------+
| 索引 | 类型 | 长度 | 数据 |
| (2B) | (1B) | (2/4B) | (变长) |
+--------+--------+--------+----------------+
3.2 Beacon端布局
+---------------------+--------+------------------+
| 对齐偏移(索引×16) | 类型 | 数据/指针 |
+---------------------+--------+------------------+
0x04 技术要点总结
-
索引作用:
- 用于快速定位配置项
- Beacon端通过索引×16实现内存对齐访问
-
类型系统:
- 三种基本数据类型覆盖所有配置需求
- 动态内存分配处理变长数据
-
安全设计:
- 解析前异或解密(0x2E)
- 严格的长度检查防止越界
- 解析完成后清零敏感内存
-
扩展性:
- 通过索引系统可方便添加新配置项
- 类型系统支持未来扩展新数据类型
0x05 检测与规避
-
检测点:
- 内存中的C2Profile特征(如0x2E异或)
- 特定索引位置的已知配置值
- 解析过程中的内存分配模式
-
规避建议:
- 修改默认异或密钥
- 自定义索引映射关系
- 实现替代的解析逻辑
- 混淆内存中的数据结构
0x06 参考实现
以下是简化的C2Profile解析伪代码:
void parse_c2profile(void* profile_data, int total_size) {
while (total_size >= 2) {
short index = read_short(&profile_data, &total_size);
char type = read_byte(&profile_data, &total_size);
int entry_offset = index * 16;
*(char*)(entry_offset) = type;
switch(type) {
case 1: // SHORT
short s_val = read_short(&profile_data, &total_size);
*(short*)(entry_offset + 4) = s_val;
break;
case 2: // INT
int i_val = read_int(&profile_data, &total_size);
*(int*)(entry_offset + 4) = i_val;
break;
case 3: // PTR
int data_size = read_int(&profile_data, &total_size);
void* p_val = malloc(data_size);
memcpy(p_val, profile_data, data_size);
profile_data += data_size;
total_size -= data_size;
*(void**)(entry_offset + 4) = p_val;
break;
}
}
}
通过深入理解C2Profile的生成和解析机制,安全研究人员可以更有效地分析CobaltStrike流量和行为,同时为工具定制提供理论基础。