Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
AbxOverflow — Writeup y exploit para CVE-2024-34740, desbordamiento de entero en el BinaryXmlSerializer de Android para escritura de archivos en system_server y posterior ejecución de código en system_server desde una aplicación normal instalada. | Kitploit
Herramientas/GitHubGitHub/michalbednarski/abxoverflow
Seguridad AndroidEscalada de PrivilegiosAnálisis de VulnerabilidadesAnálisis de CódigoExplotaciónAprendizaje y EducaciónDesarrollo de PayloadsExplotación de Binarios
GitHubmichalbednarski/abxoverflow

AbxOverflow

Writeup y exploit para CVE-2024-34740, desbordamiento de entero en el BinaryXmlSerializer de Android para escritura de archivos en system_server y posterior ejecución de código en system_server desde una aplicación normal instalada.

6825hace 10 mesesRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio

Captura de pantalla de la aplicación Android con el título AbxDroppedApk y mucho texto que describe que se está ejecutando dentro de system_server

Las correcciones para el problema descrito aquí aparecieron bajo CVE-2024-34740 / A-307288067:

  • Boletín
  • Parche enlazado desde el boletín
  • Otros dos parches: 1 2

XML binario de Android

Dentro de system_server de Android, muchos servicios guardan su estado entre reinicios en archivos 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

root@kitploit:~
Históricamente, estos han sido archivos XML de texto plano con sangría, lo que permitía a los desarrolladores leerlos fácilmente; sin embargo, [en Android 12 se introdujo una nueva versión binaria de ese formato, citando que el 1,5 % de todo el tiempo empleado por `system_server` se gastaba en estas operaciones XML](https://android.googlesource.com/platform/frameworks/base/+/4ccea8796991d678ead4399130ec31edf63ff4fa%5E%21/)

Cabe señalar que este formato solo se utiliza internamente por el sistema y tiene archivos con el valor mágico `"ABX\x00"`. Es diferente del [formato utilizado dentro de los APK](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=1770;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) para `AndroidManifest.xml`, `res/xml/*.xml`, `res/layout/*.xml`, etc., que no tiene un "valor mágico" explícito, aunque normalmente comienza con `0300 0800` (que es el [encabezado](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h;l=608;drc=d4e49e63519397789d284a03aea5fafc119cb1b0) con `type=RES_XML_TYPE` y `headerSize=8`)

Cada vez que el sistema lee uno de estos archivos XML de estado interno, [utiliza el valor mágico `"ABX\0"` en el archivo para elegir entre el analizador de archivos XML binarios o el analizador XML normal](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;l=188-192;drc=97a370a95275e79c69e79d7ead11aa38934a5575). El que estos archivos se guarden como XML binario está [controlado por una propiedad del sistema](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;drc=97a370a95275e79c69e79d7ead11aa38934a5575;l=74?q=Xml.java) y está habilitado de forma predeterminada

Cuando los archivos XML binarios están en uso, puedes leer su contenido por ejemplo mediante `adb shell su 0 abx2xml /data/system/packages.xml -`

Una de las cosas que hace este formato binario es ofrecer métodos de acceso tipados, por lo que el serializador ofrece el método `attributeInt(String namespace, String name, int value)`, que escribe el valor como un entero binario, evitando el viaje de ida y vuelta a través de String que supondría una nueva asignación y un objeto posterior para el recolector de basura

Otro tipo que se puede serializar directamente es la matriz de bytes```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;
}

También existe un método similar attributeBytesHex que solo se diferencia por la etiqueta TYPE_* escrita. Esa etiqueta la usa la herramienta abx2xml para convertir el array de bytes a la representación String adecuada.

mOut es una instancia de FastDataOutput, que proporciona las funciones del DataOutputStream de Java. writeByte/writeShort/writeInt/writeUTF/write usan el mismo formato que el DataOutputStream estándar.

De forma similar a Parcel, si algo no coincide durante la escritura/lectura, los datos leídos posteriormente se tomarán de offsets incorrectos; sin embargo, a diferencia de Parcel, los errores en el uso de BinaryXmlSerializer/BinaryXmlPullParser no dan al atacante la capacidad de manipular arbitrariamente los datos leídos (el atacante no puede introducir nuevos nombres/valores de etiquetas o atributos en ese caso).

Sin embargo, los errores dentro de la propia clase BinaryXmlSerializer o en FastDataOutput sí lo permiten.

En el método anterior, si intentáramos escribir un array de bytes de longitud 65536, escribiríamos la longitud con writeShort(), lo que efectivamente escribiría 0; después se escribiría el contenido real del array.

Elegir el objetivo de inyección ABX

Para explotar ese desajuste, necesitaremos elegir algún archivo en el que podamos inyectar un array de bytes arbitrario a attributeBytesBase64 o attributeBytesHex, y cuya modificación además sea valiosa para el atacante.

La clase PackageInstaller ofrece la capacidad de preparar un paquete para su instalación. Sin necesidad de ningún permiso, cualquier app puede escribir un nuevo APK para que se instale en un directorio temporal. Una vez que se ha escrito todo lo necesario para la instalación, la app instaladora puede commit() PackageInstaller.Session, lo que significa que no podrá realizar más cambios en los archivos de instalación y que la Session está lista para la aprobación del usuario o para la instalación real.

El estado de estas operaciones se almacena en /data/system/install_sessions.xml. La app instaladora puede, por ejemplo, descargar la mitad de un APK grande en el directorio temporal creado por el Package Manager Service para su PackageInstaller.Session; después de un reinicio, reanudar la descarga, escribir la mitad restante y confirmar la instalación.

Una de las posibilidades es escribir datos en install_sessions.xml para marcar la sesión como staged, lo que significa que se instalará tras el siguiente arranque.

Otra posibilidad, presentada aquí, es cambiar la ruta al directorio temporal en el que se preparan los archivos de instalación, ya que openWrite()/openRead() aceptan cualquier nombre de archivo válido siempre que no haya path traversal y colocan ese archivo en el directorio señalado por el campo stageDir, que se lee desde el XML.

Explotando la inyección ABX

Ahora necesitamos conseguir que nuestro array de bytes controlado llegue a attributeBytesBase64().

PackageInstaller.Session ofrece el método setChecksums().

En el lado de system_server, los Checksum proporcionados se verifican opcionalmente contra la firma proporcionada por el llamador y luego se colocan en mChecksums.

Cuando se escribe install_sessions.xml, checksum.getValue() se pasa a writeByteArrayAttribute, que a su vez lo pasa a attributeBytesBase64().

Hay pocos eventos que provocan la escritura de install_sessions.xml; uno de ellos es la creación de una nueva Session. Por tanto, este exploit, después de establecer un Checksum en una sesión, crea una nueva Session para asegurarse de que la primera Session se ha guardado en el archivo.

Ahora escribimos un array de bytes de longitud 65536; cuando se lee, su tamaño se interpreta como cero y el contenido de ese array se convierte en datos crudos que BinaryXmlPullParser analiza.

No se especifica ningún recuento de atributos; cada entrada tiene un byte de etiqueta que contiene token. En el nibble bajo hay uno de los tipos de evento definidos en XmlPullParser, como START_TAG, END_TAG o END_DOCUMENT. Además de estos tipos, existe el tipo especial ATTRIBUTE, que no se notifica mediante next(), sino que después de ver el token START_TAG, el analizador inspecciona los siguientes tokens hasta que ve un token que no es ATTRIBUTE.

Como no se especifica ningún recuento de atributos, podemos proceder inmediatamente a cerrar el elemento actual mediante el token END_TAG. Luego también cerramos </session>, ya que todos los atributos interesantes de los elementos están en la etiqueta de apertura <session>, pero ya hemos pasado ese punto; sin embargo, ahora podemos abrir un nuevo elemento <session> y establecerlos allí.

Como se señaló antes, FastDataInput es compatible con DataInputStream de Java, excepto que hay un método adicional readInternedUTF() que puede referirse a Strings internadas previamente. Como no sabemos qué Strings se internaron antes, siempre especificamos que se escribió una String no vista anteriormente. Esto también añade las Strings recién leídas al pool, lo que podría causar problemas al leer datos escritos después de nuestro punto de inyección; sin embargo, como parte de la inyección inserto todas las etiquetas de cierre y el token END_DOCUMENT, por lo que después de mi inyección no se leerá nada más de ese archivo.

Usando PackageInstaller.Session con stageDir manipulado

Una vez que el sistema lee el install_sessions.xml modificado, obtenemos un objeto PackageInstallerSession con stageDir establecido al valor que controlamos.

Mi primera idea fue establecer stageDir en /proc/self, luego leer maps y escribir mem; sin embargo, esto no funcionó.

Cuando intenté usar openRead() para abrir /proc/self/maps, system_server abrió el archivo correctamente; sin embargo, pasar ese archivo a untrusted_app a través de Binder fue bloqueado por SELinux.

Las escrituras, sin embargo, no se realizan pasando el descriptor de archivo sin procesar a otro proceso, sino que se hacen a través de system_server, ya que system_server debe poder revocar el acceso de escritura una vez que la sesión se confirma. ¿Significa eso que podríamos escribir en /proc/self/mem? Resulta que, aunque system_server puede abrir ese archivo, antes de escribir cualquier cosa llama a Os.chmod() en ese archivo, algo que no puede hacer en /proc/self/mem. Por lo tanto, no podemos usarlo para explotación aquí, aunque por lo demás system_server puede abrir ese archivo y realizar escrituras en offsets que especifiquemos, y ese archivo permite sobrescribir páginas de código, lo que nos daría directamente ejecución de código.

Como esa opción no era viable, probé la siguiente idea: reemplazar el contenido de /data/system/packages.xml. Este es el archivo que contiene el estado de PackageManagerService, sobre todo qué apps están instaladas y qué uids se les asignan.

Parece que system_server no tiene permitido escribir directamente en ese archivo: en su lugar, cada vez que el sistema escribe ese archivo primero escribe en un archivo temporal y luego reemplaza packages.xml con ese archivo temporal y habilita protección sobre él.

Sin embargo, al leer /data/system/packages.xml, el sistema primero comprobará si existe el archivo /data/system/packages-backup.xml; si es así, considerará que el packages.xml principal está corrupto y leerá la copia de seguridad en su lugar. Durante el funcionamiento normal, el archivo /data/system/packages-backup.xml no está presente y podemos crear uno usando un PackageInstallerSession manipulado con stageDir establecido en /data/system.

Además, system_server tiene permitido enviar un descriptor de archivo de solo lectura de /data/system/packages.xml cuando uso openRead(), por lo que puedo construir fácilmente un archivo parcheado que contenga solo mis modificaciones sin corromper el contenido anterior.

Concediendo acceso a sharedUserId="android.uid.system"

En packages.xml tengo las definiciones de las aplicaciones instaladas registradas, como:```xml <package name="com.android.settings" codePath="/system_ext/priv-app/Settings" ... sharedUserId="1000" ...>

root@kitploit:~
¿Podríamos escribir un nuevo APK en alguna parte de `/data/app` (usando otro `PackageInstallerSession`) y añadir un nuevo elemento `<package>` a `packages.xml` para que se instale de esa manera?

Sí, sin embargo debemos proporcionar en `<cert>` la firma válida de nuestro APK recién instalado y el sistema la verificará contra el archivo APK durante el arranque.

¿Podríamos establecer el atributo `userId` (en lugar de `sharedUserId` para indicar un APK sin el atributo `<manifest android:sharedUserId>` en `AndroidManifest.xml`) al valor que queramos?

Sí, sin embargo no debemos usar un valor que ya esté siendo utilizado por otro paquete o por `sharedUserId`.

¿Podríamos establecer `sharedUserId="1000"` para nuestra aplicación?

Si lo hacemos, durante el arranque el sistema validará ese ajuste mediante [`canJoinSharedUserId()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java;l=658-750;drc=7ec13b04c3bbaeac99cbbc4db9f9f80492c508fe).

En particular, ese método utilizará [`checkCapability()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=613-637;drc=97a370a95275e79c69e79d7ead11aa38934a5575) para comprobar si las firmas coinciden exactamente o si la firma de un lado coincide con una de las firmas anteriores del otro.

Estas "firmas anteriores" provienen de `packages.xml`; en particular, cuando tenemos un elemento `<sigs>` con un `<cert>`, podemos añadir un elemento `<pastSigs>` dentro de `<sigs>` para agregar nuevas entradas a [`SigningDetails.mPastSigningCertificates`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=74-87;drc=97a370a95275e79c69e79d7ead11aa38934a5575).

Al final, nuestro elemento `<shared-user>` manipulado se ve así:```xml
<shared-user name="android.uid.system" userId="1000">
  <sigs count="1" schemeVersion="3">
    <cert index="3" />
    <pastSigs count="2" schemeVersion="3">
      <cert index="19" flags="2" />
      <cert index="19" flags="2" />
    </pastSigs>
  </sigs>
</shared-user>

El elemento <cert> bajo <pastSigs> se inserta dos veces porque la última firma pasada se considera actual y, por lo tanto, no se tiene en cuenta

flags="2" significa que el certificado está permitido para sharedUserId

Además, el registro de <package sharedUserId="1000"> debe aplicarse a la aplicación que declara android:sharedUserId="android.uid.system" en el manifiesto, por lo que debe ser un APK separado del que realiza la explotación.

La aplicación recién instalada con UID de sistema no se inicia

Aunque pude registrar un nuevo certificado confiable para android:sharedUserId="android.uid.system", normalmente una aplicación firmada con ese certificado y que declara solo sharedUserId en el manifiesto no podría iniciarse. Al lanzarla, veremos el siguiente mensaje en logcat:``` signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- Abort message: 'JNI FatalError called: (com.example.abxoverflow.droppedapk) frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:1976: selinux_android_setcontext(1000, 0, "default:privapp:targetSdkVersion=33:complete", "com.example.abxoverflow.droppedapk") failed'

root@kitploit:~
Esto se debe a que ninguna de las definiciones en [el archivo `seapp_contexts`](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/seapp_contexts) ha coincidido

La regla `user=` en ese archivo se [mapea desde `uid`](https://cs.android.com/android/platform/superproject/main/+/main:external/selinux/libselinux/src/android/android_seapp.c;l=819-833;drc=530165a996d8ca5ab5959c33bc040c78951bcb59) (primer argumento de `selinux_android_setcontext()`), en nuestro caso será `user=system`, para aplicaciones normales esto es `user=_app`

Lo otro que debe coincidir es la regla `seinfo=`, que se toma del tercer argumento de `selinux_android_setcontext()` hasta el primer carácter de dos puntos. Originalmente ese valor proviene de la comparación de la firma de la aplicación lanzada contra las definidas en `/system/etc/selinux/plat_mac_permissions.xml`

Al final, nuestra aplicación intenta hacer coincidir `user=system seinfo=default` y no existe tal regla en `seapp_contexts`

Sin embargo, aunque el proceso de nuestra nueva aplicación con `android:sharedUserId="android.uid.system"` no puede iniciarse, la aplicación aún puede cargarse en un proceso existente si se especifica a través del [atributo `android:process`](https://developer.android.com/guide/topics/manifest/application-element#proc). En particular, las aplicaciones que se ejecutan bajo `android.uid.system` pueden especificar `android:process="system"` para cargarse en `system_server`

# Hacer fallar el sistema

En general, [una aplicación que provoca un fallo de `system_server` se considera un error con Impacto de Seguridad Insignificante](https://bughunters.google.com/learn/invalid-reports/android-platform/5148417640366080/bugs-with-negligible-security-impact#triggering-a-local-temporary-denial-of-service), y aquí solo vale la pena mencionarlo porque es parte de una cadena de explotación que requiere dos reinicios de `system_server`

De todos modos, tenemos una cadena `Parcelable`:

* [El método AIDL `IAlarmManager.set()` acepta `AlarmManager.AlarmClockInfo`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl;l=32-35;drc=ca41ed611ac9c6584c6d5c38ae8428b8e4f3b135)
* [`AlarmClockInfo` llama al obsoleto `readParcelable()` sin argumento de tipo](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/AlarmManager.java;l=1598;drc=04bf84e220ade9d7ad8ef0b2f7e6ce6ec72841c8) (porque está en el módulo apex y no se migraron a los métodos nuevos)
* Especifico [`android.content.pm.PackageParser$Activity`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=8240;drc=7d3ffbae618e9e728644a96647ed709bf39ae759) como clase `Parcelable`
* La lectura de eso lleva a [la invocación de cualquier constructor público que acepte un único argumento `Parcel`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=7789-7795;drc=7d3ffbae618e9e728644a96647ed709bf39ae759)
* Especifico [`android.os.PooledStringWriter`, que llama a `writeInt(0)` en el `Parcel` proporcionado](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=55;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb)
* Esa llamada a `writeInt()` se realizó en un `Parcel` recibido como argumento `data` de [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)), que está respaldado por memoria de solo lectura mapeada con `mmap` desde `/dev/binder`. Escribir sobre eso provoca `SIGSEGV`

También cabe destacar que [he usado la combinación `PackageParser`+`PooledStringWriter` como parte de informes anteriores, por ejemplo para CVE-2023-21098](https://github.com/michalbednarski/TheLastBundleMismatch)

# El flujo completo

Esto es lo que sucede cuando pulsas el botón "Do everything" dentro de la aplicación.

1. `RebootBackgroundRunner` se inicia como un proceso separado, que ahora simplemente usará [`setsid()`](https://man7.org/linux/man-pages/man2/setsid.2.html) para sobrevivir al reinicio del espacio de usuario y después esperará en segundo plano
2. Se asigna una nueva `PackageInstaller.Session` y se le añade un nuevo objeto `Checksum`. Ese objeto `Checksum` contiene una matriz de bytes con un tamaño que causará un desbordamiento de entero durante la serialización y, una vez que sus datos se deserialicen, el sistema verá objetos `PackageInstaller.Session` cuyos datos eran anteriormente la carga útil de `Checksum`. En particular, hay dos sesiones inyectadas
    * Una con `sessionStageDir="/data/system"` y `prepared="true"` (lo que significa que el directorio de stage ya está listo y no necesita crearse)
    * Una con `sessionStageDir="/data/app/dropped_apk"` y `prepared="false"` (lo que significa que el directorio se creará en el primer `Session.openWrite()`)
3. Se asigna una nueva `PackageInstaller.Session` y luego se destruye inmediatamente. Esto hace que el sistema escriba el contenido actualizado en `install_sessions.xml`
4. Después de un pequeño retraso, se provoca un fallo de `system_server`
5. Durante el siguiente inicio de `system_server`, se lee el archivo `install_sessions.xml` y ahora las sesiones `PackageInstaller.Session` que inyectamos pueden utilizarse
6. `RebootBackgroundRunner` ha estado esperando en segundo plano durante el reinicio del espacio de usuario y, una vez que nota que el sistema está de nuevo activo y listo, realiza los siguientes pasos
7. Usando una `PackageInstaller.Session`, se extrae un nuevo APK de los assets y se escribe en `/data/app/dropped_apk/base.apk`
8. La otra sesión se usa para leer `/data/system/packages.xml`; ese archivo se modifica para declarar que el APK recién colocado ya está instalado y que el certificado utilizado para él se usó anteriormente para `android:sharedUserId="android.uid.system"` y sigue siendo de confianza para ese propósito. El archivo alterado se escribe como `/data/system/packages-backup.xml`
9. Se provoca otro fallo de `system_server`
10. Cuando `system_server` durante el inicio ve `packages-backup.xml`, considera que el `packages.xml` original está corrupto y usa la copia de seguridad en su lugar
11. Como el sistema ha leído el `packages.xml` modificado, la aplicación recién colocada está presente y se inicia desde [`ACTION_BOOT_COMPLETED`](https://developer.android.com/reference/android/content/Intent#ACTION_BOOT_COMPLETED). Esa nueva aplicación se ejecuta dentro de `system_server` porque tiene `<manifest android:sharedUserId="android.uid.system">` y `<application android:process="system">` en `AndroidManifest.xml`

# Scripts en `utils/`

Junto con la aplicación PoC hay un directorio `utils` con algunos scripts

* `moveapk.sh` mueve el APK compilado para colocarlo en `assets` del dropper; debe ejecutarse después de `gradle :droppedapk:assembleRelease`
* `peeksessions.sh` permite ver el contenido actual de `install_sessions.xml` (requiere compilación de Android `eng`/`userdebug`)
* `wipesessions.sh` limpia cualquier `PackageInstaller.Session` presente y reinicia el sistema (requiere compilación de Android `eng`/`userdebug`)

# Curiosidades

No estoy seguro de si esto está relacionado, pero revisando el historial de posibles errores relacionados con ABX (`cd frameworks/base ; git log -S ABX`) encontré [el commit "Stop processing on IOException"](https://android.googlesource.com/platform/frameworks/base/+/5112cfef2a2023a2629a426154547444593e9f9b%5E!/), que **incluye la adición de una prueba unitaria con un archivo ABX truncado**. Ese commit fue la continuación de ["Ignore malformed shortcuts"](https://android.googlesource.com/platform/frameworks/base/+/d5122bfaf18f1503e73c1a3a177a56d0f604a008%5E%21/), que fue [descrito en el boletín como DoS](https://source.android.com/docs/security/bulletin/2022-12-01#framework)
Descargar herramienta