From e8124c02cc0d505ce25bee01cd0e06d83bcd0442 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 29 Mar 2025 14:19:25 +0800 Subject: [PATCH] feat: support asm relocate to ignore server inner asm --- common/build.gradle | 1 + .../javaweb/asm/ClassRenameUtils.java | 53 +++++++++++++++++++ .../javaweb/asm/InnerClassDiscovery.java | 6 +-- .../generator/CustomShellGenerator.java | 22 +------- .../memshell/packer/jar/AgentJarPacker.java | 46 +++++++++++++--- .../javaweb/memshell/utils/CommonUtil.java | 2 +- .../glassfish/GlassFish3ContainerTest.java | 6 +-- .../payara/Payara5201ContainerTest.java | 4 +- .../weblogic/WebLogic14110ContainerTest.java | 4 +- .../jetty/CommandHandlerAsmMethodVisitor.java | 2 +- 10 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java diff --git a/common/build.gradle b/common/build.gradle index 39571346..f5de88d2 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -12,6 +12,7 @@ java { dependencies { implementation 'net.bytebuddy:byte-buddy' + implementation 'org.ow2.asm:asm-commons' implementation 'commons-io:commons-io' implementation 'org.apache.commons:commons-lang3' implementation 'commons-codec:commons-codec' diff --git a/common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java b/common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java new file mode 100644 index 00000000..477b1070 --- /dev/null +++ b/common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java @@ -0,0 +1,53 @@ +package com.reajason.javaweb.asm; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.SimpleRemapper; + +/** + * @author ReaJason + * @since 2025/3/29 + */ +public class ClassRenameUtils { + + public static byte[] renameClass(byte[] classBytes, String newName) { + ClassReader reader = null; + try { + reader = new ClassReader(classBytes); + } catch (Exception e) { + throw new RuntimeException("invalid class bytes"); + } + String oldClassName = reader.getClassName(); + String newClassName = newName.replace('.', '/'); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassRemapper adapter = new ClassRemapper(writer, new SimpleRemapper(oldClassName, newClassName)); + reader.accept(adapter, 0); + return writer.toByteArray(); + } + + public static byte[] relocateClass(byte[] classBytes, String relocateClassPackage, String relocatePrefix) { + ClassReader reader = null; + try { + reader = new ClassReader(classBytes); + } catch (Exception e) { + throw new RuntimeException("invalid class bytes"); + } + String oldClassName = relocateClassPackage.replace('.', '/'); + String newClassName = relocatePrefix.replace('.', '/'); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassRemapper adapter = new ClassRemapper(writer, new Remapper() { + @Override + public String map(String typeName) { + if (typeName.startsWith(oldClassName)) { + return typeName.replaceFirst(oldClassName, newClassName); + } else { + return typeName; + } + } + }); + reader.accept(adapter, 0); + return writer.toByteArray(); + } +} diff --git a/common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java b/common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java index 947e8da7..9021d6cc 100644 --- a/common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java +++ b/common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java @@ -1,9 +1,9 @@ package com.reajason.javaweb.asm; import lombok.Getter; -import net.bytebuddy.jar.asm.ClassReader; -import net.bytebuddy.jar.asm.ClassVisitor; -import net.bytebuddy.jar.asm.Opcodes; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; import java.io.IOException; import java.io.InputStream; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java index 74be7ff3..20611f9f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java @@ -1,12 +1,9 @@ package com.reajason.javaweb.memshell.generator; import com.reajason.javaweb.ClassBytesShrink; +import com.reajason.javaweb.asm.ClassRenameUtils; import com.reajason.javaweb.memshell.config.CustomConfig; import com.reajason.javaweb.memshell.config.ShellConfig; -import net.bytebuddy.jar.asm.ClassReader; -import net.bytebuddy.jar.asm.ClassWriter; -import net.bytebuddy.jar.asm.commons.ClassRemapper; -import net.bytebuddy.jar.asm.commons.SimpleRemapper; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; @@ -31,23 +28,8 @@ public class CustomShellGenerator { throw new IllegalArgumentException("Custom shell class is empty"); } - byte[] bytes = renameClass(Base64.decodeBase64(shellClassBase64), customConfig.getShellClassName()); + byte[] bytes = ClassRenameUtils.renameClass(Base64.decodeBase64(shellClassBase64), customConfig.getShellClassName()); return ClassBytesShrink.shrink(bytes, shellConfig.isShrink()); } - - private static byte[] renameClass(byte[] classBytes, String newName) { - ClassReader reader = null; - try { - reader = new ClassReader(classBytes); - } catch (Exception e) { - throw new RuntimeException("invalid class bytes"); - } - String oldClassName = reader.getClassName(); - String newClassName = newName.replace('.', '/'); - ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - ClassRemapper adapter = new ClassRemapper(writer, new SimpleRemapper(oldClassName, newClassName)); - reader.accept(adapter, 0); - return writer.toByteArray(); - } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jar/AgentJarPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jar/AgentJarPacker.java index 7fd788f4..5da7d65e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jar/AgentJarPacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jar/AgentJarPacker.java @@ -1,6 +1,8 @@ package com.reajason.javaweb.memshell.packer.jar; +import com.reajason.javaweb.asm.ClassRenameUtils; import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.SneakyThrows; import net.bytebuddy.ByteBuddy; import org.apache.commons.io.IOUtils; @@ -41,24 +43,44 @@ public class AgentJarPacker implements JarPacker { manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true"); manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + String RELOCATE_PREFIX = CommonUtil.getRandomPackageName().replace(".", "/") + "/"; + boolean RELOCATE_ENABLED = true; + try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) { + String dependencyPackageName = null; if (generateResult.getShellConfig().getShellType().endsWith("ASM")) { - addDependency(targetJar, Opcodes.class); + dependencyPackageName = Opcodes.class.getPackage().getName(); + addDependency(targetJar, Opcodes.class, true, RELOCATE_PREFIX); } else { - addDependency(targetJar, ByteBuddy.class); + RELOCATE_ENABLED = false; + dependencyPackageName = ByteBuddy.class.getPackage().getName(); + addDependency(targetJar, ByteBuddy.class, false, RELOCATE_PREFIX); } + byte[] injectorBytes = generateResult.getInjectorBytes(); + if (RELOCATE_ENABLED) { + injectorBytes = ClassRenameUtils.relocateClass(injectorBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); + } targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class")); - targetJar.write(generateResult.getInjectorBytes()); + targetJar.write(injectorBytes); targetJar.closeEntry(); + byte[] shellBytes = generateResult.getShellBytes(); + if (RELOCATE_ENABLED) { + shellBytes = ClassRenameUtils.relocateClass(shellBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); + } targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class")); - targetJar.write(generateResult.getShellBytes()); + targetJar.write(shellBytes); targetJar.closeEntry(); for (Map.Entry entry : generateResult.getInjectorInnerClassBytes().entrySet()) { targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class")); - targetJar.write(entry.getValue()); + byte[] innerClassBytes = entry.getValue(); + if (RELOCATE_ENABLED) { + innerClassBytes = ClassRenameUtils.relocateClass(innerClassBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); + } + targetJar.write(innerClassBytes); targetJar.closeEntry(); } } @@ -66,7 +88,7 @@ public class AgentJarPacker implements JarPacker { } @SneakyThrows - public static void addDependency(JarOutputStream targetJar, Class baseClass) { + public static void addDependency(JarOutputStream targetJar, Class baseClass, boolean relocate, String relocatePrefix) { String packageToMove = baseClass.getPackage().getName().replace('.', '/'); URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation(); String sourceUrlString = sourceUrl.toString(); @@ -89,8 +111,16 @@ public class AgentJarPacker implements JarPacker { String entryName = entry.getName(); if (entryName.startsWith(packageToMove)) { InputStream entryStream = sourceJar.getInputStream(entry); - targetJar.putNextEntry(new JarEntry(entryName)); - IOUtils.copy(entryStream, targetJar); + byte[] bytes = IOUtils.toByteArray(entryStream); + if (relocate) { + targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName)); + if (bytes.length > 0) { + bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove); + } + } else { + targetJar.putNextEntry(new JarEntry(entryName)); + } + targetJar.write(bytes); targetJar.closeEntry(); entryStream.close(); } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/utils/CommonUtil.java b/generator/src/main/java/com/reajason/javaweb/memshell/utils/CommonUtil.java index 1c3673c5..8406b02e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/utils/CommonUtil.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/utils/CommonUtil.java @@ -87,7 +87,7 @@ public class CommonUtil { return sb.toString(); } - private static String getRandomPackageName() { + public static String getRandomPackageName() { return PACKAGE_NAMES[new Random().nextInt(PACKAGE_NAMES.length)] + "." + getRandomString(5); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/glassfish/GlassFish3ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/glassfish/GlassFish3ContainerTest.java index 866955b5..d5cde641 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/glassfish/GlassFish3ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/glassfish/GlassFish3ContainerTest.java @@ -60,9 +60,9 @@ public class GlassFish3ContainerTest { List supportedShellTypes = List.of( ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, ShellType.AGENT_FILTER_CHAIN, -// ShellType.AGENT_FILTER_CHAIN_ASM, 内置了 asm 但是版本太低 - ShellType.CATALINA_AGENT_CONTEXT_VALVE -// ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM + ShellType.AGENT_FILTER_CHAIN_ASM, + ShellType.CATALINA_AGENT_CONTEXT_VALVE, + ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize); List> unSupportedCases = List.of( diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/payara/Payara5201ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/payara/Payara5201ContainerTest.java index 4122f7ef..0dd2e272 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/payara/Payara5201ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/payara/Payara5201ContainerTest.java @@ -53,8 +53,8 @@ public class Payara5201ContainerTest { Server server = Server.Payara; List supportedShellTypes = List.of( ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, - ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE -// ShellType.AGENT_FILTER_CHAIN_ASM, ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM // 内置了 ASM,但是版本较低,是 7 版本不兼容 + ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE, + ShellType.AGENT_FILTER_CHAIN_ASM, ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM ); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.ScriptEngine); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java index c1065ce6..9ffb0ee0 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java @@ -53,8 +53,8 @@ public class WebLogic14110ContainerTest { Server server = Server.WebLogic; List supportedShellTypes = List.of( ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, - ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT -// ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT_ASM // 内置 ASM,但只有 7.1 版本 + ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT, + ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT_ASM ); List testPackers = List.of(Packers.Base64); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java index 191dbd8f..e14ae084 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/jetty/CommandHandlerAsmMethodVisitor.java @@ -193,4 +193,4 @@ public class CommandHandlerAsmMethodVisitor extends MethodVisitor { // End of catch block mv.visitLabel(afterCatch); } -} \ No newline at end of file +} \ No newline at end of file