CVE-2021-0928,android.hardware.camera2.params.OutputConfiguration 中 writeToParcel/createFromParcel 序列化不匹配
这是一个利用该漏洞进行权限提升的漏洞利用程序,可从已安装的 Android 应用提权至 Android 设置应用(或任何其他已安装应用可向其发送到 AndroidManifest.xml 中声明的 <receiver> 的应用,通过发送到 <activity> 进行权限提升也是可行的,尽管此处未展示)
我最初在 Android 12 Developer Preview 3 上发现了该问题
本仓库中提供的漏洞利用版本适用于 Android 12 Beta 2 和 3
该漏洞已在首个正式版 Android 12 中修复
以下分析最初是为 Google 撰写的,供其考虑将此报告视为完整漏洞利用链
在撰写本文时,Android 12 尚未在 AOSP 中提供(Android Developer Preview/Beta 版本并非开源)

Android 上的大多数 IPC 都是通过名为 Parcel 的类完成的
Parcel 的基本用法如下:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");
然后,`Parcel` 会[通过 `Binder`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))被发送到另一个进程。或者,在测试时,可以调用 [`p.setDataPosition(0)`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int)) 将 parcel 回绕到起始位置并开始读取:```java
int a = p.readInt(); // a = 1
String b = p.readString(); // b = "Hello"
需要注意的是,parcel 内部会保存执行读取操作时的位置。确保 read* 方法与之前使用的 write* 方法相匹配是 Parcel 类使用者的责任,否则后续读取将发生在缓冲区中的错误位置。
Parcel 还提供了写入自定义对象的能力,推荐的方式是实现 Parcelable 接口。
以下是 Parcelable 接口的示例实现(已移除无关代码,WindowContainerTransaction 类 在漏洞利用中作为 gadget 链的一部分被使用,但该类本身并无任何问题):```java
package android.window;
public final class WindowContainerTransaction implements Parcelable {
private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
private final ArrayList mHierarchyOps = new ArrayList<>();
private WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */);
in.readList(mHierarchyOps, null /* loader */);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mChanges);
dest.writeList(mHierarchyOps);
}
@NonNull
public static final Creator<WindowContainerTransaction> CREATOR =
new Creator<WindowContainerTransaction>() {
@Override
public WindowContainerTransaction createFromParcel(Parcel in) {
return new WindowContainerTransaction(in);
}
};
}
如上所示,`writeToParcel()` 方法在写入时被使用。然后在读取时,会调用 `CREATOR.createFromParcel()` 工厂方法。`Parcelable` 实现有责任确保 `createFromParcel` 读取的数据量与 `writeToParcel` 写入的数据量相同,否则对该 `Parcel` 的所有后续读取都将从错误的偏移量读取数据
此类可以通过以下方式写入/读取 `Parcel`:
* 直接调用 `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`,这通常用于已知类类型的情况,例如当 `Parcelable` 具有不同类型的 `Parcelable` 字段时,或者在 AIDL 生成的代码中,当定义的 RPC 方法以 `Parcelable` 作为参数时
* 通过 `Parcel.writeParcelable`/`readParcelable`。[`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) 首先写入类的名称,然后调用 `Parcelable` 接口中的 `writeToParcel` 方法。[`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) 读取写入的类名,在提供的 `ClassLoader` 中查找具有该名称的类,如果提供了 null 则在 `BOOTCLASSPATH` 中查找。一旦找到类,其静态字段 `CREATOR` 被用于获取 [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) 实例,该实例是用于读取该类的工厂。需要注意的是,当使用 `readParcelable` 方法时,它可以读取类路径中可用的任何 `Parcelable`,因为要创建的对象的名称是从同一个 `Parcel` 中读取的
* `readParcelable` 被许多其他 `Parcel` 方法使用,例如上面示例中看到的 `readList` 通过 `readValue` 读取元素,`readValue` 是在 `Parcel` 中传输对象的最通用方法,其使用方式之一就是通过 `readParcelable`。另外,在上面的示例中,由于 Java 的类型擦除,`ArrayList<HierarchyOp> mHierarchyOps` 字段实际上可以包含 Parcel 支持的任何对象,而不仅仅是与泛型类型声明中指定的类型兼容的对象
# `writeToParcel`/`createFromParcel` 不匹配
如上所述,`Parcelable` 接口实现有责任确保 `createFromParcel` 从 `Parcel` 读取的数据量与匹配的 `writeToParcel` 先前写入的数据量相同。每当 `BOOTCLASSPATH` 中存在可能违反该契约的 `Parcelable` 时,就会产生漏洞,因为它允许以下场景:
1. 恶意应用向 `system_server` 发送一个 `Bundle` 或 `Parcelable`,其中包含有缺陷的 `Parcelable` 实例,以及专门构造的数据,这些数据将在步骤 3 中实际被读取,但在步骤 2 中会被原样传递
2. `system_server` 验证 `Bundle` 是安全的,然后转发它,或者 `system_server` 将提供的 `Parcelable` 传递给 AIDL 方法,该方法还在下一个参数中传递关键数据(如果该参数中接收的数据可以被修改,则会导致安全问题)
3. 另一个应用从 `system_server` 接收数据并信任它,然而由于有缺陷的序列化,它实际看到的数据与 `system_server` 打算发送的数据不同
我在上述步骤中使用了“或”,因为这些步骤既描述了一个[旧漏洞变体,该变体导致启动任意 Activity,我于 2017 年发布](https://github.com/michalbednarski/ReparcelBug)(在“或”的左侧),也描述了一个新变体,我将在下一节中描述
# `BroadcastReceiver` 在应用中如何执行
从使用 Android SDK 中可用 API 的应用开发者的角度来看,[`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) 的工作方式是,一个应用调用 [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent))(尽管应用通常希望接收来自系统的广播,而不是应用自身的广播),然后广播的 Intent 与 `AndroidManifest.xml` 中定义的 `<receiver>` 进行匹配,当匹配发生时,系统启动接收应用的过程,实例化 `<receiver android:name>` 属性中定义的 `BroadcastReceiver` 子类,然后调用 [`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent)) 方法
让我们看一下接收广播的过程中与 `system_server` 的通信:
* 当应用进程最初启动时,它[调用 `IActivityManager.attachApplication()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7340;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c),通过这样做,它传递了 [`IApplicationThread`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl) 句柄,系统使用该句柄来告诉应用进程该做什么
* 当系统想要在应用进程中执行清单注册的 `BroadcastReceiver` 时,它使用前一点中描述的 `IApplicationThread` 调用 [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c) 方法。此方法有多个参数,但这里最重要的前两个参数是:
1. `Intent intent`,它之前是在调用 `sendBroadcast()` 时传递给系统的
2. `ActivityInfo info`,它包含有关必须执行的组件的信息。此参数的值由系统从 Package Manager Service 获取。最重要的是,此参数中传递的数据包括将从中加载处理接收到的广播的 Java 类的文件路径
此时你可能已经猜到这条新漏洞路径是什么了:调用 `sendBroadcast()` 并传递一个 `Intent`,该 `Intent` 将导致当系统尝试调用 `scheduleReceiver` 时,调用 `scheduleReceiver` 的应用将看到被篡改的 `ActivityInfo`
应该注意的是,这条新漏洞路径在 Android 12 中才变得可行,因为以前没有办法将任意 `Parcelable` 放入 `Intent`([Intent extras](https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)) 不算,因为它们被放入 `Bundle` 中,`Bundle` 的整个长度被写入 Parcel 并[作为单个 blob 读取](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1671-1681;drc=5d123b67756dffcfdebdb936ab2de2b29c799321),因此 extras 不能导致包含它们的 `Intent` 对象被误解)
# 触发 `writeToParcel`/`createFromParcel` 不匹配
大多数情况下,`writeToParcel`/`createFromParcel` 不匹配是指在这些方法之一中某个字段被遗忘或写入两次的情况,在这种情况下,发送此类对象将始终触发不匹配。(大多数情况下,当对象虽然是 `Parcelable`,但实际上并未跨进程使用时会发生这种情况,否则在正常使用过程中会很快被发现)
然而这次情况并非如此,触发不匹配并不明显
让我们看一下易受攻击的类([原始代码在这里](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/camera2/params/OutputConfiguration.java;drc=46c390a1c695e2dc458cb889e40559f259f60aed),标记为 `// New in Android 12` 的行是手动添加的,因为它们在撰写本文时尚未出现在 AOSP 中)([这里是最初引入漏洞的提交](https://android.googlesource.com/platform/frameworks/base/+/a69b1bc58b0838e06deefb190e226774a34671e6%5E%21/#F10),但它是在 Android 12 发布后发布的)```java
package android.hardware.camera2.params;