使用jclasslib修改字节码/源码

  查看源码很简单,一些常用IDE里如idea、eclipse都提供了查看class文件源码的功能,虽然跟源码有些出入(解语法糖等),但功能实现上是一致的,且比源码更贴近于JVM运行时的情况。
  有时候我们需要修改源码以满足使用要求,对于java代码生成的字节码重新源码就比较简单了,一种方式是继承然后重写待修改的功能,另一种方式是直接创建一个同名类文件,把反编译的源码复制进去,修改后,将新生成的class文件替换原jar包中的class文件,但有些字节码是由其它语言生成的,反编译后的文件并不能满足java编译语法,也就无法编译成新的class文件,对于这种情况,有种通用的方式就是直接修改字节码来实现。
  网上有很多修改字节码的文章,但大都是修改常量池来输出不同的值,本文使用jclasslib直接修改字节码中的源码逻辑,为方便演示,本文的demo可能比较简单,但是这种方法可以用到更复杂的类中,如有需要欢迎留言探讨。

1、java源码

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

输出两个参数中的较小值:1
目标:通过修改字节码的方式使printMin方法输出较大值:2

2、class文件反编译结果

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    public JclasslibTest() {
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

  可以看出,生成的class文件只是比java源码多加了一个默认的无参构造方法(因为demo比较简单,所以未体现编译优化的多种策略)

3、下载安装jclasslib

https://github.com/ingokegel/jclasslib/releases

4、使用jclasslib打开class文件

在这里插入图片描述
字节码各部分名称已经很明确类,这里不在过多解释,看我们需要修改的位置Methods->printMin
在这里插入图片描述
printMin方法对应的字节码一共为19行
0:将第一个参数入栈(参数a=1)
1:将第二个参数入栈(参数b=2)
2:比较栈顶两int型数值的大小,当结果大于0时跳转到第9行字节码指令(判断条件进入相应逻辑块)
5:将第一个参数入栈(选择a)
6:无条件跳转到第10行字节码指令
9:将第二个参数入栈(选择b)
10:将栈顶int型数值存入第三个本地变量(存储选择结果c)
11:获取指定类的静态域,并将其压入栈顶(获取待运行的实例及方法)
14:将第三个参数入栈(将c入栈以供11中的实例方法运行参数)
15:调用实例方法
18:从当前方法返回void
更多字节码指令可参考 虚拟机字节码指令表
参照虚拟机字节码指令表,如要实现将pringMin输出参数中的较大值,只要把第三条指令if_icmpgt修改成if_icmple即可。

助记符 if_icmpgt if_icmple
指令含义 比较栈顶两int型数值的大小,当结果大于0时跳转 比较栈顶两int型数值的大小,当结果小于或等于0时跳转
字节码 0xa3 0xa4
有符号型十进制数 -93 -92

5、修改字节码

package com.zhanghao.test.jclasslib;

import com.alibaba.fastjson.JSON;
import java.io.*;
import org.gjt.jclasslib.io.ClassFileWriter;
import org.gjt.jclasslib.structures.AttributeInfo;
import org.gjt.jclasslib.structures.ClassFile;
import org.gjt.jclasslib.structures.MethodInfo;
import org.gjt.jclasslib.structures.attributes.CodeAttribute;

public class JclasslibModify {
    public static void main(String[] args) throws Exception {
        String filePath = "/Users/zhanghao/Desktop/jclasslib/JclasslibTest.class";
        FileInputStream fis = new FileInputStream(filePath);

        DataInput di = new DataInputStream(fis);
        ClassFile cf = new ClassFile();
        cf.read(di);
        System.out.println(JSON.toJSONString(cf));
        MethodInfo[] methodInfos = cf.getMethods();
        MethodInfo methodInfo = methodInfos[2];
        AttributeInfo[] attributeInfos = methodInfo.getAttributes();
        CodeAttribute codeAttribute = (CodeAttribute) attributeInfos[0];
        byte[] bytes = codeAttribute.getCode();
        bytes[2] = -92;

        fis.close();
        File f = new File(filePath);
        ClassFileWriter.writeToFile(f, cf);
    }
}

读取class文件,可通过debug或者输出json的方式查看ClassFile的类结构,修改类结构中字节码然后重写文件。
需要用到jclasslib.jar,下载链接:jclasslib.jar

6、查看修改后的结果

package com.zhanghao.test;

public class JclasslibTest {
    public JclasslibTest() {
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a > b ? a : b;
        System.out.println(min);
    }
}

在这里插入图片描述

本文只是简单的阐述修改字节码的方法,对于实际项目中需要修改字节码时,情况会更为复杂,但是换汤不换药,可以先用java生成待修改部分的字节码,然后替换掉原字节码中相应的字节码块,其它文件也需要相应修改,比如常量池等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章