写在前面
感觉流量分析真的是MISC中的一个大项,开一篇专门的文章好学习流量分析
CS流量——CobaltStrike流量
“Cobalt Strike 是一款GUI的框架式渗透工具,集成了端口转发、服务扫描,自动化溢出,多模式端口监听,win exe木马生成,win dll木马生成,java木马生成,office宏病毒生成,木马捆绑;钓鱼攻击包括:站点克隆,目标信息获取,java执行,浏览器自动攻击等等。”
一些细节知识可以看这个 Cobalt Strike | Defining Cobalt Strike Components & BEACON | Google Cloud Blog
更多CS流量相关看 前言 | Cobalt Strike
Cobalt Strike
服务端和客户端是通过 SSL
加密通讯的 (默认证书对 cobaltstrike.store
)。
Beacon
的元数据传输过程中虽然使用的是 RSA
算法,但是 Beacon
任务的传输使用的却是 AES
算法加密的,而 AES
密钥则是 Beacon
随机生成的然后通过 RSA
交换 AES
密钥。加解密算法为 AES
,密钥位长 128
,CBC
模式,填充标准 PKCS7
,其通信具体流程如下。
流量传递
Cobalt Strike
分为 客户端
与 服务端
,服务端是一个,客户端可以有多个,可被团队进行分布式协作操作。
Cobalt Strike
的 Beacon
支持异步通信和交互式通信。Beacon
可以选择通过 DNS
还是 HTTP
协议出口网络,你甚至可以在使用Beacon
通讯过程中切换 HTTP
和 DNS
。其支持多主机连接,部署好 Beacon
后提交一个要连回的域名或主机的列表,Beacon
将通过这些主机轮询。
http-beacon
通信中,默认使用 GET
方法向 /dpixel
、/__utm.gif
、/pixel.gif
等地址发起请求,同时 Cobalt Strike
的Beacon
会将元数据(例如AES密钥)使用 RSA
公钥加密后发送给 C2
服务器。
这些元数据通常被编码为 Base64
字符串并作为 Cookie
发送。如下图:

流量特征
cs流量的http通讯一般会有以下的特征
比如搜索http类型数据后查看到有出现post
请求/sumbit.php?id=xxx
的特征,该特征可以判断 cobalt strike。
这是因为cs在下发特征时会请求这个/sumbit.php?id=xxx
,同时 POST
传递了一串 0000
开头的16进制数据,这是 cs 流量的发送任务数据(可以根据这个辨别cs),任务数据里的内容十分重要,如下:
心跳包特征:
间隔一定时间,均有通信,且流级上的上下行数据长度固定;
使用Wireshark等网络抓包工具,可以捕获到后门的HTTP数据包“GET /4Ekx HTTP/1.1”,其中的 “4Ekx”就是心跳包,通过查看心跳包获取一般cs心跳包会有4个数字或者字母组成。
https特征:
https-beacon
通信中,默认使用空证书建立加密通道,流量中可以看见这一过程。
在数据包的client hello中,数据包有JA3(JA3
是由 John Althouse
、Jeff Atkinson
和 Josh Atkins
创建的开源项目。 JA3
/ JA3S
可以为客户端和服务器之间的通信创建 SSL
指纹。)
和操作系统有关,每个操作系统都有固定的值。这里列出几个已知的 ja3
/ ja3s
指纹信息,这个值在不同操作系统上是不一样的
JA3
- 72a589da586844d7f0818ce684948eea
- a0e9f5d64349fb13191bc781f81f42e1
JA3s
- b742b407517bac9536a77a7b0fee28e9
- ae4edc6faf64d08308082ad26be60767
解密
那拿到的cs流量我们怎么解密呢?
大致是需要三样东西: .cobaltstrike.beacon_keys
文件、cookie
元数据、submit.php
的data
第一种方法:
这种一步到位
有了这三样东西我们便可以直接执行脚本,项目地址 5ime/CS_Decrypt: CobaltStrike流量解密脚本
安装库:
1
| pip install hexdump javaobj-py3
|
脚本如下:
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
| import hmac import base64 import hashlib import hexdump import binascii import javaobj.v2 as javaobj from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5
file_path = ".cobaltstrike.beacon_keys"
encode_data = "U8jm3+oqzYLuUiRd9F3s7xVz7fGnHQYIKF9ch6GRseWfcBSSk+aGhWP3ZUyHIkwRo1/oDCcKV7LYAp022rCm9bC7niOgMlsvgLRolMKIz+Eq5hCyQ0QVScH8jDYsJsCyVw1iaTf5a7gHixIDrSbTp/GiPQIwcTNZBXIJrll540s="
encrypt_data = "000000c093dff6b2f058ba4231e3900276566441f2bb4c76e5c8480874a4d99df083054a5ea1dd4aea5523c751af7d123ee8e9f2253a5ccdcf54427d147c556b15657ee2607e92b35732f26341bc0a26c58bf2bcf2383ad640641c364159387223360cc16ff3dc14ab1f00e6ee4fb53f5e15b767bd379451d0d7b6f4aeae9db0c3f30f3ef167b7db3e6ac241643ed2513e73f9e9148ebe7afaa122ea75e945c8ab8a816179e43180257bd8be752827dd0de26826d5611ee09391ee5545897dae1d3a9698"
def format_key(key_data): key_data = bytes(map(lambda x: x & 0xFF, key_data)) formatted_key = f"-----BEGIN PRIVATE KEY-----\n" formatted_key += base64.encodebytes(key_data).decode() formatted_key += f"-----END PRIVATE KEY-----" return formatted_key
def decrypt(encrypted_data, iv_bytes, signature, shared_key, hmac_key): if hmac.new(hmac_key, encrypted_data, digestmod="sha256").digest()[:16] != signature: print("message authentication failed") return
cipher = AES.new(shared_key, AES.MODE_CBC, iv_bytes) return cipher.decrypt(encrypted_data)
with open(file_path, "rb") as fd: pobj = javaobj.load(fd)
PRIVATE_KEY = format_key(pobj.array.value.privateKey.encoded.data) private_key = RSA.import_key(PRIVATE_KEY.encode()) cipher = PKCS1_v1_5.new(private_key) ciphertext = cipher.decrypt(base64.b64decode(encode_data), 0)
if ciphertext[0:4] == b'\x00\x00\xBE\xEF': raw_aes_keys = ciphertext[8:24] raw_aes_hash256 = hashlib.sha256(raw_aes_keys).digest() aes_key = raw_aes_hash256[0:16] hmac_key = raw_aes_hash256[16:]
SHARED_KEY = binascii.unhexlify(aes_key.hex()) HMAC_KEY = binascii.unhexlify(hmac_key.hex())
encrypt_data = base64.b64encode(bytes.fromhex(encrypt_data)).decode() encrypt_data = base64.b64decode(encrypt_data) encrypt_data_length = int.from_bytes(encrypt_data[:4], byteorder='big', signed=False) encrypt_data_l = encrypt_data[4:] data1 = encrypt_data_l[:encrypt_data_length-16] signature = encrypt_data_l[encrypt_data_length-16:encrypt_data_length] iv_bytes = b"abcdefghijklmnop"
dec = decrypt(data1, iv_bytes, signature, SHARED_KEY, HMAC_KEY) print("AES key: {}".format(aes_key.hex())) print("HMAC key: {}".format(hmac_key.hex())) print(dec[12:int.from_bytes(dec[4:8], byteorder='big', signed=False)]) print(hexdump.hexdump(dec))
|
需要修改的就只有三个:file_path、encode_data、encrypt_data
同时因为心跳包(submit.php
的data)不止一个,需要我们修改脚本里的encrypt_data
多次尝试。
。
。
。
第二种方法:
这种是一步步来的,分了大概四步
因为是CTF比赛所以会有.cobaltstrike.beacon_keys
文件,同时该文件本质上为 KeyPair
的 Java 对象,Python 的 javaobj-py3
库可以直接读取里面存储的数据。
首先,我们需要先获取 .cobaltstrike.beacon_keys
文件中的私钥。这个私钥是 RSA
私钥,用于解密元数据。
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import base64 import javaobj.v2 as javaobj
with open(".cobaltstrike.beacon_keys", "rb") as fd: pobj = javaobj.load(fd)
def format_key(key_data, key_type): key_data = bytes(map(lambda x: x & 0xFF, key_data)) formatted_key = f"-----BEGIN {key_type} KEY-----\n" formatted_key += base64.encodebytes(key_data).decode() formatted_key += f"-----END {key_type} KEY-----" return formatted_key
privateKey = format_key(pobj.array.value.privateKey.encoded.data, "PRIVATE") publicKey = format_key(pobj.array.value.publicKey.encoded.data, "PUBLIC")
print(privateKey) print(publicKey)
|
然后我们在通过私钥解密元数据、获取 AES KEY
,其中 encode_data
为元数据,也就是之前看到的 cookie
的值。
那什么是AESkey呢?
因为Cobalt Strike
的 Beacon
通信主要依赖于 AES key
和 HMAC key
。这两个密钥都是由 Beacon
在每次执行时随机生成的 16字节数据。
AES key
:这个密钥用于加密和解密 Beacon
与 C2
服务器之间的通信内容。具体来说,它用于 AES
算法,该算法用于加密和解密Beacon任务的传输。
HMAC key
:这个密钥用于验证数据的完整性和真实性。HMAC
(Hash-based Message Authentication Code)是一种基于密钥的哈希算法,用于在不安全的通信环境中验证消息的完整性和真实性。
这两个密钥都是由同一组16字节数据生成的。具体来说,这组16字节数据的 SHA256
哈希的前半部分作为 HMAC
密钥,后半部分作为 AES
密钥。
脚本:
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
| import hashlib from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 import hexdump PRIVATE_KEY = """-----BEGIN PRIVATE KEY----- {MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIzAss/1Vcd49UN5XT+pVELCnX1r To4LhSzcP7sPOrIOQg0onSpKO1tzOVX+2DqtZsSFoFrAmrEV+gZCbFfhYR9vs5DGLUg9aa0i5Gqh Pz/s4v5wcmgUgfnvjh4oK7yPQ5BMcqESCjEim9MXs70by1U7ZN+wOYZEorInV9gPkCJdAgMBAAEC gYAJbRpMjQyamEIsq6MQEWIAOpJbhOU05BaeI33tJB71L7lCslacL258OGI9nRyUCWrZfG15xm5V r7gX1Tj2RbTAUZmGigY1X2rCyz00DFjj5iIQVWsl8eSI1EmjFmQ+rYnCezQcrt4V3c7BZtW9RjFW vHh09PF808Yl4/+++vrMoQJBAKhCa/adRGEFqiVcSZG2FdlUG4bPMfwRkYMERZG5D6fjVHOVNEyL 3MK+EtafnYIDD1IS+97K0cbg922RKXNdv+kCQQDWJk0kNe8ePBpwJU4slig1Y+4VWuwTRz6r+MNp v+WrVMzo/LHzAKYn87pyAdxLaZyKAFKs86WpJ2n93ZslC9pVAkA0KMMHJCF6YiMoib9UqDmFsYkG 9VvtZBTTpJNcZR3xUYtweSRJRmIdDIcSeVB+aSxqqO/jVMRK/po1IPbUiI9hAkEAi93wPFpNlv3C dsSmzlA0asqd0azUy7KYqFGNsB/5rXFxdCq3PvOJkkaJ27SDYW3VI/0aAoQQCu8HNxvqHMQlEQJB AIFIkfpeSfksLu8NgiFvZsTV8EWF9PfF2VLyqeSGtmySujqb0HbxGnM9SDc0k48wOvIn5YGJPyY2 ddsyNI6XbCU=} -----END PRIVATE KEY-----"""
encode_data = "U8jm3+oqzYLuUiRd9F3s7xVz7fGnHQYIKF9ch6GRseWfcBSSk+aGhWP3ZUyHIkwRo1/oDCcKV7LYAp022rCm9bC7niOgMlsvgLRolMKIz+Eq5hCyQ0QVScH8jDYsJsCyVw1iaTf5a7gHixIDrSbTp/GiPQIwcTNZBXIJrll540s="
private_key = RSA.import_key(PRIVATE_KEY.encode())
cipher = PKCS1_v1_5.new(private_key) ciphertext = cipher.decrypt(base64.b64decode(encode_data), 0)
if ciphertext[0:4] == b'\x00\x00\xBE\xEF': raw_aes_keys = ciphertext[8:24] raw_aes_hash256 = hashlib.sha256(raw_aes_keys).digest() aes_key = raw_aes_hash256[0:16] hmac_key = raw_aes_hash256[16:]
print("AES key: {}".format(aes_key.hex())) print("HMAC key: {}".format(hmac_key.hex()))
hexdump.hexdump(ciphertext)
|
到此我们就得到了 AES key
和 HMAC key
接着要去解密 submit.php
所传递的 Data ,首先我们要先对该串16进制数据进行处理,转字符串后进行 Base64
编码
1 2 3 4 5 6 7 8 9
| import base64
encode_data = '000000c093dff6b2f058ba4231e3900276566441f2bb4c76e5c8480874a4d99df083054a5ea1dd4aea5523c751af7d123ee8e9f2253a5ccdcf54427d147c556b15657ee2607e92b35732f26341bc0a26c58bf2bcf2383ad640641c364159387223360cc16ff3dc14ab1f00e6ee4fb53f5e15b767bd379451d0d7b6f4aeae9db0c3f30f3ef167b7db3e6ac241643ed2513e73f9e9148ebe7afaa122ea75e945c8ab8a816179e43180257bd8be752827dd0de26826d5611ee09391ee5545897dae1d3a9698'
bytes_data = bytes.fromhex(encode_data) encrypt_data = base64.b64encode(bytes_data)
print(encrypt_data.decode())
|
最终分别填入 SHARED_KEY
,HMAC_KEY
,encrypt_data
即可
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
| import hmac import binascii import base64 import hexdump from Crypto.Cipher import AES
SHARED_KEY = binascii.unhexlify("ef08974c0b06bd5127e04ceffe12597b")
HMAC_KEY = binascii.unhexlify("bd87fa356596a38ac3e3bb0b6c3496e9")
encrypt_data = "AAAAwJPf9rLwWLpCMeOQAnZWZEHyu0x25chICHSk2Z3wgwVKXqHdSupVI8dRr30SPujp8iU6XM3PVEJ9FHxVaxVlfuJgfpKzVzLyY0G8CibFi/K88jg61kBkHDZBWThyIzYMwW/z3BSrHwDm7k+1P14Vt2e9N5RR0Ne29K6unbDD8w8+8We32z5qwkFkPtJRPnP56RSOvnr6oSLqdelFyKuKgWF55DGAJXvYvnUoJ90N4mgm1WEe4JOR7lVFiX2uHTqWmA=="
def decrypt(encrypted_data, iv_bytes, signature, shared_key, hmac_key): if hmac.new(hmac_key, encrypted_data, digestmod="sha256").digest()[:16] != signature: print("message authentication failed") return
cipher = AES.new(shared_key, AES.MODE_CBC, iv_bytes) return cipher.decrypt(encrypted_data)
encrypt_data = base64.b64decode(encrypt_data) encrypt_data_length = int.from_bytes(encrypt_data[:4], byteorder='big', signed=False) encrypt_data_l = encrypt_data[4:]
data1 = encrypt_data_l[:encrypt_data_length-16] signature = encrypt_data_l[encrypt_data_length-16:encrypt_data_length] iv_bytes = b"abcdefghijklmnop"
dec = decrypt(data1, iv_bytes, signature, SHARED_KEY, HMAC_KEY)
print("counter: {}".format(int.from_bytes(dec[:4], byteorder='big', signed=False))) print("任务返回长度: {}".format(int.from_bytes(dec[4:8], byteorder='big', signed=False))) print("任务输出类型: {}".format(int.from_bytes(dec[8:12], byteorder='big', signed=False))) print(dec[12:int.from_bytes(dec[4:8], byteorder='big', signed=False)]) print(hexdump.hexdump(dec))
|
就OK了
例题: 2023 SICTF 一起上号不 解析: Misc Record | MetaVi (在#杂题那)