
CVE-2022-20452のエクスプロイト、recycle()後のParcelを使用したLazyValue経由で、Android上でインストールされたアプリからシステムアプリ(または別のアプリ)への特権昇格
Android 13 では、Parcel のシリアライゼーションメカニズムを強化するために多くの機能強化が導入されています。
こちらが Android セキュリティ&プライバシーチームによる機能強化のプレゼンテーションです
これは素晴らしく、多くの脆弱性を排除するか、悪用不可能にしています。また、以前のエクスプロイト(アプリが他のアプリ(システムアプリを含む)にコードをロードできるようにするもの)を破ったことについても説明しています。
しかし今、私は同じことを別の方法で実現する新しいエクスプロイトを持ち帰ってきました。これは、前述の Parcel の強化中に導入された以下の脆弱性に依存しています。
![アプリケーションのテキストを表示するスクリーンショット。タイトル: LeakValue。メインテキスト: Created 6 ValueLeaker-s. Locking ActivityTaskManagerService. Locked ActivityTaskManagerService. Unlocking ActivityTaskManagerService. Unlocked ActivityTaskManagerService. leakedBinders=[android.os.BinderProxy@f06702e]. Leaked interface: android.app.IApplicationThread. コード実行を要求中。シェルコードが実行されました uid=1000 pid=6904 packageName=com.android.settings 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. 画面下部には2つのボタンがあります: START と MANUAL TESTING](Screenshot_20220723-081920.png)
(アプリ実行時の logcat も参照。エクスプロイトはログにノイズが多い)
Parcel と Parcelable の不一致バグの紹介Android の Parcel クラスはプロセス間通信の基盤です。
オブジェクトは Parcelable インターフェースを実装することで、Parcel に書き込めるようになります。例 (AOSP からコピー):```java
public class UsbAccessory implements Parcelable {
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
public UsbAccessory createFromParcel(Parcel in) {
String manufacturer = in.readString();
String model = in.readString();
String description = in.readString();
String version = in.readString();
String uri = in.readString();
IUsbSerialReader serialNumberReader = IUsbSerialReader.Stub.asInterface(
in.readStrongBinder());
return new UsbAccessory(manufacturer, model, description, version, uri,
serialNumberReader);
}
};
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mManufacturer);
parcel.writeString(mModel);
parcel.writeString(mDescription);
parcel.writeString(mVersion);
parcel.writeString(mUri);
parcel.writeStrongBinder(mSerialNumberReader.asBinder());
} }
`Parcel`は内部的に書き込みまたは読み取りが行われる位置を保存しており、`readString()`はデータをStringに解析すると同時に位置を進めることに注意してください。この位置は[`dataPosition()`](https://developer.android.com/reference/android/os/Parcel#dataPosition())/[`setDataPosition()`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int))で手動で取得/設定できます。`Parcelable`インターフェースの実装は、`writeToParcel`と`createFromParcel`が同じ量のデータを書き込み/読み取りすることを保証しなければなりません。そうでない場合、後続のすべての読み取りは間違ったオフセットからデータを取得することになります。
[`Bundle`](https://developer.android.com/reference/android/os/Bundle) (プロセス間で送信できるキー・バリューマップ) には、[`writeValue()`でParcelに書き込めるさまざまなオブジェクト](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/Parcel.java;l=1792-1937)を含めることができます。`Bundle`の内容が`Parcel`から読み取られる際、システムで利用可能な任意の`Parcelable`クラスが読み取られる可能性があります。
`Bundle`は、parcel化されたデータ全体の長さを`Parcel`に書き込み、その後元のParcelの該当部分を`mParcelledData`に保存された二次Parcelに[コピーする](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/BaseBundle.java;l=1675-1683)ことで、実際の内容の解析を延期します (これにより、例えば[`Activity.onSaveInstanceState()`](https://developer.android.com/reference/android/app/Activity#onSaveInstanceState(android.os.Bundle))は`system_server`では利用できない`Parcelable`を提供でき、`Bundle`全体は内容を解析せずにそのまま`system_server`に渡されて返されます)。
しかし、`Bundle`内の値に一度アクセスされると、`Bundle`内のすべての値が[アンパーセル化](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/BaseBundle.java;l=227-313)され、[存在するすべてのキーと値のペアが解析されます](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/Parcel.java;l=3613-3632)。そのようなマップに`writeToParcel`と`createFromParcel`のメソッドが不均衡な`Parcelable`が含まれており、その後そのような`Bundle`が別のプロセスに転送された場合、別のプロセスは`Bundle`の異なる内容を認識する可能性があります。これにより、システム内のクラスの不均衡がすべて[脆弱性](https://github.com/michalbednarski/ReparcelBug)となり、システム内に[Bundleを安全であると検査してから別のプロセスに転送する場所](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=5037-5046)が存在するためです。
このwriteupでは、そのような、転送後に別の内容を提示するBundleを自己変更Bundleと呼びます。
もう一つ重要な点として、バイト列 (文字列、数値、それらから構成されるオブジェクト) だけでなく、`Parcel`はファイルディスクリプタや`Binder`も含めることができます。`Binder`はRPC呼び出しが可能なオブジェクトであり、あるプロセスが`Binder`オブジェクトを作成し、[`onTransact()`メソッド](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))をオーバーライドします。その後、`Binder`は別のプロセスに渡されます。上のコード例では、`read`/`writeStrongBinder()`呼び出しを使用してParcelに読み書きしていることがわかります。別のプロセスでは、`readStrongBinder()`を使用すると`BinderProxy`オブジェクトが作成されます ([`IBinder`インターフェース](https://developer.android.com/reference/android/os/IBinder)の背後に隠されています)。その後、その別のプロセスはそのオブジェクトに対して[`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))を呼び出すことができ、元のオブジェクトでは`onTransact()`が実行されます。通常、`transact()`/`onTransact()`を手動で記述するのではなく、[代わりにAIDLを使用します](https://developer.android.com/guide/components/aidl)。
# `LazyValue`の登場:自己変更`Bundle`の終焉
これまで多くのクラスで`writeToParcel`/`createFromParcel`の不一致が発生していたため、Android 13では、自己変更Bundleの構築を可能にするようなクラスがシステム内のどこかに存在する問題を解決するために、[`LazyValue`を導入しました](https://android.googlesource.com/platform/frameworks/base/+/9ca6a5e21a1987fd3800a899c1384b22d23b6dee%5E%21/)。
現在、`writeValue`が使用される際、書き込まれる値がプリミティブでない場合、[値の長さも`Parcel`に書き込まれます](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=2331-2344;drc=03c34f57c05feecfb090de3917787f049cb5f804)。
通常のアプリが直接`Parcel.readValue()`を使用する場合、[`Parcel`から読み取った`length`が実際に読み取ったデータのサイズと一致しない場合に警告が出力される以外は、以前と同じように動作します](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4330-4348;drc=03c34f57c05feecfb090de3917787f049cb5f804) (ただし、[`Slog.wtfStack`は決して例外をスローしません](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/util/Slog.java;l=108-116;drc=23c7543b8e608ebcbb38b952761b54bb56065577))。
しかし、`Bundle`は代わりに[`Parcel.readLazyValue()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4350-4420;drc=03c34f57c05feecfb090de3917787f049cb5f804)を使用するようになりました。
詳しく見てみましょう。`LazyValue`クラスには、[`Parcel`内の`LazyValue`データの構造を説明する素敵なコメント](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)があります。```
| 4B | 4B |
mSource = Parcel{... | type | length | object | ...}
a b c d
length = d - c
mPosition = a
mLength = d - a
mSource は readLazyValue() が呼ばれた元の Parcel への参照です。