type
status
date
slug
summary
tags
category
icon
password
 
 

0x01 漏洞分析

该漏洞在Android 12 Developer Preview 3版本是上被发现,并在Android 12 release中就已经完成了修复。安全公告:Android 安全公告 - 2021 年 11 月  |  Android 开源项目
notion image
 

1. Android广播

BroadcastReceiver(广播接收器) 是Android APP的四大组件之一,APP可以通过在AndroidManifest.xml文件中添加一条<receiver>的声明来完成组件的声明或者通过动态注册的方式进行广播接收器的注册。其他APP通过构造一个Intent并通过sendBroadcast方法即可发送广播,实际上进行了进程间的通信,广播接收器则可以接收到广播和数据。
 
当应用程序进程最初启动时调用IActivityManager.attachApplication(),以此来传递IApplicationThread句柄,系统使用该句柄获得应用程序的控制权。
notion image
当系统希望在应用程序进程中执行清单注册的BroadcastReceiver时,它使用IApplicationThread 句柄调用scheduleReceiver方法。进而通过Bindertransact实现跨进程调用。下跨进程通过onTransact 处理请求,需要跨进程传递的参数都会被放到同一个Parcel对象中,按顺序依次读取各种参数,包括Intent ActivityInfo 等。
其中ActivityInfo 包含有关要执行的组件的信息。此参数中传递的数据包括加载处理接收的广播的Java类的文件路径。
💡
这时候你可能已经猜到这个新的漏洞路径是什么了:调用sendBroadcast传递一个Intent,当系统尝试调用scheduleReceiver时,将导致在调用scheduleReceiver的应用程序中看到被篡改的ActivityInfo
需要注意的是,这条新的漏洞路径仅在Android 12中可行,因为先前没有办法将任意的Parcelable放入Intent(Intent通过putExtras添加不算,因为这样会将数据放入Bundle中,Bundle序列化后被写入mParcelData,在读取时不发生反序列化)而漏洞作者是用了一个在 Android 12 中加入的 Intent.ClipData字段去实现的,该字段原本用于携带剪切板数据。
sendMessage 后会有handleReceiver 进行处理。handleReceiver 调用getPackageInfoNoCheck方法拿到packageInfo,并在其中获取到需要拉起的receiver对象并调用其onReceive方法来完成BroadcastReceiver组件的启动。
继续深入getPackageInfoNoCheck ,调用了getPackageInfo
漏洞利用思路就是通过 intent参数的反序列化数据残留,间接地修改 info参数,因为应用 B 会使用 ActivityInfo中的数据去实例化代码,具体来说就是应用会通过传入的 ActivityInfo 构造 LoadedApk,其中会使用内部的 zipPaths 创建 ClassLoader,攻击者如果可以控制这个字段,就能实现针对应用 B 的任意代码执行。

2. Parcel MisMatch Point

OutputConfiguration类,其中有个序列化的字段 sensorPixelModesUsed ArrayList<Integer>类型,在反序列化该字段时,使用的是Parcel.readList方法,其调用链路是:
  • Parcel.readList
  • Parcel.readListInternal
  • Parcel.readValue
最终是使用 readValue读取 List中每个元素的值。因此实际上可以读出任何 readValue支持的类型,比如 ParcelableIBinder等,并不局限于 Integer
仅仅进行反序列化并不会出现任何问题,只不过在使用具体的元素时,如果我们实际读取的类型无法转换为整数,就会出现 ClassCastException异常
OutputConfiguration在反序列化时用try-catch包裹了createFromParcel方法,这意味着如果在反序列化过程中抛出异常,对象的读取会提前结束,但之前写入的数据不会被读取,造成数据长度不匹配。在这个漏洞场景中,我们并不会使用这个数组的元素,因此我们可以指定任意的序列化类。又由于我们需要使目标类在序列化/反序列化过程产生不匹配,那么就需要找到一个类,使得该类可以在 system_server中成功反序列化,但是在应用 B 中出现ClassNotFoundException异常
 

0x02 漏洞利用

漏洞利用思路就是通过 intent参数的反序列化数据残留,间接地修改 info参数,因为应用 B 会使用 ActivityInfo中的数据去实例化代码,具体来说就是应用会通过传入的 ActivityInfo 构造 LoadedApk,其中会使用内部的 zipPaths 创建 ClassLoader,攻击者如果可以控制这个字段,就能实现针对应用 B 的任意代码执行。
接着上文Parcel MisMatch Point继续:
  • 上文提到为了让系统服务能序列化但应用不能反序列化,可以使用只在系统服务中存在的类,如PackageManagerException。但需要额外通过不指定ClassLoaderreadList来绕过类加载约束,因为如果指定了ClassLoader,那么应用B就可以在类加载器中找到对于的类,从而不会抛出异常,而如果没有指定ClassLoader,会默认只在BOOTCLASSPATH中搜索类,而不会去system_server类路径中查找,从而使system_server也反序列化失败。原作者的利用是使用了 PackageManagerException,这是一个 Serializable类而不是 Parcelable反序列化Serializable对象时,会使用ObjectInputStream它在反序列化类时,会自动从堆栈轨迹中选择最近的非空非BootClassLoader的ClassLoader。这里由于在系统服务的onTransact等方法在堆栈中,ObjectInputStream会优先使用包含system_server类的ClassLoader,所以可以成功加载到系统服务中定义的可序列化类。但在应用中反序列化该对象时,由于堆栈中没有系统服务的类,会导致ClassNotFoundException
  • 目标是在Intent对象中触发反序列化长度不匹配,因为Intent会在系统服务的onTransact方法中作为第一个参数传递,这样就可以修改第二个参数ActivityInfo 。但是上文提到这条攻击链路仅在Android12中可行,因为先前没有办法将任意的Parcelable放入Intent中。在Android12-bete中的在 ClipData$Item中新增了一个字段 mActivityInfo ,在ClipData构造方法中通过in.readTypedObject(ActivityInfo.CREATOR) 读取。
    • ActivityInfo 继承自 ComponentInfo,并具有 applicationInfo字段。在 ActivityInfo(Parcel source)构造函数中,无法直接放置自定义 Parcelable对象,但是ApplicationInfo 中有一个字段 splitDependencies,它是 SparseArray<int[]> 类型。在读取 splitDependencies时,使用了 readSparseArray方法,该方法又使用 readValue方法来读取 SparseArray 。可以将 OutputConfiguration放置在 splitDependencies中。
      • notion image
    • 但后面还有一些readString调用,为了完全控制不匹配后的未读数据,可以OutputConfiguration中放一个Bundle类型的原始数据,然后用ZenPolicy类将OutputConfiguration包裹起来,以在反序列化时跳过Bundle的前三个int字段VAL_BUNDLE 、长度和魔术字)。这样当触发PackageManagerException时会停止继续正常反序列化,而留下了Bundle数据,再由ZenPolicy 反序列化过程中后三个readInt读取Bundle字段的三个int字段,并且将后续内容暴露出来,_arg114 = (ActivityInfo) ActivityInfo.CREATOR.createFromParcel(data);继续读取剩下的Bundle数据作为ActivityInfo 数据
      • notion image
     
    • handleReceiver调用了getPackageInfoNoCheck,并传入了可以被攻击者控制的ApplicationInfogetPackageInfoNoCheck会根据ApplicationInfo生成一个LoadedApk实例。这里攻击者传入一个新的packageName,以确保会创建新实例。然后调用ContextImpl.getClassLoader,首次调用会代理到LoadedApkgetClassLoader,然后会调用createOrUpdateClassLoaderLockedcreateOrUpdateClassLoaderLocked通过makePaths生成类加载的路径zipPaths其中主要使用了ApplicationInfo中的sourceDir信息。攻击者控制的ApplicationInfosourceDir设置为攻击者的apk路径。 因此makePaths将生成包含攻击者apkzipPaths,交给createClassLoader加载。 最终BroadcastReceiver会从攻击者的apk中加载类并执行,实现了代码执行。
     
    notion image

    0x03 漏洞修复

    这个漏洞本身对终端用户的影响不是很大,毕竟只在 Android 12 Preview 版本中就修复了。但通过这个漏洞,Google 引入了许多修复和缓释方案,直接影响了后续的漏洞挖掘和利用思路。
    首先针对漏洞本身,修复方案为:
    • 对上述类去除隐式的异常处理,修复读写不一致的问题;
    • 使用 readIntArray 而不是readList/readValue 去读取数据,消灭类型擦除的副作用;
    • 防止 ClipData.mActivityInfo写入 Parcel,除非显式指定。这消除了向 Intent 写入任意 Parcelable 的一个攻击链路;
    另外,在 Andorid 13 中,引入了更强的反序列化缓释方案:
    • 新增了一个 readListInternal方法的重载,增加额外的 Class参数,显式指定读取列表的元素的类型,并且将原来的方法标记为 @Deprecated
    • 新增了Parcel.enforceNoDataAvail 方法,用于确保反序列化结束后,Parcel 中不再存在多余的数据;回想上一节中 Bundle 风水的利用,实际上第三个元素在第二次反序列化中是多出来的,因此这个修改会导致上述 Bundle 风水的失败;当然也有一些绕过的手法,比如通过更复杂的风水使得第二次反序列化能够到 Parcel 的末尾即可;
    • LazyBundle patch,在 LazyBundle 实现中,ParcelableList 等类型会在序列化数据的元素开头单独存储长度信息。不过,这个 patch 并不会影响非 Bundle 造成的反序列化漏洞,比如这个漏洞。

    0x04 参考

     
    1. CVE-2021-0928 - Android Code Search
    1. Android 安全公告 - 2021 年 11 月  |  Android 开源项目  |  Android Open Source Project (google.cn)
    1. Android parcels: the bad, the good and the better - Introducing Android’s Safer Parcel (blackhat.com)
    1. Android 反序列化漏洞攻防史话 - evilpan
    1. Android Parcel Mismatch系列漏洞整理(二) | 失眠想睡觉的blog
    1. ReparcelBug2
      michalbednarskiUpdated Apr 17, 2024
    1. Broadcast 源码分析 | 小强的开发笔记 (solarqiang.github.io)
    Android-Flutter逆向De1CTF-2020-Pwn-BroadCastTest
    • Twikoo