
Разбор и эксплойт для CVE-2024-34740: целочисленное переполнение в BinaryXmlSerializer в Android, ведущее к записи файла в system_server, а затем к выполнению кода в system_server из обычного установленного приложения

Исправления для проблемы, описанной здесь, появились под CVE-2024-34740 / A-307288067:
Внутри Android system_server многие сервисы хранят своё состояние между перезагрузками в XML-файлах```
$ adb shell su 0 find /data/system -name '*.xml' | sort
/data/system/appops_accesses.xml
/data/system/cachequota.xml
/data/system/device_policies.xml
/data/system/device_policy_state.xml
/data/system/display-manager-state.xml
/data/system/input-manager-state.xml
/data/system/inputmethod/subtypes.xml
/data/system/install_sessions.xml
/data/system/job/jobs_1000.xml
/data/system/job/jobs_10131.xml
/data/system/log-files.xml
/data/system/netpolicy.xml
/data/system/notification_policy.xml
/data/system/overlays.xml
/data/system/packages.xml
/data/system/package-watchdog.xml
/data/system/sensor_privacy_impl.xml
/data/system/sensor_privacy.xml
/data/system/shortcut_service.xml
/data/system/users/0/app_idle_stats.xml
/data/system/users/0/appwidgets.xml
/data/system/users/0/package-restrictions.xml
/data/system/users/0/settings_global.xml
/data/system/users/0/settings_secure.xml
/data/system/users/0/settings_system.xml
/data/system/users/0/wallpaper_info.xml
/data/system/users/0.xml
/data/system/users/userlist.xml
/data/system/watchlist_settings.xml
Исторически это были обычные текстовые XML-файлы с отступами, что позволяло разработчикам легко их читать, однако [в Android 12 была представлена новая двоичная версия этого формата, со ссылкой на то, что 1,5% всего времени, проводимого `system_server`, тратилось на эти XML-операции](https://android.googlesource.com/platform/frameworks/base/+/4ccea8796991d678ead4399130ec31edf63ff4fa%5E%21/)
Следует отметить, что этот формат используется системой только внутренне, и файлы имеют магическое значение `"ABX\x00"`. Он отличается от [формата, используемого внутри APK](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=1770;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) для `AndroidManifest.xml`, `res/xml/*.xml`, `res/layout/*.xml` и т.д., в котором нет явного «магического значения», однако он обычно начинается с `0300 0800` (это [заголовок](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h;l=608;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) с `type=RES_XML_TYPE` и `headerSize=8`)
Когда система читает один из этих внутренних XML-файлов состояния, она [использует магическое значение `"ABX\0"` в файле, чтобы выбрать либо парсер для двоичного XML-файла, либо обычный XML-парсер](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;l=188-192;drc=97a370a95275e79c69e79d7ead11aa38934a5575). Сохранение этих файлов в виде двоичного XML [управляется системным свойством](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;drc=97a370a95275e79c69e79d7ead11aa38934a5575;l=74?q=Xml.java) и включено по умолчанию
Когда используются двоичные XML-файлы, их содержимое можно прочитать, например, с помощью `adb shell su 0 abx2xml /data/system/packages.xml -`
Одна из функций этого двоичного формата — предоставление типизированных аксессоров, поэтому сериализатор предлагает метод `attributeInt(String namespace, String name, int value)`, который записывает значение как двоичное целое число, избегая преобразования через String, которое потребовало бы нового выделения памяти и последующего объекта для сборщика мусора
Другой тип, который может быть сериализован напрямую, — это массив байтов```java
@Override
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
mOut.writeInternedUTF(name);
mOut.writeShort(value.length);
mOut.write(value);
return this;
}
Существует также аналогичный метод attributeBytesHex, который отличается лишь записываемым тегом TYPE_*. Этот тег используется инструментом abx2xml для преобразования массива байт в соответствующее строковое представление.
mOut — это экземпляр FastDataOutput, который предоставляет функции Java-класса DataOutputStream. writeByte/writeShort/writeInt/writeUTF/write используют тот же формат, что и стандартный DataOutputStream.
Аналогично Parcel, если при записи/чтении возникает несоответствие, последующие читаемые данные будут взяты с неправильных смещений, однако, в отличие от Parcel, ошибки при использовании BinaryXmlSerializer/BinaryXmlPullParser не дают атакующему возможности произвольно изменять читаемые данные (в этом случае атакующий не может ввести новые имена тегов/атрибутов или значения).
Ошибки же внутри самого класса BinaryXmlSerializer или в FastDataOutput такую возможность дают.
В приведённом выше методе, если мы попытаемся записать массив байт длиной 65536, мы запишем длину с помощью writeShort(), что фактически запишет 0, после чего будет записано реальное содержимое массива.
Чтобы использовать это несоответствие, нам нужно выбрать файл, в который мы сможем внедрить произвольный массив байт через attributeBytesBase64 или attributeBytesHex, а также изменение этого файла должно быть ценно для атакующего.
Класс PackageInstaller предоставляет возможность подготовить пакет к установке. Без необходимости каких-либо разрешений любое приложение может записать новый APK для установки во временный каталог. Когда всё необходимое для установки записано, устанавливающее приложение может commit() PackageInstaller.Session, что означает, что оно больше не сможет вносить изменения в файлы установки, и Session готов либо к одобрению пользователем, либо к фактической установке.
Состояние этих операций хранится в /data/system/install_sessions.xml. Устанавливающее приложение может, например, загрузить половину большого APK во временный каталог, созданный Package Manager Service для своего PackageInstaller.Session, затем после перезагрузки возобновить загрузку, записать оставшуюся половину и зафиксировать установку.
Одна из возможностей — записать данные в install_sessions.xml, чтобы пометить сессию как staged, то есть она будет установлена после следующей перезагрузки.