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.080-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_beaconmetadata() 方法中调用 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==")