使用ASM修改Jar字节码后报类版本0.0不兼容错误如何解决
问题根因
报错里的类版本为0.0不是需要手动配置类版本号,是字节码生成逻辑完全写错了:你将原始类字节码读入ClassNode完成了方法修改,但最后生成字节码时,仅创建了传入原始ClassReader的ClassWriter对象,既没有把修改后的ClassNode写入ClassWriter,也没有触发原始类内容到ClassWriter的写入流程,最终输出的class文件结构残缺,JVM解析类版本字段时读到空值0,才抛出版本不兼容异常。
修复方案
- 初始化
ClassWriter时不需要传入原始ClassReader作为优化参数,直接传入自动计算栈帧、本地变量数的标志位即可 - 将修改完成的
ClassNode通过accept方法写入ClassWriter,再输出字节数组,ASM会自动沿用原类的版本号,不需要手动设置 - 额外处理:输出Jar包时记得剔除
META-INF目录下的.SF、.DSA、.RSA签名文件,否则类字节码修改后签名校验失败会导致启动报错
修复后的transform方法代码
public static byte[] transform(byte[] buf){ ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(buf); classReader.accept(classNode, ClassReader.EXPAND_FRAMES); for (MethodNode method : classNode.methods) { if(method.name.equals(shouldSideBeRenderedMethod) && method.desc.startsWith("(L") && method.desc.endsWith(";IIII)Z")){ System.out.println("[*] Patching bytecode of shouldSideBeRendered..."); InsnList insnList = new InsnList(); Label label0 = new Label(); insnList.add(new LabelNode(label0)); insnList.add(new VarInsnNode(ALOAD, 0)); insnList.add(new FieldInsnNode(GETFIELD, blockClass.replaceAll("\\.", "/"), unlocalizedNameField, "Ljava/lang/String;")); insnList.add(new LdcInsnNode("stone")); insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false)); Label label1 = new Label(); insnList.add(new JumpInsnNode(IFNE, new LabelNode(label1))); insnList.add(new InsnNode(ICONST_1)); Label label2 = new Label(); insnList.add(new JumpInsnNode(GOTO, new LabelNode(label2))); insnList.add(new LabelNode(label1)); insnList.add(new FrameNode(Opcodes.F_SAME, 0, null, 0, null)); insnList.add(new InsnNode(ICONST_0)); insnList.add(new LabelNode(label2)); insnList.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{Opcodes.INTEGER})); insnList.add(new InsnNode(IRETURN)); Label label3 = new Label(); insnList.add(new LabelNode(label3)); method.instructions.insertBefore(method.instructions.getFirst(), insnList); } } // 修复核心逻辑:将修改后的classNode写入ClassWriter再输出 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(cw); return cw.toByteArray(); }
补充:Jar写入逻辑里可以加个判断,遇到META-INF目录下的签名文件直接跳过不写入输出包,避免签名校验报错。
内容的提问来源于stack exchange,提问作者Sysy's
相关产品推荐
相关产品推荐

