type
status
date
slug
summary
tags
category
icon
password
0x01 漏洞分析
该漏洞在Android 12 Developer Preview 3版本是上被发现,并在Android 12 release中就已经完成了修复。安全公告:Android 安全公告 - 2021 年 11 月 | Android 开源项目

1. Android广播
BroadcastReceiver(广播接收器) 是Android APP的四大组件之一,APP可以通过在AndroidManifest.xml文件中添加一条<receiver>的声明来完成组件的声明或者通过动态注册的方式进行广播接收器的注册。其他APP通过构造一个Intent并通过sendBroadcast方法即可发送广播,实际上进行了进程间的通信,广播接收器则可以接收到广播和数据。当应用程序进程最初启动时调用
IActivityManager.attachApplication(),以此来传递IApplicationThread句柄,系统使用该句柄获得应用程序的控制权。
当系统希望在应用程序进程中执行清单注册的
BroadcastReceiver时,它使用IApplicationThread 句柄调用scheduleReceiver方法。进而通过Binder 的transact实现跨进程调用。下跨进程通过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支持的类型,比如Parcelable、IBinder等,并不局限于Integer。仅仅进行反序列化并不会出现任何问题,只不过在使用具体的元素时,如果我们实际读取的类型无法转换为整数,就会出现ClassCastException异常。
OutputConfiguration在反序列化时用try-catch包裹了createFromParcel方法,这意味着如果在反序列化过程中抛出异常,对象的读取会提前结束,但之前写入的数据不会被读取,造成数据长度不匹配。在这个漏洞场景中,我们并不会使用这个数组的元素,因此我们可以指定任意的序列化类。又由于我们需要使目标类在序列化/反序列化过程产生不匹配,那么就需要找到一个类,使得该类可以在 system_server中成功反序列化,但是在应用 B 中出现ClassNotFoundException异常。0x02 漏洞利用
漏洞利用思路就是通过
intent参数的反序列化数据残留,间接地修改 info参数,因为应用 B 会使用 ActivityInfo中的数据去实例化代码,具体来说就是应用会通过传入的 ActivityInfo 构造 LoadedApk,其中会使用内部的 zipPaths 创建 ClassLoader,攻击者如果可以控制这个字段,就能实现针对应用 B 的任意代码执行。接着上文
Parcel MisMatch Point继续:- 上文提到为了让系统服务能序列化但应用不能反序列化,可以使用只在系统服务中存在的类,如
PackageManagerException。但需要额外通过不指定ClassLoader的readList来绕过类加载约束,因为如果指定了ClassLoader,那么应用B就可以在类加载器中找到对于的类,从而不会抛出异常,而如果没有指定ClassLoader,会默认只在BOOTCLASSPATH中搜索类,而不会去system_server类路径中查找,从而使system_server也反序列化失败。原作者的利用是使用了PackageManagerException,这是一个Serializable类而不是Parcelable。反序列化Serializable对象时,会使用ObjectInputStream,它在反序列化类时,会自动从堆栈轨迹中选择最近的非空非BootClassLoader的ClassLoader。这里由于在系统服务的onTransact等方法在堆栈中,ObjectInputStream会优先使用包含system_server类的ClassLoader,所以可以成功加载到系统服务中定义的可序列化类。但在应用中反序列化该对象时,由于堆栈中没有系统服务的类,会导致ClassNotFoundException。
- 实际利用中由于读取使用了带
classLoader的readList,因此到readSerializable时loader变量不为null,从而不会调用resolveClass在堆栈轨迹中寻找最近的loader,而是直接使用forName加载类,从而导致直接抛出异常,因为当Class.forName无法找到类时,它会抛出异常而不是返回null。解决方案是将Serializable再套一层Parcelable,使用不带ClassLoader的readList去进行反序列化。这里选用的是WindowContainerTransaction类。因此有了下面的构造对象方法。

这个类加载器在堆栈trace中存在,是因为在
ActivityManagerService中重写的onTransact方法存在,而不是Binder#execTransact()、IActivityManager$Stub#onTransact()(由AIDL生成),以及所有使用的Parcelable类中的方法存在。- 目标是在
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中。

- 但后面还有一些
readString调用,为了完全控制不匹配后的未读数据,可以OutputConfiguration中放一个Bundle类型的原始数据,然后用ZenPolicy类将OutputConfiguration包裹起来,以在反序列化时跳过Bundle的前三个int字段VAL_BUNDLE、长度和魔术字)。这样当触发PackageManagerException时会停止继续正常反序列化,而留下了Bundle数据,再由ZenPolicy反序列化过程中后三个readInt读取Bundle字段的三个int字段,并且将后续内容暴露出来,_arg114 = (ActivityInfo) ActivityInfo.CREATOR.createFromParcel(data);继续读取剩下的Bundle数据作为ActivityInfo数据

handleReceiver调用了getPackageInfoNoCheck,并传入了可以被攻击者控制的ApplicationInfo。getPackageInfoNoCheck会根据ApplicationInfo生成一个LoadedApk实例。这里攻击者传入一个新的packageName,以确保会创建新实例。然后调用ContextImpl.getClassLoader,首次调用会代理到LoadedApk的getClassLoader,然后会调用createOrUpdateClassLoaderLocked。createOrUpdateClassLoaderLocked通过makePaths生成类加载的路径zipPaths,其中主要使用了ApplicationInfo中的sourceDir信息。攻击者控制的ApplicationInfo将sourceDir设置为攻击者的apk路径。 因此makePaths将生成包含攻击者apk的zipPaths,交给createClassLoader加载。 最终BroadcastReceiver会从攻击者的apk中加载类并执行,实现了代码执行。

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 实现中,Parcelable、List等类型会在序列化数据的元素开头单独存储长度信息。不过,这个 patch 并不会影响非 Bundle 造成的反序列化漏洞,比如这个漏洞。
0x04 参考
- 作者:LLeaves
- 链接:https://lleavesg.top//article/CVE-2021-0928
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章







