
Writeup и эксплойт для повышения привилегий установленного приложения до системных на Android 12 Beta через CVE-2021-0928, несоответствие сериализации `writeToParcel`/`createFromParcel` в `OutputConfiguration`
CVE-2021-0928, несоответствие сериализации writeToParcel/createFromParcel в android.hardware.camera2.params.OutputConfiguration
Это эксплойт, использующий данную уязвимость для повышения привилегий из установленного Android-приложения в приложение «Настройки» Android (или любое другое установленное приложение, которому можно отправить сообщение в <receiver>, объявленный в AndroidManifest.xml; повышение привилегий путём отправки в <activity> также было возможно, хотя здесь не представлено)
Изначально я обнаружил проблему в Android 12 Developer Preview 3
Версия эксплойта, представленная в этом репозитории, работает на Android 12 Beta 2 и 3
Уязвимость была исправлена в первом официальном выпуске Android 12
Описание ниже изначально было написано для Google для рассмотрения этого отчёта как полной цепочки эксплойта
На момент написания Android 12 не был доступен в AOSP (релизы Android Developer Preview/Beta не являются открытым исходным кодом)

Большая часть IPC в Android осуществляется через класс 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 внутренне хранит позицию, с которой выполняются чтения. Ответственность пользователя класса Parcel заключается в том, чтобы методы read* соответствовали ранее использованным методам write*, иначе последующие чтения будут выполняться с неправильных позиций в буфере.
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);
}
};
}
As can be seen above, `writeToParcel()` method is used during writing. Then while reading `CREATOR.createFromParcel()` factory method is called. It is responsibility of `Parcelable` implementation to ensure that `createFromParcel` reads same amount of data as was written by `writeToParcel`, otherwise all subsequent reads from that `Parcel` will read data from wrong offset
Such class can be written to/read from `Parcel` through:
* Directly calling `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`, this is often used when type of class is known, for example when `Parcelable` has field with different `Parcelable` or in code generated by AIDL when defined RPC method has `Parcelable` as argument
* Through `Parcel.writeParcelable`/`readParcelable`. [`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) first writes name of class and then calls `writeToParcel` method from `Parcelable` interface. [`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) reads written class name, finds class with that name in provided `ClassLoader` or `BOOTCLASSPATH` if null was provided. Once class is found it's static field `CREATOR` is used to obtain [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) instance which is factory used to read that class. It is important to note that when `readParcelable` method is used it can read any `Parcelable` available in class path as name of object to be created is read from same `Parcel`
* `readParcelable` is used by many other `Parcel` methods, for example `readList` seen in example above reads elements through `readValue`, which is most generic method of transferring objects in `Parcel` and one of ways it uses is through `readParcelable`. Also in above example due to Java's Type Erasure `ArrayList<HierarchyOp> mHierarchyOps` field can actually contain any objects supported by Parcel, not only those compatible with type specified in generic type declaration
# Несоответствия `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` выполняется в приложении