Skip to content

Commit e8124c0

Browse files
committed
feat: support asm relocate to ignore server inner asm
1 parent 52b8000 commit e8124c0

File tree

10 files changed

+106
-40
lines changed

10 files changed

+106
-40
lines changed

common/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ java {
1212

1313
dependencies {
1414
implementation 'net.bytebuddy:byte-buddy'
15+
implementation 'org.ow2.asm:asm-commons'
1516
implementation 'commons-io:commons-io'
1617
implementation 'org.apache.commons:commons-lang3'
1718
implementation 'commons-codec:commons-codec'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.reajason.javaweb.asm;
2+
3+
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassWriter;
5+
import org.objectweb.asm.commons.ClassRemapper;
6+
import org.objectweb.asm.commons.Remapper;
7+
import org.objectweb.asm.commons.SimpleRemapper;
8+
9+
/**
10+
* @author ReaJason
11+
* @since 2025/3/29
12+
*/
13+
public class ClassRenameUtils {
14+
15+
public static byte[] renameClass(byte[] classBytes, String newName) {
16+
ClassReader reader = null;
17+
try {
18+
reader = new ClassReader(classBytes);
19+
} catch (Exception e) {
20+
throw new RuntimeException("invalid class bytes");
21+
}
22+
String oldClassName = reader.getClassName();
23+
String newClassName = newName.replace('.', '/');
24+
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
25+
ClassRemapper adapter = new ClassRemapper(writer, new SimpleRemapper(oldClassName, newClassName));
26+
reader.accept(adapter, 0);
27+
return writer.toByteArray();
28+
}
29+
30+
public static byte[] relocateClass(byte[] classBytes, String relocateClassPackage, String relocatePrefix) {
31+
ClassReader reader = null;
32+
try {
33+
reader = new ClassReader(classBytes);
34+
} catch (Exception e) {
35+
throw new RuntimeException("invalid class bytes");
36+
}
37+
String oldClassName = relocateClassPackage.replace('.', '/');
38+
String newClassName = relocatePrefix.replace('.', '/');
39+
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
40+
ClassRemapper adapter = new ClassRemapper(writer, new Remapper() {
41+
@Override
42+
public String map(String typeName) {
43+
if (typeName.startsWith(oldClassName)) {
44+
return typeName.replaceFirst(oldClassName, newClassName);
45+
} else {
46+
return typeName;
47+
}
48+
}
49+
});
50+
reader.accept(adapter, 0);
51+
return writer.toByteArray();
52+
}
53+
}

common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.reajason.javaweb.asm;
22

33
import lombok.Getter;
4-
import net.bytebuddy.jar.asm.ClassReader;
5-
import net.bytebuddy.jar.asm.ClassVisitor;
6-
import net.bytebuddy.jar.asm.Opcodes;
4+
import org.objectweb.asm.ClassReader;
5+
import org.objectweb.asm.ClassVisitor;
6+
import org.objectweb.asm.Opcodes;
77

88
import java.io.IOException;
99
import java.io.InputStream;
Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.reajason.javaweb.memshell.generator;
22

33
import com.reajason.javaweb.ClassBytesShrink;
4+
import com.reajason.javaweb.asm.ClassRenameUtils;
45
import com.reajason.javaweb.memshell.config.CustomConfig;
56
import com.reajason.javaweb.memshell.config.ShellConfig;
6-
import net.bytebuddy.jar.asm.ClassReader;
7-
import net.bytebuddy.jar.asm.ClassWriter;
8-
import net.bytebuddy.jar.asm.commons.ClassRemapper;
9-
import net.bytebuddy.jar.asm.commons.SimpleRemapper;
107
import org.apache.commons.codec.binary.Base64;
118
import org.apache.commons.lang3.StringUtils;
129

@@ -31,23 +28,8 @@ public byte[] getBytes() {
3128
throw new IllegalArgumentException("Custom shell class is empty");
3229
}
3330

34-
byte[] bytes = renameClass(Base64.decodeBase64(shellClassBase64), customConfig.getShellClassName());
31+
byte[] bytes = ClassRenameUtils.renameClass(Base64.decodeBase64(shellClassBase64), customConfig.getShellClassName());
3532

3633
return ClassBytesShrink.shrink(bytes, shellConfig.isShrink());
3734
}
38-
39-
private static byte[] renameClass(byte[] classBytes, String newName) {
40-
ClassReader reader = null;
41-
try {
42-
reader = new ClassReader(classBytes);
43-
} catch (Exception e) {
44-
throw new RuntimeException("invalid class bytes");
45-
}
46-
String oldClassName = reader.getClassName();
47-
String newClassName = newName.replace('.', '/');
48-
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
49-
ClassRemapper adapter = new ClassRemapper(writer, new SimpleRemapper(oldClassName, newClassName));
50-
reader.accept(adapter, 0);
51-
return writer.toByteArray();
52-
}
5335
}

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

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

3+
import com.reajason.javaweb.asm.ClassRenameUtils;
34
import com.reajason.javaweb.memshell.config.GenerateResult;
5+
import com.reajason.javaweb.memshell.utils.CommonUtil;
46
import lombok.SneakyThrows;
57
import net.bytebuddy.ByteBuddy;
68
import org.apache.commons.io.IOUtils;
@@ -41,32 +43,52 @@ public byte[] packBytes(GenerateResult generateResult) {
4143
manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true");
4244
manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true");
4345
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
46+
47+
String RELOCATE_PREFIX = CommonUtil.getRandomPackageName().replace(".", "/") + "/";
48+
boolean RELOCATE_ENABLED = true;
49+
4450
try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) {
51+
String dependencyPackageName = null;
4552
if (generateResult.getShellConfig().getShellType().endsWith("ASM")) {
46-
addDependency(targetJar, Opcodes.class);
53+
dependencyPackageName = Opcodes.class.getPackage().getName();
54+
addDependency(targetJar, Opcodes.class, true, RELOCATE_PREFIX);
4755
} else {
48-
addDependency(targetJar, ByteBuddy.class);
56+
RELOCATE_ENABLED = false;
57+
dependencyPackageName = ByteBuddy.class.getPackage().getName();
58+
addDependency(targetJar, ByteBuddy.class, false, RELOCATE_PREFIX);
4959
}
5060

61+
byte[] injectorBytes = generateResult.getInjectorBytes();
62+
if (RELOCATE_ENABLED) {
63+
injectorBytes = ClassRenameUtils.relocateClass(injectorBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName);
64+
}
5165
targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class"));
52-
targetJar.write(generateResult.getInjectorBytes());
66+
targetJar.write(injectorBytes);
5367
targetJar.closeEntry();
5468

69+
byte[] shellBytes = generateResult.getShellBytes();
70+
if (RELOCATE_ENABLED) {
71+
shellBytes = ClassRenameUtils.relocateClass(shellBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName);
72+
}
5573
targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class"));
56-
targetJar.write(generateResult.getShellBytes());
74+
targetJar.write(shellBytes);
5775
targetJar.closeEntry();
5876

5977
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
6078
targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class"));
61-
targetJar.write(entry.getValue());
79+
byte[] innerClassBytes = entry.getValue();
80+
if (RELOCATE_ENABLED) {
81+
innerClassBytes = ClassRenameUtils.relocateClass(innerClassBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName);
82+
}
83+
targetJar.write(innerClassBytes);
6284
targetJar.closeEntry();
6385
}
6486
}
6587
return byteArrayOutputStream.toByteArray();
6688
}
6789

6890
@SneakyThrows
69-
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass) {
91+
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, boolean relocate, String relocatePrefix) {
7092
String packageToMove = baseClass.getPackage().getName().replace('.', '/');
7193
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
7294
String sourceUrlString = sourceUrl.toString();
@@ -89,8 +111,16 @@ public static void addDependency(JarOutputStream targetJar, Class<?> baseClass)
89111
String entryName = entry.getName();
90112
if (entryName.startsWith(packageToMove)) {
91113
InputStream entryStream = sourceJar.getInputStream(entry);
92-
targetJar.putNextEntry(new JarEntry(entryName));
93-
IOUtils.copy(entryStream, targetJar);
114+
byte[] bytes = IOUtils.toByteArray(entryStream);
115+
if (relocate) {
116+
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
117+
if (bytes.length > 0) {
118+
bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove);
119+
}
120+
} else {
121+
targetJar.putNextEntry(new JarEntry(entryName));
122+
}
123+
targetJar.write(bytes);
94124
targetJar.closeEntry();
95125
entryStream.close();
96126
}

generator/src/main/java/com/reajason/javaweb/memshell/utils/CommonUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private static String getRandomStringInternal(int length) {
8787
return sb.toString();
8888
}
8989

90-
private static String getRandomPackageName() {
90+
public static String getRandomPackageName() {
9191
return PACKAGE_NAMES[new Random().nextInt(PACKAGE_NAMES.length)] + "." + getRandomString(5);
9292
}
9393

integration-test/src/test/java/com/reajason/javaweb/integration/glassfish/GlassFish3ContainerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ static Stream<Arguments> casesProvider() {
6060
List<String> supportedShellTypes = List.of(
6161
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
6262
ShellType.AGENT_FILTER_CHAIN,
63-
// ShellType.AGENT_FILTER_CHAIN_ASM, 内置了 asm 但是版本太低
64-
ShellType.CATALINA_AGENT_CONTEXT_VALVE
65-
// ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM
63+
ShellType.AGENT_FILTER_CHAIN_ASM,
64+
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
65+
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM
6666
);
6767
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
6868
List<Triple<String, ShellTool, Packers>> unSupportedCases = List.of(

integration-test/src/test/java/com/reajason/javaweb/integration/payara/Payara5201ContainerTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ static Stream<Arguments> casesProvider() {
5353
Server server = Server.Payara;
5454
List<String> supportedShellTypes = List.of(
5555
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
56-
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE
57-
// ShellType.AGENT_FILTER_CHAIN_ASM, ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM // 内置了 ASM,但是版本较低,是 7 版本不兼容
56+
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE,
57+
ShellType.AGENT_FILTER_CHAIN_ASM, ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM
5858
);
5959
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.ScriptEngine);
6060
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);

integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ static Stream<Arguments> casesProvider() {
5353
Server server = Server.WebLogic;
5454
List<String> supportedShellTypes = List.of(
5555
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56-
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
57-
// ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT_ASM // 内置 ASM,但只有 7.1 版本
56+
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT,
57+
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT_ASM
5858
);
5959
List<Packers> testPackers = List.of(Packers.Base64);
6060
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);

memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,4 @@ public void visitCode() {
193193
// End of catch block
194194
mv.visitLabel(afterCatch);
195195
}
196-
}
196+
}

0 commit comments

Comments
 (0)