Bundle data structure and deserialization analysis

Bundle data structure analysis

foreword

Screenshot_20230414163328107_com.ss.android.article.newsedit.jpg

Recent news: Pinduoduo is suspected of using Android serialization vulnerabilities to attack users' mobile phones, steal competitor software data, and prevent itself from being uninstalled.

Serialization and deserialization refer to the process of converting an in-memory data structure into a byte stream, transmitting it over a network or saving it to disk, and then restoring the byte stream into a memory object. In Webthe security field, there have been many deserialization vulnerabilities, such as PHP反序列化, Java反序列化etc. Due to the unintended program logic triggered during the deserialization process, the attacker uses a carefully constructed byte stream to trigger and exploit the vulnerability to finally achieve arbitrary code execution and other purposes.

AndroidIn addition to the traditional ones Java序列化机制, there is a special serialization method, viz Parcel. According to the official documentation, the main function of Parcelableand Bundleobjects is for data transmission across process boundaries (IPC/Binder), but Parcelis not a general serialization method, so it is not recommended for developers to save Parceldata to disk or transmit it over the network .

As a data structure for IPCtransmission , Parcelit is originally designed to be lightweight and efficient, so it lacks perfect security checks. This has led to a situation that has appeared many times in history Android 反序列化漏洞.

The previous article launchAnyWhere: Analysis of Activity Component Permission Bypass Vulnerability introduced launchAnyWhereand analyzed the vulnerability.

If you want to continue to learn about the follow-up knowledge about this vulnerability, you need to master Bundlethe data structure and how it is serialized and deserialized.

Introduction to Bundle

"In the Android platform, the binder is used for nearly everything that happens across processes in the core platform."

–Dianne Hackborn,Google

https://lkml.org/lkml/2009/6/25/3

Android BinderIt is a re-implemented IPC mechanism developed by a well-known female programmer Dianne Hackbornbased on her own development , and it is the core mechanism in the game. Different from the following pipelines, shared memory, message queues, sockets, etc., it is a communication mechanism with high transmission efficiency, good operability, and high security. The underlying inter-process communication is realized through the driver, and the high performance is realized through the shared memory, and its safety is guaranteed by passing.OpenBinderAndroidAndroidLinuxClient-ServerAndroid Binder/dev/binderBinder Token

BinderProxy PatternProxy mode ( ), intermediary mode ( Mediator Pattern), and bridge mode ( ) are used in it Bridge Pattern. Familiarity with these design patterns facilitates a better understanding Binder机制. Need to understand the following concepts: Binder, Binder Object, Binder Protocol, IBinder interface, Binder Token, AIDL(Android interface definition language), ServiceManager, etc. The figure below roughly describes the important functions involved in Binder from the kernel layer, the middleware layer to the application layer, which will be used in the exploit part of this article.

Source code analysis

Bundlesource code:

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
    
    
    /****部分代码省略****/
}

The deserialization core function of Parcel`` is located android.os.BaseBundlein the class:

//路径:frameworks/base/core/java/android/os/BaseBundle.java
public class BaseBundle {
    
    
    BaseBundle(Parcel parcelledData) {
    
    
        readFromParcelInner(parcelledData);
    }
    void readFromParcelInner(Parcel parcel) {
    
    
        // Keep implementation in sync with readFromParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        int length = parcel.readInt();//所有的数据长度
        readFromParcelInner(parcel, length);
    }
}

BaseBundleThe main logic of the serialization construction is again readFromParcelInner:

//路径:frameworks/base/core/java/android/os/BaseBundle.java
public class BaseBundle {
    
    
    BaseBundle(Parcel parcelledData) {
    
    
        readFromParcelInner(parcelledData);
    }
    void readFromParcelInner(Parcel parcel) {
    
    
        // Keep implementation in sync with readFromParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        int length = parcel.readInt();//所有的数据长度
        readFromParcelInner(parcel, length);
    }
    private void readFromParcelInner(Parcel parcel, int length) {
    
    
        if (length < 0) {
    
    
            throw new RuntimeException("Bad length in parcel: " + length);
        } else if (length == 0) {
    
    
            // Empty Bundle or end of data.
            mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
            mParcelledByNative = false;
            return;
        }
        final int magic = parcel.readInt();//读取魔数,判断是JavaBundle还是NativeBundle
        final boolean isJavaBundle = magic == BUNDLE_MAGIC;//0x4C444E42
        final boolean isNativeBundle = magic == BUNDLE_MAGIC_NATIVE;//0x4C444E44
        if (!isJavaBundle && !isNativeBundle) {
    
    
            throw new IllegalStateException("Bad magic number for Bundle: 0x" +
                Integer.toHexString(magic));
        }
        //如果Parcel存在读写Helper,就不懒惰进行数据解析,而是直接数据解析操作
        if (parcel.hasReadWriteHelper()) {
    
    
            synchronized (this) {
    
    
                initializeFromParcelLocked(parcel, /*recycleParcel=*/
                    false, isNativeBundle);
            }
            return;
        }
        //对这个Parcel进行数据解析
        int offset = parcel.dataPosition();
        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
        Parcel p = Parcel.obtain();
        p.setDataPosition(0);
        p.appendFrom(parcel, offset, length);
        p.adoptClassCookies(parcel);
        p.setDataPosition(0);
        mParcelledData = p;
        mParcelledByNative = isNativeBundle;
    }   
}

Normally, it is a usage lazily-unparcelmode, so it is actually called to perform deserialization Bundlewhen operating on the content . This method helps to improve performance when the same one is continuously passed between multiple processes without accessing the content. .initializeFromParcelLockedBundle

//路径:frameworks/base/core/java/android/os/BaseBundle.java
public class BaseBundle {
    
    
    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
            boolean parcelledByNative) {
    
    
        if (isEmptyParcel(parcelledData)) {
    
    //判断是否为空的Bundle
            if (mMap == null) {
    
    
                mMap = new ArrayMap<>(1);
            } else {
    
    
                mMap.erase();
            }
            mParcelledData = null;
            mParcelledByNative = false;
            return;
        }
        //Bundle中键值对的数量
        final int count = parcelledData.readInt();
        if (count < 0) {
    
    
            return;
        }
        ArrayMap<String, Object> map = mMap;
        if (map == null) {
    
    
            map = new ArrayMap<>(count);
        } else {
    
    
            map.erase();
            map.ensureCapacity(count);
        }
        try {
    
    
            if (parcelledByNative) {
    
    
                // If it was parcelled by native code, then the array map keys aren't sorted
                // by their hash codes, so use the safe (slow) one.
                //对于Native Bundle,其Key没有按照hashcode进行排序,使用另一个安全方式读取
                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
            } else {
    
    
                // If parcelled by Java, we know the contents are sorted properly,
                // so we can use ArrayMap.append().
                //对于JavaBundle,我们知道内容已经正确排序,因此可以使用ArrayMap.append()。
                parcelledData.readArrayMapInternal(map, count, mClassLoader);
            }
        } catch (BadParcelableException e) {
    
    
            if (sShouldDefuse) {
    
    
                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
                map.erase();
            } else {
    
    
                throw e;
            }
        } finally {
    
    
            mMap = map;
            if (recycleParcel) {
    
    
                recycleParcel(parcelledData);
            }
            mParcelledData = null;
            mParcelledByNative = false;
        }
    }        
}

There is a noteworthy problem here is the problem of Bundlesorting Key, which we Parcelhave to consider when we initially construct the original data . Otherwise, it will be reordered after deserialization , affecting our subsequent use.Keyhashcode排序Bundlekey

Remind here again hashcode排序, it is very important! ! !

//路径:frameworks/base/core/java/android/os/Parcel.java
public final class Parcel {
    
    
    /* package */ void readArrayMapInternal(ArrayMap outVal, int N, ClassLoader loader) {
    
    
        if (DEBUG_ARRAY_MAP) {
    
    
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
        }
        int startPos;
        //循环将Parcel中的数据读取到Map中
        while (N > 0) {
    
    
            if (DEBUG_ARRAY_MAP) {
    
    
                startPos = dataPosition();
            }
            //读取key,key是一个字符串类型
            String key = readString();
            //读取value,是一个Object类型
            Object value = readValue(loader);
            if (DEBUG_ARRAY_MAP) {
    
    
                Log.d(TAG,
                    "  Read #" + (N - 1) + " " + (dataPosition() - startPos) +
                    " bytes: key=0x" +
                    Integer.toHexString(((key != null) ? key.hashCode() : 0)) +
                    " " + key);
            }
            //需要了解ArrayMap的append和put的区别
            outVal.append(key, value);
            N--;
        }
        outVal.validate();
    }
}

readValuevalueThe implementation will vary according to the different types .

public final class Parcel {
    
    
    public final Object readValue(ClassLoader loader) {
    
    
        int type = readInt();
        switch (type) {
    
    
        case VAL_NULL:
            return null;
        case VAL_STRING:
            return readString();
        case VAL_INTEGER:
            return readInt();
        case VAL_MAP:
            return readHashMap(loader);
        case VAL_PARCELABLE:
            return readParcelable(loader);
        case VAL_SHORT:
            return (short) readInt();
        case VAL_LONG:
            return readLong();
        case VAL_FLOAT:
            return readFloat();
        case VAL_DOUBLE:
            return readDouble();
        case VAL_BOOLEAN:
            return readInt() == 1;
        case VAL_CHARSEQUENCE:
            return readCharSequence();
        case VAL_LIST:
            return readArrayList(loader);
        case VAL_BOOLEANARRAY:
            return createBooleanArray();
        case VAL_BYTEARRAY:
            return createByteArray();
        case VAL_STRINGARRAY:
            return readStringArray();
        case VAL_CHARSEQUENCEARRAY:
            return readCharSequenceArray();
        case VAL_IBINDER:
            return readStrongBinder();
        case VAL_OBJECTARRAY:
            return readArray(loader);
        case VAL_INTARRAY:
            return createIntArray();
        case VAL_LONGARRAY:
            return createLongArray();
        case VAL_BYTE:
            return readByte();
        case VAL_SERIALIZABLE:
            return readSerializable(loader);
        case VAL_PARCELABLEARRAY:
            return readParcelableArray(loader);
        case VAL_SPARSEARRAY:
            return readSparseArray(loader);
        case VAL_SPARSEBOOLEANARRAY:
            return readSparseBooleanArray();
        case VAL_BUNDLE:
            return readBundle(loader); // loading will be deferred
        case VAL_PERSISTABLEBUNDLE:
            return readPersistableBundle(loader);
        case VAL_SIZE:
            return readSize();
        case VAL_SIZEF:
            return readSizeF();
        case VAL_DOUBLEARRAY:
            return createDoubleArray();
        default:
            int off = dataPosition() - 4;
            throw new RuntimeException("Parcel " + this +
                ": Unmarshalling unknown type code " + type + " at offset " +
                off);
        }
    }
    public final String readString() {
    
    
        return mReadWriteHelper.readString(this);
    }
    public static class ReadWriteHelper {
    
    
        public static final ReadWriteHelper DEFAULT = new ReadWriteHelper();
        public void writeString(Parcel p, String s) {
    
    
            nativeWriteString(p.mNativePtr, s);
        }
        public String readString(Parcel p) {
    
    
            return nativeReadString(p.mNativePtr);
        }
    }
    static native String nativeReadString(long nativePtr);
}

readStringCalled , the method that ReadWriteHelper.readStringis called the most .NativenativeReadString

//路径:/frameworks/base/core/jni/android_os_Parcel.cpp
static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    
    
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
    
    
        size_t len;
        const char16_t* str = parcel->readString16Inplace(&len);
        if (str) {
    
    
            return env->NewString(reinterpret_cast<const jchar*>(str), len);
        }
        return NULL;
    }
    return NULL;
}

readString16InplaceGet the corresponding string through the method.

//路径:/frameworks/native/libs/binder/Parcel.cpp
const char16_t* Parcel::readString16Inplace(size_t* outLen) const
{
    
    
    int32_t size = readInt32();//获取字符串的长度
    // watch for potential int overflow from size+1
    if (size >= 0 && size < INT32_MAX) {
    
    
        *outLen = size;
        //注意,即使是size=0长度的String16,依旧会调用readInplace(1*sizeof(char16_t)),也就是4字节。
        const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));
        if (str != NULL) {
    
    
            return str;
        }
    }
    *outLen = 0;
    return NULL;
}
const void* Parcel::readInplace(size_t len) const
{
    
    
    if (len > INT32_MAX) {
    
    
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        return NULL;
    }
    if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize
            && len <= pad_size(len)) {
    
    
        if (mObjectsSize > 0) {
    
    
            status_t err = validateReadData(mDataPos + pad_size(len));
            if(err != NO_ERROR) {
    
    
                // Still increment the data position by the expected length
                mDataPos += pad_size(len);
                ALOGV("readInplace Setting data pos of %p to %zu", this, mDataPos);
                return NULL;
            }
        }
        const void* data = mData+mDataPos;
        mDataPos += pad_size(len);
        ALOGV("readInplace Setting data pos of %p to %zu", this, mDataPos);
        return data;
    }
    return NULL;
}

BundleAt the same time writeToParcelalso has a similar logic.

Bundle structure

Objects after serialization are generally not transmitted separately, but are stuffed Bundlein and Bundlecarried by objects. BundleThere is an internal ArrayMaptable hashfor management, so it carries Key-Valueserialized data in the form of key-value pairs. ValueCan be a variety of data types, including int, Boolean, Stringand Parcelableobjects, among others. BundleThe following figure is a simple schematic diagram of the serialized data in :

  1. The header is the total length of the data;
  2. Bundle magic number;
  3. the number of key-value pairs;
  4. the length of the key;
  5. the value of Key;
  6. the length of Value;
  7. the value of Value;

insert image description here

The article is all told here, if you have other needs to communicate, you can leave a message~!

If you want to know about the Bundle serialization vulnerability and the LaunchAnyWhere patch vulnerability, you can continue to read Bundle Feng Shui-Android Parcel Serialization and Deserialization Mismatch Series Vulnerabilities

Guess you like

Origin blog.csdn.net/stven_king/article/details/130318486