mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support asm relocate to ignore server inner asm
This commit is contained in:
@@ -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;
|
||||
|
||||
+2
-20
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+38
-8
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+1
-1
@@ -193,4 +193,4 @@ public class CommandHandlerAsmMethodVisitor extends MethodVisitor {
|
||||
// End of catch block
|
||||
mv.visitLabel(afterCatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user