
# CVE-2021-0928を利用したAndroid 12 Betaのインストール済みアプリからシステム権限への権限昇格に関するWriteupとエクスプロイト `OutputConfiguration`における`writeToParcel`/`createFromParcel`のシリアライゼーション不一致を悪用した、Android 12 Betaのインストール済みアプリからシステム権限への権限昇格です。
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");
Then `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クラスはエクスプロイトでガジェットチェーンの一部として使用されていますが、それ自体に問題はありません)```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` メソッドを使用する場合、作成されるオブジェクトの名前が同じ `Parcel` から読み取られるため、クラスパス内で利用可能な任意の `Parcelable` を読み取ることができることに注意することが重要です
* `readParcelable` は、他の多くの `Parcel` メソッドによって使用されます。例えば、上記の例で見られる `readList` は `readValue` を介して要素を読み取ります。`readValue` は `Parcel` 内でオブジェクトを転送する最も汎用的なメソッドであり、その使用方法の 1 つが `readParcelable` を介するものです。また、上記の例では、Java の型消去 (Type Erasure) により、`ArrayList<HierarchyOp> mHierarchyOps` フィールドには、ジェネリック型宣言で指定された型と互換性のあるオブジェクトだけでなく、Parcel がサポートする任意のオブジェクトを実際に含めることができます
# `writeToParcel`/`createFromParcel` の不一致
上記のとおり、`Parcelable` インターフェースの実装は、`createFromParcel` が、対応する `writeToParcel` が以前に書き込んだのと同じ量のデータを `Parcel` から読み取ることを保証する責任があります。`BOOTCLASSPATH` 内にこの契約に違反する可能性のある `Parcelable` が存在する場合、それは脆弱性を生み出します。なぜなら、以下のシナリオが可能になるからです:
1. 悪意のあるアプリが、欠陥のある `Parcelable` インスタンスと、ステップ 3 で実際に読み取られるがステップ 2 ではそのまま渡される特別に構築されたデータを含む `Bundle` または `Parcelable` を `system_server` に送信します
2. `system_server` は `Bundle` が安全であることを検証してからそれを転送するか、または `system_server` は提供された `Parcelable` を、次のパラメータで重要なデータも渡される AIDL メソッドに渡します (そのパラメータで受信したデータが変更可能な場合、セキュリティ問題が発生する可能性があります)
3. 別のアプリが `system_server` からデータを受信し、それを信頼します。しかし、欠陥のあるシリアライゼーションにより、実際に表示されるデータは `system_server` が送信しようとしたデータとは異なります
上記のステップで「または」を使用しました。これらのステップは、[2017 年に公開した任意の Activity を起動する古いエクスプロイトの亜種](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) メソッドを呼び出します。このメソッドには複数の引数がありますが、ここで最も重要なのは最初の 2 つです:
1. `Intent intent`。これは、`sendBroadcast()` が呼び出されたときに以前にシステムに渡されたものです
2. `ActivityInfo info`。実行する必要があるコンポーネントに関する情報が含まれています。このパラメータの値は、システムが Package Manager Service から取得します。最も重要なのは、このパラメータで渡されるデータに、受信したブロードキャストを処理する Java クラスがロードされるファイルへのパスが含まれていることです
この時点で、この新しいエクスプロイト経路が何であるかを推測できるでしょう: `sendBroadcast()` を呼び出して、システムが `scheduleReceiver` を呼び出そうとしたときに、`scheduleReceiver` が呼び出されるアプリケーションが改ざんされた `ActivityInfo` を認識するような `Intent` を渡します。