mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
refactor: rename modules
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
group = 'io.github.reajason'
|
||||
description = "Common Utilities for MemShellParty"
|
||||
version = rootProject.version
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
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'
|
||||
implementation 'org.jetbrains:annotations'
|
||||
testImplementation platform('org.junit:junit-bom')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
testImplementation "org.mockito:mockito-core"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.reajason.javaweb;
|
||||
|
||||
import net.bytebuddy.jar.asm.ClassReader;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.ClassWriter;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/2/25
|
||||
*/
|
||||
public class ClassBytesShrink {
|
||||
|
||||
public static byte[] shrink(byte[] bytes, boolean full) {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
|
||||
@Override
|
||||
public void visitSource(String source, String debug) {
|
||||
|
||||
}
|
||||
};
|
||||
cr.accept(cv, full ? ClassReader.SKIP_DEBUG : 0);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.reajason.javaweb.asm;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class InnerClassDiscovery {
|
||||
|
||||
/**
|
||||
* Discovers all inner classes for a given class
|
||||
*
|
||||
* @param originalClass The class to discover inner classes for
|
||||
* @return A set of fully qualified inner class names
|
||||
*/
|
||||
public static Set<String> findAllInnerClasses(Class<?> originalClass) throws IOException {
|
||||
Set<String> innerClasses;
|
||||
String resourceName = originalClass.getName().replace('.', '/') + ".class";
|
||||
try (InputStream is = originalClass.getClassLoader().getResourceAsStream(resourceName)) {
|
||||
if (is == null) {
|
||||
throw new IOException("Could not find class file for " + originalClass.getName());
|
||||
}
|
||||
|
||||
ClassReader reader = new ClassReader(is);
|
||||
InnerClassCollector collector = new InnerClassCollector(originalClass.getName());
|
||||
reader.accept(collector, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
|
||||
innerClasses = new HashSet<>(collector.getInnerClasses());
|
||||
}
|
||||
try {
|
||||
for (Class<?> innerClass : originalClass.getDeclaredClasses()) {
|
||||
innerClasses.add(innerClass.getName());
|
||||
innerClasses.addAll(findAllInnerClasses(innerClass));
|
||||
}
|
||||
} catch (SecurityException ignored) {
|
||||
}
|
||||
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
private static class InnerClassCollector extends ClassVisitor {
|
||||
private final String originalClassName;
|
||||
@Getter
|
||||
private final Set<String> innerClasses = new HashSet<>();
|
||||
|
||||
public InnerClassCollector(String originalClassName) {
|
||||
super(Opcodes.ASM9);
|
||||
this.originalClassName = originalClassName.replace('/', '.');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
String className = name.replace('/', '.');
|
||||
if (outerName != null) {
|
||||
String outerClassName = outerName.replace('/', '.');
|
||||
if (outerClassName.equals(originalClassName)) {
|
||||
innerClasses.add(className);
|
||||
}
|
||||
} else if (className.startsWith(originalClassName + "$")) {
|
||||
innerClasses.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
|
||||
|
||||
/**
|
||||
* JDK9 引入的 module 系统,只有主动声明 exports 的才能被外部访问。当前用于打破 module 的限制,使我们能像低版本一样任意反射获取方法
|
||||
*
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class ByPassJavaModuleInterceptor {
|
||||
@Advice.OnMethodEnter
|
||||
public static void enter(@Advice.Origin Class<?> clazz) {
|
||||
try {
|
||||
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
|
||||
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
|
||||
unsafeField.setAccessible(true);
|
||||
Object unsafe = unsafeField.get(null);
|
||||
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
|
||||
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
|
||||
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
|
||||
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
|
||||
getAndSetObjectM.invoke(unsafe, clazz, offset, module);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference1: <a href="https://stackoverflow.com/questions/62664427/can-i-create-a-bytebuddy-instrumented-type-with-a-private-static-final-methodhan">stackoverflow</a>
|
||||
* Reference2: <a href="https://github.com/raphw/byte-buddy/issues/1153">issue</a>
|
||||
* <br>
|
||||
* 在静态代码块中执行 byPassJdkModule 代码
|
||||
* 值得注意的一点,builder 是不可变类型,所以都是需要重新赋值,例如以下代码示例
|
||||
* # code that not work
|
||||
* builder = new Bytebuddy().redefine(class);
|
||||
* builder.visit(something);
|
||||
* builder.make();
|
||||
* <br>
|
||||
* # code that work
|
||||
* <br>
|
||||
* builder = new Bytebuddy().redefine(class);
|
||||
* builder = builder.visit(something);
|
||||
* builder.make();
|
||||
*
|
||||
* @param builder bytebuddy builder
|
||||
* @return new builder with bypass
|
||||
*/
|
||||
public static DynamicType.Builder<?> extend(DynamicType.Builder<?> builder) {
|
||||
return builder.visit(Advice.to(ByPassJavaModuleInterceptor.class)
|
||||
.on(isTypeInitializer()));
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
||||
import net.bytebuddy.jar.asm.commons.Remapper;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/3/27
|
||||
*/
|
||||
public class ClassRenameVisitorWrapper implements AsmVisitorWrapper {
|
||||
public final String originalClassName;
|
||||
public final String newClassName;
|
||||
|
||||
public ClassRenameVisitorWrapper(String originalClassName, String newClassName) {
|
||||
this.originalClassName = originalClassName.replace('.', '/');
|
||||
this.newClassName = newClassName.replace('.', '/');
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull ClassVisitor classVisitor,
|
||||
@NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool,
|
||||
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
|
||||
@NotNull MethodList<?> methods,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new ClassRemapper(
|
||||
classVisitor,
|
||||
new Remapper() {
|
||||
@Override
|
||||
public String map(String typeName) {
|
||||
if (typeName.startsWith(originalClassName)) {
|
||||
return typeName.replaceFirst(originalClassName, newClassName);
|
||||
} else {
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import net.bytebuddy.utility.OpenedClassReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 移除静态代码块
|
||||
*
|
||||
* @author ReaJason
|
||||
*/
|
||||
public enum ClinitRemovingAsmVisitorWrapper implements AsmVisitorWrapper {
|
||||
|
||||
/**
|
||||
* The singleton instance.
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
private static final String CLINIT = "<clinit>";
|
||||
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull ClassVisitor classVisitor,
|
||||
@NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool,
|
||||
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
|
||||
@NotNull MethodList<?> methods,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new ClinitRemovingClassVisitor(classVisitor);
|
||||
}
|
||||
|
||||
protected static class ClinitRemovingClassVisitor extends ClassVisitor {
|
||||
protected ClinitRemovingClassVisitor(ClassVisitor classVisitor) {
|
||||
super(OpenedClassReader.ASM_API, classVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(
|
||||
int modifiers, String name, String descriptor, String signature, String[] exception) {
|
||||
MethodVisitor methodVisitor =
|
||||
super.visitMethod(modifiers, name, descriptor, signature, exception);
|
||||
return name.equals(CLINIT)
|
||||
? null
|
||||
: methodVisitor;
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
||||
import net.bytebuddy.jar.asm.commons.Remapper;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 修改方法中局部变量的赋值
|
||||
*
|
||||
* @author ReaJason
|
||||
* @since 2025/1/5
|
||||
*/
|
||||
public class LdcReAssignVisitorWrapper implements AsmVisitorWrapper {
|
||||
private final Map<Object, Object> map;
|
||||
|
||||
public LdcReAssignVisitorWrapper(Map<Object, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ClassVisitor wrap(@NotNull TypeDescription instrumentedType, @NotNull ClassVisitor classVisitor,
|
||||
Implementation.@NotNull Context implementationContext, @NotNull TypePool typePool,
|
||||
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
|
||||
@NotNull MethodList<?> methods, int writerFlags, int readerFlags) {
|
||||
return new ClassRemapper(classVisitor, new Remapper() {
|
||||
@Override
|
||||
public Object mapValue(Object value) {
|
||||
if (map.containsKey(value)) {
|
||||
return map.get(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static net.bytebuddy.jar.asm.Opcodes.INVOKEVIRTUAL;
|
||||
import static net.bytebuddy.jar.asm.Opcodes.POP;
|
||||
|
||||
/**
|
||||
* Debug 信息打印移除器
|
||||
* 目前仅支持移除以下几种
|
||||
* <br />
|
||||
* 1. System.out.println() - (printf 还不支持)
|
||||
* 2. e.printStackTrace()
|
||||
* 3. Logger.info (java.util)
|
||||
*
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class LogRemoveMethodVisitor implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
|
||||
public static final LogRemoveMethodVisitor INSTANCE = new LogRemoveMethodVisitor();
|
||||
|
||||
public static DynamicType.Builder<?> extend(DynamicType.Builder<?> builder) {
|
||||
return builder.visit(
|
||||
new AsmVisitorWrapper.ForDeclaredMethods()
|
||||
.method(ElementMatchers.any(), LogRemoveMethodVisitor.INSTANCE));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MethodVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull MethodDescription instrumentedMethod,
|
||||
@NotNull MethodVisitor methodVisitor,
|
||||
@NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new MethodVisitor(Opcodes.ASM9, methodVisitor) {
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
||||
if ((opcode == INVOKEVIRTUAL && owner.equals("java/io/PrintStream") && name.equals("println"))
|
||||
|| (opcode == INVOKEVIRTUAL && owner.endsWith("Exception") && name.equals("printStackTrace"))
|
||||
|| (opcode == INVOKEVIRTUAL && owner.equals("java/util/logging/Logger") && (name.equals("info") || name.equals("warning")))
|
||||
) {
|
||||
String[] args = descriptor.substring(1, descriptor.indexOf(')')).split(";");
|
||||
for (String arg : args) {
|
||||
if (StringUtils.isNotBlank(arg)) {
|
||||
super.visitInsn(POP);
|
||||
}
|
||||
}
|
||||
super.visitInsn(POP);
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class MethodCallReplaceVisitorWrapper implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
|
||||
|
||||
private final String targetClassName;
|
||||
private final Set<String> replaceClassNames;
|
||||
|
||||
public MethodCallReplaceVisitorWrapper(String targetClassName, Set<String> replaceClassNames) {
|
||||
this.targetClassName = targetClassName.replace(".", "/");
|
||||
this.replaceClassNames = replaceClassNames.stream().map(s -> s.replace(".", "/")).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MethodVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull MethodDescription instrumentedMethod,
|
||||
@NotNull MethodVisitor methodVisitor,
|
||||
@NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new MethodVisitor(Opcodes.ASM9, methodVisitor) {
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
||||
if (opcode == Opcodes.INVOKESTATIC
|
||||
&& replaceClassNames.contains(owner)) {
|
||||
super.visitMethodInsn(Opcodes.INVOKESTATIC,
|
||||
targetClassName,
|
||||
name,
|
||||
descriptor,
|
||||
false);
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
||||
import net.bytebuddy.jar.asm.commons.Remapper;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Servlet 包名替换,扫描包中所有 javax/servlet 将其替换成 jakarta/servlet。
|
||||
*
|
||||
* @author ReaJason
|
||||
* @since 2024/11/23
|
||||
*/
|
||||
public class ServletRenameVisitorWrapper implements AsmVisitorWrapper {
|
||||
public static ServletRenameVisitorWrapper INSTANCE = new ServletRenameVisitorWrapper();
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull ClassVisitor classVisitor,
|
||||
@NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool,
|
||||
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
|
||||
@NotNull MethodList<?> methods,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new ClassRemapper(
|
||||
classVisitor,
|
||||
new Remapper() {
|
||||
@Override
|
||||
public String map(String typeName) {
|
||||
if (typeName.startsWith("javax/servlet/")) {
|
||||
return typeName.replaceFirst("javax", "jakarta");
|
||||
} else {
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import net.bytebuddy.utility.nullability.MaybeNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 通过 classVisitor 将 classFileVersion 改为指定 JDK 版本,用于 JDK8 的环境能生成任意 JDK 版本的字节码,默认使用 JDK6
|
||||
*
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class TargetJreVersionVisitorWrapper implements AsmVisitorWrapper {
|
||||
|
||||
public static final TargetJreVersionVisitorWrapper DEFAULT = new TargetJreVersionVisitorWrapper();
|
||||
|
||||
private final int targetJdkVersion;
|
||||
|
||||
public TargetJreVersionVisitorWrapper() {
|
||||
targetJdkVersion = Opcodes.V1_6;
|
||||
}
|
||||
|
||||
public TargetJreVersionVisitorWrapper(int targetJdkVersion) {
|
||||
this.targetJdkVersion = targetJdkVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
|
||||
@NotNull ClassVisitor classVisitor, @NotNull Implementation.Context implementationContext,
|
||||
@NotNull TypePool typePool, @NotNull FieldList<FieldDescription.InDefinedShape> fields,
|
||||
@NotNull MethodList<?> methods, int writerFlags, int readerFlags) {
|
||||
return new ClassVisitor(Opcodes.ASM9, classVisitor) {
|
||||
@Override
|
||||
public void visit(int version, int modifiers, String name, @MaybeNull String signature, @MaybeNull String superClassName, @MaybeNull String[] interfaceName) {
|
||||
super.visit(targetJdkVersion, modifiers, name, signature, superClassName, interfaceName);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.reajason.javaweb.util;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class ClassDefiner extends ClassLoader {
|
||||
private ClassDefiner() {
|
||||
}
|
||||
|
||||
public static Class<?> defineClass(byte[] code) {
|
||||
return new ClassDefiner().defineClass(null, code, 0, code.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.reajason.javaweb.util;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/11/23
|
||||
*/
|
||||
public class ClassUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static Class<?> defineClass(byte[] bytes) {
|
||||
return ClassDefiner.defineClass(bytes);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Object newInstance(byte[] bytes) {
|
||||
Class<?> clazz = defineClass(bytes);
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Object getFieldValue(Object object, String fieldName) {
|
||||
Field field = object.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(object);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes, Object[] parameters) {
|
||||
Method method = object.getClass().getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method.invoke(object, parameters);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.reajason.javaweb.buddy;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/2/22
|
||||
*/
|
||||
class MethodCallReplaceVisitorWrapperTest {
|
||||
|
||||
public static class ExternalClass {
|
||||
public static String externalMethod(String input) {
|
||||
return "External: " + input;
|
||||
}
|
||||
|
||||
public static String replacementMethod(String input) {
|
||||
return "Replaced: " + input;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TargetClass {
|
||||
public String targetMethod(String input) {
|
||||
System.out.println("targetMethod");
|
||||
return ExternalClass.replacementMethod(input);
|
||||
}
|
||||
|
||||
public static String replacementMethod(String input) {
|
||||
return "Replaced: " + input;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
String newClassName = TargetClass.class.getName() + "Redefinition";
|
||||
DynamicType.Unloaded<TargetClass> dynamicType = new ByteBuddy()
|
||||
.redefine(TargetClass.class)
|
||||
.name(newClassName)
|
||||
.visit(new AsmVisitorWrapper
|
||||
.ForDeclaredMethods()
|
||||
.method(named("targetMethod"),
|
||||
new MethodCallReplaceVisitorWrapper(
|
||||
newClassName,
|
||||
Collections.singleton(ExternalClass.class.getName())
|
||||
)
|
||||
)
|
||||
)
|
||||
.make();
|
||||
Class<?> redefinedClass = dynamicType.load(MethodCallReplaceVisitorWrapperTest.class.getClassLoader())
|
||||
.getLoaded();
|
||||
Object object = redefinedClass.newInstance();
|
||||
Object result = object.getClass().getMethod("targetMethod", String.class).invoke(object, "xixi");
|
||||
assertEquals("Replaced: xixi", result);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user