
Analyse et exploit pour CVE-2024-34740, débordement d'entier dans BinaryXmlSerializer d'Android menant à une écriture de fichier dans system_server puis à une exécution de code dans system_server depuis une application installée normale

Les correctifs pour le problème décrit ici sont apparus sous CVE-2024-34740 / A-307288067 :
Dans system_server Android, de nombreux services enregistrent leur état d'un redémarrage à l'autre dans des fichiers 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
Historiquement, il s'agissait de fichiers XML en texte brut avec indentation, ce qui permettait aux développeurs de les lire facilement. Cependant, [dans Android 12, une nouvelle version binaire de ce format a été introduite, citant 1,5% de tout le temps passé par `system_server` sur ces opérations XML](https://android.googlesource.com/platform/frameworks/base/+/4ccea8796991d678ead4399130ec31edf63ff4fa%5E%21/)
Il convient de noter que ce format n'est utilisé qu'en interne par le système et possède des fichiers avec la valeur magique `"ABX\x00"`. Il est différent du [format utilisé dans les APK](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=1770;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) pour `AndroidManifest.xml`, `res/xml/*.xml`, `res/layout/*.xml`, etc. qui n'a pas de "valeur magique" explicite, mais commence généralement par `0300 0800` (qui est un [en-tête](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h;l=608;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) avec `type=RES_XML_TYPE` et `headerSize=8`)
Lorsque le système lit l'un de ces fichiers XML d'état interne, il [utilise la valeur magique `"ABX\0"` dans le fichier pour choisir soit l'analyseur pour le fichier XML binaire, soit l'analyseur XML régulier](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;l=188-192;drc=97a370a95275e79c69e79d7ead11aa38934a5575). Le fait que ces fichiers soient enregistrés sous forme de XML binaire est [contrôlé par une propriété système](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;drc=97a370a95275e79c69e79d7ead11aa38934a5575;l=74?q=Xml.java) et est activé par défaut
Lorsque les fichiers XML binaires sont utilisés, vous pouvez lire leur contenu par exemple via `adb shell su 0 abx2xml /data/system/packages.xml -`
L'une des choses que ce format binaire fait est d'offrir des accesseurs typés, donc le sérialiseur propose la méthode `attributeInt(String namespace, String name, int value)`, qui écrit la valeur sous forme d'entier binaire, évitant un aller-retour via String qui serait une nouvelle allocation et un objet ultérieur pour le ramasse-miettes (Garbage Collection)
Un autre type qui peut être directement sérialisé est le tableau d'octets (byte array)```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;
}
Il existe aussi une méthode similaire attributeBytesHex qui ne diffère que par le tag TYPE_* écrit. Ce tag est utilisé par l'outil abx2xml pour convertir un tableau d'octets en une représentation String appropriée.
mOut est une instance de FastDataOutput, qui fournit les fonctions du DataOutputStream de Java. writeByte/writeShort/writeInt/writeUTF/write utilisent le même format que le DataOutputStream standard.
De la même manière que Parcel, si quelque chose ne correspond pas pendant l'écriture/la lecture, les données lues par la suite seront prises à des décalages erronés. Cependant, contrairement à Parcel, les erreurs d'utilisation de BinaryXmlSerializer/BinaryXmlPullParser ne donnent pas à l'attaquant la capacité de falsifier arbitrairement les données lues (l'attaquant ne peut pas introduire de nouveaux noms/valeurs de tag ou d'attribut dans ce cas).
Les erreurs dans la classe BinaryXmlSerializer elle-même ou dans FastDataOutput le permettent en revanche.
Dans la méthode ci-dessus, si nous essayions d'écrire un tableau d'octets de longueur 65536, nous écririons la longueur avec writeShort(), ce qui écrirait effectivement 0, après quoi le contenu réel du tableau serait écrit.
Afin d'exploiter ce décalage, nous devons choisir un fichier dans lequel nous pourrons injecter un tableau d'octets arbitraire dans attributeBytesBase64 ou attributeBytesHex, et dont la modification sera précieuse pour l'attaquant.
La classe PackageInstaller offre la possibilité de préparer un package pour l'installation. Sans nécessiter de permissions, toute application peut écrire un nouvel APK à installer dans un répertoire temporaire. Une fois tout le nécessaire pour l'installation écrit, l'application installatrice peut commit() PackageInstaller.Session, ce qui signifie qu'elle ne pourra plus apporter de modifications aux fichiers d'installation et que la Session est prête soit pour l'approbation de l'utilisateur, soit pour l'installation réelle.
L'état de ces opérations est stocké dans /data/system/install_sessions.xml. L'application installatrice peut par exemple télécharger la moitié d'un gros APK dans le répertoire temporaire créé par le Package Manager Service pour sa PackageInstaller.Session, puis après un redémarrage reprendre le téléchargement, écrire la moitié restante et valider l'installation.
Une des possibilités est d'écrire des données dans install_sessions.xml pour marquer la session comme staged, ce qui signifie qu'elle sera installée après le prochain démarrage.