介绍
javassit是一个可以操作字节码class文件的库,他可以对以及编译好的Class对象添加或者修改类方法或熟悉,也可以直接生成一个新的Class类对象,这个库的操作方法类似于反射,所以学起来也比较简单。
Javassist是一个开源的分析、编辑和创建Java字节码的类库;相比ASM,Javassist提供了更加简单便捷的API,使用Javassist我们可以像写Java代码一样直接插入Java代码片段,让我们不再需要关注Java底层的字节码的和栈操作,仅需要学会如何使用Javassist的API即可实现字节码编辑。
API简介
ClassPool
首先这个ClassPool是这个javassit库的核心对象,你可以通过这个ClassPool与java字节码进行交互,允许你动态加载已经编译好的Class对象进行反编译,让你可以查看或者修改这个Class对象,另外你还可以通过makeClass创建新的类对象,并通过writeFile方法对生成的类对象进行持久化保存。除此以外他还担任管理者的操作,他会管理所以通过他生成或者加载的类对象,并且提供detach方法帮助你在不需要某个管理的对象时主动与其分离,从而释放资源。
总而言之,ClassPool对象可以理解为一个入口类,通过他完成对象创建修改操作;一个对象池,管理存储生成或修改的Class定义(即CtClass)。
getDefault()
1 | ClassPool pool = ClassPool.getDefault(); |
获取默认的ClassPool对象,其实就是使用JVM默认的Classpath,而这个Classpath就是用来在调用get请求时用来搜索的路径,如果存在其他Classpath的情况下,比如tomcat这种需要自己制定库路径的就可以通过pool.insertClassPath(new ClassClassPath(
1 | pool.insertClassPath(new ClassClassPath(<Class>)); |
get()
用来搜索ClassPool存储的class对象或者Classpath上的Class,这里可以调试看看
1 | pool.get("java.lang.String") |
在这里通过系统的Classpath路径找到了String的Class文件位置
接着我们调试一个pool已有并管理了的CtClass
可以看到这里直接通过Pool.classes获取了
总之CtClass默认就只会存储九大基本数据结构不包括String类型,然后后面再使用过程中会不断寻找然后加载进classes属性里面进行管理
makeClass()
1 | CtClass ctClass = pool.makeClass("Javasist.person"); |
直接生成一个CtClass对象,而这个CtClass可以理解为Class对象的拓展,提供了跟多的接口用来操控Class对象,这一行代码默认生成的Class字节码如下
然后我们可以通过CtClass的各种接口来对该Class进行补充修改,并且ClassPool通过get方法获取到的就是CtClass对象
CtClass
Class的加强版,重点记一下他的常用接口
setModifiers
ctClass.setModifiers(Modifier.PRIVATE);
修改类属性
给类添加方法字段构造器以及继承接口等,相反也有各种getter
这些就是设置类的各种属性,第一个以及讲了,还有一个setSuperClass是设置父类的,也就是继承某个CtClass
1 | ctClass.toBytecode(); |
然后就是ctClass保存方式,一个保存字节码一个保存成文件
defrost 方法
defrost方法用于“解冻”一个CtClass对象。在Javassist中,当你从ClassPool获取一个CtClass对象时,它可能是“冻结”状态,这意味着你不能修改这个类的定义。冻结的CtClass对象代表了一个已经加载的、存在于JVM中的类,对其进行修改可能会导致不可预测的行为。
使用defrost方法可以将CtClass对象转换为可修改的状态,这样你就可以添加新的方法、字段或者修改现有的类定义。defrost方法会创建一个新的CtClass对象,它是原始类的副本,但不受原始类的限制,可以进行修改。
detach 方法
detach方法用于从ClassPool中移除一个CtClass对象,释放与该对象相关联的资源。当你完成了对类的修改,并将其写入文件系统后,通常你会调用detach方法来清理ClassPool中的这个类的定义,这样可以避免内存泄漏。
调用detach方法后,CtClass对象将不再与ClassPool关联,你不能再对其进行修改。如果你尝试在已经分离的CtClass对象上调用方法,将会抛出异常。
toClass 方法
将CtClass对象转换为java.lang.Class对象,这样就可以使用Java反射API来实例化和操作这个类
CtField
- getName(): 获取字段的名称。
- getType(): 获取字段的类型。
- getDeclaringClass(): 获取声明该字段的类。
- setModifiers(): 设置字段的修饰符(如public、private等)。
- getInitializer(): 获取字段的初始值或表达式。
- setInitializer(): 设置字段的初始值或表达式。
- addField(): 在CtClass中添加一个新的CtField实例。
CtMethod
- getName(): 获取方法的名称。
- getReturnType(): 获取方法的返回类型。
- getParameterTypes(): 获取方法的参数类型数组。
- getDeclaringClass(): 获取声明该方法的类。
- setModifiers(): 设置方法的修饰符。
- getBody(): 获取方法的实现代码。
- setBody(): 设置或替换方法的实现代码。
- addMethod(): 在CtClass中添加一个新的CtMethod实例。
- CtNewMethod: 一个帮助类,用于创建和添加新方法,包括getter、setter和构造函数等。
CtConstructor
- getParameterTypes(): 获取构造函数的参数类型数组。
- getDeclaringClass(): 获取声明该构造函数的类。
- setModifiers(): 设置构造函数的修饰符。
- setBody(): 设置构造函数的实现代码,用于定义构造逻辑。
- addConstructor(): 在CtClass中添加一个新的CtConstructor实例。
- CtClass[]: 表示构造函数参数的CtClass数组。
setbody
代码编写
生成新对象
1 | package Javasist; |
生成恶意对象
写个templateImpl的恶意字节码,这里建议不参照demo自己按照api来写
1 | package Javasist; |
可以看到通过javassit生成的恶意字节码不需要基础transform方法,这样就可以缩短payload的长度,后面ROME反序列化那里能够用到,这里的无参构造器是默认生成的
在 Java 中,如果一个类没有显式定义任何构造函数,编译器会自动添加一个默认的无参构造函数。