Paquete de estructura de datos y análisis de deserialización

Paquete de análisis de estructura de datos

prefacio

Captura de pantalla_20230414163328107_com.ss.android.article.newsedit.jpg

Noticias recientes: se sospecha que Pinduoduo usa vulnerabilidades de serialización de Android para atacar los teléfonos móviles de los usuarios, robar datos de software de la competencia y evitar que se desinstale.

La serialización y la deserialización se refieren al proceso de convertir una estructura de datos en memoria en un flujo de bytes, transmitirlo a través de una red o guardarlo en el disco y luego restaurar el flujo de bytes en un objeto de memoria. En Webel campo de la seguridad , ha habido muchas vulnerabilidades de deserialización, como PHP反序列化, Java反序列化etc. Debido a la lógica del programa no intencionada que se activa durante el proceso de deserialización, el atacante usa un flujo de bytes construido cuidadosamente para activar y explotar la vulnerabilidad para finalmente lograr la ejecución de código arbitrario y otros propósitos.

AndroidAdemás de los tradicionales Java序列化机制, existe un método de serialización especial, a saber Parcel. De acuerdo con la documentación oficial, la función principal de los objetosParcelable y es la transmisión de datos a través de los límites del proceso (IPC/Binder), pero no es un método de serialización general, por lo que no se recomienda que los desarrolladores guarden datos en el disco o los transmitan a través de la redBundleParcelParcel

Como estructura de datos para IPCla transmisión , Parceloriginalmente se diseñó para ser liviana y eficiente, por lo que carece de controles de seguridad perfectos. Esto ha llevado a una situación que se ha presentado muchas veces en la historia Android 反序列化漏洞.

El artículo anterior launchAnywhere: análisis de la vulnerabilidad de omisión de permisos del componente de actividad introdujo launchAnyWherey analizó la vulnerabilidad.

Si desea continuar aprendiendo sobre el conocimiento de seguimiento sobre esta vulnerabilidad, debe dominar Bundlela estructura de datos y cómo se serializa y deserializa.

Introducción al paquete

"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 BinderEs un mecanismo IPC reimplementado desarrollado por una conocida programadora Dianne Hackbornbasada en su propio desarrollo , y es el mecanismo central del juego. A diferencia de las siguientes canalizaciones, memoria compartida, colas de mensajes, sockets, etc., es un mecanismo de comunicación con alta eficiencia de transmisión, buena operatividad y alta seguridad. La comunicación subyacente entre procesos se realiza a través del controlador, y el alto rendimiento se realiza a través de la memoria compartida, y su seguridad está garantizada al pasar.OpenBinderAndroidAndroidLinuxClient-ServerAndroid Binder/dev/binderBinder Token

BinderEn él se utilizan el modo proxy ( Proxy Pattern), el modo intermediario ( Mediator Pattern) y el modo puente ( ) Bridge Pattern. La familiaridad con estos patrones de diseño facilita una mejor comprensión Binder机制. Necesita comprender los siguientes conceptos: Binder, Binder Object, Binder Protocol, IBinder interface, Binder Token, AIDL(lenguaje de definición de interfaz de Android), ServiceManager, etc. La siguiente figura describe aproximadamente las funciones importantes involucradas en Binder desde la capa del kernel, la capa del middleware hasta la capa de la aplicación, que se usará en la parte de explotación de este artículo.

Análisis de código fuente

Bundlecódigo fuente:

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

La función principal de deserialización de Parcel`` se encuentra android.os.BaseBundleen la clase:

//路径: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);
    }
}

BaseBundleLa lógica principal de la construcción de serialización es nuevamente 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;
    }   
}

Normalmente, es un lazily-unparcelpatrón de uso, por lo que en realidad se llama para realizar la deserialización Bundlecuando se opera en el contenido . Este método ayuda a mejorar el rendimiento cuando el mismo se pasa continuamente entre múltiples procesos sin acceder al contenido. .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;
        }
    }        
}

Aquí hay un problema digno de mención: el problema de Bundlela clasificación Key, que Parceldebemos tener en cuenta cuando construimos inicialmente los datos originales . De lo contrario, será reordenado después de la deserialización , afectando nuestro uso posterior.Keyhashcode排序Bundlekey

Recordatorio aquí de nuevo hashcode排序, ¡muy importante! ! !

//路径: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();
    }
}

readValuevalueLa implementación variará según los diferentes tipos .

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);
}

readStringLlamado , el método que ReadWriteHelper.readStringsiempre se llama .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;
}

readString16InplaceObtenga la cadena correspondiente a través del método.

//路径:/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;
}

BundleAl mismo tiempo writeToParceltambién tiene una lógica similar.

Estructura del paquete

Los objetos después de la serialización generalmente no se transmiten por separado, sino que los objetos los introducen Bundley Bundlelos transportan. BundleHay una ArrayMaptabla interna hashpara la gestión, por lo que lleva Key-Valuedatos serializados en forma de pares clave-valor. Puede Valueser una variedad de tipos de datos, incluidos int, y objetos, entre otros. La siguiente figura es un diagrama esquemático simple de los datos serializados en :BooleanStringParcelableBundle

  1. El encabezado es la longitud total de los datos;
  2. Número mágico del paquete;
  3. el número de pares clave-valor;
  4. la longitud de la clave;
  5. el valor de Clave;
  6. la duración del Valor;
  7. el valor del Valor;

inserte la descripción de la imagen aquí

El artículo se cuenta aquí, si tiene otras necesidades para comunicarse, ¡puede dejar un mensaje ~!

Si desea conocer la vulnerabilidad de serialización del paquete y la vulnerabilidad del parche LaunchAnywhere, puede continuar leyendo Vulnerabilidades de la serie de desajustes de serialización y deserialización de paquete Feng Shui-Android

Supongo que te gusta

Origin blog.csdn.net/stven_king/article/details/130318486
Recomendado
Clasificación