Java ASM 字节码修改-更改方法体

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11770353/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 06:18:01  来源:igfitidea点击:

Java ASM Bytecode Modification-Changing method bodies

javaclassloadercode-injectionbytecodejava-bytecode-asm

提问by emist

I have a method of a class in a jar whose body I want to exchange with my own. In this case I just want to have the method print out "GOT IT" to the console and return true;

我有一个罐子里的一个类的方法,我想用自己的身体交换它的身体。在这种情况下,我只想让该方法将“GOT IT”打印到控制台并返回 true;

I am using the system loader to load the classes of the jar. I am using reflection to make the system classloader be able to load classes by bytecode. This part seems to be working correctly.

我正在使用系统加载器来加载 jar 的类。我正在使用反射使系统类加载器能够通过字节码加载类。这部分似乎工作正常。

I am following the method replacement example found here: asm.ow2.org/current/asm-transformations.pdf.

我正在遵循此处找到的方法替换示例:asm.ow2.org/current/asm-transformations.pdf。

My code is as follows:

我的代码如下:

public class Main 
{
    public static void main(String[] args) 
    {
        URL[] url = new URL[1];
        try
        {
            url[0] = new URL("file:////C://Users//emist//workspace//tmloader//bin//runtime//tmgames.jar");
            verifyValidPath(url[0]);
        }
        catch (Exception ex)
        {
            System.out.println("URL error");
        }
        Loader l = new Loader();
        l.loadobjection(url);
    }

    public static void verifyValidPath(URL url) throws FileNotFoundException
    {
        File filePath = new File(url.getFile());
        if (!filePath.exists()) 
        {
          throw new FileNotFoundException(filePath.getPath());
        }
    }
}

class Loader
{
    private static final Class[] parameters = new Class[] {URL.class};

    public static void addURL(URL u) throws IOException
    {
        URLClassLoader sysloader = (URLClassLoader)  ClassLoader.getSystemClassLoader();
        Class sysclass = URLClassLoader.class;

        try 
        {
            Method method = sysclass.getDeclaredMethod("addURL", parameters);
            method.setAccessible(true);
            method.invoke(sysloader, new Object[] {u});
        }
        catch (Throwable t) 
        {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }

    }

    private Class loadClass(byte[] b, String name) 
    {
        //override classDefine (as it is protected) and define the class.
        Class clazz = null;
        try 
        {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            Class cls = Class.forName("java.lang.ClassLoader");
            java.lang.reflect.Method method =
                cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });

            // protected method invocaton
            method.setAccessible(true);
            try 
            {
                Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
                clazz = (Class) method.invoke(loader, args);
            }
            finally 
            {
                method.setAccessible(false);
            }
        }
        catch (Exception e) 
        {
            e.printStackTrace();
            System.exit(1);
        }
        return clazz;
    }

    public void loadobjection(URL[] myJar)
    {
        try 
        {
            Loader.addURL(myJar[0]);            
            //tmcore.game is the class that holds the main method in the jar
            /*
            Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
            if(classToLoad == null)
            {
                System.out.println("No tmcore.game");
                return;
            }
            */
            MethodReplacer mr = null;

            ClassReader cr = new ClassReader("tmcore.objwin");
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            MethodVisitor mv = null;
            try
            {
                mr = new MethodReplacer(cw, "Test", "(Ljava/lang/String;ZLjava/lang/String;)Z");
            }
            catch (Exception e)
            {
                System.out.println("Method Replacer Exception");
            }
            cr.accept(mr, ClassReader.EXPAND_FRAMES);

            PrintWriter pw = new PrintWriter(System.out);
            loadClass(cw.toByteArray(), "tmcore.objwin");
            Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
            if(classToLoad == null)
            {
                System.out.println("No tmcore.game");
                return;
            }

            //game doesn't have a default constructor, so we need to get the reference to public game(String[] args)
            Constructor ctor = classToLoad.getDeclaredConstructor(String[].class);
            if(ctor == null)
            {
                System.out.println("can't find constructor");
                return;
            }

            //Instantiate the class by calling the constructor
            String[] args = {"tmgames.jar"};
            Object instance = ctor.newInstance(new Object[]{args});
            if(instance == null)
            {
                System.out.println("Can't instantiate constructor");
            }

            //get reference to main(String[] args)
            Method method = classToLoad.getDeclaredMethod("main", String[].class);
            //call the main method
            method.invoke(instance);

        }   
        catch (Exception ex)
        {
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }
}


public class MethodReplacer extends ClassVisitor implements Opcodes
{
    private String mname;
    private String mdesc;
    private String cname;

    public MethodReplacer(ClassVisitor cv, String mname, String mdesc)
    {
        super(Opcodes.ASM4, cv);
        this.mname = mname;
        this.mdesc = mdesc;
    }

    public void visit(int version, int access, String name, String signature, 
                      String superName, String[] interfaces)
    {
        this.cname = name;
        cv.visit(version, access, name, signature, superName, interfaces);
    }

    public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                                     String[] exceptions)
    {
        String newName = name;
        if(name.equals(mname) && desc.equals(mdesc))
        {
            newName = "orig$" + name;
            generateNewBody(access, desc, signature, exceptions, name, newName);
            System.out.println("Replacing");
        }
        return super.visitMethod(access,  newName,  desc,  signature,  exceptions);
    }

    private void generateNewBody(int access, String desc, String signature, String[] exceptions,
                                String name, String newName)
    {
        MethodVisitor mv = cv.visitMethod(access,  name,  desc,  signature,  exceptions);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(access, cname, newName, desc);
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn("GOTit!");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
        mv.visitInsn(ICONST_0);
        mv.visitInsn(IRETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
}

The problem seems to be at mv.visitMethodInsn(access, cname, newName, desc);in generateMethodBodyinside MethodReplacer.

问题似乎mv.visitMethodInsn(access, cname, newName, desc);generateMethodBody里面MethodReplacer

I get an "Illegal Type in constant pool" error.

我收到“常量池中的非法类型”错误。

I'm not sure what I'm missing...but after reading and testing for about 3 days I'm still not getting anywhere.

我不确定我错过了什么……但是在阅读和测试了大约 3 天后,我仍然一无所获。

[Edit]

[编辑]

In case you were wondering, tmcoreis a single player "Objection" game for lawyers. I'm doing this for the fun of it. The program successfully launches the game and everything is fine, removing the modifications from MethodReplacermakes the game behave as designed. So the issue seems to be isolated to bad bytecode/modifications by me inside the method replacer.

如果您想知道,这tmcore是一款律师单人“异议”游戏。我这样做是为了好玩。程序成功启动游戏,一切正常,删除修改MethodReplacer使游戏按设计运行。所以这个问题似乎与我在方法替换器中的错误字节码/修改无关。

[EDIT2]

[编辑2]

CheckClassAdapter.verify(cr, true, pw);returns the exact same bytecode that the function is supposed to have before editing. It is as if the changes are not being done.

CheckClassAdapter.verify(cr, true, pw);返回与该函数在编辑之前应该具有的完全相同的字节码。就好像没有进行更改一样。

[EDIT3]

[编辑3]

copy of classtoloadcommented out as per comments

根据classtoload评论注释掉的副本

采纳答案by Arne

If you are using Eclipse, you should install Bytecode Outline- it is indispensible.

如果您使用 Eclipse,您应该安装Bytecode Outline- 它是必不可少的。

I built a small test for what you want to achieve (this should match the signature of your test method, you will have to change package and classname):

我为您想要实现的目标构建了一个小测试(这应该与您的测试方法的签名相匹配,您必须更改包和类名):

package checkASM;

public class MethodCall {

    public boolean Test(String a, boolean b, String c) {
        System.out.println("GOTit");
        return false;
    }
}

requires the following bytecode to build the method:

需要以下字节码来构建方法:

{
mv = cw.visitMethod(ACC_PUBLIC, "Test",
    "(Ljava/lang/String;ZLjava/lang/String;)Z", null, null);
mv.visitCode();
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System",
   "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
   "println", "(Ljava/lang/String;)V");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "LcheckASM/MethodCall;", null, l1, l3, 0);
mv.visitLocalVariable("a", "Ljava/lang/String;", null, l1, l3, 1);
mv.visitLocalVariable("b", "Z", null, l1, l3, 2);
mv.visitLocalVariable("c", "Ljava/lang/String;", null, l1, l3, 3);
mv.visitMaxs(4, 4);
mv.visitEnd();
}

Calls to visitLineNumbercan be omitted. So apparently, you are missing all labels, forgot to load the method parameters, did not ignore the return value, set the wrong values for visitMaxs(this is not necessarily needed, it depends on your ClassWriter flags if I recall correctly) and did not visit local variables (or parameters in this case).

visitLineNumber可以省略对的调用。显然,您缺少所有标签,忘记加载方法参数,没有忽略返回值,设置错误的值visitMaxs(这不一定需要,如果我没记错的话,这取决于您的 ClassWriter 标志)并且没有访问局部变量(或本例中的参数)。

Additionally, your classloading seems to be a little confused / messed up. I don't have the jar (so I can't say if these work), but maybe you could replace Main and Loader:

此外,您的类加载似乎有点混乱/混乱。我没有罐子(所以我不能说这些是否有效),但也许你可以替换 Main 和 Loader:

Main:

主要的:

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;

public class Main {
    public static void main(String[] args) {
        try {
            Loader.instrumentTmcore(args);
        } catch (Exception e) {
            System.err.println("Ooops");
            e.printStackTrace();
        }
    }
}

Loader:

装载机:

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

public class Loader {

    public static ClassReader fetchReader(String binaryName) throws Exception {
        return new ClassReader(
                Loader.class.getClassLoader().getSystemResourceAsStream(
                    binaryName.replace('.', '/') + ".class"
                )
            )
        ;
    }

    public static synchronized Class<?> loadClass(byte[] bytecode)
                throws Exception {
        ClassLoader scl = ClassLoader.getSystemClassLoader();
        Class<?>[] types = new Class<?>[] {
                String.class, byte[].class, int.class, int.class
        };
        Object[] args = new Object[] {
                null, bytecode, 0, bytecode.length
        };
        Method m = ClassLoader.class.getMethod("defineClass", types);
        m.setAccessible(true);
        return (Class<?>) m.invoke(scl, args);
    }

    public static void instrumentTmcore(String[] args) throws Exception {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MethodReplacer mr = new MethodReplacer(cw, "Test",
                    "(Ljava/lang/String;ZLjava/lang/String;)Z");
        fetchReader("tmcore.objwin").accept(mr, ClassReader.EXPAND_FRAMES);
        loadClass(cw.toByteArray());
        Class.forName("tmcore.game")
            .getMethod("main", new Class<?>[] {args.getClass()})
            .invoke(null, new Object[] { args });
    }
}

回答by Moshe Katz

ASKER'S ANSWER MOVED FROM QUESTION

提问者的回答从问题移开

The java bytecode was never the problem. It is the way I was loading the jar which made it impossible to instrument the code.

java字节码从来都不是问题。这是我加载 jar 的方式,导致无法检测代码。

Thanks to Ame for helping me tackle it.

感谢 Ame 帮我解决了这个问题。

The following code works:

以下代码有效:

MAIN

主要的

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.io.FileInputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class Main implements Opcodes
{
    public static void main(String[] args) throws Exception
    {

        byte[] obj = readClass("tmcore/obj.class");
        ClassReader objReader = new ClassReader(obj);
        ClassWriter objWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        MethodReplacer demoReplacer = new MethodReplacer(objWriter, "run", "()V");
        demoReplacer.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "tmcore/obj", null, "java/applet/Applet", new String[] { "java/lang/Runnable" });
        objReader.accept(demoReplacer, ClassReader.EXPAND_FRAMES);

        objReader = new ClassReader(objWriter.toByteArray());

        Class objC = Loader.loadClass(objWriter.toByteArray(), "tmcore.obj");
        if(objC == null)
        {
            System.out.println("obj cannot be loaded");
        }

        Class game = ClassLoader.getSystemClassLoader().loadClass("tmcore.game");
        if(game == null)
        {
            System.out.println("Can't load game");
            return;
        }

        Constructor ctor = game.getDeclaredConstructor(String[].class);
        if(ctor == null)
        {
            System.out.println("can't find constructor");
            return;
        }

        //Instantiate the class by calling the constructor
        String[] arg = {"tmgames.jar"};
        Object instance = ctor.newInstance(new Object[]{args});
        if(instance == null)
        {
            System.out.println("Can't instantiate constructor");
        }

        //get reference to main(String[] args)
        Method method = game.getDeclaredMethod("main", String[].class);
        //call the main method
        method.invoke(instance);

    }


    public static void verifyValidPath(String path) throws FileNotFoundException
    {
            File filePath = new File(path);
            if (!filePath.exists()) 
            {
              throw new FileNotFoundException(filePath.getPath());
            }
    }

    public static byte[] readClass(String classpath) throws Exception
    {
        verifyValidPath(classpath);
        File f = new File(classpath);

        FileInputStream file = new FileInputStream(f);
        if(file == null)
            throw new FileNotFoundException();

        byte[] classbyte = new byte[(int)f.length()];

        int offset = 0, numRead = 0;
        while (offset < classbyte.length
                && (numRead=file.read(classbyte, offset, classbyte.length-offset)) >= 0) 
        {
             offset += numRead;
        }

        if (offset < classbyte.length) 
        {
            file.close();
            throw new IOException("Could not completely read file ");
        }

        file.close();
        return classbyte;
    }
}

LOADER:

装载机:

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

class Loader
{
    private static final Class[] parameters = new Class[] {URL.class};

     public static void addURL(URL u) throws IOException
     {
            URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            Class sysclass = URLClassLoader.class;

            try 
            {
                Method method = sysclass.getDeclaredMethod("addURL", parameters);
                method.setAccessible(true);
                method.invoke(sysloader, new Object[] {u});
            } 
            catch (Throwable t) 
            {
                t.printStackTrace();
                throw new IOException("Error, could not add URL to system classloader");
            }

     }

     public static Class loadClass(byte[] b, String name) 
     {
            //override classDefine (as it is protected) and define the class.
         Class clazz = null;
         try 
         {
             ClassLoader loader = ClassLoader.getSystemClassLoader();
             Class cls = Class.forName("java.lang.ClassLoader");
             java.lang.reflect.Method method =
                     cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });

              // protected method invocaton
              method.setAccessible(true);
              try 
              {
                  Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
                  clazz = (Class) method.invoke(loader, args);
              } 

              finally 
              {
                  method.setAccessible(false);
              }
         } 
         catch (Exception e) 
         {
             e.printStackTrace();
             System.exit(1);
         }
            return clazz;
    }
}

MethodReplacer remains the same.

MethodReplacer 保持不变。