电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 1541 人浏览分享

Cobalt Strike 4.3 破解思路

[复制链接]
1541 0
本帖最后由 zhaorong 于 2021-9-8 15:03 编辑

Cobalt Strike破解思路

Cobalt Strike现在已经更新到了4.4版本 学习一下破解思路 以后就不怕各种后门版本了。

环境配置

目录结构以4.3版本(已破解)为例:
  1. cobaltstrike4.3
  2. ├─ agscript  扩展脚本
  3. ├─ c2lint    检查C2文件配置
  4. ├─ cobaltstrike        客户端启动脚本
  5. ├─ cobaltstrike.auth   认证密钥文件
  6. ├─ cobaltstrike.exe
  7. ├─ cobaltstrike.jar    主程序jar包
  8. ├─ icon.jpg
  9. ├─ peclone
  10. ├─ start.sh
  11. ├─ teamserver          服务端启动脚本
  12. ├─ third-party
  13. │    ├─ README.winvnc.txt
  14. │    ├─ winvnc.x64.dll  vnc服务端dll
  15. │    └─ winvnc.x86.dll
  16. ├─ update               更新脚本
  17. ├─ update.bat
  18. └─ update.jar
复制代码

主要是针对cobaltstrike.jar 反编译修改后再打包成jar包 此处的思路主要来自于RedCore@Moriarty师傅的公开课。

反编译

使用IDEA自带的java-decompiler.jar进行反编译:
  1. java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDe
  2. compiler -dgs=true cs_original/cobaltstrike.jar cs_src
复制代码

cs_original/cobaltstrike.jar是原包,cs_src是反编译后的输出目录 得到一个jar后缀文件 解压缩即可得到源码。

IDEA项目环境

IDEA新建项目 将反编译后的所有源码放入decompiled_src目录 原包放入lib目录 再在
File-Project Structure-Modules-Dependencies中添加原包:

2021081610570024rDgv.png

在File-Project Structure-Artifacts中添加JAR,主类为aggressor.Aggressor:

9998.png

需要修改相应文件时,右键选择Refactor-Copy file To directory选择src目录里新建的目录:

9997.png

修改完成后就可以进行编译 选择Build-Build Artifacts,在out目录下得到jar包:

9996.png

接下来调试运行,配置选择JAR Application,VM options填入-XX:+Aggre
ssiveHeap -XX:+UseParallelGC:

9995.png

最后将cobaltstrike.auth放在刚刚打包好的JAR包目录下即可。

认证流程

主类Aggressor中开始进行认证流程:
  1. License.checkLicenseGUI(new Authorization());
复制代码

跟入checkLicenseGUI,这里主要检测.auth文件的有效性:

9993.png

调用了三个Authorization类的方法进行验证,从第一个isValid开始看 跟入后可以看到isValid相当于一个flag
默认为false,在Authorization类的构造方法中进行验证,成功后设置为true.
第二个isPerpetual则是验证关键字forever是否存在 不存在就说明你的不是正式发行版
而是试用版或者已经过期的版本。

第三个isAlmostExpired计算了有效期。

来看Authorization类的构造方法:
  1. public Authorization() {
  2.     String var1 = CommonUtils.canonicalize("cobaltstrike.auth");
  3.     if (!(new File(var1)).exists()) {
  4.         try {
  5.             File var2 = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
  6.             if (var2.getName().toLowerCase().endsWith(".jar")) {
  7.                 var2 = var2.getParentFile();
  8.             }

  9.             var1 = (new File(var2, "cobaltstrike.auth")).getAbsolutePath();
  10.         } catch (Exception var17) {
  11.             MudgeSanity.logException("trouble locating auth file", var17, false);
  12.         }
  13.     }

  14.     byte[] var18 = CommonUtils.readFile(var1);
  15.     if (var18.length == 0) {
  16.         this.error = "Could not read " + var1;
  17.     } else {
  18.         AuthCrypto var3 = new AuthCrypto();
  19.         byte[] var4 = var3.decrypt(var18);
  20.         if (var4.length == 0) {
  21.             this.error = var3.error();
  22.         } else {
  23.             try {
  24.                 DataParser var5 = new DataParser(var4);
  25.                 var5.big();
  26.                 int var6 = var5.readInt();
  27.                 this.watermark = var5.readInt();
  28.                 byte var7 = var5.readByte();
  29.                 if (var7 < 43) {
  30.                     this.error = "Authorization file is not for Cobalt Strike 4.3+";
  31.                     return;
  32.                 }

  33.                 byte var8 = var5.readByte();
  34.                 var5.readBytes(var8);
  35.                 byte var10 = var5.readByte();
  36.                 var5.readBytes(var10);
  37.                 byte var12 = var5.readByte();
  38.                 var5.readBytes(var12);
  39.                 byte var14 = var5.readByte();
  40.                 byte[] var15 = var5.readBytes(var14);
  41.                 if (29999999 == var6) {
  42.                     this.validto = "forever";
  43.                     MudgeSanity.systemDetail("valid to", "perpetual");
  44.                 } else {
  45.                     this.validto = "20" + var6;
  46.                     MudgeSanity.systemDetail("valid to", CommonUtils.formatDateAny("MM
  47. MMM d, YYYY", this.getExpirationDate()));
  48.                 }

  49.                 this.valid = true;
  50.                 MudgeSanity.systemDetail("id", this.watermark + "");
  51.                 SleevedResource.Setup(var15);
  52.             } catch (Exception var16) {
  53.                 MudgeSanity.logException("auth file parsing", var16, false);
  54.             }

  55.         }
  56.     }
  57. }
复制代码

前面都是判断文件存在和读取的代码,主要从这里开始看起:
  1. AuthCrypto var3 = new AuthCrypto();
  2. byte[] var4 = var3.decrypt(var18);
复制代码

初始化了一个AuthCrypto类 调用decrypt方法解密 得到一个字节数组。跟入AuthCrypto类就可以发
现它的构造函数中调用了一个load()方法,继续跟入:
  1. public void load() {
  2.     try {
  3.         byte[] var1 = CommonUtils.readAll(CommonUtils.class.getClassLoader().getRe
  4. sourceAsStream("resources/authkey.pub"));
  5.         byte[] var2 = CommonUtils.MD5(var1);
  6.         if (!"8bb4df00c120881a1945a43e2bb2379e".equals(CommonUtils.toHex(var2))) {
  7.             CommonUtils.print_error("Invalid authorization file");
  8.             System.exit(0);
  9.         }

  10.         X509EncodedKeySpec var3 = new X509EncodedKeySpec(var1);
  11.         KeyFactory var4 = KeyFactory.getInstance("RSA");
  12.         this.pubkey = var4.generatePublic(var3);
  13.     } catch (Exception var5) {
  14.         this.error = "Could not deserialize authpub.key";
  15.         MudgeSanity.logException("authpub.key deserialization", var5, false);
  16.     }

  17. }
复制代码

resources/authkey.pub就是公钥文件 对比此文件的hash防止篡改。

decrypt方法是用来解密.auth文件的,并对文件头进行校验:
  1. public byte[] decrypt(byte[] var1) {
  2.     byte[] var2 = this._decrypt(var1);
  3.     try {
  4.         if (var2.length == 0) {
  5.             return var2;
  6.         } else {
  7.             DataParser var3 = new DataParser(var2);
  8.             var3.big();
  9.             int var4 = var3.readInt();
  10.             if (var4 == -889274181) {
  11.                 this.error = "pre-4.0 authorization file. Run update to get new file";
  12.                 return new byte[0];
  13.             } else if (var4 != -889274157) {
  14.                 this.error = "bad header";
  15.                 return new byte[0];
  16.             } else {
  17.                 int var5 = var3.readShort();
  18.                 byte[] var6 = var3.readBytes(var5);
  19.                 return var6;
  20.             }
  21.         }
  22.     } catch (Exception var7) {
  23.         this.error = var7.getMessage();
  24.         return new byte[0];
  25.     }
  26. }
复制代码

真正RSA解密的部分是_decrypt方法:
  1. protected byte[] _decrypt(byte[] var1) {
  2.     byte[] var2 = new byte[0];

  3.     try {
  4.         if (this.pubkey == null) {
  5.             return new byte[0];
  6.         } else {
  7.             synchronized(this.cipher) {
  8.                 this.cipher.init(2, this.pubkey);
  9.                 var2 = this.cipher.doFinal(var1);
  10.             }

  11.             return var2;
  12.         }
  13.     } catch (Exception var6) {
  14.         this.error = var6.getMessage();
  15.         return new byte[0];
  16.     }
  17. }
复制代码

这里要提一下RSA算法的加密和解密 它是一种非对称加密算法 也就是有公钥和私钥, 公钥用来加密 私钥用来解密
但并不是说公钥就只能用来加密 就像这里,.auth文件需要用公钥来解密 它的明文就是用私钥来加密的。
好了 现在我们看完了RSA解密.auth文件及验证的部分 接着看:
  1. DataParser var5 = new DataParser(var4);
  2. var5.big();
  3. int var6 = var5.readInt();
  4. this.watermark = var5.readInt();
  5. byte var7 = var5.readByte();
  6. if (var7 < 43) {
  7.     this.error = "Authorization file is not for Cobalt Strike 4.3+";
  8.     return;
复制代码

将解密之后的.auth文件解析为byte 类型,之后读取四个字节转换为整数 var6的值就是用来判断授权有效与否的
与前面说过的isPerpetual方法相关 如果不为29999999就是20天的试用版本。
再继续读取四个字节 这里this.watermark值是用来判断是否填充水印特征的
在common/ListenerConfig中可以看到:
  1. if (this.watermark == 0) {
  2.    var3.append("5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\u0000");
  3. } else {
  4.    var3.append((char)CommonUtils.rand(255));
  5. }
复制代码

watermark值为0就会添加这个字符串这是EICAR测试字符扫描到这个字符串的杀软会直接报毒
因为它是被用来测试杀毒软件响应程度的。
继续读取一个字节 var7是用来判断版本 高版本不能使用低版本的.auth文件。
接下来是这段:
  1. byte var8 = var5.readByte();
  2. var5.readBytes(var8);
  3. byte var10 = var5.readByte();
  4. var5.readBytes(var10);
  5. byte var12 = var5.readByte();
  6. var5.readBytes(var12);
  7. byte var14 = var5.readByte();
  8. byte[] var15 = var5.readBytes(var14);
复制代码

这里4.3版本相比4.0版本多了一些代码,实际上是包含了前面版本的key 也就是说4.3版本的.auth文件里有4.0
4.1、4.2的key,应该是为了兼容以前的版本。最后得到var15 用在这里:
  1. SleevedResource.Setup(var15);
复制代码

这是4.0版本新增的验证步骤跟入这个类:
  1. public class SleevedResource {
  2.     private static SleevedResource singleton;
  3.     private SleeveSecurity data = new SleeveSecurity();

  4.     public static void Setup(byte[] var0) {
  5.         singleton = new SleevedResource(var0);
  6.     }

  7.     public static byte[] readResource(String var0) {
  8.         return singleton._readResource(var0);
  9.     }

  10.     private SleevedResource(byte[] var1) {
  11.         this.data.registerKey(var1);
  12.     }

  13.     private byte[] _readResource(String var1) {
  14.         String var2 = CommonUtils.strrep(var1, "resources/", "sleeve/");
  15.         byte[] var3 = CommonUtils.readResource(var2);
  16.         if (var3.length > 0) {
  17.             long var7 = System.currentTimeMillis();
  18.             byte[] var6 = this.data.decrypt(var3);
  19.             return var6;
  20.         } else {
  21.             byte[] var4 = CommonUtils.readResource(var1);
  22.             if (var4.length == 0) {
  23.                 CommonUtils.print_error("Could not find sleeved resource: " + var1 + " [ERROR]");
  24.             } else {
  25.                 CommonUtils.print_stat("Used internal resource: " + var1);
  26.             }

  27.             return var4;
  28.         }
  29.     }
  30. }
复制代码

初始化了SleevedResource类 其私有构造方法里又调用了SleeveSecurity.registerKey方
法参数为刚刚最后得到的var15:
  1. public void registerKey(byte[] var1) {
  2.     synchronized(this) {
  3.         try {
  4.             MessageDigest var3 = MessageDigest.getInstance("SHA-256");
  5.             byte[] var4 = var3.digest(var1);
  6.             byte[] var5 = Arrays.copyOfRange(var4, 0, 16);
  7.             byte[] var6 = Arrays.copyOfRange(var4, 16, 32);
  8.             this.key = new SecretKeySpec(var5, "AES");
  9.             this.hash_key = new SecretKeySpec(var6, "HmacSHA256");
  10.         } catch (Exception var8) {
  11.             var8.printStackTrace();
  12.         }

  13.     }
  14. }
复制代码

使用传入的值计算一个长度为256的摘要,再取0-16作为AES的密钥 取16-32作为HmacSHA256的密钥这里
就结束了 但是既然取了密钥 那么肯定要进行操作 可以在SleeveSecurity.decrypt方法中看到:
  1. public byte[] decrypt(byte[] var1) {
  2.     try {
  3.         byte[] var2 = Arrays.copyOfRange(var1, 0, var1.length - 16);
  4.         byte[] var3 = Arrays.copyOfRange(var1, var1.length - 16, var1.length);
  5.         Object var4 = null;
  6.         byte[] var14;
  7.         synchronized(this) {
  8.             this.mac.init(this.hash_key);
  9.             var14 = this.mac.doFinal(var2);
  10.         }

  11.         byte[] var5 = Arrays.copyOfRange(var14, 0, 16);
  12.         if (!MessageDigest.isEqual(var3, var5)) {
  13.             CommonUtils.print_error("[Sleeve] Bad HMAC on " + var1.le
  14. ngth + " byte message from resource");
  15.             return new byte[0];
  16.         } else {
  17.             Object var6 = null;
  18.             byte[] var15;
  19.             synchronized(this) {
  20.                 var15 = this.do_decrypt(this.key, var2);
  21.             }

  22.             DataInputStream var7 = new DataInputStream(new ByteArrayInputStream(var15));
  23.             int var8 = var7.readInt();
  24.             int var9 = var7.readInt();
  25.             if (var9 >= 0 && var9 <= var1.length) {
  26.                 byte[] var10 = new byte[var9];
  27.                 var7.readFully(var10, 0, var9);
  28.                 return var10;
  29.             } else {
  30.                 CommonUtils.print_error("[Sleeve] Impossible message length: " + var9);
  31.                 return new byte[0];
  32.             }
  33.         }
  34.     } catch (Exception var13) {
  35.         var13.printStackTrace();
  36.         return new byte[0];
  37.     }
  38. }
复制代码

这里校验HMAC,正确后进行AES解密。

寻找调用 SleevedResource._readResource方法中存在调用:
  1. private byte[] _readResource(String var1) {
  2.     String var2 = CommonUtils.strrep(var1, "resources/", "sleeve/");
  3.     byte[] var3 = CommonUtils.readResource(var2);
  4.     if (var3.length > 0) {
  5.         long var7 = System.currentTimeMillis();
  6.         byte[] var6 = this.data.decrypt(var3);
  7.         return var6;
  8.     } else {
  9.         byte[] var4 = CommonUtils.readResource(var1);
  10.         if (var4.length == 0) {
  11.             CommonUtils.print_error("Could not find sleeved resource: " + var1 + " [ERROR]");
  12.         } else {
  13.             CommonUtils.print_stat("Used internal resource: " + var1);
  14.         }

  15.         return var4;
  16.     }
  17. }
复制代码

这个方法接受一个字符串作为文件路径 并将路径中的resources/替换为sleeve 之后读取文件内容
并进行解密此处存放的都是重要功能的dll文件,如果不能正常解密 就会出现虽然能正常打开登录
但是使用功能时会出现很大限制。

破解方法

其实从流程上可以看出 最重要的部分就是:
  1. SleevedResource.Setup(var15);
复制代码

这个key非常关键拿到了它才能进行之后的解密。
那么有没有可能从末尾反推到这个值?末尾是HMAC校验和AES解密所使用的密钥了解过
密码学之后就会发现这无异于痴人说梦。
官方用这个key加密了sleeve下的dll 将key放在了.auth文件中 那么key应该是一个固定值 如果是随机
或者根据用户身份计算得到的话 就无法保证官网jar包的hash值全部一样了。

1. 自己生成auth文件

拿到key之后 可以自己生成一份.auth文件。前面说过 .auth文件是用RSA公钥解密的 我们没私钥怎么加密明文呢?
答案就是自己生成一对密钥 用自己的公钥替换官方给的公钥即可。

从头梳理一下.auth文件的要求:

6位字节,特定的文件头
4位字节,转换为有符号整数后等于29999999
4位字节,转换为有符号整数后不等于0
1位字节,其值大于43小于128
1位字节,其值为16
16位字节,值为key,这里注意4.3版本还包含了之前的key和key长度

那么4.3版本的.auth文件有效长度应该为83位字节 即4.0版本为32位 之后每一个版本都
在前面版本的基础上增加17位。

转换一下
  1. public class authTest {
  2.     public byte[] intToByteArray(int num){
  3.         return new byte[] {
  4.                 (byte) ((num >> 24) & 0xFF),
  5.                 (byte) ((num >> 16) & 0xFF),
  6.                 (byte) ((num >> 8) & 0xFF),
  7.                 (byte) (num & 0xFF)
  8.         };
  9.     }

  10.     public static void main(String[] args){
  11.         authTest authTest = new authTest();
  12.         int header = -889274157;
  13.         int num = 29999999;
  14.         int watermark = 1;

  15.         byte[] bheader = authTest.intToByteArray(header);
  16.         byte[] bnum = authTest.intToByteArray(num);
  17.         byte[] bwatermark = authTest.intToByteArray(watermark);
  18.     }
  19. }
复制代码

9992.png

得出4.0版本的byte[]为:
  1. byte[] decrypt = {
  2.         -54, -2, -64, -45, 0, 0, //文件头
  3.         1, -55, -61, 127, //时间
  4.         0, 0, 0, 1,  //水印
  5.         50, //版本
  6.         16, //key长度
  7.         27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6 //key
  8. };
复制代码

4.1的key为:
  1. byte[] key41 = {-128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86, 118 };
复制代码

4.2的key为:
  1. byte[] key42 = {-78, 13, 72, 122, -35, -44, 113, 52, 24, -14, -43, -93, -82, 2, -89, -96};
复制代码

4.3的key为:
  1. byte[] key43 = {58, 68, 37, 73, 15, 56, -102, -18, -61, 18, -67, -41, 88, -83, 43, -103};
复制代码

已经明确了auth文件的内容 剩下就只需要生成RSA公私钥 然后使用私钥加密.auth文件 并把公钥文件
authkey.pub替换到resources目录下 最后记得修改common/AuthCrypto中load()方法的MD5值。

2. 解密dll

还有一种思路是先将sleeve目录下的dll解密 自定义key 再使用新的私钥加密dll 或者直接把key硬
编码在代码中注释掉从.auth文件读取key的流程。
前一种方法RedCore@Moriarty师傅和Castiel师傅都提供了工具 贴一个链接
GitHub - ca3tie1/CrackSleeve: 破解CS4.0

3. Hook

Hook方法可以不修改原来的源码 将认证的Authorization类做热替换即可 对Java不够熟悉 就不实践了。

收尾工作

众所周知 Cobalt Strike官方会在代码里埋暗桩,4.3版本有一个在beacon/BeaconData的shouldPad方法中
此处会对beacon产生影响,造成30分钟自动退出的情况,原因在beacon/BeaconC2中 使用isPaddingReq
uired方法对文件进行了校验 防止被篡改:

9991.png
9990.png

修改时只需将shouldPad方法的值写死即可:
  1. public void shouldPad(boolean var1) {
  2.     this.shouldPad = false;
  3.     this.when = System.currentTimeMillis() + 1800000L;
  4. }
复制代码

关于Cobalt Strike的破解思路和方法已经介绍完了 目前最新版本是4.4 但我还没有拿到
key看样子增加了更多的暗桩 有机会再详细介绍。

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

关注

0

粉丝

9021

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Powered by Pcgho! X3.4

© 2008-2022 Pcgho Inc.