Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
ReparcelBug2 — Android 12 Beta 上已安装应用通过 CVE-2021-0928 实现系统权限提升的 Writeup 与漏洞利用,该漏洞源于 `OutputConfiguration` 中 `writeToParcel`/`createFromParcel` 的序列化不匹配。 | Kitploit
工具/GitHubGitHub/michalbednarski/reparcelbug2
Android安全权限提升漏洞分析漏洞利用移动安全二进制分析
GitHubmichalbednarski/reparcelbug2

ReparcelBug2

Android 12 Beta 上已安装应用通过 CVE-2021-0928 实现系统权限提升的 Writeup 与漏洞利用,该漏洞源于 `OutputConfiguration` 中 `writeToParcel`/`createFromParcel` 的序列化不匹配。

查看仓库
123234年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

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 通知的截图:Hello from uid=1000(system) gid=1000(system) groups=1000(system),1007(log),1065(reserved_disk),1077(external_storage),3001(net_bt_admin),3002(net_bt),3003(inet),3007(net_bw_acct),9997(everybody) context=u:r:system_app:s0

Parcel 简介

Android 上的大多数 IPC 都是通过名为 Parcel 的类完成的

Parcel 的基本用法如下:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");

root@kitploit:~
然后,`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<>();

root@kitploit:~
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);
            }
        };

}

root@kitploit:~
如上所示,`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;

public final class OutputConfiguration implements Parcelable {
    private OutputConfiguration(@NonNull Parcel source) {
        int rotation = source.readInt();
        int surfaceSetId = source.readInt();
        int surfaceType = source.readInt();
        int width = source.readInt();
        int height = source.readInt();
        boolean isDeferred = source.readInt() == 1;
        boolean isShared = source.readInt() == 1;
        ArrayList<Surface> surfaces = new ArrayList<Surface>();
        source.readTypedList(surfaces, Surface.CREATOR);
        String physicalCameraId = source.readString();
        boolean isMultiResolution = source.readInt() == 1; // New in Android 12
        ArrayList<Integer> sensorPixelModesUsed = new ArrayList<Integer>(); // New in Android 12
        source.readList(sensorPixelModesUsed, Integer.class.getClassLoader()); // New in Android 12

		// SNIP: copy values from variables set above to fields of this class
    }

    public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
            new Parcelable.Creator<OutputConfiguration>() {
        @Override
        public OutputConfiguration createFromParcel(Parcel source) {
            try {
                OutputConfiguration outputConfiguration = new OutputConfiguration(source);
                return outputConfiguration;
            } catch (Exception e) {
                Log.e(TAG, "Exception creating OutputConfiguration from parcel", e);
                return null;
            }
        }

        @Override
        public OutputConfiguration[] newArray(int size) {
            return new OutputConfiguration[size];
        }
    };

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (dest == null) {
            throw new IllegalArgumentException("dest must not be null");
        }
        dest.writeInt(mRotation);
        dest.writeInt(mSurfaceGroupId);
        dest.writeInt(mSurfaceType);
        dest.writeInt(mConfiguredSize.getWidth());
        dest.writeInt(mConfiguredSize.getHeight());
        dest.writeInt(mIsDeferredConfig ? 1 : 0);
        dest.writeInt(mIsShared ? 1 : 0);
        dest.writeTypedList(mSurfaces);
        dest.writeString(mPhysicalCameraId);
        dest.writeInt(mIsMultiResolution ? 1 : 0); // New in Android 12
        dest.writeList(mSensorPixelModesUsed); // New in Android 12
    }

    private ArrayList<Surface> mSurfaces;
    private final int mRotation;
    private final int mSurfaceGroupId;
    private final int mSurfaceType;
    private final Size mConfiguredSize;
    private final int mConfiguredFormat;
    private final int mConfiguredDataspace;
    private final int mConfiguredGenerationId;
    private final boolean mIsDeferredConfig;
    private boolean mIsShared;
    private String mPhysicalCameraId;
    private boolean mIsMultiResolution; // New in Android 12
    private ArrayList<Integer> mSensorPixelModesUsed; // New in Android 12
}

那么这里到底出了什么问题,为什么这个改动会引入漏洞?正如我在讨论 WindowContainerTransaction 这个 Parcelable 示例时所说,readList 实际上可以用 Parcel 支持的任何对象填充列表,而不仅仅是符合泛型声明(ArrayList<Integer>)的对象。然而,由于我们只是将这个类用作序列化 gadget 链的一部分,并不会真正使用它,该字段除了读写 Parcel 之外不会有其他用途(而且尝试使用 ArrayList 中与泛型声明不匹配的元素也只会导致 ClassCastException),所以这本身并不是问题。

在这个类中,createFromParcel 里还有一个 try-catch,这意味着如果在读取过程中抛出 Exception,对 OutputConfiguration 的读取将会停止,而包含 OutputConfiguration 的对象的读取将继续进行。发生这种情况时,整个 OutputConfiguration 会被写入 Parcel,但只会被读取到 Exception 发生的位置。这会造成不匹配,因为 OutputConfiguration.writeToParcel 中写入的未消费数据实际上会被调用 OutputConfiguration.CREATOR.createFromParcel 的对象读取。

现在,这两点的结合(允许嵌套 Parcel 支持的任何对象,并将其包裹在未重新抛出的 try-catch 中)使我们能够构造一个 Parcelable,它可以被 system_server 写入,随后以由最初构造该 Parcelable 的应用控制的方式被读取,而该 Parcelable 由 system_server 转发。

好的,为了构造这样的 Parcelable,我们现在需要找到一些可以放入 mSensorPixelModesUsed 的东西,它能在 system_server 中被成功读取(因为这个对象是通过 Parcel 从攻击者应用接收的),能被 system_server 成功写入,然后在受害应用中反序列化失败并抛出 Exception。

其中一种方法是使用存在于 system_server 中但不存在于应用中的类,这样尝试反序列化它就会导致 ClassNotFoundException。然而,我不能从 system_server 中选择一个 Parcelable,因为 readParcelable 在没有显式指定 ClassLoader 的情况下只会搜索 BOOTCLASSPATH,而其中不包含 system_server 特有的类。解决这个问题的方法是使用一个 Serializable 类,因为 ObjectInputStream 会从堆栈跟踪中选择第一个非 BOOTCLASSPATH 方法中的 ClassLoader。

我选择了 PackageManagerException,但在使用它之前还有一件事需要做。在 OutputConfiguration 构造函数中调用 readList 时,loader 参数被显式设置为 Integer.class.getClassLoader()。这个 loader 值会被传播到 readValue(),然后到 readSerializable(),并且在 readSerializable() 中,如果 loader 参数不为 null,则会使用它而不是 ObjectInputStream 中的 (那个 检查不起作用,因为当 找不到类时会抛出异常而不是返回 null)。绕过的方法其实很简单,我们只需要将 包装在某个执行 但不指定 的 中。这就是上面描述的 类的用武之地。

所以,此时我们有以下对象:

  • OutputConfiguration
    • mSensorPixelModesUsed.get(0) = WindowContainerTransaction
      • mHierarchyOps.get(0) = PackageManagerException

现在这样的对象可以在 system_server 中成功反序列化:当 WindowContainerTransaction 调用 readList 时,它会尝试使用系统服务器的 ClassLoader(而不是 BootClassLoader)查找 PackageManagerException 类,因为它可以在堆栈跟踪中找到它。这个类加载器恰好出现在堆栈跟踪中,因为虽然以下所有方法都不来自系统服务器类路径:Binder#execTransact()、由 AIDL 生成的 IActivityManager$Stub#onTransact() 以及所有使用的 Parcelable 类的方法,但堆栈跟踪中有一个在系统服务器内声明的方法:ActivityManagerService 中重写的 onTransact。因此,system_server 可以读取并随后将这样的对象写入 Parcel,当目标应用尝试读取它时, 类将不可用,因此会抛出 ,,然后。

所以我们触发了这个不匹配。嗯,此时其实还没有,因为当 OutputConfiguration.writeToParcel 没有留下未读数据时异常就被捕获了,但我们可以轻松地向 mSensorPixelModesUsed List 添加另一个项目,该项目将通过 Parcel.writeValue 写入,并在读取 OutputConfiguration 后保持未读状态。

将其放入 Intent

如上所述,我想从 Intent 对象触发不匹配,因为它将由 system_server 传递给一个 AIDL 方法,该方法第一个参数是 Intent,第二个参数是执行信息,这样第一个参数中传递的 Intent 的序列化/反序列化会导致第二个参数的值被修改。

在 Intent.readFromParcel() 中,所有值都是通过专用的类型化方法读取的,因此我们无法在那里指定自定义的 Parcelable 类。

然而,在 Intent 内部,有一个嵌套的 ClipData,并且从 Android 12 开始,在 ClipData$Item 中有一个新字段 ActivityInfo mActivityInfo(在最初撰写时 AOSP 中不存在,这里是引入该字段的提交,该字段在 ClipData(Parcel in) 构造函数 中通过 in.readTypedObject(ActivityInfo.CREATOR) 读取)。

然后在 ActivityInfo(Parcel source) 构造函数 中,同样没有办法放入自定义的 Parcelable,但由于 ActivityInfo 继承自 ComponentInfo,它有一个 applicationInfo 字段。

最后,在 ApplicationInfo 中有一个 SparseArray<int[]> splitDependencies 字段,它通过 readSparseArray 读取,而后者又使用 readValue 读取 SparseArray 项目。

此时我们可以将 OutputConfiguration 放入 splitDependencies,但是读取 splitDependencies 之后会跟着几次 readString8() 调用,如果能在不匹配发生后完全控制未消费的数据,以便直接放置空字符串而不用担心对未消费数据的不同解释,那就更好了。

为此,首先我们需要在 OutputConfiguration.mSensorPixelModesUsed 中放入一些原始数据容器,它将通过 writeValue 写入。我选择了 Bundle。这样在未消费的数据中我们会留下:

  1. writeValue 的 VAL_BUNDLE 标签
  2. 原始数据的长度(此链接也适用于此列表中的其余项目)
  3. BUNDLE_MAGIC
  4. 通过 Parcel.appendFrom 原样传递的原始数据

所以我们会有三个未消费的 Parcel.writeInt 项目,我们可以通过将 OutputConfiguration 包装在某个 Parcelable 中来消除它们,该 Parcelable 在读取时先读取任意 Parcelable 值,然后读取三个整数。我在 ZenPolicy CREATOR 中找到了这一点。

总结一下,我们有以下对象层次结构(它存在于 system_server 中,并且它尝试将其传递给 scheduleReceiver):

  • Intent
    • mClipData = ClipData
      • mItems.get(0).mActivityInfo = ActivityInfo
        • applicationInfo = ApplicationInfo
          • splitDependencies.get(0) = ZenPolicy
            • mVisualEffects.get(0) = OutputConfiguration
              • mSensorPixelModesUsed.get(0) = WindowContainerTransaction
                • mHierarchyOps.get(0) = PackageManagerException
              • mSensorPixelModesUsed.get(1) = Bundle

这由 system_server 写入。然后接收应用正常读取所有内容(包括 PackageManagerException 的 readSerializable 数据),但是在读取完 PackageManagerException 的 Serializable 数据后抛出异常,OutputConfiguration 以下所有内容的读取被取消,Bundle 保持未读状态。读取继续进行到 ZenPolicy,它消费了 Bundle 中原始数据之前的三个整数。然后 ApplicationInfo 的读取继续进行,读取之前是 Bundle 中原样传递的原始数据。该原始数据的读取将继续处理此堆栈中的其余对象(ApplicationInfo、ActivityInfo、ClipData 和 Intent),然后该原始数据将用于读取下一个 方法参数。

随后在 handleReceiver 中发生什么

正如我刚才所说,现在剩余的 scheduleReceiver 参数是从攻击者控制的缓冲区中读取的。

让我们看看该方法被调用后会发生什么。

首先,scheduleReceiver 将所有参数的值打包,并使用 sendMessage() 将执行传递给主线程。

接下来,在主线程上调用 handleReceiver。

handleReceiver 调用 getPackageInfoNoCheck,将 ApplicationInfo 传递给它,该 ApplicationInfo 是作为 ActivityInfo 的一部分接收的,而 ActivityInfo 是作为 scheduleReceiver 参数传递的。

getPackageInfo 检查具有给定名称的包是否已经存在于缓存中,如果不存在,则构造一个新的 LoadedApk 实例,并将之前接收的 ApplicationInfo 对象传递给它(由于攻击者想要构造一个新的 LoadedApk,因此使用了此进程中之前未见过的包的 packageName)。

然后使用 ContextImpl.getClassLoader() 方法,它首次运行时委托给 mPackageInfo.getClassLoader(),其中 mPackageInfo 是上一段中构造的 LoadedApk。

然后是 createOrUpdateClassLoaderLocked,它调用 makePaths 来填充 zipPaths,其中包含要用于 ClassLoader 的路径,然后将它们连接并赋值给 zip 变量,并传递给 createClassLoader。

makePaths 使用 ApplicationInfo 中的信息填充 zipPaths,最重要的是这包括 sourceDir。攻击者应用使注入的 ApplicationInfo 的 sourceDir 设置为指向自己 apk 的路径,因此接收器类实际上将从攻击者的 apk 加载。这直接导致在接收广播的应用中执行攻击者控制的代码。

关于隐藏 API 检查的说明

还有一件事需要绕过:隐藏 API 检查。这些从来不是安全边界(因为应用总是可以使用 NDK 并直接调用底层 syscall),但在这种情况下,它们是通过手动将数据写入 Parcel 然后使用 readParcelable 构造精心制作的 ClipData 来绕过的。这样的 ClipData 可以正常附加到 Intent,然后传递给 sendBroadcast(),因此发送广播本身只使用了公共 API。

修复

上述报告最初发送给了 Google,看起来他们已经利用了它,因为由此产生了多个修复(我认为如此,但我没有直接因果关系的证据)。

随 Android 12 发布:

  • 从 OutputConfiguration 及相关类中移除了异常吞噬
  • OutputConfiguration#mSensorPixelModesUsed 不再通过 writeValue 写入
  • ClipData#mActivityInfo 不再写入 Parcel,除非在写入时显式请求(因此 Intent 不再能包含来自 BOOTCLASSPATH 的任意 Parcelable,消除了这种利用技术)

撰写本文时仅存在于 master 分支,未在发布版本中,可能会出现在 Android 13 中(而非 12L):

  • Parcel 上有新的 List 读取方法,用于检查项目类型,并且无类型版本已被标记为已弃用
  • 有一个新方法 Parcel#enforceNoDataAvail(),用于检查 Parcel 中是否还有未读数据,显然 AIDL 在读取 RPC 调用参数后会使用它。通常我的漏洞利用依赖于这样一个事实:从 Parcel 读取所有数据后,其余内容会被忽略,这不再适用,尽管我认为在许多情况下可以构造数据使其在结束时定位到最终位置,所以这不是一个强有力的缓解措施。无论如何,有时这类问题会自然发生而未被检测到,因此这可以捕获这些情况。关于此问题的进一步讨论在 issue #3 中
  • Bundle 中的每个项目都会单独保存其长度。 这几乎消灭了我自 2014 年以来私下向 Google 报告的一整类漏洞,2017 年发布了描述,大约一年后发布了代码本身。如果我数得没错,这将是该类漏洞 8 年的生命周期(老实说我不知道这是否算长,尽管可能仍然存在非 Bundle 的利用变体,比如这个(尽管这个已经被修复了))。
下载工具
resolveClass
c != null
Class.forName
PackageManagerException
readList
ClassLoader
Parcelable
WindowContainerTransaction
PackageManagerException
ClassNotFoundException
被包装成 RuntimeException
被 OutputConfiguration 的 CREATOR 捕获
handleReceiver