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

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

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

我按照这里找到的方法替换示例:asm.ow2.org/current/asm-transformations.pdf。

我的代码如下:

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(); } } 

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

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

我不确定我错过了什么……但是在阅读和测试了大约3天后,我仍然没有到达任何地方。

[编辑]

如果您想知道, tmcore是针对律师的单人“异议”游戏。 我这样做是为了它的乐趣。 该程序成功启动游戏,一切都很好,删除MethodReplacer的修改使游戏的行为符合设计。 所以这个问题似乎与我在方法替换器中的错误字节码/修改隔离开来。

[EDIT2]

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

[EDIT3]

classtoload副本根据评论评论

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

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

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

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

 { 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(); } 

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

另外,你的类加载似乎有点困惑/搞砸了。 我没有jar(所以我不能说这些是否有效),但也许你可以替换Main和Loader:

主要:

 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(); } } } 

装载机:

 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 }); } } 

ASKER从问题中得到了回答

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

感谢Ame帮助我解决它。

以下代码有效:

主要

 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; } } 

装载机:

 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保持不变。