
Эксплойт для CVE-2022-20452, повышение привилегий на Android от установленного приложения до системного приложения (или другого приложения) через LazyValue с использованием Parcel после recycle()
Android 13 вносит множество улучшений, направленных на усиление защиты механизма сериализации Parcel
Вот презентация команды Android Security and Privacy о внесённых улучшениях
Это отлично, определённо устраняет или делает неэксплуатируемыми многие уязвимости. Также они описывают, как сломали мой предыдущий эксплойт, позволяющий приложениям загружать свой код в другие приложения (включая системные)
Но теперь я вернулся с новым эксплойтом, который достигает того же, хотя и другим способом. Он опирается на следующие уязвимости, которые были внесены в ходе вышеупомянутого усиления защиты Parcel:
![Скриншот приложения с текстом. Заголовок: LeakValue. Основной текст: Создано 6 ValueLeaker-s. Блокирую ActivityTaskManagerService. ActivityTaskManagerService заблокирован. Разблокирую ActivityTaskManagerService. ActivityTaskManagerService разблокирован. leakedBinders=[android.os.BinderProxy@f06702e]. Утёкший интерфейс: 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. Внизу экрана две кнопки: START и MANUAL TESTING](Screenshot_20220723-081920.png)
(Также logcat от выполнения приложения: эксплуатация сильно шумит в логах)
Parcel и ParcelableКласс Parcel в Android является основой взаимодействия между процессами
Объекты могут реализовывать интерфейс 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) (карта «ключ-значение», которую можно передавать между процессами) может содержать [разнообразные объекты, которые можно записать в `Parcel` через `writeValue()`](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`](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)) предоставлять `Parcelable`-объекты, недоступные в `system_server`; весь `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). Если такая карта содержала `Parcelable` с несбалансированными методами `writeToParcel` и `createFromParcel`, и позже такой `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), а затем пересылается в другой процесс.
В этом описании я называю такой `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`