CobaltStrike Charset Improvement
CobaltStrike Charset
Improvement
Posted on March 16, 2022 by AgeloVito@深藍攻防實驗室
0x01 場景概述
在使用CobaltStrike的過程中,經常會配合一些第三方工具,比如一些帶web探測功能的工具,這些第三方工具會將獲取到的webtitle或其他內容通過 beacon console >回顯給我們,而這個時候,獲取到的webtitle大概率是utf-8編碼格式,甚至還帶中文,很多時候回顯是亂碼的。

或者我們再來看看以下小場景,在一個簡體中文的win10系統上新建兩個文件,分別以utf-8和gb2312兩種編碼格式存儲 中文+英文 的內容,gb2312.txt 和 utf-8.txt。


然后我們從 beacon console > 讀取這兩個文本的內容可以發現 gb2312編碼的gb2312.txt文件中的中文字符顯示正常,而utf-8.txt文件中的中文字符則顯示亂碼。

通過以上兩個小場景的簡單fuzz,可以得出一個初步的大概結論,該現象的*原因是因為編碼不統一*導致的,問題轉變為哪里的編碼不統一。
0x02 編碼定位
要精確的定位問題所在并尋找到比較科學的解決方案就離不開debug,cobaltstrike屬于cs架構,從*MANIFEST.MF* 中我們可以得知原作者的開發環境為 *1.7.0_80-b15 (Oracle Corporation)*,所以我們的反編譯環境只需要大于該jdk版本即可。client和server的代碼都在同一個jar包中,因此我們需要將client端和server端的debug環境都跑起來。

1、環境搭建
整體的環境嚴格來說應該分為三端,分別為 client.jar (cs使用者),server.jar(teamserver端),client.exe(被控者機器),而cobaltstike的代碼量和涉及到的技術含量也不少,所以最佳的選擇是增量二開從而減少工作量,我們的需求只是對cs的編碼問題進行改善或者增強。
反編譯源代碼
使用 *IDEA* 自帶的反編譯插件 java-decompiler.jar 對cobalstrike.jar先進行一次整體的反編譯
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true cs_bin/cobaltstrike.jar cs_src/

反編譯全部完成后的代碼會被重新打包成 *cobaltstrike.jar* ,將其解壓就得到了我們需要的所有源代碼


構建二開環境
拿到反編譯后的源代碼,使用 *IDEA* 和原始 *cobalstrike.jar* 就可以構建一個增量二開的環境了,過程細節不在本文中展開,可參考 *@RedCore* 之前的公開課。

想要修改哪部分的代碼就從之前反編譯完得到的代碼文件中copy該到相應工程目錄中進行修改,最后 *Build Artifacts* 即可,經過修改的代碼文件就會在依賴于原始jar包的環境下被編譯然后增量替換進原始的cobalstrike.jar中。

配置調試環境
配置server端
*Main class* 填寫server端的主類
server.TeamServer
*VM options* 填寫teamserver文件中的啟動參數
-XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=50050 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar server.TeamServer $*
*Program arguments* 填寫我們在運行 teamserver 時給的參數 (./teamserver 172.16.119.1 123456)
172.16.119.1 123456
ip password


在idea中以調試模式開啟teamserver

配置client端
*VM options* 配置參數
-Dfile.encoding=UTF-8 -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC

server端和client端的調試環境到這里就成功搭建起來了

2、流程分析
每一次client.exe和server.jar的交互都會在metadata中攜帶當前系統編碼信息,其攜帶的metadata將在*beacon.BeaconC2.process_beacon_metadata()* 方法中調用 commcon.*WindowsCharsets.getName()* 將其解析為對應的編碼類型的值

其中 WindowsCharsets.*getName()* 通過 switch case 維護了一個解析對應表

包含了幾乎所有的編碼類型

隨后在 beaconEntry = new BeaconEntry(var6, var9, var2, var11) 處將 beaconEntry 通過帶參構造函數實例化,并將編碼類型值復制給 *beaconEntry.chst* 屬性


*common.BeaconEntry* 會封裝關于一條會話信息的所有信息供在其他業務層級的代碼中流通,一些字段的含義通過命名就能猜出個八九不離十


最后在 *this.getCharsets().register(beaconEntry.getId(), var9, var10)* 處將編碼信息進行注冊

其中 *register()* 的實現是在 *beacon.BeaconCharsets.register()*

public void register(Map map, String var2, String var3) {
if (var3 != null) {
try {
Charset var4 = Charset.forName(var3);
synchronized(this) {
map.put(var2, var4);
}
} catch (Exception var8) {
MudgeSanity.logException("Could not find charset '" + var3 + "' for Beacon ID " + var2, var8, false);
}
}
}
我們測試一下在這里修改被注冊的編碼類型是否可以起到作用,我們將其強制賦值為utf-8

*bingo!!!*
起作用了,utf-8編碼的內容現在能顯示正常而gb2312編碼的內容變成了亂碼


流程走清楚,并且找到了能實現效果的代碼位置,接下來就只需要思考如何更好的二開了。
0x03 功能實現
為了更好的實用性,我們選取仿造 *Note* 功能 去實現一個可以動態修改當前編碼的功能,包括以下兩個部分
1、*beacon console >*
2、**Note...**
代碼分為client端和server端,其中各部分處理流程如下所訴

client.jar 端
*beacon.TaskBeacon.Note()* 處理的是 *beacon console>* 中輸入的 note xxx 指令
在 aggressor.windows.BeaconConsole中第一次處理該請求
if (var3.is("note")) {
if (var3.verify("Z")) {
var4 = var3.popString();
this.master.Note(var4);
} else if (var3.isMissingArguments()) {
this.master.Note("");
}
}

判斷 beacon console > 中的 note 指令,并調用 *this.master.Note()* ,其中 this.master 的定義為protected TaskBeacon master;


在 *beacon.TaskBeacon.Note()* 中處理*note xxx*指令

最終在 *common.TeamQueue.run()* 將 beacon console > 中需要執行的指令通過
*TeamQueue.this.socket.writeObject(req);*
發送給 teamserver 端,其實client和server的所有交互最終都會通過此處。

server.jar 端
server 端接收到指令后在 *server.Beacons.buildBeaconModel()*中進行處理,其中 *this.notes* 的定義為
*protected Map notes = new HashMap();*
其中維護的是 *beaconId* 和 *note* 的鍵值對
*this.notes.get(beaconEntry.getId())* 的返回值是*Object* 類型 Object + "" 的作用是強制類型轉換(一點點coder的高級小技巧 ~ )
if (this.notes.containsKey(beaconEntry.getId())) {
beaconEntry.setNote(this.notes.get(beaconEntry.getId()) + "");
}

組裝完 *beaconEntry* 之后 流程走到 *server.Beacons.moment()* 并進行廣播
*this.resources.broadcast("beacons", this.buildBeaconModel());*
后續的動作我們就不用在跟下去了,廣播的具體實現在這里也不再深入跟進了。

代碼實現
在分析完 *note* 命令功能的整體實現以后,仿造其代碼實現一個動態修改編碼功能就很容易了,本文不再累述,有興趣的去看看 *note* 的相關代碼 ctrl c +v 大法就能很容易實現。其中 *gui (Note...)* 的實現原作者使用的是在 *default.cna* 寫的,我們也仿造其實現就好,這里選用的是下拉框,可供選擇的編碼類型給了9中,能滿足大部分的需求場景,其中包括 *("UTF-8", "GBK", "GB2312", "GB18030", "ISO-8859-1", "BIG5", "UTF-16", "UTF-16LE", "UTF-16BE")*,完整代碼如下所示。
item "Setchar" {
$bid = $1;
$dialog = dialog("Setchar", %(charsets => ""), &Setchar);
dialog_description($dialog, "Set the Beacon's Charset ");
drow_combobox($dialog, "charsets", "charset:", @("", "UTF-8", "GBK", "GB2312", "GB18030", "ISO-8859-1", "BIG5", "UTF-16", "UTF-16LE", "UTF-16BE"));
dbutton_action($dialog, "Setchar");
dialog_show($dialog);
}
sub Setchar {
binput($bid, "setchar $3['charsets']");
beacon_setchar($bid, $3['charsets']);
}

看著一般,能用就行 ~
0x04 效果演示
1、在console中的效果
添加了一個 *setchar* 命令來設置當前 *beacon* 編碼,并在試圖中將其展示出來,默認顯示的初始值是從metadata中解析出來的。
Use: setchar [text]
e.g: setchar utf-8 | setchar
set a charset to this Beacon.



在console中使用setchar命令將其設置為utf-8




2、使用gui設置的效果
圖形菜單的功能其實更實用,和 *Note...* 功能一樣,它能方便操作多個beacon。



選擇空值就會將編碼重置會初始值

0x05 一些總結
其實調試的過程并不是那么快速,本文只是直接給出了記憶中的結論。剛開始如何實現功能也沒想的太好,嘗試過一下其他的粗暴實現,覺得實在是不夠看,在后來調試過程中偶然想到可以借鑒note功能,并且它的功能場景完全符合需求,整個流程分析明白了,代碼實現起來就很簡單了,最終才做出了這比較滿意的效果。
最后,一年一度的節日快到了,想獲取完整修改版的朋友,帶簡歷私我喲,We Want You !!!wechat me at Base64.decode("bnVsbC1fLTQwMw==")