赞
踩
目录
这篇文章的主要内容是利用流量分析 Beacon 与 Cobalt Strike 服务端之间的通信过程,包括上线、心跳、命令下发与返回命令结果。当然,流量是加密的,需要一些辅助工具或脚本,其中的功能包括提取公钥/私钥,数据包解构与字段分析等等。
先从 payload 开始说起。
Cobalt Strike 提供了两种类型的 payload —— staged(有阶段的)和 stageless(无阶段的)。有阶段是类似于小马拉大马的过程,先上传一个小马到受害者主机,执行后再通过这个小马下载功能更强大的大马去执行,而无阶段是直接上传大马去执行。
比较下来,staged 比 stageless 多一次下载大马的过程,所以前者的通信过程要比后者的多一次请求-响应。
这里以 staged payload 做描述,文件名为 beacon.exe,设置的监听器是 windows/beacon_http/reverse_http,即 HTTP 类型的 beacon。
通信的流程如下:
图中描述了 4 次交互过程,如果是 stageless payload 省略第一次请求-响应,换句话说,直接到上线的步骤。以下这 4 次交互过程进行说明:
1. 第一次请求时, beacon.exe 程序请求下载完整的 payload;
2. 下好 payload 就直接在内存里运行,也就是说 beacon 开始运行,它做了如下操作:
(1)收集主机信息,生成用于协商的原始密钥 raw key,还有一些其他数据;
(2)用 RSA 公钥加密这些数据,将其存储在请求包的 cookie 中,然后发送给 CS 服务器。
这是第一次心跳,之后 beacon 进入睡眠时间。而CS 服务器收到后,做以下操作:
(1)用 RSA 私钥解开密文,从中提取主机信息和 raw key;
(2)在 target 列表生成一个新的主机;
(3)基于 raw key 生成 AES KEY 和 HMAC KEY,用于后续的加密通信。
3. 睡眠时间过后,beacon 再次发送心跳包,这次发送的目的是询问 CS 服务器是否有命令下发。
如果有,CS 服务器将包含命令的数据用 AES 加密得到密文,以及用 HMAC KEY 计算出 hash ,以 (AES 密文+hash) 的格式存放在 response 报文的响应体中。
如果没有,就跟第一次心跳一样,返回响应体为空的 response 报文。
4. 如果 beacon 收到命令,就在受害者主机上执行。执行完后,将命令执行结果发送给 CS 服务器,并重新进入睡眠时间。
上述过程对应的流量:
双击 beacon.exe 第一次请求的流量:
请求 URI 是 4 个随机字符,请求它就会从 CS 服务器监听的端口下载 beacon,Response 里的数据就是 beacon。用 1768.py 解析其中的配置数据,并提取 RSA 公钥、CS 服务器的地址、监听器的端口等等:
这里 RSA 公钥是 16 进制,有些工具要求 base64 编码才能使用,这就要先转换成 2 进制,再用 base64 编码。这里用 python 完成这些步骤:
- import binascii
- import base64
-
- b = binascii.a2b_hex(b"30819f300d06092a864886f70d010101050003818d0030818902818100a5974fa8fe39957c54df8150faa30f2b61d98f8f0472108d89f899360a29e0dc2da506813f4a18e5976f55c1aad090688ee89abac2434f01fc8d0a9a5225b05ecb5b32c2a1dbc63b90714882e167202b1e62875bb98c000960eb145d70bbd431295d4ea07da029caef6fb625576cd96e638e816d6ed9f04d737550fbca95d841020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
-
- base64.b64encode(b)
注意,后面一连串的 "AAA..." 是填充数据,要去掉的。
下载的 beacon 直接在内存中运行起来,此时会发送第一个心跳包,用于上线:
Cookie 里的数据包含了受害者主机的信息,这部分数据用了 RSA 公钥加密。如果我们想看看原始数据是什么样的,就需要对应的 RSA 私钥才能解开,但从攻击者的角度是没法拿到 RSA 私钥的。这里为了分析,用 Dump.java 程序从 .cobaltstrike.beacon_keys 提取私钥。
注意,要把 Dump.java 放在 cs 根目录下,再执行命令:
java -cp "cobaltstrike.jar" Dump.java
然后用私钥把 cookie 里的数据解开:
用 cs-decrypt-metadata.py 脚本也可以,不过要先把 RSA 私钥转换成 16 进制:
- import base64
- base64.b64decode("<RSA 私钥>").hex()
然后执行 cs-decrypt-metadata.py 解开数据:
python cs-decrypt-metadata.py -p <RSA 数字私钥> H0K9Mco+gUT3pTZyd5p13I1SYigQx3uSvk5tjIphuVfF/o4yyrhx4V0B00DwwrHVaavoCmpmcJEVC1jpslLuNwj7VwyJbD8IF82vmINNW2hA11uEVqfdDiTmI6R6Nl2vjB4Nkkft1hxUh3o0ophkho8I41Srgi4QehXUKlmGeq4=
解密结果:
第一部分是协商的密钥,后面会讲述如何利用 raw key 生成 AES 密钥和 HMAC 密钥的。第二部分是主机信息,包含主机名、用户名、beacon 进程名。
在讲述获取下发命令之前,先明白 AES 密钥和 HMAC 密钥是如何生成的,因为这两个密钥将用于后续 CS 和 beacon 之间的加密通信。上一节得到了 3 个 key:
- Raw key: 342ec70264b61f9749aa17558239eddb
- aeskey: e4e61a550d0cec57179ebbdb14ab6952
- hmackey: 6967ae5499b973f13e0b6bfc012cda27
AES 密钥和 HMAC 密钥是基于 raw key 生成的,使用的是 sha256 算法:
- # 将十六进制字符串转换成二进制 byte
- b = binascii.a2b_hex(b"342ec70264b61f9749aa17558239eddb")
- # 计算它的 sha256 摘要
- hashlib.sha256(b).digest().hex()
计算结果:
每次睡眠时间结束,beacon 就会主动请求一次 CS 服务器,看看有没有下发命令。在 CS 客户端里执行 “shell whoami” 命令:
就会抓取到这样的流量:
上面的 Request 报文是 beacon 发送的心跳包,与上线时第一次发送的基本一致。下面的 Response 报文返回了加密数据,这里面就有下发的命令。
加密数据分成两部分,第一部分是 AES 密文的,第二部分是 HMAC 签名,用于校验报文的完整性。密码算法是 CBC 模式 AES。
下面的脚本是我根据 cs-mitm.py 改写的:
- import binascii
- import Crypto.PublicKey.RSA
- import Crypto.Cipher.PKCS1_v1_5
- import Crypto.Cipher.AES
- import hashlib
- import hmac
-
- # 密码算法的初始向量
- CS_FIXED_IV = b'abcdefghijklmnop'
-
- def decrypt(aeskey, hmackey, data):
-
- # 密钥与密文
- aeskey = bytes.fromhex(aeskey)
- hmackey = bytes.fromhex(hmackey)
- data = bytes.fromhex(data)
- if aeskey == None:
- return data
-
- print("加密数据:", data.hex())
-
- # 截取 AES 密文
- encryptedData = data[:-16]
- print("AES 密文:", encryptedData.hex())
-
- # 截取加密数据的后 16 位用于校验密文的完整性
- hmacSignatureMessage = data[-16:]
- print("HMAC 签名:", hmacSignatureMessage.hex())
-
- # 校验密文的完整性
- hmacsSgnatureCalculated = hmac.new(hmackey, encryptedData, hashlib.sha256).digest()[:16]
- if hmacSignatureMessage != hmacsSgnatureCalculated:
- raise Exception('HMAC signature invalid')
-
- # AES 解密
- cypher = Crypto.Cipher.AES.new(aeskey, Crypto.Cipher.AES.MODE_CBC, CS_FIXED_IV)
- decryptedData = cypher.decrypt(encryptedData)
- return decryptedData
-
-
- data = decrypt("e4e61a550d0cec57179ebbdb14ab6952", "6967ae5499b973f13e0b6bfc012cda27", "e702026c696bf56ef54f1726a3db31c76fcd45ab045443fc711183859057acf82ee7687017ec95972f75c035f8c703f71a93aa3baa1457ccd1f01984ef054f24")
-
- print("AES 明文:", data)
解密结果:
当 beacon 接收到下发的命令并执行完成,将会把结果 POST 发送给 CS 服务器,流量如下:
POST 请求体内包含了执行结果,也是用 AES 加密。同样地,payload 后 16 位字节是 HMAC 签名,不属于 AES 密文。此外,前 4 位字节也不属于 AES 密文。所以,这里只需要对前面的脚本修改一下,从第 4 个字节开始截取即可,这里就不粘贴代码了。
解密结果:
命令执行结果出现在明文中,解密成功。
Cobalt Strike Metadata Encryption and Decryption
如何从进程转储文件提取 AES 密钥和 HMAC?Cobalt Strike: Using Process Memory To Decrypt Traffic – Part 3 – NVISO Labs
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。