写在前面
继我们前面铺垫的章节,我们继续改造我们的Cobaltstrike,本章我们对Beacon Stage进行特征掩藏,以更好的保护我们的C2服务器。同时目前已有成熟的汉化方案开源,我们纳入改造以适应我们的项目。
http://cn-sec.com/archives/300922.html Cobaltstrike4.x基础特征修改之从端口到checksum8
Beacon Stage:
Beacon Staging Server 就是分阶段模式中,提供shellcode等功能扩展存储的Stage服务器。
Beacon Staging Server的作用是为了防止Payload过大或者适应不同的攻击场景,可以分阶段进行payload投递。
首先通过投递一个被称为stager的小巧的payload,然后去Beacon staging server下载体积较大更复杂的stage,并且访问stage的URL通过checksum8进行校验。
由Windows Execute 模块生成的就是Stager。
stager上线逻辑:——>运行stager——>自动生成并访问符合checksum8校验的URI进行远程下载stage——>上线
stage隐匿的常规思路:
1.修改checkSum8算法,使得按照默认的checkSum8 URI无法下载stage文件。
2.修改内部beacon的解密密钥,使得NSE解析脚本无法解析下载到的stage的信息。
3.从防火墙上限制访问beacon的端口。
4.每次手动kill掉site管理中的stage和stage64
我们这里选择针对dll和源码的xorkey修改作为方案。
XOR KEY修改
现成脚本地址:https://github.com/ca3tie1/CrackSleeve/blob/master/CrackSleeve.java
脚本原理参考:https://mp.weixin.qq.com/s/Pneu8R0zoG0ONyFXF9VLpg
private static byte[] OriginKey = {58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103};
带参运行:
或者构建运行:java -classpath MyCS.jar;./ mytools.CrackSleeve decode,即可以获得加密后的dll文件
关于可能遇到的报错问题,这里抄下师傅们的作业,如下是4.0和4.1版本的代码片段,来自零队,所以师傅对脚本源码进行了修改:
在4.3版本中的片段如下:
# common.SleevedResource line9
public static void Setup(byte[] var0) {
singleton = new SleevedResource(var0);
}
# common.Authorization line65
SleevedResource.Setup(var15);
跟踪发现是从auth文件中读key所得,所以脚本维持原样即可。顺便吐槽下CS4.3为什么所有的变量名几乎都是var*,var1-var18应有尽有,故意而为之以降低代码可读性达到代码保护的效果???
先说下异或问题,已知异或key 3.x为0x69,4.x为0x2e
4.3中这里写的是十进制46 对应二进制即 0x2e,我们这里进行修改,十进制100对应十六进制64:
# beacon.BeaconPayload line3
public static byte[] beacon_obfuscate(byte[] var0) {
byte[] var1 = new byte[var0.length];
for(int var2 = 0; var2 < var0.length; ++var2) {
// var1[var2] = (byte)(var0[var2] ^ 46);
var1[var2] = (byte)(var0[var2] ^ 100);
}
return var1;
}
dll解密完成后,进入修改环节,在ida中打开,进行二进制检索,Search▶Sequence ofBytes或者alt+B,找到xor:
修改其中的hex
然后应用即可:
其它dll修改类似
beacon.dll
beacon.x64.dll
dnsb.dll
dnsb.x64.dll
pivot.dll
pivot.x64.dll
extc2.dll
extc2.x64.dll
师傅们的文章里都提到了上述dll,为防止4.3版本又在这里留了新的暗桩,这里用笨办法对下属非功能dll挨个审查了一般,未发现新的dll使用该xor,所以继续抄之前版本的作业即可,机制无改变,排雷结束。
这里略改了下上面提到的脚本的加密策略,不在输入新的key,使用原key:
if (option.toLowerCase().equals("encode"))
{
// if (args.length <= 1){
// System.out.println("[-] Please enter key.");
// System.exit(0);
// }
// String CustomizeKeyStr = args[1];
// if (CustomizeKeyStr.length() < 16)
// {
// System.out.println("[-] key length must be 16.");
// System.exit(0);
// }
// System.out.println("Init Key: "+CustomizeKeyStr.substring(0,16));
CustomizeKey = OriginKey;
}
直接带参运行即可对修改后的dll进行加密
将加密后的sleeve直接放到根目录即可,进行重构的时候会覆盖原项目,再说一句,记得点构建工件前点重建项目!!!
改动前:
改动后:
在教程最后,破解相关代码全部未删除一并封装到了最终的cobaltstrike.jar中,方便各位小伙伴学习
当然也是可以单独调用的:
java -classpath cobaltstrike.jar;./ mytools.CrackSleeve //dll加解密工具,小改动encode,封装官方key不再传参
java -classpath cobaltstrike.jar;./ mytools.GetMd5 //内置公钥哈希校验
java -classpath cobaltstrike.jar;./ mytools.RSAKeyPairGenerator //auth生成脚本,4.3官方key已封装
checkSum8算法修改
修改checkSum8算法,使得按照默认的checkSum8 URI无法下载stage文件。
checkSum8算法的修改方式与其优缺点:
1.修改checkSum8的92L与93L为非默认的值,从而加大连接获取难度。
缺点:最多进过256次破解,也能够导致stage被下载。
2.通过修改sum值算法,固定下载URI,这样只有指定的uri可以获取到下载连接
缺点:更换URI需要重新编译计算sum值。
Beacon Stager listener 去特征https://mp.weixin.qq.com/s/HibtLfikI_0ezcLVCRxqaA
关于CobaltStrike的Stager被扫问题https://mp.weixin.qq.com/s/0MPM3bysJJYr5jbRnES_Vg
其中修改默认值92L、93L更为简单
# cloudstrike.WebServer line166
public static boolean isStager(String uri) {
return checksum8(uri) == 92L;
}
public static boolean isStagerX64(String uri) {
return checksum8(uri) == 93L && uri.matches("/[A-Za-z0-9]{4}");
}
但优缺点大家仁者见仁智者见智,目前的团队内部版本暂未采用此方案。
汉化
这个已经有师傅给出4.x通用的成熟方案了,借助java的预加载进行非侵入式篡改。
但Releases中加入了作者的个人banner的license修改,导致和我们的服务端不能适配,不过师傅贴心的提供了源码,且源码中并未有banner添加,所以在这里自己编译顺便进行小小的修改。
主要改动涉及以下,首先我们前面已经提到了,暗桩和认证部分我们已经自己处理好了,所以这些通通可以注释掉。
if (className == null) {
return classfileBuffer;
// } else if (className.equals("beacon/BeaconData")) {
// // 暗桩修复,修改zip包后,30分钟所有命令都会变成exit,非侵入式修改下其实不需要
// CtClass cls = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
// CtMethod mtd = cls.getDeclaredMethod("shouldPad");
// mtd.setBody("{$0.shouldPad = false;}");
// return cls.toBytecode();
} else if (className.equals("common/Authorization")) {
// 设置破解key
CtClass cls = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
String func = "public static byte[] hex2bytes(String s) {" +
" int len = s.length();" +
" byte[] data = new byte[len / 2];" +
" for (int i = 0; i < len; i += 2) {" +
" data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));" +
" }" +
" return data;" +
"}";
CtMethod hex2bytes = CtNewMethod.make(func, cls);
cls.addMethod(hex2bytes);
CtConstructor mtd = cls.getDeclaredConstructor(new CtClass[]{});
mtd.setBody("{$0.watermark = 1234567890;" +
"$0.validto = \"forever\";" +
"$0.valid = true;" +
"common.MudgeSanity.systemDetail(\"valid to\", \"perpetual\");" +
"common.MudgeSanity.systemDetail(\"id\", String.valueOf($0.watermark));" +
"common.SleevedResource.Setup(hex2bytes(\"" + hexkey + "\"));" +
"}");
return cls.toBytecode();
}
然后是对common.MudgeSanity的修改,主要是增加了systeminfo中的Loader内容
else if (className.equals("common/MudgeSanity")) {
CtClass cls = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod mtd = cls.getDeclaredMethod("systemInformation");
mtd.instrument(
new ExprEditor() {
public void edit(MethodCall m)
throws CannotCompileException {
if (m.getClassName().equals("java.lang.StringBuffer")
&& m.getMethodName().equals("append")) {
m.replace("{" +
"if ($1.startsWith(\"Version:\")) {" +
" $1 += \"Loader: https://github.com/Twi1ight/CSAgent\\n\";" +
"}" +
"$_ = $proceed($$);" +
"}");
}
}
});
return cls.toBytecode();
即这里,原版和csagent对比:
不过这里不影响license比对,可以不改动,但是如果用的Releases中打好的包,是无法登录c2的,因为那个修改是改的aggressor.Aggressor
if (className.equals("aggressor/Aggressor") || className.equals("server/TeamServer") || className.equals("aggressor/headless/Start")) {
ctClass = this.classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
ctMethod = ctClass.getDeclaredMethod("main");
this.classPool.importPackage("java.lang.reflect");
ctMethod.insertBefore("Field field = aggressor.Aggressor.class.getDeclaredField(\"VERSION\");field.setAccessible(true);Field modifiersField = null;try { modifiersField = Field.class.getDeclaredField(\"modifiers\");} catch (NoSuchFieldException e) { try { Method getDeclaredFields0 = Class.class.getDeclaredMethod(\"getDeclaredFields0\", new Class[]{boolean.class}); boolean accessibleBeforeSet = getDeclaredFields0.isAccessible(); getDeclaredFields0.setAccessible(true); Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, new Object[]{Boolean.FALSE}); getDeclaredFields0.setAccessible(accessibleBeforeSet); for (int i=0; i<fields.length; i++){ Field field = fields[i]; if (\"modifiers\".equals(field.getName())) { modifiersField = field; break; } } if (modifiersField == null) { throw e; } } catch (NoSuchMethodException ex) { e.addSuppressed(ex); throw e; } catch (InvocationTargetException ex) { e.addSuppressed(ex); throw e; }}modifiersField.setAccessible(true);modifiersField.setInt(field, Modifier.STATIC);String fieldValue = (String) field.get(null);field.set(null, fieldValue.replace(\")\", \"-Twi1ight@T00ls.Net)\"));");
return ctClass.toBytecode();
}
也就是相当于修改主函数aggressor/Aggressor中的VERSION 字段:
public class Aggressor {
public static final String VERSION = "4.3 (20210317) " + (License.isTrial() ? "Trial" : "Licensed to WingsSec");
public static final String VERSION_SHORT = "4.3";
public static MultiFrame frame = null;
... ...
}
另外大家在使用csagent的时候还需要传参,这个参数传递如下:
最终出现在我们上面提到的破解部分,所以这块也是不需要的:
else if (className.equals("common/Authorization")) {
// 设置破解key
... ...
mtd.setBody("{$0.watermark = 1234567890;" +
"$0.validto = \"forever\";" +
"$0.valid = true;" +
"common.MudgeSanity.systemDetail(\"valid to\", \"perpetual\");" +
"common.MudgeSanity.systemDetail(\"id\", String.valueOf($0.watermark));" +
"common.SleevedResource.Setup(hex2bytes(\"" + hexkey + "\"));" +
"}");
return cls.toBytecode();
}
最终修改如下:
public class PreMain {
public static void premain(String agentArgs, Instrumentation inst) {
// if (agentArgs == null) {
// System.out.println("[CSAgent] Agent options not found!");
// return;
// }
inst.addTransformer(new CobaltStrikeTransformer(), true);
}
static class CobaltStrikeTransformer implements ClassFileTransformer {
private final ClassPool classPool = ClassPool.getDefault();
// private final String hexkey;
private final Boolean needTranslation;
public CobaltStrikeTransformer() {
// this.hexkey = args;
this.needTranslation = Files.exists(Paths.get("resources/translation.txt"));
}
... ...
}
总的来说,移除破解功能,保留汉化功能,这样一个好处就是可适用于任意破解版本的汉化。
那么最终客户端的使用为:
java -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC -javaagent:CSAgent.jar -jar cobaltstrike.jar
服务端正常使用即可,不需要额外修改,最终效果: