Cobalt Strike逆向教程
本文最后更新于:2024年7月12日 上午
工具准备
这里采用的是Cobalt Strike 4.7原版进行逆向教程(因为我没找到最新版的原版程序),Cobalt Strike 4.7原版sha256校验码如下,其他版本校验码请访问校验码官网校验地址:https://verify.cobaltstrike.com/
1 |
|
windows可使用以下命令获取校验码
1 |
|
环境搭建
反编译程序
我们原版获取到的Cobalt_Strike
原版程序都是客户端与服务端合并在一起的,安装的使用重新进行解压,首先我们先将客户端程序提取出来。将cs_bin
目录下的Cobalt_Strike_4.7.jar
反编译并将反编译后的文件放在cs_src
文件夹中,最终也是生成一个jar文件,不同的是此时jar文件中的class
文件全部被反编译为java
源程序文件。
1 |
|
使用压缩软件直接解压反编译后的jar
文件,其目录结构如下
1 |
|
看到TeamServerImage
是不是有点眼熟呢?TeamServerImage
正是服务端的程序,CSExtract.java
执行了一些解压操作,这里不做重点;反编译cobaltstrike-client.jar
。
1 |
|
打开idea,新建项目,创建好后,在项目根目录创建decompiled_src
和lib
文件夹,分别存放上一步反编译后的cobaltstrike-client.jar
和原始未反编译的cobaltstrike-client.jar
,将反编译后的cobaltstrike-client.jar
解压,项目最终目录结构如下:
1 |
|
添加项目依赖
选择File->Project Structure->Modules
对Dependencies
进行设置,点击+号选择JARs or Directories...
选择lib
中的cobaltstrike-client.jar
,点击ok,确定export
打勾,点击ok,程序反编译完成。
构建工件
打开lib->META-INF->MANIFEST.MF
,可以看到Main Class,复制aggressor.Aggressor
进入File->Project Structure->Artifacts—>JAR—>From modules with dependencies
。
在Main Class
处内填写上面获得的主类名称,点击ok。
此时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
至我们的项目中。
此时,可以通过Build—>Build Artifacts—>Build
进行工件编译,点击Run->Profile->Edit Configurations...
设置运行参数。
点击+号,选择JAR Application
,名字自己定义;Path to JAR
选项目生成的jar
文件;VM options
设置为-XX:+AggressiveHeap -XX:+UseParallelGC
。
此时,直接运行项目不报错,且弹框输出Your authorization file is not vaild.
,证明项目运行成功。
密钥文件生成
各版本key:
1 |
|
cobaltstrike.auth认证密钥文件,rsa加密,大端存放,解密内容:
4.7
1 |
|
CobaltStrike client破解
在
aggressor/Aggressor.main
中调用License.checkLicenseGUI(new Authorization());
开始证书验证。``Authorization
类中是
cobaltstrike.auth文件的处理,读取文件内容,创建
AuthCrypto类,对
cobaltstrike.auth进行MD5验证,并使用
RSA/ECB/PKCS1Padding`进行RSA解密。调用
AuthCrypto().decrypt
对内容进行处理:前四个字节证书文件头,在
common.AutoCrypto.decrypt
进行验证(-889274181为3.x版本;-889274157为4.x版本),读取后两个字节作为auth
长度,存入var6
中并返回。从解密数据中获取
watermark
、版本号以及watermarkHash,并跳过之前版本的密钥。获取过期时间,29999999表示永久证书,并将key传入
SleevedResource.Setup
进行数据解密。在
Authorization
类中调用SleevedResource.Setup
方法对arrayOfByte6
进行处理。在this.A.registerKey
中把key设定为AES、HmacSHA256解密的秘钥,在this.A
中的this.A.decrypt(var3)
进行解密调用,解密的内容为/sleeve/
中的dll文件:- 整个密钥文件内容分析流程到此结束,可以知道比较关键的内容就是证书有效时间和解密key,解密key只能通过分析正版的auth文件获取。
可以直接使用使用大佬的代码生成密钥文件。
1 |
|
将RSA
目录下的密钥,复制到src/resources
目录下,同时将decompiled_src/cobaltstrike-client/common/AuthCrypto.java
复制到src/common/AuthCrypto
,计算authkey.pub
的md5,并替换src/common/AuthCrypto
第28行的数值。
1 |
|
最后将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
52from 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
文件中读取解密后的内容即可。
这里我也写了一个python脚本,一键破解原版TeamServeImage:
1 |
|
密钥文件生成代码也需要进行稍微修改
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
227import 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
用于检测认证文件的可用性,包括证书是否合法、是否过期等。
checkui
关键函数为commom.Starter.initializeStarter
函数校验完整性,。
在A
函数中,Var5为crc校验码,var4为校验class,其实现主要是在Initializer.isOK(var1, var3, var4, var5, true)
。
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
同样的,可以直接暴力注释掉。
第三次验证
第三次完整性验证位于aggressor.Aggressor
,此处验证与第一次验证函数调用相同,都是调用commom.Starter.initializeStarter
,因此无需其他修改。
第四次验证
在ConnectDialog
处,还存在第四处完整性校验,ConnectDialog
初始化时调用initialize
进行校验。
此处校验代码位于commom.Starter2.A(Class var1)
,与第一次校验类似,同样是验证crc码,唯一不同是crc码位于commom.Starter2.A(Object var1)
,此处验证文件包括:
common/Starter.class
common/Initializer.class
aggressor/Aggressor.class
common/AggressorInfo.class
完整性验证文件列表
实际上类似的验证非常多,因此我们可以在整个项目中搜索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 |
|
getPayloadStager()
方法调用aggressor\DataUtils.shellcode()
,最终在stagers\Stagers.shellcode()
根据监听器类型动态生成Stager
类,例如Beacon http监听器的Stager
类为BeaconHTTPStagerX64
,其继承自相应的GenericHTTPStager
,并由generate()
生成shellcode。
1 |
|
1 |
|
在GenericHTTPStager.generate()
中,程序读取resources/httpstager64.bin
,并根据监听器的host和port等值组合为Packer,最终替换到多个X、Y占位的bin文件中,最后返回bytes[] 类型的shellcode。
1 |
|
CVE-2022-39197
CVE-2022-39197是由于xss引起的,因此我们可以才采用转义手段,将传入的字符串及进行转义,避免xss攻击。
修改位置common.BeaconEntry.update
提取自cs 4.8的转义代码common.CommonHTMLUtils
1 |
|
参考链接
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中被称为终止关键字,其他详细信息见终止关键字一节。
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
表示编码结束并指定数据存放位置。
- id:配置任务id规则。
- server
其他规则
终止关键字
数据编码完成后由终止关键字表明后续不要其他编码并指定存放位置,CobaltStrike支持的终止关键字有:
header "header"
:将数据存放在指定HTTP请求头中。parameter "key"
:将数据存放在指定URL请求参数中。print
:将数据存放在http body中。uri-append
:将数据直接拼接到URL后面
http-get.client.metadata
不能使用
http-get.server.output
、http-post.server.output
、http-stager.server.output
只能使用
参考链接
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,上线直接没反应,啊这。。。。。
疑似bug
kits/artifact/build.sh
似乎存在bug,报错语法错误:无效的算术运算符
,按理说官方文件不应该会出现这种问题,可能是我linux环境有问题,大家各自构建一下,不报错就没问题,如果大家遇到同样的问题,可以将下面的函数替换掉kits/artifact/build.sh
的同名函数。
1 |
|
CobaltStrike通信过程详解
CobaltStrike通信流程一般步骤为:
- 使用内置公钥加密aes密钥和beacon基础信息发送至服务端
- 服务端通过私钥解密后,通过获取到的aes密钥进行后续加密通信。
这里我们直接先介绍beacon使用公钥加密的数据内容。
当服务端收到来自beacon的数据请求时,会将原始数据发送至beacon.BeaconC2.process_beacon_metadata
函数进行处理:
this.getAsymmetricCrypto().decrypt(var3)
为私钥解密过程,跟进查看
这里展示的是CS4.5版本的this.getAsymmetricCrypto().decrypt(var3)
,因为4.5版本后,CS就没有现成的服务端源码了,基本逻辑也大差不差,问题倒不是很大,可以看到经过this.cipher.doFinal
解密数据存放在var2
中,后续判断Magic number
,以及读取了后续相应内容。
综上所诉,CS首次通信内容格式如下所示:
1 |
|
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
指令即为该处。
dns.AsymmetricCrypto.decrypt
在dns.AsymmetricCrypto.decrypt
存在Magic number
强特征,直接在ida
中搜索EF BE 00 00
(48879对应的小端16进制),存在cmp
指令即为该处。
由于,服务端与客户端代码基本类似,因此我们可以以该函数为起点,逆向分析其他功能函数,做相应修改即可。
实际上,存在更加简单方法,我们直接搜索替换二进制文件的RAS密钥就破解完成了,哈哈,偷鸡成功。