Bundle数据结构分析

前言

最近发生的新闻:拼多多疑似利用Android序列化漏洞攻击用户手机,窃取竞争对手软件数据,防止自己被卸载。

序列化和反序列化是指将内存数据结构转换为字节流,通过网络传输或者保存到磁盘,然后再将字节流恢复为内存对象的过程。在 Web 安全领域,出现过很多反序列化漏洞,比如 PHP反序列化Java反序列化等。由于在反序列化的过程中触发了非预期的程序逻辑,从而被攻击者用精心构造的字节流触发并利用漏洞从而最终实现任意代码执行等目的。

Android 中除了传统的 Java序列化机制,还有一个特殊的序列化方法,即 Parcel。根据官方文档的介绍,ParcelableBundle 对象主要的作用是用于跨进程边界的数据传输(IPC/Binder),但 Parcel 并不是一个通用的序列化方法,因此不建议开发者将 Parcel 数据保存到磁盘或者通过网络传输。

作为 IPC 传输的数据结构,Parcel 的设计初衷是轻量和高效,因此缺乏完善的安全校验。这就引发了历史上出现过多次的 Android 反序列化漏洞

上一篇文章 launchAnyWhere: Activity组件权限绕过漏洞解析 对launchAnyWhere漏洞进行了介绍和分析。

要想继续学习关于这个漏洞后续的知识就需要掌握Bundle的数据结构以及它怎么进行的序列化和反序列化。

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 Binder是知名女程序员Dianne Hackborn基于自己开发的OpenBinder重新实现的Android IPC机制,是Android里最核心的机制。不同于Linux下的管道、共享内存、消息队列、socket等,它是一套传输效率高、可操作性好、安全性高的Client-Server通信机制。Android Binder通过/dev/binder驱动实现底层的进程间通信,通过共享内存实现高性能,它的安全通过Binder Token来保证。

Binder里用到了代理模式(Proxy Pattern)、中介者模式(Mediator Pattern)、桥接模式(Bridge Pattern)。熟悉这些设计模式有助于更好的理解Binder机制。需要了解以下概念:BinderBinder ObjectBinder ProtocolIBinder interfaceBinder TokenAIDL(Android interface definition language)、ServiceManager等。下图大致描述了Binder从kernel层、中间件层到应用层中涉及的重要函数,本文漏洞利用部分会用到。

源码分析

Bundle源代码:

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

Parcel``的反序列化核心函数位于android.os.BaseBundle类中:

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

BaseBundle的序列化构造主要逻辑再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还是NativeBundlefinal boolean isJavaBundle = magic == BUNDLE_MAGIC;//0x4C444E42final boolean isNativeBundle = magic == BUNDLE_MAGIC_NATIVE;//0x4C444E44if (!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;}
}

正常都是使用lazily-unparcel模式,所以在对Bundle内容进行操作的时候才会实际调用initializeFromParcelLocked来执行反序列化,这种方法有助于在多个进程之间连续传递同一个Bundle而不需要访问其中的内容时提高性能。

//路径:frameworks/base/core/java/android/os/BaseBundle.java
public class BaseBundle {private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,boolean parcelledByNative) {if (isEmptyParcel(parcelledData)) {//判断是否为空的Bundleif (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;}}
}

这里面有一个值得注意的问题是BundleKey排序的问题,我们在初始构造原始Parcel数据的时候,要考虑到Keyhashcode排序问题。否则在反序列化之后Bundlekey会被重新排序,影响我们后续的利用。

再次提醒一下这里的hashcode排序,很重要!!!

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

readValue的实现则会根据不同类型的value而有所不同。

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

readString调用到ReadWriteHelper.readString,最总调用到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;
}

通过readString16Inplace方法获取对应的字符串。

//路径:/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+1if (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 lengthmDataPos += 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;
}

同时BundlewriteToParcel也具有类似的逻辑。

Bundle结构

序列化之后的对象一般不会单独的进行传输,而是将其塞入Bundle中,利用Bundle对象进行携带。Bundle内部有一个ArrayMaphash表进行管理,所以它是以Key-Value键值对的形式携带序列化后的数据的。Value可以为各种数据类型,包括intBooleanStringParcelable对象等等。下图是序列化后的数据在Bundle中的简单示意图:

  1. 头部为数据总长度;
  2. Bundle魔数;
  3. 键值对的数量;
  4. Key的长度;
  5. Key的值;
  6. Value的长度;
  7. Value的值;

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!

想要了解Bundle序列化漏洞以及LaunchAnyWhere补丁漏洞可以继续阅读 Bundle 风水 - Android Parcel 序列化与反序列化不匹配系列漏洞

Bundle数据结构和反序列化分析相关推荐

  1. Java安全之SnakeYaml反序列化分析

    Java安全之SnakeYaml反序列化分析 0x00 前言 偶然间看到SnakeYaml的资料感觉挺有意思,发现SnakeYaml也存在反序列化利用的问题.借此来分析一波. 0x01 SnakeYa ...

  2. 841南昌大学计算机考研,2020考研南昌大学841数据结构试题特点分析

    2020考研初试已正式开始,中公考研网初试后为大家整理发布"2020考研南昌大学841数据结构试题特点分析"考研试题内容,中公考研各科研究院老师将对2020考研试题进行试题变化及难 ...

  3. 925计算机考研,计算机#2020考研究生北京交通大学925数据结构试题特点分析

    类目:三人行考研网>计算机>正文 时间:2020-07-27 18:30:30 2020考研初试已正式开始,初试后为大家整理发布"2020考研北京交通大学925数据结构试题特点分 ...

  4. 数据结构:复杂度分析以及数据结构整体概览

    复杂度无非是空间,时间复杂度. 掌握了时间,空间复杂度的分析,基本算掌握了数据结构与算法的一半内容. 之所以引入这几个复杂度概念,是因为,同一段代码,在不同输入的情况下,复杂度量级有可能是不一样的. ...

  5. 2.python数据结构的性能分析

    一.引言 - 现在大家对 大O 算法和不同函数之间的差异有了了解.本节的目标是告诉你 Python 列表和字典操作的 大O 性能.然后我们将做一些基于时间的实验来说明每个数据结构的花销和使用这些数据结 ...

  6. 【数据结构-源码分析】HashMap源码分析(超级详细)

    文章内容 1.HashMap简介 2.类结构 3.属性 4.构造方法 5.方法 5.1.put方法(新增) 5.2.resize方法(扩容) 5.3.get方法(遍历) 5.4.remove方法(删除 ...

  7. weblogic之CVE-2016-0638反序列化分析

    此漏洞是基于CVE-2015-4852漏洞进行黑名单的绕过,CVE-2015-4852补丁主要应用在三个位置上 weblogic.rjvm.InboundMsgAbbrev.class :: Serv ...

  8. (转载)Unity3d开发中常用的数据结构总结与分析

    来到周末,小匹夫终于有精力和时间来更新下博客了.前段时间小匹夫读过一份代码,对其中各种数据结构灵活的使用赞不绝口,同时也大大激发了小匹夫对各种数据结构进行梳理和总结的欲望.正好最近也拜读了若干大神的文 ...

  9. 口碑 App 各 Bundle 之间的依赖分析指南

    背景 口碑的 O2O 业务 Bundle,目前需要在支付宝和口碑独客这两个 App 中的运行.目前口碑 App 也是使用 mPaaS 框架,一些基础服务比如 ConfigService,H5 容器,R ...

最新文章

  1. AtcoderCodeForces杂题11.6
  2. mysql-主从服务器同步搭建
  3. Linux内核分析--内核中的数据结构双向链表续【转】
  4. static在内存层面的作用_static的作用和内存划分?
  5. 普及vmware连接上网
  6. Hadoop Yarn任务优先级(作业优先级、应用优先级)设置
  7. 马化腾亲身分享:腾讯兵法教你做一款高口碑的产品
  8. yuv420sp转jpg
  9. ad采样正弦电压计算c语言程序,TMS320F2812 DSP编程之AD采样精度的校准算法(转)...
  10. 1054 The Dominant Color(20 分)
  11. Rayson API 框架分析系列之3:RSON序列化格式
  12. JAVA设计模式--代理模式(动态)(一)
  13. linux网络编程之shutdown() 与 close()函数详解
  14. 在排查性能过程中如遇到cpu的wa高时该如何做(一)
  15. 彻底搞懂原生事件流和 React 事件流
  16. 秋招面试准备 JS1
  17. springboot+quartz报错:Table ‘XXXX.QRTZ_TRIGGERS‘ doesn‘t exist
  18. 人力资本、人均受教育年限,受高等教育人数比重(1997-2020)
  19. html格子像素画,html – rotateY()文本模糊/像素化
  20. ANSI C、C89、C99和C51的区别

热门文章

  1. Facebook、Twitter网页分享
  2. 内网穿透工具--Sunny-Ngrok讲解
  3. containsKey方法——判断是否包含指定的键名
  4. 最小二乘法入门(Matlab直线和曲线拟合)
  5. 100% 解决 VMware Workstation 与 Hyper-V 不兼容。请先从系统中移除 Hyper-V 角色
  6. 基于51单片机ds1302时钟、ds18b20、lcd12864的恒温器
  7. Python中inplace参数
  8. Nginx基本使用方法
  9. S.M.A.R.T 参数详解及推荐指标
  10. [PHP响应式营销型万能H5建站系统源码] 免费开源建站利器+可视化自由布局页面