1. 什么是javassist
javassist是一个处理Java字节码的jar包,里面有很多类。
2. 什么是ClassPool
可以想象成一个容器,里面放着指定路径下的class文件,使用javassist对类进行操作的时候,必须先创建一个ClassPool。
它也可以暂时存放我们编辑的class文件,等写完后再拿出来放到指定的位置。我们对class文件的操作是在ClassPool中的进行的。
假如我们想获取一个Class文件进行修改,如果ClassPool的路径中没有它,那么我们是找不到的,必须使用insertClassPath(); 函数将class文件路径导入ClassPool中才可以。
如果我们不自定义路径,那么它的类的搜索路径包括平台库、扩展库以及由-classpath选项或CLASSPATH环境变量指定的搜索路径。
3. 什么是CtClass
CtClass是javassist中的一个类文件,它的对象可以理解成一个class文件的抽象表示。
一个CtClass对象可以用来修改一个class文件。
4. 写一个Class文件test.class
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import java.io.FileOutputStream;
public class JavasistTest {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Test");
try {
ctClass.addField(CtField.make("private int age;", ctClass));
ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
output.write(byteArray);
output.close();
System.out.println("文件写入成功!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
新创建好的test文件如图所示:
5. 修改这个写好了的test.class
我们先将test文件挪到桌面然后执行下面的代码,发现报错,找不到名为Test的class文件:
import javassist.ClassPool;
import javassist.CtClass;
public class Javassisttest2 {
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
try {
CtClass ctClass = pool.get("Test");
System.out.println(ctClass.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后我们将注释取消掉,再执行一遍,发现已经不报错了:
完整的修改代码:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import java.io.FileOutputStream;
public class Javassisttest2 {
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
try {
pool.insertClassPath("/Users/zhujiayu/Desktop/");
CtClass ctClass = pool.get("Test");
System.out.println(ctClass.getName());
if (ctClass.isFrozen()) {
ctClass.defrost();
}
ctClass.addField(CtField.make("private String sex;", ctClass));
ctClass.addField(CtField.make("private String name;", ctClass));
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
output.write(byteArray);
output.close();
System.out.println("文件修改成功!!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改后我们打开test.class查看发现代码确实被修改了
6. 加载类并获取类中的私有变量
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
public class Javassisttest2 {
public static void main(String[] args) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/Users/zhujiayu/Desktop/");
try {
CtClass ctClass = pool.get("Test");
Class<?> c = ctClass.toClass();
Object qq = c.newInstance();
System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",null).invoke(qq,null));
System.out.println("通过getDeclaredField函数获取的age的数据为:"+c.getDeclaredField("age").get(qq));
} catch (Exception e) {
e.printStackTrace();
}
}
}
7. 利用原始ClassLoader的方式配合javassist加载class文件
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import java.lang.reflect.Method;
public class Javassisttest2 {
public static void main(String[] args) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/Users/zhujiayu/Desktop/");
try {
CtClass ctClass = pool.get("Test");
Class<?> clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
byte[] b = ctClass.toBytecode();
Class c = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader(), "Test", b, 0, b.length);
Object qq = c.newInstance();
System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",![在这里插入图片描述](https://img-blog.csdnimg.cn/59225d0d76304fd38ba381182b3fe962.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAU2hhbmZlbmdsYW43,size_20,color_FFFFFF,t_70,g_se,x_16)
null).invoke(qq,null));
} catch (Exception e) {
e.printStackTrace();
}
}
}
9. apache CC2链逻辑
- 利用javassist写恶意的class文件,恶意代码在这个class文件的构造函数下,并将其class文件转化为byte的格式。
- 通过创建一个
TemplatesImpl 对象,并给其中传入恶意参数,分别是将_bytecodes 设置为恶意的class文件的byte数据,_name 设置为name,_class 设置为null。_tfactory 设置为new TransformerFactoryImpl()。如果触发了TemplatesImpl对象的newTransformer方法会触发getTransletInstance(),getTransletInstance()方法会将class文件实例化,即可触发恶意的class文件中的构造函数,进而触发rce。 - 然后用反射创建一个InvokerTransformer对象,这个InvokerTransformer对象构造函数中的值是
newTransformer ,如果我们调用了invokerTransformer的transform函数,就会触发指定类的newTransformer 函数,指定类是可控的 。 - 然后将InvokerTransformer对象传入TransformingComparator对象中,这个对象会变成TransformingComparator的tranformer参数的值。TransformingComparator的tranformer的transform函数会在执行
TransformingComparator 的compare 时被触发,compare函数的参数会被传给transform,接着被传给newTransformer接着会被传给getTransletInstance函数。综上,只要触发TransformingComparator的compara函数,就能触发rce。 - PriorityQueue类的readobject函数中有一个heapify函数,heapify函数中有一个siftdown函数,siftdown函数函数中有一个siftDownUsingComparator函数。siftDownUsingComparator函数中会调用PriorityQueue类中一个comparator对象的compare方法,compare方法的参数是PriorityQueue类的queue对象中的值。
- PriorityQueue类中的comparator的值,我们可以控制,PriorityQueue类的queue对象中的值我们也可以控制。
- 综上,我们创建一个PriorityQueue类的对象,并将TransformingComparator传给comparator,将恶意TemplatesImpl对象按照特定格式传给queue,然后将这个PriorityQueue对象进行序列化,传送给服务端等待被反序列化,反序列化时就会触发RCE攻击。
8. 参考文章
ClassPool CtClass浅析 Java动态编程初探——Javassist Java反序列化-CommonsCollections2分析
|