Cobalt Strike逆向教程

本文最后更新于:2024年7月12日 上午

工具准备

​ 这里采用的是Cobalt Strike 4.7原版进行逆向教程(因为我没找到最新版的原版程序),Cobalt Strike 4.7原版sha256校验码如下,其他版本校验码请访问校验码官网校验地址:https://verify.cobaltstrike.com/

1
2
3
cobaltstrike.jar sha256:

c1cda82b39fda2f77c811f42a7a55987adf37e06a522ed6f28900d77bbd4409f

windows可使用以下命令获取校验码

1
certutil -hashfile 文件 [md5|sha1|sha256]

环境搭建

反编译程序

​ 我们原版获取到的Cobalt_Strike原版程序都是客户端与服务端合并在一起的,安装的使用重新进行解压,首先我们先将客户端程序提取出来。将cs_bin目录下的Cobalt_Strike_4.7.jar反编译并将反编译后的文件放在cs_src文件夹中,最终也是生成一个jar文件,不同的是此时jar文件中的class文件全部被反编译为java源程序文件。

1
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true cs_bin\Cobalt_Strike_4.7.jar cs_src

使用压缩软件直接解压反编译后的jar文件,其目录结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
E:.
│ cobaltstrike-client.jar
│ cobaltstrike-client.md5
│ cobaltstrike-client.next.md5
│ TeamServerImage
│ TeamServerImage.md5
│ TeamServerImage.next.md5

├─cszip
│ CSExtract.java

└─META-INF
MANIFEST.MF

​ 看到TeamServerImage是不是有点眼熟呢?TeamServerImage正是服务端的程序,CSExtract.java执行了一些解压操作,这里不做重点;反编译cobaltstrike-client.jar

1
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true cs_src\Cobalt_Strike_4.7\cobaltstrike-client.jar cs_src\cs_client_src\

​ 打开idea,新建项目,创建好后,在项目根目录创建decompiled_srclib文件夹,分别存放上一步反编译后的cobaltstrike-client.jar和原始未反编译的cobaltstrike-client.jar,将反编译后的cobaltstrike-client.jar解压,项目最终目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
│  .gitignore
│ CobaltStrikeCrack.iml

├─.idea
│ misc.xml
│ modules.xml
│ vcs.xml
│ workspace.xml

├─decompiled_src
│ │ cobaltstrike-client.jar
│ │
│ └─cobaltstrike-client

├─lib
│ cobaltstrike-client.jar

└─src

添加项目依赖

选择File->Project Structure->ModulesDependencies进行设置,点击+号选择JARs or Directories...

image-20230923172941606

选择lib中的cobaltstrike-client.jar,点击ok,确定export打勾,点击ok,程序反编译完成。

image-20230923173025005

构建工件

打开lib->META-INF->MANIFEST.MF,可以看到Main Class,复制aggressor.Aggressor

image-20230923202619073

进入File->Project Structure->Artifacts—>JAR—>From modules with dependencies

image-20230923202314413

Main Class处内填写上面获得的主类名称,点击ok。

image-20230923202341832

​ 此时src目录下生成META-INF配置,打开MANIFEST.MF,将decompiled_src/cobaltstrike-client/META-INF/MANIFEST.MF的内容复制粘贴覆盖,至此工件构建配置完成。

源码编译测试

进入ecompiled_src/cobaltstrike-client/aggressor/Aggressor.java,右键选择Refactor->Copy File,在src目录中新建相同路径,即可将Aggressor.java至我们的项目中。

image-20230923203008559

此时,可以通过Build—>Build Artifacts—>Build进行工件编译,点击Run->Profile->Edit Configurations...设置运行参数。

点击+号,选择JAR Application,名字自己定义;Path to JAR选项目生成的jar文件;VM options设置为-XX:+AggressiveHeap -XX:+UseParallelGC

image-20230923204356838

此时,直接运行项目不报错,且弹框输出Your authorization file is not vaild.,证明项目运行成功。

密钥文件生成

各版本key:

1
2
3
4
5
6
7
8
9
27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6,        // 4.0 key
-128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86, 118, // 4.1 key
-78, 13, 72, 122, -35, -44, 113, 52, 24, -14, -43, -93, -82, 2, -89, -96, // 4.2 key
58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103, // 4.3 key
94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42, // 4.4 key
-13, -114, -77, -47, -93, 53, -78, 82, -75, -117, -62, -84, -34, -127, -75, 66, // 4.5 key
-122, 56, -75, 17, -32, 91, 85, 123, -7, 112, -60, 24, 53, 109, 68, -12 // 4.7 key
-118, 9, -22, -51, -53, 27, 95, 70, -99, -54, 53, 124, -9, -7, -26, 74 // 4.8 key
// 4.9 key

cobaltstrike.auth认证密钥文件,rsa加密,大端存放,解密内容:

4.7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-54, -2, -64, -45, //文件头
0, -54, //后续长度
1, -55, -61, 127, // 证书有效时间,永久有效
23, 80, 101, -22, // watermask
47, // 版本号 47
16, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6, // 4.0 key
16, -128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86, 118, // 4.1 key
16, -78, 13, 72, 122, -35, -44, 113, 52, 24, -14, -43, -93, -82, 2, -89, -96, // 4.2 key
16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103, // 4.3 key
16, 94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42, // 4.4 key
16, -13, -114, -77, -47, -93, 53, -78, 82, -75, -117, -62, -84, -34, -127, -75, 66, // 4.5 key
0, 0, 0, 24, // watermask length
105, 100, 118, 121, 85, 97, 77, 68, 75, 117, 98, 87, 87, 52, 84, 76, 51, 105, 80, 106, 66, 119, 61, 61, // watermask ascii base64
16, -71, -86, 51, 8, 10, 90, -112, 49, 62, -15, -99, -64, 98, 125, 46, -95,
28,
115, 53, 57, 108, 53, 105, 113, 49, 101, 106, 90, 51, 79, 81, 100, 47, 115, 103, 118, 78, 97, 103, 61, 61, 113, -112, -42, 104,
16, // 4.7 sleeve key length
-122, 56, -75, 17, -32, 91, 85, 123, -7, 112, -60, 24, 53, 109, 68, -12 // key

CobaltStrike client破解

  • aggressor/Aggressor.main中调用License.checkLicenseGUI(new Authorization());开始证书验证。

  • ``Authorization类中是cobaltstrike.auth文件的处理,读取文件内容,创建AuthCrypto类,对cobaltstrike.auth进行MD5验证,并使用RSA/ECB/PKCS1Padding`进行RSA解密。

    image-20231002131410814

  • 调用AuthCrypto().decrypt对内容进行处理:

    image-20231002131104667

  • 前四个字节证书文件头,在common.AutoCrypto.decrypt进行验证(-889274181为3.x版本;-889274157为4.x版本),读取后两个字节作为auth长度,存入var6中并返回。

    image-20231002131134484

  • 从解密数据中获取watermark、版本号以及watermarkHash,并跳过之前版本的密钥。

    image-20231002132300627

  • 获取过期时间,29999999表示永久证书,并将key传入SleevedResource.Setup进行数据解密。

    image-20231002132514771

  • Authorization类中调用SleevedResource.Setup方法对arrayOfByte6进行处理。在this.A.registerKey中把key设定为AES、HmacSHA256解密的秘钥,在this.A中的this.A.decrypt(var3)进行解密调用,解密的内容为/sleeve/中的dll文件:

    image-20231002132855178

    image-20231002132738793

    • 整个密钥文件内容分析流程到此结束,可以知道比较关键的内容就是证书有效时间和解密key,解密key只能通过分析正版的auth文件获取。

可以直接使用使用大佬的代码生成密钥文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.*;

public class RSAKeyPairGenerator {
private PrivateKey privateKey;
private PublicKey publicKey;

public RSAKeyPairGenerator() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
this.privateKey = pair.getPrivate();
this.publicKey = pair.getPublic();
}

// 将byte 写入文件
public void byte2File(String path, byte[] data) throws IOException {
File f = new File(path);
f.getParentFile().mkdirs();

FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.flush();
fos.close();
}

public PrivateKey getPrivateKey() {
return privateKey;
}

public PublicKey getPublicKey() {
return publicKey;
}

// 加密数据
public byte[] encryptPri(byte[] data, PrivateKey privateKey) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
return cipher.doFinal(data);
}

public static void main(String[] args) throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, BadPaddingException {
RSAKeyPairGenerator PairGenerator = new RSAKeyPairGenerator();
//byte[] data = {-54, -2, -64, -45, 0, 77, 1, -55, -61, 127, 0, 0, 0, 1, 43, 16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103 };
// byte[] data ={
// -54, -2, -64, -45,
// 0, 77, 1, -55, -61,
// 127,
// 0, 0, 0, 1,
// 43,
// 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
// 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
// 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
// 16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103
// }; // 4.3
byte[] data={
-54, -2, -64, -45,
0, -54,
1, -55, -61, 127,
23, 80, 101, -22,
47,
16, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6,
16, -128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86, 118,
16, -78, 13, 72, 122, -35, -44, 113, 52, 24, -14, -43, -93, -82, 2, -89, -96,
16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103,
16, 94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42,
16, -13, -114, -77, -47, -93, 53, -78, 82, -75, -117, -62, -84, -34, -127, -75, 66,
0, 0, 0, 24,
105, 100, 118, 121, 85, 97, 77, 68, 75, 117, 98, 87, 87, 52, 84, 76, 51, 105, 80, 106, 66, 119, 61, 61,
16, -71, -86, 51, 8, 10, 90, -112, 49, 62, -15, -99, -64, 98, 125, 46, -95,
28, 115, 53, 57, 108, 53, 105, 113, 49, 101, 106, 90, 51, 79, 81, 100, 47, 115, 103, 118, 78, 97, 103, 61, 61, 113, -112, -42, 104,
16, -122, 56, -75, 17, -32, 91, 85, 123, -7, 112, -60, 24, 53, 109, 68, -12
}; // 4.7
byte[] rsaByte = PairGenerator.encryptPri(data, PairGenerator.getPrivateKey());
PairGenerator.byte2File("RSA/cobaltstrike.auth", rsaByte);
PairGenerator.byte2File("RSA/authkey.private", PairGenerator.getPrivateKey().getEncoded());
PairGenerator.byte2File("RSA/authkey.pub", PairGenerator.getPublicKey().getEncoded());
}
}

​ 将RSA目录下的密钥,复制到src/resources目录下,同时将decompiled_src/cobaltstrike-client/common/AuthCrypto.java复制到src/common/AuthCrypto,计算authkey.pub的md5,并替换src/common/AuthCrypto第28行的数值。

1
certutil -hashfile authkey.pub MD5

最后将RSA/cobaltstrike.auth复制到out文件下,和编译好的jar包同一目录。

CobaltStrike server 破解

​ 与CobaltStrike client类似,CobaltStrike server端同样需要替换authkey.pub文件和authkey.pub的MD5,由于CobaltStrike 4.7版本以后,为了防破解,CobaltStrike server端变成了二进制文件TeamServerImage,因此我们可以使用winhex或者010 Editor直接对二进制文件进行修改。

  • 修改authkey.pub。从CobaltStrike client中提取官方resources/authkey.pub,使用winhex或者010 Editor复制二进制数据并在TeamServerImage全局搜索(大概位置在0x48F2570附近),例如CobaltStrike 4.7的authkey.pub的数据如下,将其修改为上面自己认证的authkey.pub内容即可。

    1
    30820122300d06092A864886F70D0101.....
  • 修改authkey.pub的MD5,从CobaltStrike client中的/common/AuthCrypto提取第28行的原始MD5数值,使用winhex或者010 Editor复制二进制数据并在TeamServerImage全局搜索(大概位置在0x3e08120附近),例如CobaltStrike 4.7的authkey.pub的数据为8bb4df00c120881a1945a43e2bb2379e,将其修改为上面自己认证的authkey.pub的MD5即可。

  • 可以使用以下python脚本一键替换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    from argparse import ArgumentParser, FileType
    import hashlib
    import re


    def CalcMD5(data: bytes):
    md5 = hashlib.md5()
    md5.update(data)
    return md5.hexdigest()


    def ReplaceData(data, original_data, replacement_data):
    pos = data.find(original_data)
    while pos != -1:
    for i in range(len(replacement_data)):
    data[pos+i] = replacement_data[i]
    pos = data.find(original_data)
    return data


    def Crack(src, dst):
    with open('./TeamServerImage', "rb") as f1, open("./TeamServerImageCrack", "wb")as f2:
    dataSrc = src.read()
    dataDst = dst.read()
    md5Src = CalcMD5(dataSrc)
    md5Dst = CalcMD5(dataDst)
    data = bytearray(f1.read())

    ReplaceData(data, md5Src.encode(), md5Dst.encode())
    ReplaceData(data, dataSrc, dataDst)
    f2.write(data)


    def parseArgs():
    parser = ArgumentParser(
    prog='crack.py', description='Crack TeamServerImage')
    parser.add_argument('-s', '--src', help=u'origin authkey.pub file',
    type=FileType('rb'), required=True)
    parser.add_argument('-d', '--dst', help=u'crack authkey.pub file',
    type=FileType('rb'), required=True)
    parser.add_argument('-t', '--target', help=u'crack TeamServerImage file',
    type=FileType('rb'), required=True)
    return parser.parse_args()


    def main():
    args = parseArgs()
    Crack(args.src, args.dst)


    if __name__ == "__main__":
    main()

    使用方法

    1
    2
    3
    4
    5
    ./crack.py -s authkey_orign.pub -d authkey_crack.pub -t TeamServerImage
    # authkey_orign.pub 官方原版公钥aythkey.pub
    # authkey_crack.pub 子签证公钥aythkey.pub
    # TeamServerImage 官方原版TeamServerImage
    # output TeamServerImageCrack
  • 服务端程序启动方法:创建bash脚本,启动即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #!/bin/bash
    #
    # Start Cobalt Strike Team Server
    #

    # make pretty looking messages (thanks Carlos)
    function print_good () {
    echo -e "\x1B[01;32m[+]\x1B[0m $1"
    }

    function print_error () {
    echo -e "\x1B[01;31m[-]\x1B[0m $1"
    }

    function print_info () {
    echo -e "\x1B[01;34m[*]\x1B[0m $1"
    }

    # check that we're r00t
    if [ $UID -ne 0 ]; then
    print_error "Superuser privileges are required to run the team server"
    exit
    fi

    # check if java is available...
    if [ $(command -v java) ]; then
    true
    else
    print_error "java is not in \$PATH"
    echo " is Java installed?"
    exit
    fi

    # check if keytool is available...
    if [ $(command -v keytool) ]; then
    true
    else
    print_error "keytool is not in \$PATH"
    echo " install the Java Developer Kit"
    exit
    fi

    # generate a certificate
    # naturally you're welcome to replace this step with your own permanent certificate.
    # just make sure you pass -Djavax.net.ssl.keyStore="/path/to/whatever" and
    # -Djavax.net.ssl.keyStorePassword="password" to java. This is used for setting up
    # an SSL server socket. Also, the SHA-1 digest of the first certificate in the store
    # is printed so users may have a chance to verify they're not being owned.
    if [ -e ./cobaltstrike.store ]; then
    print_info "Will use existing X509 certificate and keystore (for SSL)"
    else
    print_info "Generating X509 certificate and keystore (for SSL)"
    keytool -keystore ./cobaltstrike.store -storepass 123456 -keypass 123456 -genkey -keyalg RSA -alias cobaltstrike -dname "CN=Microsoft IT TLS CA 5, OU=Microsoft IT, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
    fi

    ./TeamServerImage -Dcobaltstrike.server_port=43681 -Dcobaltstrike.server_bindto=0.0.0.0 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 teamserver $*

CobaltStrike 4.9破解说明

​ 在CobaltStrike破解中,我们介绍了CobaltStrike破解过程,该方法正对CobaltStrike 4.9之前的版本是完全成功的,但在CobaltStrike 4.9 server版本中server端加入更多验证,需要额外的一些操作,因此有了本节CobaltStrike server 4.9补充破解注意,本节需要在CobaltStrike破解过程的基础上进行

  • 使用IDA打开TeamServerImage,定位到0x0000000000BC8DE7,可以看到Arrays_copyOfRange函数的参数为256和512,表示取cobaltstrike.auth文件的256到512字节,通过common_AuthCrypto_decrypt2进行解密处理,common_AuthCrypto_decrypt2的解密逻辑暂时还未破解成功,因此。我们直接nop掉就行,直接从cobaltstrike.auth文件中读取解密后的内容即可。

image-20231023183422779

image-20231023183730904

​ 这里我也写了一个python脚本,一键破解原版TeamServeImage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from argparse import ArgumentParser, FileType
import base64
import hashlib
import re


authkey_b64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkgtvDimGFGRAs2rwqZ7EOnLJknr4LNtQwZ1n8PiXEegmnP//rdXal4VenANymQXZ1F6Ln3+98oFTWNQrxpDrau3NR5lMoELx41SxA46p/+ljNBqQ8+HMkxDlueImMbNgizI4uT9XV+UPB0mhv31v1FT+dMMKS/UKKhz/r9yoEgwmXTIfTGLUS6+GTfyvrjotN3xsJlx3aHtO1yL3bz0h4Jxz8v6DanuqBkz2K0T1r++ECqNopH0vtvWihLrmkDYm0ST+/NXLhd5djyYQuaEc9nYrip/iefs9BVFGBuKMUmSoT9+1bHp4GXWhloEq/5+w+UlYLI0pNNqVJVEgAtdiRwIDAQAB"

def CalcMD5(data: bytes):
md5 = hashlib.md5()
md5.update(data)
return md5.hexdigest()


def ReplaceData(data, original_data, replacement_data):
pos = data.find(original_data)
while pos != -1:
for i in range(len(replacement_data)):
data[pos+i] = replacement_data[i]
pos = data.find(original_data)
return data


def Crack(dst):
with open('./TeamServerImage', "rb") as f1, open("./TeamServerImageCrack", "wb")as f2:
dataSrc = base64.b64decode(authkey_b64)
dataDst = dst.read()
md5Src = CalcMD5(dataSrc)
md5Dst = CalcMD5(dataDst)
print(str(len(dataSrc))+" "+md5Src)
print(str(len(dataSrc))+" "+md5Dst)
data = bytearray(f1.read())

ReplaceData(data, md5Src.encode(), md5Dst.encode())
ReplaceData(data, dataSrc, dataDst)
ReplaceData(data,b"\xE8\x36\x1d\x00\x00",b"\x90\x90\x90\x90\x90")
f2.write(data)
print("Crack TeamServerImage success.")


def parseArgs():
parser = ArgumentParser(
prog='crack.py', description='Crack TeamServerImage')
parser.add_argument('-s', '--src', help=u'origin authkey.pub file',
type=FileType('rb'), required=False)
parser.add_argument('-d', '--dst', help=u'crack authkey.pub file',
type=FileType('rb'), required=True)
parser.add_argument('-t', '--target', help=u'crack TeamServerImage file',
type=FileType('rb'), required=True)
return parser.parse_args()


def main():
args = parseArgs()
Crack(args.dst)


if __name__ == "__main__":
main()

  • 密钥文件生成代码也需要进行稍微修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import java.io.*;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;

    public class RSAKeyPairGenerator {
    private PrivateKey privateKey;
    private PublicKey publicKey;
    public boolean chunk;

    public RSAKeyPairGenerator() throws NoSuchAlgorithmException {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(4096);
    KeyPair pair = keyGen.generateKeyPair();
    this.privateKey = pair.getPrivate();
    this.publicKey = pair.getPublic();
    }

    public RSAKeyPairGenerator(String publicKeyPath, String privateKeyPath)
    throws NoSuchAlgorithmException, InvalidKeySpecException {

    byte[] publicKeyByte = byteFromFile(publicKeyPath);
    byte[] privateKeyByte = byteFromFile(privateKeyPath);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyByte);
    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
    this.publicKey = keyFactory.generatePublic(publicKeySpec);
    this.privateKey = keyFactory.generatePrivate(privateKeySpec);

    }

    // 将byte 写入文件
    public void byte2File(String path, byte[] data) throws IOException {
    File f = new File(path);
    f.getParentFile().mkdirs();

    FileOutputStream fos = new FileOutputStream(f);
    fos.write(data);
    fos.flush();
    fos.close();
    }

    // 从文件种读取bye
    public byte[] byteFromFile(String path) {

    Path filePath = Path.of(path);

    try {
    byte[] data = Files.readAllBytes(filePath);
    return data;
    } catch (IOException e) {
    e.printStackTrace();
    }
    return new byte[1];

    }

    public PrivateKey getPrivateKey() {
    return privateKey;
    }

    public PublicKey getPublicKey() {
    return publicKey;
    }

    public static byte[] subByte(byte[] original, int from, int to) {
    int newLength = to - from;
    if (newLength < 0)
    throw new IllegalArgumentException(from + " > " + to);
    byte[] copy = new byte[newLength];
    System.arraycopy(original, from, copy, 0,
    Math.min(original.length - from, newLength));
    return copy;
    }

    public static byte[] mergerByte(byte[] original1, byte[] original2) {
    byte[] copy = new byte[original1.length + original2.length];
    System.arraycopy(original1, 0, copy, 0, original1.length);
    System.arraycopy(original2, 0, copy, original1.length, original2.length);
    return copy;
    }

    // 加密数据
    public byte[] encryptPri(byte[] data, PrivateKey privateKey) throws BadPaddingException, IllegalBlockSizeException,
    InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IOException {
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
    if (!this.chunk)
    return cipher.doFinal(data);
    int inputLen = data.length;
    int offLen = 0;
    ByteArrayOutputStream bops = new ByteArrayOutputStream();
    int i = 0;
    while (inputLen - offLen > 0) {
    byte[] cache;
    if (inputLen - offLen > 245) {
    cache = cipher.doFinal(data, offLen, 245);
    } else {
    cache = cipher.doFinal(data, offLen, inputLen - offLen);
    }
    bops.write(cache);
    i++;
    offLen = 245 * i;
    }

    return bops.toByteArray();
    }

    // 解密数据
    public byte[] decryptPub(byte[] data, PublicKey publicKey) throws BadPaddingException, IllegalBlockSizeException,
    InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IOException,
    InvalidKeySpecException {

    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(2, this.publicKey);
    return cipher.doFinal(data);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException,
    InvalidKeyException, NoSuchPaddingException, BadPaddingException, InvalidKeySpecException {
    File file = new File("RSA/authkey.private");
    RSAKeyPairGenerator PairGenerator;
    if (file.exists())
    PairGenerator = new RSAKeyPairGenerator("RSA/authkey.pub", "RSA/authkey.private");
    else {
    PairGenerator = new RSAKeyPairGenerator();
    PairGenerator.byte2File("RSA/authkey.private",
    PairGenerator.getPrivateKey().getEncoded());
    PairGenerator.byte2File("RSA/authkey.pub",
    PairGenerator.getPublicKey().getEncoded());
    }
    // byte[] data = {-54, -2, -64, -45, 0, 77, 1, -55, -61, 127, 0, 0, 0, 1, 43,
    // 16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103
    // };
    // byte[] data ={
    // -54, -2, -64, -45,
    // 0, 77, 1, -55, -61,
    // 127,
    // 0, 0, 0, 1,
    // 43,
    // 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
    // 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
    // 16, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
    // 16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103
    // }; // 4.3
    // byte[] data={
    // -54, -2, -64, -45,
    // 0, -54,
    // 1, -55, -61, 127,
    // 23, 80, 101, -22,
    // 47,
    // 16, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6,
    // 16, -128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86,
    // 118,
    // 16, -78, 13, 72, 122, -35, -44, 113, 52, 24, -14, -43, -93, -82, 2, -89, -96,
    // 16, 58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103,
    // 16, 94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110,
    // -42,
    // 16, -13, -114, -77, -47, -93, 53, -78, 82, -75, -117, -62, -84, -34, -127,
    // -75, 66,
    // 0, 0, 0, 24,
    // 105, 100, 118, 121, 85, 97, 77, 68, 75, 117, 98, 87, 87, 52, 84, 76, 51, 105,
    // 80, 106, 66, 119, 61, 61,
    // 16, -71, -86, 51, 8, 10, 90, -112, 49, 62, -15, -99, -64, 98, 125, 46, -95,
    // 28, 115, 53, 57, 108, 53, 105, 113, 49, 101, 106, 90, 51, 79, 81, 100, 47,
    // 115, 103, 118, 78, 97, 103, 61, 61, 113, -112, -42, 104,
    // 16, -122, 56, -75, 17, -32, 91, 85, 123, -7, 112, -60, 24, 53, 109, 68, -12
    // }; // 4.7

    byte[] data = {
    -54, -2, -64, -45,
    0, -11,
    16, -1, 12, -6, 65, 7, -47, 91, 48, 17, 61, 29, 43, -99, -23, 21, 109,
    34, -96, 55, 34, -9, 88, 123, -79, 80, 106, -125, -118, 85, -85, 2, -46, 23, 65, 48, 102, -102, -20, -2,
    -79, -124, -24, -67, -116, 50, -96, 4, 19, -57, -125, 76,
    49,
    1, -55, -61, 127,
    58, -34, 104, -79,
    0, 0, 0, 24,
    78, 116, 90, 79, 86, 54, 74, 122, 68, 114, 57, 81, 107, 69, 110, 88, 54, 98, 111, 98, 80, 103, 61, 61,
    28, 115, 53, 57, 108, 53, 105, 113, 49, 101, 106, 90, 51, 79, 81, 100, 47, 115, 103, 118, 78, 97, 103,
    61, 61, 113, -112, -42, 104,
    -6, -89, 53, -104, -110, 84, -33, 40, -58, -6, 77, 38, -11, -106, -63, 78, -60, 85, 78, 104, 120, 7,
    -94, 68, 45, -23, -118, 111, 91, 26, -125, -52, 113, -113, 57, 90, 63, -124, -88, 71, -81, 22, -4, 6,
    -37, -32, -61, 96, 27, -20, -120, 111, 70, 106, 16, 59, 88, -47, -13, -118, 37, -48, -103, -128, -47, 6,
    25, -123, 8, -126, 10, -12, 112, -84, -97, 126, -62, 90, 105, -64, -119, -10, 68, -128, -121, 95, 82,
    -110, -25, -31, 40, -27, 59, -89, 71, -60, 127, 91, -51, -8, 63, 9, 35, -55, 60, 124, 49, -6, -123, -92,
    79, -21, 37, -13, -23, 76, -42, 74, -111, -4, -24,
    // 4, 5, 33, 0, -35, -94, -25, -37, 60, -88, -64, -15,
    // 64, 74, -11, -84, -86,

    // 120, 39, -110, 119, // TeamServerImage header
    // 0, -56,
    50, 66, 104, -29, 106, 19, -15, 10, -88, -38, 50, -113, 50, -124, -108, 127, 107, -35, 105, 20, -47, 48, 68, 91, 86, 69, -110, 6, 16, -97, -124, 113, -103, -48, -73, 124, -6, -21, -24, 66, 11, -104, 51, 35, 59, -45, 52, -122, 7, 50, 80,

    1, -55, -61, 127,
    58, -34, 104, -79,
    49,
    0, 0, 0, 24,
    78, 116, 90, 79, 86, 54, 74, 122, 68, 114, 57, 81, 107, 69, 110, 88, 54, 98, 111, 98, 80, 103, 61, 61,
    28, 115, 53, 57, 108, 53, 105, 113, 49, 101, 106, 90, 51, 79, 81, 100, 47, 115, 103, 118, 78, 97, 103,
    61, 61,
    113, -112, -42, 104,
    16, -1, 12, -6, 65, 7, -47, 91, 48, 17, 61, 29, 43, -99, -23, 21, 109,
    34, 65, -7, -74, 111, 41, -12, 41, -53, -85, -120, -19, -60, 107, -108, 65, 65, 25, -84, 73, 56, -126, -18, -37, -113, 38, 19, 115, -24, -90, -105, -30, 127, -17, -105, 87, -94, -102, 37, 22, -26, 34,


    84, 7, 46, -84, -88, -75, -10, 30, 75, -45, 69, -31, 70, -104, -53, 91,
    1, -67, -87, 4, 6, -33, -64, -115, 50, 79, -13, 39, -85, 44, 27, -33, -54, -112, -21, -56, -84, 77, -46,
    -58, -62, -107, 50, -55, 56, -53, 35, -56, 96, 95, 86, 117, -107, -103, -1, -89, -58, -92, -63, -6, 20,
    91, 120, -26, 25, 54, 17, -107, 99, -4, -109, -39, -89, 125, -105, -14, -99, -73, -15, 59,
    }; // 4.9

    PairGenerator.chunk = true;
    System.out.println(data.length);
    byte[] rsaByte = PairGenerator.encryptPri(subByte(data,0,245), PairGenerator.getPrivateKey());
    System.out.println(rsaByte.length);
    PairGenerator.byte2File("RSA/cobaltstrike.auth", mergerByte(rsaByte,subByte(data, 245, 245+256)));
    }
    }

参考链接

https://github.com/atomxw/cobaltstrike4.5_cdf

去除反调试

​ cs的反调试代码主要就是遍历运行参数,查看是否包含-javaagent:,因此直接注释掉此处代码即可,可以直接全局文件内容搜索-javaagent:,以下为所有包含反调试代码的函数。

  • aggressor.Aggressor.A(String[])
  • aggressor.dialogs.ConnectDialog.A()
  • aggressor.dialogs.ExportReportDialog.dialogAction
  • aggressor.dialogs.GoldenTicketDialog.B
  • aggressor.dialogs.PayloadGeneratorDialog.E
  • aggressor.dialogs.ScListenerDialog.C()
  • aggressor.windows.CortanaConsole.CortanaConsole
  • aggressor.windows.ScriptManager.actionPerformed
  • aggressor.windows.SOCKSBroser.actionPerformed
  • commom.BaseArtifactUtils.A

去除文件完整性验证

第一次验证

​ Aggressor首先checkui校验完整性,主要是对几个加密函数的完整性校验;checkLicenseGUI用于检测认证文件的可用性,包括证书是否合法、是否过期等。

image-20230925155240513

checkui关键函数为commom.Starter.initializeStarter函数校验完整性,。

image-20230925155440119

A函数中,Var5为crc校验码,var4为校验class,其实现主要是在Initializer.isOK(var1, var3, var4, var5, true)

image-20230925155703184

Initializer.isFileOK(var1, var4, var5, true)函数中通过获取zipfile(jar包通过zip格式压缩)也就是本jar包内的文件获取crc并比较最开始的crc来确保文件未更改。所有校验的文件包括:

  • common/AuthCrypto.class
  • resources/authkey.pub
  • common/License.class
  • common/Authorization.class
  • common/SleevedResource.class
  • common/AggressorInfo.class
  • dns/SleeveSecurity.class
  • common/BaseArtifactUtils.class
  • common/BaseResourceUtils.class
  • aggressor/dialogs/PayloadGeneratorDialog.class

​ 如果对上述文件有所修改的话,请同时修改initializeStarter函数中的crc验证码,或者直接initializeStarter函数返回值设为true,一劳永逸。

第二次验证

​ 第二次完整性验证位于aggressor.ui.UseLookAndFeel.A(Class var0),这里是base64解码字符串,字符串为校验的文件名,再用crc校验进行文件的二次校验,使用反射动态调用函数common.CommonUtils.validClassIntegrity(),此处验证文件包括:

  • common.CommonUtils
  • common.Authorization
  • common.AuthCrypto
  • common.DataParser
  • common.SleevedResource
  • dns.SleeveSecurity
  • aggressor.Aggressor
  • common.AggressorInfo
  • common.Starter

image-20230925162610451

同样的,可以直接暴力注释掉。

第三次验证

第三次完整性验证位于aggressor.Aggressor,此处验证与第一次验证函数调用相同,都是调用commom.Starter.initializeStarter,因此无需其他修改。

image-20230925162839842

第四次验证

ConnectDialog处,还存在第四处完整性校验,ConnectDialog初始化时调用initialize进行校验。

image-20230925161514874

image-20230925161609042

此处校验代码位于commom.Starter2.A(Class var1),与第一次校验类似,同样是验证crc码,唯一不同是crc码位于commom.Starter2.A(Object var1),此处验证文件包括:

  • common/Starter.class
  • common/Initializer.class
  • aggressor/Aggressor.class
  • common/AggressorInfo.class

image-20230925161955593

完整性验证文件列表

实际上类似的验证非常多,因此我们可以在整个项目中搜索system.exit(1),查看与之相关的验证,以下罗列了部分验证位置:

  • conslole.StatusBar.A(Class var0)
  • aggressor.Prefs.A(Class var0)
  • common.Helper.startHelper

参考链接

https://www.52pojie.cn/thread-1702583-1-1.html

CobaltStrike 生成ShellCode

​ CS是使用Swing进行UI开发的,我们可以直接在aggressor\dialogs文件夹中直接找对话框对应操作类,例如aggressor\dialogs\WindowsExecutableDialog.java就是Windows stage payload的操作类。

dialogAction()即为点击生成按钮后的触发函数,可以看到dialogAction()获取到架构,通过ListenerUtils.getListener获取监听器,之后通过getPayloadStager()获取payload,最后通过sanveFile()保存文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected byte[] stager;

public WindowsExecutableDialog(AggressorClient var1) {
this.client = var1;
}

public void dialogAction(ActionEvent var1, Map var2) {
this.options = var2;
boolean var3 = DialogUtils.bool(var2, "x64");
String var4 = DialogUtils.string(var2, "listener");
ScListener var5 = ListenerUtils.getListener(this.client, var4);
if (var5 == null) {
this.dialog.setVisible(true);
DialogUtils.showError("A listener was not selected");
} else {
this.stager = var5.getPayloadStager(var3 ? "x64" : "x86");
if (this.stager.length == 0) {
this.dialog.setVisible(true);
DialogUtils.showError("No " + (var3 ? "x64" : "x86") + " stager for " + var4);
} else {
String var6 = var2.get("output") + "";
String var7 = "";
if (var6.indexOf("EXE") > -1) {
var7 = "artifact.exe";
} else if (var6.indexOf("DLL") > -1) {
var7 = "artifact.dll";
}

SafeDialogs.saveFile((JFrame)null, var7, this);
}
}
}

getPayloadStager()方法调用aggressor\DataUtils.shellcode(),最终在stagers\Stagers.shellcode()根据监听器类型动态生成Stager类,例如Beacon http监听器的Stager类为BeaconHTTPStagerX64,其继承自相应的GenericHTTPStager,并由generate() 生成shellcode。

1
2
3
public byte[] getPayloadStager(String var1) {
return Stagers.shellcode(this, this.getPayload(), var1);
}
1
2
3
4
public static byte[] shellcode(ScListener var0, String var1, String var2) {
GenericStager var3 = A.resolve(var0, var1, var2);
return var3 != null ? var3.generate() : new byte[0];
}

GenericHTTPStager.generate()中,程序读取resources/httpstager64.bin,并根据监听器的host和port等值组合为Packer,最终替换到多个X、Y占位的bin文件中,最后返回bytes[] 类型的shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
   public byte[] generate() {
try {
InputStream var1 = CommonUtils.resource(this.getStagerFile());
byte[] var2 = CommonUtils.readAll(var1);
String var3 = CommonUtils.bString(var2);
var1.close();
var3 = var3 + this.getListener().getStagerHost() + '\u0000';
Packer var4 = new Packer();
var4.little();
var4.addShort(this.getListener().getPort());
AssertUtils.TestPatchS(var2, 4444, this.getPortOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getPortOffset());
var4 = new Packer();
var4.little();
var4.addInt(1453503984);
AssertUtils.TestPatchI(var2, 1453503984, this.getExitOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getExitOffset());
var4 = new Packer();
var4.little();
var4.addShort(this.getStagePreamble());
AssertUtils.TestPatchS(var2, 5555, this.getSkipOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getSkipOffset());
var4 = new Packer();
var4.little();
var4.addInt(this.getConnectionFlags());
AssertUtils.TestPatchI(var2, this.isSSL() ? -2069876224 : -2074082816, this.getFlagsOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getFlagsOffset());
String var5;
if (CommonUtils.isin(CommonUtils.repeat("X", 303), var3)) {
var5 = this.getConfig().pad(this.getHeaders() + '\u0000', 303);
var3 = CommonUtils.replaceAt(var3, var5, var3.indexOf(CommonUtils.repeat("X", 127)));
}

int var6 = var3.indexOf(CommonUtils.repeat("Y", 79), 0);
var5 = this.getConfig().pad(this.getURI() + '\u0000', 79);
var3 = CommonUtils.replaceAt(var3, var5, var6);
return CommonUtils.toBytes(var3 + this.getConfig().getWatermark());
} catch (IOException var7) {
MudgeSanity.logException("HttpStagerGeneric: " + this.getStagerFile(), var7, false);
return new byte[0];
}
}
}

CVE-2022-39197

CVE-2022-39197是由于xss引起的,因此我们可以才采用转义手段,将传入的字符串及进行转义,避免xss攻击。

修改位置common.BeaconEntry.update

image-20231001135114168

提取自cs 4.8的转义代码common.CommonHTMLUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package common;

public class CommonHTMLUtils {
public static final boolean potentialXSS(String var0) {
if (var0 == null) {
return false;
} else if (var0.contains("<")) {
return true;
} else if (var0.contains(">")) {
return true;
} else if (var0.contains("&")) {
return true;
} else if (var0.contains("'")) {
return true;
} else {
return var0.contains("\"");
}
}

public static final boolean potentialUserNameXSS(String var0) {
if (var0 == null) {
return false;
} else if (var0.contains("<")) {
return true;
} else if (var0.contains(">")) {
return true;
} else if (var0.contains("&")) {
return true;
} else {
return var0.contains("\"");
}
}

public static String escapeHtml(String var0) {
if (var0 == null) {
return "";
} else {
int var1 = var0.length();
int var2 = (int)((double)var1 * 1.3);
StringBuilder var3 = new StringBuilder(var2);

for(int var4 = 0; var4 < var1; ++var4) {
char var5 = var0.charAt(var4);
if (var5 == '<') {
var3.append("&lt;");
} else if (var5 == '>') {
var3.append("&gt;");
} else if (var5 == '"') {
var3.append("&quot;");
} else if (var5 == '&') {
var3.append("&amp;");
} else if (var5 >= ' ' && var5 != '\'') {
int var6 = var5 & '\uffff';
if (var6 > 127) {
var3.append("&#").append(var6).append(';');
} else {
var3.append(var5);
}
} else {
var3.append("&#").append(var5).append(';');
}
}

return var3.toString();
}
}
}

参考链接

https://cloud.tencent.com/developer/article/2220765

CobaltStrike profile参数介绍

1

  • set sample_name "name";:设置配置文件名称,此处会显示在输出的报告中。
  • set sleeptime "45000";:设置休眠时间,单位毫秒。
  • set jitter "49";:设置抖动频率,单位%。
  • set data_jitter "100";:设置请求数据抖动大小,单位字节。
  • set useragent "agent";:设置请求的UA。

http-get

1

​ Beacon在执行后会通过get请求不断请求c2服务器以表明Beacon存活状态,http-get代码块中,client代表Beacon向服务器请求规则,server代表服务器向beacon端的响应规则,下面我们详细介绍个字段:

  • set url <url>:设置http-get通信所使用的url,如果设置多个,Beacon每次都会随机选择一个进行通信。

  • client:Beacon向服务器请求规则。

    • header:设置http请求头

    • metadata:设置元数据编码规则。

      • CobaltStrike支持5中编码方案。RSA加密后的元数据经过编码处理后,可以方便地在网络协议中传输,CobaltStrike支持地编码方案为Base64、Base64url、Mask、NetBIOS、NetBIOSU,在metadata可以设置多种编码方案,每行一种,CobaltStrike会按照顺序依次进行编码。

      • prepend:将指定字符串加载头部。

      • append:将指定字符串附加在末尾。

      • header:编码完成数据最终存放位置,例如header "Cookie"表示存放在请求头地Cookie字段,我们使用c2lint可以查看具体访问信息。实际上header这种关键字在CobaltStrike中被称为终止关键字,其他详细信息见终止关键字一节。

        image-20231102161129064

  • server:server代表服务器向beacon端的响应规则,配置与client类似,这里不展开介绍。

http-post

1

​ Beacon在执行时一般会先通过http get请求与c2服务器建立连接,如果c2想要在Beacon中执行命令,会在get请求返回包中发送任务,其中包括此任务地id和任务的具体内容。Beacon在执行完任务后会通过http post请求回传结果,post请求中包括任务id和执行结果,因此在http-post规则配置中要比http-get多一个关于任务id的控制块。

  • client
    • id:配置任务id规则。
      • parameter:将数据存放在指定url参数中,例如parameter "id";代表将任务id放在url参数中?id=id中。
      • output:任务执行结果规则,例如output {base64;print;}表示将执行结果base64编码后存储在body中,print表示编码结束并指定数据存放位置。
  • server

其他规则

终止关键字

​ 数据编码完成后由终止关键字表明后续不要其他编码并指定存放位置,CobaltStrike支持的终止关键字有:

  • header "header":将数据存放在指定HTTP请求头中。
  • parameter "key":将数据存放在指定URL请求参数中。
  • print:将数据存放在http body中。
  • uri-append:将数据直接拼接到URL后面

http-get.client.metadata不能使用print,不在范围内。

http-get.server.outputhttp-post.server.outputhttp-stager.server.output只能使用print终止关键字。

参考链接

Malleable C2

Cobalt Strike 的 Profile 文件解析

Artifact Kit安装

简介

​ Artifact Kit 是一个制作免杀 EXE、DLL 和 Service EXE 的源代码框架,在 Cobalt Strike 的 Help --> Arsenal 处可下载 Artifact Kit。

Cobalt Strike在生成木马时会自动调用Artifact Kit。

食用方法

  • 修改配置,在/Arsenal/arsenal_kit.config可以修改构建配置,默认只构建artifact_kit
  • 安装编译环境:sudo apt-get install mingw-w64
  • 构建工件:执行/Arsenal/build_arsenal_kit.sh,不报错的话会在/Arsenal/生成构建好的工件。
  • Load 加载 /Arsenal/artifact/dist-peek/artifact.cna 插件,之后在 Attacks -> Packages -> Windows Executable 中生成木马文件。
  • 之后在payloads -> Windows Stager payload正常生成木马即可,脚本会自动进行替换,当然可以在Cobalt Strike -> Script Console查看到替换日志输出。

实验效果

使用Artifact Kit生成的Stagerless马,竟然能够直接免杀火绒、360,上线直接没反应,啊这。。。。。

image-20231029094155837

image-20231029101609991

image-20231029101624166

疑似bug

kits/artifact/build.sh似乎存在bug,报错语法错误:无效的算术运算符,按理说官方文件不应该会出现这种问题,可能是我linux环境有问题,大家各自构建一下,不报错就没问题,如果大家遇到同样的问题,可以将下面的函数替换掉kits/artifact/build.sh的同名函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function check_alignment () {
# This will check the file size and print an error when the
# size is not a multiple of 4-bytes.
# Same as the following command:
# ls -l dist-pipe | grep -v cna | awk '($5 % 4) != 0 {print $5 "\t" $9 "\t Is not 4-byte aligned."}'
files=$(ls -l "${1}" | egrep -v "cna|total")
i=-2;
size=0;
file="";
for f in ${files} ; do
if [ $i -lt 0 ] ; then
# 前两次迭代跳过
i=$((i + 1))
continue
fi
if [ ${i} == 4 ] ; then
size=${f}
elif [ ${i} == 8 ] ; then
file=${f}
if [ $((${size} % 4)) != 0 ] ; then
print_warning "[OPSEC] ${f} is not 4-byte aligned. Check the compiler options."
fi
i=-8
fi
i=$(($i + 1))
done
}

CobaltStrike通信过程详解

CobaltStrike通信流程一般步骤为:

  • 使用内置公钥加密aes密钥和beacon基础信息发送至服务端
  • 服务端通过私钥解密后,通过获取到的aes密钥进行后续加密通信。

这里我们直接先介绍beacon使用公钥加密的数据内容。

当服务端收到来自beacon的数据请求时,会将原始数据发送至beacon.BeaconC2.process_beacon_metadata函数进行处理:

image-20231203195958163

this.getAsymmetricCrypto().decrypt(var3)为私钥解密过程,跟进查看

image-20231203200137114

这里展示的是CS4.5版本的this.getAsymmetricCrypto().decrypt(var3),因为4.5版本后,CS就没有现成的服务端源码了,基本逻辑也大差不差,问题倒不是很大,可以看到经过this.cipher.doFinal解密数据存放在var2中,后续判断Magic number,以及读取了后续相应内容。

综上所诉,CS首次通信内容格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct {
DWORD MagicNum; // Magic Num, 默认48879
DWORD DataLen; // 接下来数据长度
BYTE [20];
DWORD ClientID;
DWORD ProcessID;
WORD Port;
BYTE Flag; // 8bit,每bit代表含义如下(/前表示该比特为0表示含义):/ / / / hith_pric:no/yes is64:no/yes barch:x86/x64 /
BYTE MajorVersion;
BYTE MinorVersion;
WORD Build;
BYTE Base[4];
BYTE Gmh[4];
BYTE Gpa[4];
DWORD IP; // 小端存放,其余为大端存放

BYTE [] ="<<hostname>>\t<<user>>\t<<processName>>"
}

TeamServerImage 逆向

​ 从CS4.5开始,CS服务端便使用Graalvm进行的加密处理,虽然Graalvm将大部分符号都进行了处理,但是函数的逻辑相对变化不大,因此我们可以通过client源代码判断部分函数特征,这里记录了原始函数对应的判断特征:

common.AuthCrypto.decrypt

common.AuthCrypto.decrypt存在强特征,直接在ida中搜索BB C0 FE CA(-889274181对应的小端16进制)或D3 C0 FE CA(-889274157对应的小端16进制),存在cmp指令即为该处。

image-20231203202921523

image-20231203203207426

dns.AsymmetricCrypto.decrypt

dns.AsymmetricCrypto.decrypt存在Magic number强特征,直接在ida中搜索EF BE 00 00(48879对应的小端16进制),存在cmp指令即为该处。

image-20231203201622035

image-20231203201829001

由于,服务端与客户端代码基本类似,因此我们可以以该函数为起点,逆向分析其他功能函数,做相应修改即可。

实际上,存在更加简单方法,我们直接搜索替换二进制文件的RAS密钥就破解完成了,哈哈,偷鸡成功。


Cobalt Strike逆向教程
https://genioco.github.io/2023/09/23/Guide/CobaltStrike逆向教程/
作者
BadWolf
发布于
2023年9月23日
许可协议