作者: heeeeen@MS509Team

0x00 简介

最近几个月,Android安全公告公布了一系列系统框架层的高危提权漏洞,如下表所示。

CVE Parcelable对象 公布时间
CVE-2017-0806 GateKeeperResponse 2017.10
CVE-2017-13286 OutputConfiguration 2018.04
CVE-2017-13287 VerifyCredentialResponse 2018.04
CVE-2017-13288 PeriodicAdvertisingReport 2018.04
CVE-2017-13289 ParcelableRttResults 2018.04
CVE-2017-13311 SparseMappingTable 2018.05
CVE-2017-13315 DcParamObject 2018.05

这批漏洞很有新意,似乎以前没有看到过类似的,其共同特点在于框架中Parcelable对象的写入(序列化)和读出(反序列化)不一致,比如将一个成员变量写入时为long,而读入时为int。这种错误显而易见,但是能够造成何种危害,如何证明是一个安全漏洞,却难以从补丁直观地得出结论。

由于漏洞原作者也没有给出Writeup,这批漏洞披上了神秘面纱。好在漏洞预警 | Android系统序列化、反序列化不匹配漏洞[1]一文给出了漏洞利用的线索——绕过launchAnywhere的补丁。根据这个线索,我们能够利用有漏洞的Parcelable对象,实现以Settings系统应用发送任意Intent启动Activity的能力。

0x01 背景知识

Android Parcelable 序列化

Android提供了独有的Parcelable接口来实现序列化的方法,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent或Binder传输,见下面示例中的典型用法。

public class MyParcelable implements Parcelable {
     private int mData;

     public int describeContents() {
         return 0;
     }

     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mData);
     }

     public void readFromParcel(Parcel reply) {
         mData = in.readInt();
     }

     public static final Parcelable.Creator<MyParcelable> CREATOR
             = new Parcelable.Creator<MyParcelable>() {
         public MyParcelable createFromParcel(Parcel in) {
             return new MyParcelable(in);
         }

         public MyParcelable[] newArray(int size) {
             return new MyParcelable[size];
         }
     };

     private MyParcelable(Parcel in) {
         mData = in.readInt();
     }
 }

其中,关键的writeToParcel和readFromParcel方法,分别调用Parcel类中的一系列write方法和read方法实现序列化和反序列化。

Bundle

可序列化的Parcelable对象一般不单独进行序列化传输,需要通过Bundle对象携带。 Bundle的内部实现实际是Hashmap,以Key-Value键值对的形式存储数据。例如, Android中进程间通信频繁使用的Intent对象中可携带一个Bundle对象,利用putExtra(key, value)方法,可以往Intent的Bundle对象中添加键值对(Key Value)。Key为String类型,而Value则可以为各种数据类型,包括int、Boolean、String和Parcelable对象等等,Parcel类中维护着这些类型信息。

见/frameworks/base/core/java/android/os/Parcel.java

// Keep in sync with frameworks/native/include/private/binder/ParcelValTypes.h.
    private static final int VAL_NULL = -1;
    private static final int VAL_STRING = 0;
    private static final int VAL_INTEGER = 1;
    private static final int VAL_MAP = 2;
    private static final int VAL_BUNDLE = 3;
    private static final int VAL_PARCELABLE = 4;
    private static final int VAL_SHORT = 5;
    private static final int VAL_LONG = 6;
    private static final int VAL_FLOAT = 7;

对Bundle进行序列化时,依次写入携带所有数据的长度、Bundle魔数(0x4C444E42)和键值对。见BaseBundle.writeToParcelInner方法

int lengthPos = parcel.dataPosition();
        parcel.writeInt(-1); // dummy, will hold length
        parcel.writeInt(BUNDLE_MAGIC);
        int startPos = parcel.dataPosition();
        parcel.writeArrayMapInternal(map);
        int endPos = parcel.dataPosition();
        // Backpatch length
        parcel.setDataPosition(lengthPos);
        int length = endPos - startPos;
        parcel.writeInt(length);
        parcel.setDataPosition(endPos);

pacel.writeArrayMapInternal方法写入键值对,先写入Hashmap的个数,然后依次写入键和值

/**
     * Flatten an ArrayMap into the parcel at the current dataPosition(),
     * growing dataCapacity() if needed.  The Map keys must be String objects.
     */
    /* package */ void writeArrayMapInternal(ArrayMap<String, Object> val) {
        ...
        final int N = val.size();
        writeInt(N);
       ... 
        int startPos;
        for (int i=0; i<N; i++) {
            if (DEBUG_ARRAY_MAP) startPos = dataPosition();
            writeString(val.keyAt(i));
            writeValue(val.valueAt(i));
        ...

接着,调用writeValue时依次写入Value类型和Value本身,如果是Parcelable对象,则调用writeParcelable方法,后者会调用Parcelable对象的writeToParcel方法。

public final void writeValue(Object v) {
        if (v == null) {
            writeInt(VAL_NULL);
        } else if (v instanceof String) {
            writeInt(VAL_STRING);
            writeString((String) v);
        } else if (v instanceof Integer) {
            writeInt(VAL_INTEGER);
            writeInt((Integer) v);
        } else if (v instanceof Map) {
            writeInt(VAL_MAP);
            writeMap((Map) v);
        } else if (v instanceof Bundle) {
            // Must be before Parcelable
            writeInt(VAL_BUNDLE);
            writeBundle((Bundle) v);
        } else if (v instanceof PersistableBundle) {
            writeInt(VAL_PERSISTABLEBUNDLE);
            writePersistableBundle((PersistableBundle) v);
        } else if (v instanceof Parcelable) {
            // IMPOTANT: cases for classes that implement Parcelable must
点击收藏 | 0 关注 | 2
登录 后跟帖