英文:
Java ASM byte code manipulation to inject code into a method not working
问题
我想将一些代码注入到现有的类/方法中,但我无法获取类加载器以使其"找到"这个类,以便使用修改后的字节码。
MyClassInjector.java
import org.objectweb.asm.*;
public class MyClassInjector {
public static void main(String[] args) throws Exception {
// Load the MyClass class
ClassReader cr = new ClassReader("MyClass");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
MyClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, 0);
// Inject code into the myMethod method
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "myMethod", "()V", null, null);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClassInjector", "newMethod", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// Define the newMethod method
MethodVisitor mv2 = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "newMethod", "()V", null, null);
mv2.visitCode();
mv2.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("Injected code");
mv2.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv2.visitInsn(Opcodes.RETURN);
mv2.visitMaxs(0, 0);
mv2.visitEnd();
// Define the new byte array with the modified class bytecode
byte[] modifiedClass = cw.toByteArray();
// Define a new class loader to load the modified class
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals("MyClass")) {
return defineClass(name, modifiedClass, 0, modifiedClass.length);
} else {
return super.findClass(name);
}
}
};
// Load the modified class and call myMethod
Class<?> myClass = cl.loadClass("MyClass"); //<----------------------- HERE
Object myObject = myClass.newInstance();
myClass.getMethod("myMethod").invoke(myObject);
}
}
class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
}
MyClass.java
public class MyClass {
public void myMethod() {
System.out.println("Hello, world!");
}
}
当我调用loadClass(上面标有HERE的地方),它没有调用"findClass",所以方法没有被修改。根据我所了解,loadClass() 应该调用 findClass()。有什么想法吗?
英文:
I want to inject some code into an existing class/method. But I am unable to get the classloader to "find" the class in order to use the modified byte code.
MyClassInjector.java
import org.objectweb.asm.*;
public class MyClassInjector {
public static void main(String[] args) throws Exception {
// Load the MyClass class
ClassReader cr = new ClassReader("MyClass");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
MyClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, 0);
// Inject code into the myMethod method
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "myMethod", "()V", null, null);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClassInjector", "newMethod", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// Define the newMethod method
MethodVisitor mv2 = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "newMethod", "()V", null, null);
mv2.visitCode();
mv2.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("Injected code");
mv2.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv2.visitInsn(Opcodes.RETURN);
mv2.visitMaxs(0, 0);
mv2.visitEnd();
// Define the new byte array with the modified class bytecode
byte[] modifiedClass = cw.toByteArray();
// Define a new class loader to load the modified class
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals("MyClass")) {
return defineClass(name, modifiedClass, 0, modifiedClass.length);
} else {
return super.findClass(name);
}
}
};
// Load the modified class and call myMethod
Class<?> myClass = cl.loadClass("MyClass"); <----------------------- HERE
Object myObject = myClass.newInstance();
myClass.getMethod("myMethod").invoke(myObject);
}
}
class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
}
MyClass.java
public class MyClass {
public void myMethod() {
System.out.println("Hello, world!");
}
}
When I call loadClass (marked by HERE above), it is not invoking "findClass" so method is not modified. From what I read, loadClass() is supposed to class findClass(). Any idea?
答案1
得分: 2
已经有用户16320675在评论中提到,loadClass
会首先尝试从父类加载器加载类。因此,您可以将new ClassLoader()
更改为new ClassLoader(null)
,将引导类加载器设置为其父类加载器,这样它将不会看到原始的定义。
然而,这只对非常简单的情况有效,因为修改后的类无法访问应用程序类加载器定义的其他类。
如果尚未加载该类,您可以像这样实例化新定义:
Class<?> myClass = MethodHandles.lookup().defineClass(modifiedClass);
这将在当前上下文中创建类,因此即使不使用反射的代码也将使用修改后的类。
如果在此点之前无法阻止环境加载该类,则没有办法绕过使用Instrumentation API编写真正的Java代理或创建一个包含可能与修改后的类合作的所有类的自定义类加载器的新环境。换句话说,类文件变换器的环境和要进行变换的代码的环境必须不同。
无论哪种情况,在修复此问题后,您将会得到一个java.lang.ClassFormatError: Duplicate method name "myMethod" with signature "()V" in class file MyClass
,因为您的转换代码复制了原始类文件的所有构件,并添加了另一个myMethod
。要替换myMethod
,必须在类访问者在遇到原始myMethod
时拦截类访问者。
import java.lang.invoke.MethodHandles;
import org.objectweb.asm.*;
public class MyClassInjector {
public static void main(String[] args) throws Exception {
// Load the MyClass class
ClassReader cr = new ClassReader("MyClass");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
MyClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, 0);
// Define the new byte array with the modified class bytecode
byte[] modifiedClass = cw.toByteArray();
Class<?> myClass = MethodHandles.lookup().defineClass(modifiedClass);
Object myObject = myClass.getConstructor().newInstance(); // Class.newInstance() is deprecated
myClass.getMethod("myMethod").invoke(myObject);
}
}
class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if(name.equals("myMethod") && descriptor.equals("()V")) {
instrument(mv);
return null;
}
return mv;
}
private void instrument(MethodVisitor mv) {
// 修改myMethod
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClass", "newMethod", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
@Override
public void visitEnd() {
// 定义newMethod方法
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "newMethod", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("注入的代码");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
super.visitEnd();
}
}
请注意,还有另一个问题,您尝试在修改后的代码中调用MyClassInjector.newMethod()
而不是MyClass.newMethod()
。
英文:
As already said by user16320675 in a comment, loadClass
will attempt to load the class from the parent class loader first. So you can change new ClassLoader()
to new ClassLoader(null)
to set the bootstrap loader as its parent and it will not see the original definition.
However, that will only work for very simple cases as then, the modified class can’t access other classes defined by the application class loader then.
If the class has not been loaded yet, you can materialize the new definition like
Class<?> myClass = MethodHandles.lookup().defineClass(modifiedClass);
This will create the class in your current context, so even code using MyClass
without Reflection will use the modified class.
If you can’t preclude the environment from loading the class before this point, there is no way around writing a real Java Agent using the Instrumentation API or create a new environment with the custom class loader containing all classes the modified class might collaborate with. In other words, the environment of the class file transformator and the environment of the code to transform must be different then.
In either case, after fixing this issue, you’ll get a java.lang.ClassFormatError: Duplicate method name "myMethod" with signature "()V" in class file MyClass
, because your transformation code copies all artifacts of the original class file and adds another myMethod
. To replace myMethod
, you must intercept the class visitor when it encounters the original myMethod
.
import java.lang.invoke.MethodHandles;
import org.objectweb.asm.*;
public class MyClassInjector {
public static void main(String[] args) throws Exception {
// Load the MyClass class
ClassReader cr = new ClassReader("MyClass");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
MyClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, 0);
// Define the new byte array with the modified class bytecode
byte[] modifiedClass = cw.toByteArray();
Class<?> myClass = MethodHandles.lookup().defineClass(modifiedClass);
Object myObject = myClass.getConstructor().newInstance(); // Class.newInstance() is deprecated
myClass.getMethod("myMethod").invoke(myObject);
}
}
class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if(name.equals("myMethod") && descriptor.equals("()V")) {
instrument(mv);
return null;
}
return mv;
}
private void instrument(MethodVisitor mv) {
// change myMethod
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClass", "newMethod", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
@Override
public void visitEnd() {
// Define the newMethod method
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "newMethod", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Injected code");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
super.visitEnd();
}
}
Note that there was another issue that you tried to invoke MyClassInjector.newMethod()
in the modified code instead of MyClass.newMethod()
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论