通过修改字节修改方法体
先贴出示例中的代码:
public class Test{ public static void main(String[] args){ System.out.println("123"); System.out.println("345"); } public void m1(){ System.out.println("m1"); } public void m2(){ System.out.println("m2"); } }
Test2中先后调用了Test的m1,m2方法,此处通过修改字节码删除对m1方法的调用。
public class Test2{ public static void main(String[] args){ System.out.println("test2"); Test t1 = new Test(); t1.m1(); t1.m2(); } }
1、找到相对应的方法调用的字节码指令,通过JclassLib或者在命令行通过javap -verbose Test2.class来查看。
此处为16行和17行的aload_1和invokespecial #7,点击此处#7,可确定调用的方法为Test.m1(),如下所示:
此处控制台内容没有全部显示
贴出命令行中未截出的main函数中的方法调用字节码指令
2、通过虚拟机字节码指令集去查询对应的16进制字节码为:2B B6 00 07,此处00 07为B6(调用示例方法)的参数,即为调用Test的m1方法。
3、在UE中选中后右键进行选中16进制插入/删除,删除对应的字节码。此处选择删除,要删除的字节数即为4
4、在删除对应的字节码后,仍有3处需修改。
4.1 此Code属性对一个的attribute_length,在原值的基础上减4
4.2 修改此Code属性对应的code_length,从19修改为15,同样见上图。
43 修改此Code属性对应的LinerNumberTable中的对应的字节码行号,因字节码减少了,如不修改会报错
如上图所示,将00 18和 00 14此两次大于所删除的字节码行号(此处为15和16)的两处行号分别减4。保存,OK!重新运行java Test2,如下图所示,上面异常是在没有修改字节码行号记录表时所报的异常。
如果方法体内代码比较多,如此修改比较繁琐,此时采用jClassLib程序来实现,要修改的点和上述方式基本相同。代码如下:
package com.xue.jvm; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.gjt.jclasslib.io.ClassFileWriter; import org.gjt.jclasslib.structures.AttributeInfo; import org.gjt.jclasslib.structures.CPInfo; import org.gjt.jclasslib.structures.ClassFile; import org.gjt.jclasslib.structures.MethodInfo; import org.gjt.jclasslib.structures.attributes.CodeAttribute; import org.gjt.jclasslib.structures.attributes.LineNumberTableAttribute; import org.gjt.jclasslib.structures.attributes.LineNumberTableEntry; import org.testng.annotations.Test; import com.alibaba.druid.util.StringUtils; public class TestJclasslib { private static Logger log = Logger.getLogger(TestJclasslib.class); //要展示盒修改的方法名 private static String methodName="main"; //Code对应的常量池index private static int codeConstantIndex ; //LineNumberTable对应的常量池index private static int lineNumberTableConstantIndex; private static String classFileName = "D:\\Test2.class"; private static InputStream fis; private static ClassFile cf = new ClassFile(); private String deletedByteStr = "43,-74,0,7"; //被删除的代码的字节行号,在调整字节码行号表时使用 private int deletedCodeNumber = 12; //删除的代码字节数 private int deletedCodeCount = 4; public static void main(String[] args) throws Exception{ initParams(); log.info("Method info"); MethodInfo[] methods = cf.getMethods(); for(int i=0;i<methods.length;i++){ log.info(methods[i].getName()); //根据方法名找出对应的方法 if(methodName.equals(methods[i].getName())){ MethodInfo mi = methods[i]; //方法体属性,包括Code属性 AttributeInfo[] attributes = mi.getAttributes(); for(AttributeInfo ai : attributes){ log.info("AttributeInfo.name:"+ai.getName()); //找出此方法的Code,即为方法体代码 if(ai.getAttributeNameIndex()==codeConstantIndex){ CodeAttribute codeAttribute = (CodeAttribute)ai; int attributeLength = codeAttribute.getAttributeLength(); log.info("attributeLength:"+attributeLength); for(byte b:codeAttribute.getCode()){ log.info(b); } AttributeInfo[] codeAis = codeAttribute.getAttributes(); log.info("code attributeInfo length:"+codeAis.length); for(AttributeInfo codeAi:codeAis){ log.info(codeAi.getAttributeNameIndex()); //此处找出对应LineNumberTalbe,显示对应行对应关系。 if(codeAi.getAttributeNameIndex()==lineNumberTableConstantIndex){ LineNumberTableAttribute lnta = (LineNumberTableAttribute) codeAi; LineNumberTableEntry[] lineNumberTableEntrys = lnta.getLineNumberTable(); for(LineNumberTableEntry lineNumberTableEntry:lineNumberTableEntrys){ log.info(lineNumberTableEntry.getStartPc()+"="+lineNumberTableEntry.getLineNumber()); } } } } } } } fis.close(); // File f = new File("D:\\com\\xue\\test\\Test2.class"); // ClassFileWriter.writeToFile(f, cf); } /** * 此方法用来修改class文件,删除class文件中的方法调用,修改对应的attribute_length,修改对应的code_length,修改LineNumberTable * @throws Exception */ @Test public void updateClassFile() throws Exception{ //将需要用到的参数初始化 initParams(); MethodInfo[] methods = cf.getMethods(); for(int i=0;i<methods.length;i++){ log.info(methods[i].getName()); //根据方法名找出对应的方法 if(methodName.equals(methods[i].getName())){ MethodInfo mi = methods[i]; //方法体属性,包括Code属性 AttributeInfo[] attributes = mi.getAttributes(); for(AttributeInfo ai : attributes){ log.info(ai.getName()); //找出此方法的Code,即为方法体代码 if(ai.getAttributeNameIndex()==codeConstantIndex){ CodeAttribute codeAttribute = (CodeAttribute)ai; int attributeLength = codeAttribute.getAttributeLength(); //此处attributeLength为动态计算所得,不需要更改,直接更改code数组 log.info("attributeLength:"+attributeLength); byte[] changedCode = changeCode(codeAttribute.getCode()); codeAttribute.setCode(changedCode); AttributeInfo[] codeAis = codeAttribute.getAttributes(); log.info("code attributeInfo length:"+codeAis.length); for(AttributeInfo codeAi:codeAis){ log.info(codeAi.getAttributeNameIndex()); //此处找出对应LineNumberTalbe,显示对应行对应关系。 if(codeAi.getAttributeNameIndex()==lineNumberTableConstantIndex){ LineNumberTableAttribute lnta = (LineNumberTableAttribute) codeAi; LineNumberTableEntry[] lineNumberTableEntrys = lnta.getLineNumberTable(); for(LineNumberTableEntry lineNumberTableEntry:lineNumberTableEntrys){ int startPc = lineNumberTableEntry.getStartPc(); if(lineNumberTableEntry.getStartPc()>deletedCodeNumber){ lineNumberTableEntry.setStartPc(startPc-deletedCodeCount); } } } } } } } } fis.close(); File f = new File("D:\\com\\xue\\test\\Test2.class"); ClassFileWriter.writeToFile(f, cf); } public byte[] changeCode(byte[] code){ log.info(deletedByteStr); StringBuffer sb = new StringBuffer(); for(byte b:code){ sb.append(b).append(","); } log.info(sb); sb.delete(sb.indexOf(deletedByteStr), sb.indexOf(deletedByteStr)+deletedByteStr.length()); log.info(sb); String[] bStrs = sb.toString().split(","); List<Byte> bytes = new ArrayList<Byte>(); for(String bs:bStrs){ if(!StringUtils.isEmpty(bs)){ bytes.add(Byte.valueOf(bs)); } } byte[] result = new byte[bytes.size()]; for(int i=0;i<bytes.size();i++){ result[i] = bytes.get(i); } return result; } private static void initParams() throws Exception{ File file = new File(classFileName); fis = new FileInputStream(file); DataInput di = new DataInputStream(fis); cf.read(di); CPInfo[] infos = cf.getConstantPool(); log.info(infos.length); for(int i=0;i<infos.length;i++){ if(infos[i]!=null){ System.out.print(i); System.out.print("="); System.out.print(infos[i].getVerbose()); System.out.print("="); System.out.println(infos[i].getTagVerbose()); if(infos[i].getVerbose().equals("Code")){ codeConstantIndex=i; } if(infos[i].getVerbose().equals("LineNumberTable")){ lineNumberTableConstantIndex=i; } } } } }
关于Java类文件的结构参考:Orclae Class File Format
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步