文章总结: 文档阐述了利用CobaltStrikeExternalC2实现基于ICMP隧道的通信技术。作者选用ICMPEcho消息,设计了包含标签的分片协议克服MTU限制。通过自定义客户端与控制器实现了Beacon的内存加载与指令代理,展示了完整交互流程,并规划了后续的免杀与规避增强方案。
综合评分: 91
文章分类: 红队,安全工具,内网渗透,渗透测试
利用 Cobalt Strike External C2 实现 ICMP 隧道通信
Ryan Kleffman
Ryan Kleffman
securitainment
2026年2月11日 16:40
中国香港
| 原文链接 | 作者 |
| — | — |
| https://ryanq47.github.io//posts/CobaltStrike_ICMP_Tunnel/ | Ryan Kleffman |
能够构建自己的 C2 通信层一直让我着迷——你可以想象当我发现 Cobalt Strike 的 External C2 能做到这一点时有多兴奋——于是我决定深入探索一番!
最初的想法
起初,我只是想试验一下 External C2,探索它的能力。在研究 Fortra 提供的示例时,我产生了构建自己通信层的想法。我一直想在 ICMP 上构建一个 C2 通道——因为 C2 Matrix 显示目前只有两个现有实现 (INNUENDO 和 Nighthawk)——而这个项目提供了一个完美的机会。
在所有 ICMP 消息类型中,我选择了 Echo Request (Type 8) 和 Echo Reply (Type 0),因为它们具有以下优势:
- 广泛的网络放行
ICMP Echo 消息在许多网络环境中通常被允许——尤其是出站流量——与自定义 TCP 或 UDP 端口相比,不太可能被阻断。
- 内置字段
Echo Request 和 Reply 消息包含简单但实用的字段,如标识符、序列号和 payload。这些可以用于基础 C2 通道中的轻量级消息传递、追踪和排序。虽然结构有限,但对于 beaconing 或命令轮询等简单任务已经足够。
- 原生操作系统支持
几乎每个操作系统——包括 Windows、macOS 和 Linux——都有内置的 ICMP 协议栈。你不需要特殊的库或驱动程序来收发 ICMP 数据包。
实现
所有的技术细节都可以在这里找到,在我的仓库 https://github.com/ryanq47/CS-EXTC2-ICMP 的 README 中。
作为参考,以下是 Fortra 提供的 External C2 流程图。
Cobalt Strike External C2 Flow
宏观来看,客户端的工作流程是:
- 向 Controller 请求一个 Beacon 二进制文件。
- 在内存中运行该 Beacon,并通过自身管道传输流量。
- 在 Controller 和 TeamServer 之间代理 Beacon 流量。
相当常规的流程。不同之处在于,这里将通信映射到了 ICMP 上:
- Client 将数据嵌入 Echo Request并发送给 Controller。
- Controller 将响应嵌入 Echo Reply并返回。
非常简单,但它仅使用出站 ICMP 就实现了一个通信通道。
ICMP_PAYLOAD_SIZE = 1000
在 client_x86.c和 controller.py中,我们硬编码了:
#defineICMP_PAYLOAD_SIZE1000
#defineTAG_SIZE4// "RQ47"
这意味着每个 ICMP 数据包的 data 字段最多可容纳 1000 字节。在这 1000 字节中,前 4 字节始终是 TAG (RQ47)。因此:
- 每个分块的最大数据量 = 1000 – 4 = 996 字节。
如果你想要更小的分块 (例如模拟标准 Windows ping 的 32 字节,或 Unix ping 的 52 字节),可以更改
ICMP_PAYLOAD_SIZE(并同时更新 client 和 controller)。我选择 1000 是为了优化批量传输,避免调试时花费太长时间。线上数据包大小= 20 字节 (IP 头) + 8 字节 (ICMP 头) + 1000 字节 (数据) = 1028 字节。
Client 与 Controller 的逐步工作流程
注意:本示例使用默认的 1000字节作为 payload 大小。如果你将 payload 大小更改为 32,那么每个分块将只有 32 字节 (减去 TAG 后为 28 字节)。
- Client 初始化 (请求 Beacon)
-
Client 打开一个原始 ICMP socket,准备与 Controller 通信。
-
为了请求一个新的 Beacon,Client 发送分块的 ICMP Echo Request,字面意思就是说
I WANT A PAYLOAD:(如果你真的想保持隐蔽,请修改这个字符串,我保留它是因为觉得挺好笑的) -
Controller 的监听器会先看到 Seq 0 并记录它应该期望 17 字节。紧接着,Seq 1 携带
I WANT A PAYLOAD命令。
-
Seq 0
– “长度声明” 包:
[ ICMP Echo Request (Type 8), Seq=0 ]
Payload = [ "RQ47" (4 bytes) ] [ 4-byte big-endian integer: len("I WANT A PAYLOAD") ]
-
Seq 1
– “命令” 包:
[ ICMP Echo Request (Type 8), Seq=1 ]
Payload = [ "RQ47" (4 bytes) ] [ "I WANT A PAYLOAD" (17 bytes) ]
- Controller 接收请求
- Controller 的嗅探器检测到 Seq 0的 ICMP Echo Request:
[ ICMP Echo Request (Type 8), Seq=0 ]
Payload starts with "RQ47", next 4 bytes = 17
- 它记录下 “Seq 0 数据包发送的大小” (本例中为 17) 字节应该随后到来。为该 Client IP + ICMP ID 创建 (或更新) 一个处理线程。
- 该处理线程随后等待后续数据包,直到恰好 17 字节 (特殊命令) 到达。
- Controller 读取 “I WANT A PAYLOAD” (Seq 1)
- 处理线程看到 Seq 1的 ICMP Echo Request:
[ ICMP Echo Request (Type 8), Seq=1 ]
Payload starts with "RQ47", next 17 bytes = "I WANT A PAYLOAD"
- 识别到精确的字符串
I WANT A PAYLOAD后,Controller 连接到 Cobalt Strike 的 TeamServer,执行握手,并获取 Beacon 二进制文件。 - 获得 Beacon 字节后,Controller 准备将其发送回 Client。
- Controller → Client:发送 Beacon (Seq 0 → Seq N)
- 在向 Client 传输实际的 Beacon 数据之前,Controller 先声明总大小:
[ ICMP Echo Reply (Type 0), Seq=i ]
Payload = [ "RQ47" (4 bytes) ] [ up to 996 bytes of Beacon data (chunk i) ]
-
Seq 0 →
[RQ47][0x000005B0](0x5B0 = 1456) -
Seq 1 →
[RQ47][ 前 996 字节 ] -
Seq 2 →
[RQ47][ 剩余 460 字节 ] -
例如,如果 Beacon 大小为 1456 字节:
-
接下来,Controller 将 Beacon 拆分为每块最多 996 字节的分块 (因为总共 1000 字节减去 4 字节 TAG)。对于每个编号为 i = 1…N 的分块:
-
Seq i (Echo Reply)
-
Seq 0 (Echo Reply)
[ ICMP Echo Reply (Type 0), Seq=0 ]
Payload = [ "RQ47" (4 bytes) ] [ 4-byte big-endian integer: beacon_length ]
- Client 重组并运行 Beacon
- Client 发出 ICMP Echo Request,并监听 ICMP Echo Reply (其中包含 Beacon 数据)。收到返回的 Seq 0时:
[ ICMP Echo Reply (Type 0), Seq=0 ]
Payload starts with "RQ47", next 4 bytes = total_size (e.g., 1456)
-
它分配一个精确大小的缓冲区,并计算需要接收多少个分块 (
ceil(total_size / 996))。 -
随着后续 Echo Reply 到达 (Seq 1、Seq 2、……),Client 去掉 4 字节的 TAG,将数据部分复制到缓冲区的正确偏移位置。当所有分块就位后,Client 就拥有了完整的 Beacon 二进制文件。
-
最后,Client 分配可执行内存,将 Beacon 字节复制进去,切换权限为可执行,并生成一个线程在内存中运行 Beacon。
>
-
如果 Client 被 AV/EDR 检测到——很可能就是在这一步
-
Beacon 代理 (持续通信,Seq > 0)
-
Beacon 运行后,通过命名管道 (如
\\.\pipe\foobar) 进行通信。每当 Beacon 向该管道写入一个帧 (长度 + payload) 时,Client 再次使用相同的分块 ICMP 模式来回传输数据:
-
Seq 0
– 声明”下一帧长度”
[ ICMP Echo Request (Type 8), Seq=0 ]
Payload = [ "RQ47" (4 bytes) ] [ 4-byte big-endian integer: frame_length ]
-
Seq 1…M
– 每个数据包发送最多 996 字节的帧数据:
[ ICMP Echo Request (Type 8), Seq=i ]
Payload = [ "RQ47" (4 bytes) ] [ up to 996 bytes of pipe data ]
- Controller 端的每个处理线程读取这些分片,重组完整帧,并通过 TCP 转发给 TeamServer。
- TeamServer 的任何响应都会被立即拆分为分块 (不超过 996 字节),Controller 以 ICMP Echo Reply 的形式将它们发回,附带对应的序列号。
- Client 以相同方式重组这些 Reply,并将其写入 Beacon 的命名管道。这个循环持续进行,直到 Beacon 或 Client 被终止。
挑战
分块
分块是整个项目中最难的部分。设计一个良好的协议方案,使其能够通过 ICMP 进行可变长度 payload 的双向通信,花费了大部分时间。
最初的 POC 每次只发送一个 ICMP 数据包,这将最大 payload 限制在 1472 字节 (MTU 1500 – 20 字节 IPv4 头 – 8 字节 ICMP 头)。这对于小型传输是可行的,比如运行 ls——但任何”有意思的”操作,比如尝试运行 Mimikatz,都会失败,因为发送给 Beacon 的数据超出了单个数据包的限制。而且,这种方式极其高调,可能因一些奇怪的分片导致 ping of death,而且完全不灵活。
幸运的是,经过深思熟虑并借助老朋友 ChatGPT 的帮助,我实现了上述分块协议并使其正常运行——效果非常好!它对 Beacon 完全透明,而且相当灵活,特别是得益于提供的配置选项。
实际演示
我总是对那些不展示工具实际运行效果的 README 或博客感到沮丧——所以这里有一个演示视频:
https://ryanq47.github.io/assets/vid/cs_icmp_tunnel.mkv
已关注
关注
重播 分享 赞
关闭
观看更多
更多
退出全屏
切换到竖屏全屏退出全屏
securitainment已关注
分享视频
,时长02:29
0/0
00:00/02:29
切换到横屏模式
继续播放
[ ]
进度条,百分之0
播放
00:00
/
02:29
02:29
倍速
全屏
倍速播放中
0.5倍 0.75倍 1.0倍 1.5倍 2.0倍
超清 流畅
继续观看
利用 Cobalt Strike External C2 实现 ICMP 隧道通信
观看更多
转载
,
利用 Cobalt Strike External C2 实现 ICMP 隧道通信
securitainment已关注
分享点赞在看
已同步到看一看写下你的评论
视频详情
未来目标
以下是我计划接下来探索的几个方向:
- 带有规避技术的加固客户端
我想构建一个更具韧性的 Client,整合经过验证的规避技术。目前,它没有任何隐蔽尝试,会被现代 EDR 方案快速检测到。即使只是添加基础的规避技术,如反调试检查和动态函数解析,都会比当前设计有巨大改进。
- 支持非常规协议的 Cobalt Strike 扩展
此外,我想将这项工作转化为一个完整的 Cobalt Strike 扩展,支持超出默认范围的更广泛协议。虽然内置通道涵盖了大多数使用场景,但它们往往显得可预测且被大量指纹识别。添加小众或自定义协议听起来是一个非常酷的项目,而且可能真的会有人用到!
参考资源
本项目仓库链接在此:
- Github: CS-EXTC2-ICMP
以下是我在这个项目中发现的一些有用资源:
- Fortra – CobaltStrike External C2
- XPN Infosec Blog – Exploring Cobalt Strike’s ExternalC2 framework
- Wikipedia ICMP Overview
- The C2 Matrix
免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:securitainment Ryan Kleffman
Ryan Kleffman《利用 Cobalt Strike External C2 实现 ICMP 隧道通信》