以第八届西湖论剑MISC-CSCS为例,学习cobalstrike4.0流量分析
偷张图先
首先请求url获取beacon
过滤一下http流量
这个/mB9u就是获取beacon的URL会返回beacon文件
characters = 'mB9u'
total_sum = sum(ord(char) for char in characters)
remainder = total_sum % 256
print(f"对 256 取余的结果为: {remainder}")
# 93
93对应为x64 ,92为x86,说明这个beacon是x64的
网上有脚本可以分析这个stagebeacon
https://github.com/minhangxiaohui/CSthing
导出文件然后执行脚本server,get-uri
就是心跳url,里面的cookie存放了加密过的受害端信息
这个就是心跳包
SLHAIOj8/1icVtP6fImtJz6B6wR0t/XwLg1G0Y3AxoxnseBfPONxoyjAWCCOH84IJULnCZZrO7cIRxJPS2PtmDD4MvD8/PIpoW8Gj8536vhwd+tyXjNKyLNyNYcj+JgO4N5FTnKtkONgv7KnsMjJC3E0eI0ctqmZll8SrXLUS9k=
尝试对cookie解密,私钥存放在.cobaltstrike.beacon_keys
文件里,如果题目给了这个文件就可以直接拿来解密,但是本题没有给出
于是尝试手解rsa
0x0007 publickey 0x0003 0x0100 30819e300d06092a864886f70d010101050003818c00308188028180525e1781f2f02d132a7818a6d269baddbf39352c8d20290ec2294fbe4d77e6549ef4766d8b0e1620000adfbd7aff99cd72f05623eb0def202265cf631dd895acd5e981da8424c03a295895c8194a31641f2eecd5a8715ca89cdbf9433c5d437538767666c3bdb0f8629555375b574fe408a94ae82f92960085d416374f1654b302030100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
去掉末尾0然后转成pem格式并给出n和e
from Cryptodome.PublicKey import RSA
import base64
hex_key = "30819e300d06092a864886f70d010101050003818c00308188028180525e1781f2f02d132a7818a6d269baddbf39352c8d20290ec2294fbe4d77e6549ef4766d8b0e1620000adfbd7aff99cd72f05623eb0def202265cf631dd895acd5e981da8424c03a295895c8194a31641f2eecd5a8715ca89cdbf9433c5d437538767666c3bdb0f8629555375b574fe408a94ae82f92960085d416374f1654b30203010001"
der_data = bytes.fromhex(hex_key)
rsa_key = RSA.import_key(der_data)
n = rsa_key.n
e = rsa_key.e
pem_key = rsa_key.export_key().decode()
print("PEM 格式公钥:\n", pem_key)
print("n =", n)
print("e =", e)
得到
PEM 格式公钥:
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFJeF4Hy8C0TKngYptJput2/OTUs
jSApDsIpT75Nd+ZUnvR2bYsOFiAACt+9ev+ZzXLwViPrDe8gImXPYx3YlazV6YHa
hCTAOilYlcgZSjFkHy7s1ahxXKic2/lDPF1DdTh2dmbDvbD4YpVVN1tXT+QIqUro
L5KWAIXUFjdPFlSzAgMBAAE=
-----END PUBLIC KEY-----
n = 57840457943390562151183056895981922848981888713417943532946260250633760347281370187050725047626507035739078370095883411759062129893337622945780596526859424702568086671495882125464325172299346781795855931036288858784790023273356190549125683636953077021666004867856782395818167722491980517593426129106599564467
e = 65537
yafu分解n
PS E:\我的工具包\yafu-1.34> ./yafu-x64.exe "factor(57840457943390562151183056895981922848981888713417943532946260250633760347281370187050725047626507035739078370095883411759062129893337622945780596526859424702568086671495882125464325172299346781795855931036288858784790023273356190549125683636953077021666004867856782395818167722491980517593426129106599564467)"
fac: factoring 57840457943390562151183056895981922848981888713417943532946260250633760347281370187050725047626507035739078370095883411759062129893337622945780596526859424702568086671495882125464325172299346781795855931036288858784790023273356190549125683636953077021666004867856782395818167722491980517593426129106599564467
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
Total factoring time = 0.3899 seconds
***factors found***
P154 = 7605291443685150594150190909345113655196508809219162555499789316232908573154196070425269090153291952292016936024761413150455793038505322748933150548026527
P154 = 7605291443685150594150190909345113655196508809219162555499789316232908573154196070425269090153291952292016936024761413150455793038505322748933150548026221
ans = 1
得到p和q就能求私钥
from Cryptodome.PublicKey import RSA
from Cryptodome.Util.number import inverse
p = 7605291443685150594150190909345113655196508809219162555499789316232908573154196070425269090153291952292016936024761413150455793038505322748933150548026527
q = 7605291443685150594150190909345113655196508809219162555499789316232908573154196070425269090153291952292016936024761413150455793038505322748933150548026221
e = 65537
n = p * q
phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)
key = RSA.construct((n, e, d, p, q))
private_pem = key.export_key().decode()
print("RSA私钥(PEM):\n", private_pem)
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFJeF4Hy8C0TKngYptJput2/OTUsjSApDsIpT75Nd+ZUnvR2bYsO
FiAACt+9ev+ZzXLwViPrDe8gImXPYx3YlazV6YHahCTAOilYlcgZSjFkHy7s1ahx
XKic2/lDPF1DdTh2dmbDvbD4YpVVN1tXT+QIqUroL5KWAIXUFjdPFlSzAgMBAAEC
gYApWVrrvY2c0zZKu/VjQ/ivQUPy0b63GmVyS1Lg8frzAiAaESnE2Pl6bwsGbxTE
I+3jeYuE1IdWOAeMnKPhY80fOSgws6vSri7CcxnMUEEn3AMw4YSwBIaBGkdLnfxf
pbS/kUUb/z7/A1SRtNq1n4hZYinnG2NpUuiO1WqwHqOGoQJBAJE14+VVt8ONGIZ1
qIf4cqAnAmtonPhyDNdYZQC0IlxNzyixo/lnlTc80b3jYUA4w8GGQQZea70op4RS
fIJV5J8CQQCRNePlVbfDjRiGdaiH+HKgJwJraJz4cgzXWGUAtCJcTc8osaP5Z5U3
PNG942FAOMPBhkEGXmu9KKeEUnyCVeNtAkAhlDeuCcNj6hXYyg592tsO49ZwZhGe
dik4Bw3cOsuTUr7r5yBHBUgBLQRHh/QuOLIz50rUITOC24rZU4XNUfV7AkB6vJQu
Ke+zaDVMoXKbyxIH8DEJXFkhXjUgZ+SnXZqVbmclPFEe48Cp+cxGtkRjJhfAIZwg
p/pk3lIJdDctay9ZAkBukZv1vD/LR3Y64R5xkoLIliyCTtHgUCY44xkJvQfCGchn
xSu0tBnGgSI3El1K1eOyT6NKSZGeQUGlLGcsBtcT
-----END RSA PRIVATE KEY-----
https://github.com/minhangxiaohui/CSthing
还是使用这个项目可以进行解密cookie
python cs-decrypt-metadata.py -p 私钥 密文
有受害机器的主机名,用户名,样本名,其中需要注意的是
Raw key: 28ab951fc96bcb93ec13cf9dd5f21373
aeskey: 9fe14473479a283821241e2af78017e8
hmackey: 1e3d54f1b9f0e106773a59b7c379a89d
有了aeskey就可以加密流量包中的报文
这种心跳包post为空的流是cs没有给受害机器发送指令
这种就是发送了指令的心跳包
对这几个包依次解密,从1382个HTTP流找到上传文件的任务
用cs-parse-http-traffic.py貌似有点问题,原因是响应的报文会比下发任务的报文多4个字节,这里引用官方wp给的脚本
解密
import hmac
import binascii
import base64
import hexdump
from Crypto.Cipher import AES
AES_KEY = binascii.unhexlify("9fe14473479a283821241e2af78017e8")
HMAC_KEY = binascii.unhexlify("1e3d54f1b9f0e106773a59b7c379a89d")
encrypt_data = "hex"
def decrypt(encrypted_data, iv_bytes, signature, AES_KEY, hmac_key):
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv_bytes)
return cipher.decrypt(encrypted_data)
encrypt_data = bytes.fromhex(encrypt_data)
try:
encrypt_data_l = encrypt_data[4:]
data1 = encrypt_data_l[:-16]
signature = encrypt_data_l[-16:]
iv_bytes = b"abcdefghijklmnop"
dec = decrypt(data1, iv_bytes, signature, AES_KEY, HMAC_KEY)
except:
dec = decrypt(encrypt_data, iv_bytes, signature, AES_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(hexdump.hexdump(dec[:1000]))
open('secret.pcapng','wb').write(dec[64:-76])
导出流量包(注意包头和尾),后面内容与cobalstrike没什么关系了,但还是把他做完
基本上全是udp流量,端口27015 27005
流量解密参考
https://www.anquanke.com/post/id/261339
先编译一个解密程序,这是由rehlds项目得来的
https://github.com/rehlds/ReHLDS/blob/e9991716fc908e8b2ebf8ef5004c2a6f098f6eb5/rehlds/HLTV/common/munge.cpp#L147
extern "C" {
int _LongSwap(int l) {
unsigned int res = __builtin_bswap32(*(unsigned int *)&l);
return *(int *)&res;
}
const unsigned char mungify_table[] = {
0x7A, 0x64, 0x05, 0xF1,
0x1B, 0x9B, 0xA0, 0xB5,
0xCA, 0xED, 0x61, 0x0D,
0x4A, 0xDF, 0x8E, 0xC7
};
const unsigned char mungify_table2[] = {
0x05, 0x61, 0x7A, 0xED,
0x1B, 0xCA, 0x0D, 0x9B,
0x4A, 0xF1, 0x64, 0xC7,
0xB5, 0x8E, 0xDF, 0xA0
};
unsigned char mungify_table3[] = {
0x20, 0x07, 0x13, 0x61,
0x03, 0x45, 0x17, 0x72,
0x0A, 0x2D, 0x48, 0x0C,
0x4A, 0x12, 0xA9, 0xB5
};
void COM_UnMunge2(unsigned char *data, int len, int seq) {
int i;
int mungelen;
int c;
int *pc;
unsigned char *p;
int j;
mungelen = len & ~3;
mungelen /= 4;
for (i = 0; i < mungelen; i++) {
pc = (int *)&data[i * 4];
c = *pc;
c ^= seq;
p = (unsigned char *)&c;
for (j = 0; j < 4; j++) {
*p++ ^= (0xa5 | (j << j) | j | mungify_table2[(i + j) & 0x0f]);
}
c = _LongSwap(c);
c ^= ~seq;
*pc = c;
}
}
}
root@kali2 [/tmp] ➜ vim a.cpp
root@kali2 [/tmp] ➜ g++ -shared -o dll.so a.cpp
然后写个python脚本,利用COM_UnMunge2函数解密
from scapy.all import *
from ctypes import *
import struct
lib=CDLL('./dll.so')
COM_UnMunge=lib.COM_UnMunge2
pcaps = rdpcap("secret.pcapng")
f=open('res','wb')
for mpacket in pcaps.filter(lambda x:UDP in x and x[UDP].sport==27015):
# mpacket.show()
udp=mpacket[UDP]
data=bytes(udp.payload)[8:]
seq=bytes(udp.payload)[:4]
ack=bytes(udp.payload)[4:8]
c=create_string_buffer(data)
COM_UnMunge(c,len(data),seq[0])
print(mpacket.time,mpacket[IP].src,'->',mpacket[IP].dst)
decode_bytes=bytes(c)
if len(decode_bytes)>10:
if struct.unpack('<L', seq)[0] & (1<<30):
if len(decode_bytes)>10+struct.unpack('<h', decode_bytes[7:9])[0]+1:
print('find extra data block:')
print(decode_bytes[10+struct.unpack('<h', decode_bytes[7:9])[0]:])
decode_bytes=decode_bytes[10:10+struct.unpack('<h', decode_bytes[7:9])[0]]
f.write(decode_bytes)
print(f'finally decode data:{decode_bytes}\nlength:{len(decode_bytes)}')
f.close
root@kali2 [/tmp] ➜ strings res | grep DAS
:DASCTF{C0UnT3R_S7R1K3_4nD_C0BaLt_57RIK3_4LL_FUN}