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
ResourcePoison — Writeup y exploit para CVE-2025-22441: Escalada de privilegios desde una app instalada al proceso SystemUI en Android debido al paso de ApplicationInfo no confiable a LoadedApk | Kitploit
Herramientas/GitHubGitHub/michalbednarski/resourcepoison
Seguridad AndroidEscalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónSeguridad MóvilPapers e Investigación
GitHubmichalbednarski/resourcepoison

ResourcePoison

Writeup y exploit para CVE-2025-22441: Escalada de privilegios desde una app instalada al proceso SystemUI en Android debido al paso de ApplicationInfo no confiable a LoadedApk

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
1072849hace 11 mesesRevisado por Kitploit

La corrección para este problema ha aparecido como CVE-2025-22441: boletín parche seguimiento

Pasando ApplicationInfo

ApplicationInfo es una estructura que define diversa información sobre una aplicación instalada, sobre todo la ruta al archivo apk desde el que se cargan los recursos y el código

Normalmente se pasa del sistema a las aplicaciones, sin embargo a veces hay casos en los que un llamador que no es del sistema podría proporcionar uno propio. Por ejemplo, en el pasado hubo una vulnerabilidad en el método bindBackupAgent(), donde un atacante podía pasar como parámetro su propio objeto ApplicationInfo con valores de uid y sourceDir que no se comprobaban contra las aplicaciones realmente instaladas en el sistema, porque ese método estaba pensado para ser llamado internamente por system_server, pero estaba expuesto a adb shell

Esta vez, sin embargo, he examinado de cerca el campo ApplicationInfo dentro de RemoteViews

RemoteViews es un objeto que describe una vista que puede provenir de otro proceso. Esto se usa sobre todo para los widgets de la pantalla de inicio, donde la aplicación que proporciona el widget construye RemoteViews y luego se "aplica" dentro del proceso de la pantalla de inicio

Otros lugares donde se usan RemoteViews son las notificaciones (aplicadas por el proceso SystemUI) y los diálogos de autocompletado (proporcionados por el servicio de autocompletado, aplicados por system_server)

El campo RemoteViews.mApplication se serializa a través de Parcel y, por tanto, puede provenir de procesos remotos y, siempre que se aplican RemoteViews, lo usa el siguiente método (fuente del fragmento):```java private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) { if (mApplication != null) { if (context.getUserId() == UserHandle.getUserId(mApplication.uid) && context.getPackageName().equals(mApplication.packageName)) { return context; } try { LoadedApk.checkAndUpdateApkPaths(mApplication); return context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED); } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); } }

root@kitploit:~
return context;

}

root@kitploit:~
Lo más interesante aquí es la llamada a `LoadedApk.checkAndUpdateApkPaths()`, ya que es un método estático y modificará algún estado global [(fuente del fragmento)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=2275-2302;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)```java
public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
    // Get the LoadedApk from the cache
    ActivityThread activityThread = ActivityThread.currentActivityThread();
    if (activityThread == null) {
        Log.e(TAG, "Cannot find activity thread");
        return;
    }
    checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
    checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
}

private static void checkAndUpdateApkPaths(ActivityThread activityThread,
        ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
    String expectedCodePath = expectedAppInfo.getCodePath();
    LoadedApk loadedApk = activityThread.peekPackageInfo(
            expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
    // If there is load apk cached, or if the cache is valid, don't do anything.
    if (loadedApk == null || loadedApk.getApplicationInfo() == null
            || loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
        return;
    }
    // Duplicate framework logic
    List<String> oldPaths = new ArrayList<>();
    LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);

    // Force update the LoadedApk instance, which should update the reference in the cache
    loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
}

Discutamos qué está ocurriendo en estos métodos

Primero estamos llamando a checkAndUpdateApkPaths de 3 parámetros con cacheWithCode establecido tanto en true como en false. El objeto LoadedApk puede construirse en dos modos, ya sea con mIncludeCode siendo true o false, lo que a nivel de SDK se corresponde con un Context con el flag CONTEXT_INCLUDE_CODE establecido o no

Ese método primero usará ActivityThread.peekPackageInfo(), que devolverá la instancia de LoadedApk ya almacenada en caché, por lo tanto, si la aplicación no construyó previamente un LoadedApk con el packageName e includeCode coincidentes, peekPackageInfo() devolverá null y checkAndUpdateApkPaths() no hará nada

Volviendo a RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths(), tenemos context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED) allí, el flag CONTEXT_INCLUDE_CODE no fue especificado (lo mismo ocurre con los contextos pasados como argumento a ese método) y por lo tanto ese método siempre usará LoadedApk con mIncludeCode=false

Por lo tanto, para RemoteViews actualizar la versión con código es innecesario, ya que RemoteViews siempre usan Context sin código. El mensaje del commit solo dice que hay dos lugares donde ApplicationInfo puede estar en caché y creo que eliminar la actualización de la versión con código no causaría problemas aquí (ya que checkAndUpdateApkPaths() solo es usado por AppWidgetHostView y RemoteViews). El contraargumento a esto, sin embargo, es evitar la posibilidad de que varias cachés se desincronicen, y aunque eliminar la llamada con cacheWithCode=true elimina el impacto más severo de este error, no soluciona completamente el problema, ya que la modificación de solo recursos (en lugar de código) aún podría ser valiosa para un atacante

LoadedApk.updateApplicationInfo()

Hasta ahora, el código presentado solo se usa para RemoteViews (widgets, notificaciones, etc.), pero ahora entraremos en LoadedApk.updateApplicationInfo(), que también se usa para actualizar procesos de aplicaciones en ejecución después de que se haya instalado un nuevo split (por ejemplo, al usar la entrega bajo demanda de Play Feature Delivery)

Ahora, el objeto ApplicationInfo no confiable de RemoteViews se pasará a updateApplicationInfo(), echemos un vistazo a lo que hace ese método (fuente del fragmento)```java public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List oldPaths) { if (!setApplicationInfo(aInfo)) { return; }

root@kitploit:~
Echemos un vistazo a ese `setApplicationInfo()` [(fuente del fragmento)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=392-422;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)```java
private boolean setApplicationInfo(ApplicationInfo aInfo) {
    if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) {
        Slog.w(TAG, "New application info for package " + aInfo.packageName
                + " is out of date with TS " + aInfo.createTimestamp + " < the current TS "
                + mApplicationInfo.createTimestamp);
        return false;
    }
    // Snip: assign fields such as mAppDir and mResDir on this object from aInfo
    return true;
}

El campo createTimestamp normalmente se establece en SystemClock.uptimeMillis(), pero como este objeto proviene del atacante, esto significa que el atacante puede proporcionar un valor futuro para evitar que se ejecuten llamadas posteriores a updateApplicationInfo()

Volviendo a updateApplicationInfo() (fuente del fragmento)```java final List newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); final List addedPaths = new ArrayList<>(newPaths.size());

// Snip: populate addedPaths with items that are in newPaths and not in oldPaths (passed in argument) synchronized (mLock) { createOrUpdateClassLoaderLocked(addedPaths);

root@kitploit:~
La lista de `addedPaths` se construye: en caso de instalación de un nuevo split, `oldPaths` contendría la lista de rutas que se usaban antes de la instalación del nuevo split y `addedPaths` contendría la lista de archivos `.apk` que deben añadirse al `ClassLoader` existente; sin embargo, en el caso de `checkAndUpdateApkPaths()`, `oldPaths` se construirá a partir del mismo objeto `ApplicationInfo` exacto y, por lo tanto, `addedPaths` estará vacío y el atacante no podrá añadir nuevas rutas al `ClassLoader` existente aquí.

`createOrUpdateClassLoaderLocked()` es un método largo, pero solo hay algunas cosas interesantes aquí:

* [Si `mIncludeCode` es `false`, lo único que se hace es crear un `ClassLoader` sin hacer referencia a ningún archivo `apk`/`dex`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=968-990;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)
* Lo más peligroso es crear un nuevo `ClassLoader` usando rutas que se acaban de establecer mediante un `ApplicationInfo` controlado por el atacante; sin embargo, eso solo ocurrirá si [`mDefaultClassLoader` es `null`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1005;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), lo que significa que esta es la primera llamada a `createOrUpdateClassLoaderLocked()` en esa instancia de `LoadedApk` (el campo `mDefaultClassLoader` se refiere a la instancia de `ClassLoader` usada antes de aplicar [`AppComponentFactory.instantiateClassLoader()`](https://developer.android.com/reference/android/app/AppComponentFactory#instantiateClassLoader(java.lang.ClassLoader,%20android.content.pm.ApplicationInfo)))
* También está [añadir nuevas rutas a la ruta de búsqueda de bibliotecas nativas de ese `ClassLoader`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1050-1058;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1); sin embargo, para explotar eso, la aplicación víctima necesitaría realizar `System.loadLibrary()`, que normalmente no está presente.
* Y está [añadir rutas `apk`/`dex` del argumento `addedPaths` al `ClassLoader` existente](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1060-1065;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1); sin embargo, en la ruta de código desde `RemoteViews`, `addedPaths` estará vacío.

Volviendo a `updateApplicationInfo()` de nuevo, todavía hay una cosa relevante que hace este método: [reemplazar `LoadedApk.mResources` con una instancia que usa el nuevo valor de `mResDir`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=382;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1). Debe tenerse en cuenta que esta es una instancia nueva, no una actualización de las instancias que ya están presentes. Los `Context`s recién creados devolverán/usarán esta nueva instancia de `Resources`; sin embargo, cualquier `Context` ya creado seguirá usando el `Resources` antiguo.

# Resumen del impacto

Para resumir las secciones anteriores, cada vez que el proceso víctima aplica `RemoteViews`, esta vulnerabilidad permite:

* Reemplazar [`Resources` (cadenas de localización, diseños, etc.)](https://developer.android.com/guide/topics/resources/providing-resources) de las Activities recién creadas dentro de ese proceso.
* Añadir a la ruta de búsqueda de bibliotecas nativas usada por `System.loadLibrary()`; sin embargo, explotar eso requeriría que la víctima llamara a `System.loadLibrary()` pasando el nombre de una biblioteca que normalmente está ausente, lo cual es poco probable.
* Cargar código Java arbitrario si el proceso víctima ha usado `createPackageContext(CONTEXT_INCLUDE_CODE)`, pero no ha llamado a [`getClassLoader()` en ese `Context`](https://developer.android.com/reference/android/content/Context#getClassLoader()); sin embargo, dado que cargar código es la razón para usar la bandera `CONTEXT_INCLUDE_CODE`, esto también es poco probable que ocurra de forma natural.

Ahora bien, aunque la probabilidad de cargar código Java en este caso es poco probable que ocurra de forma natural, pude desencadenarlo.

# Cargando el `WebView`

En versiones modernas de Android, `WebView` no forma parte del sistema, sino que se carga desde un `apk` normal que está definido en la [configuración del sistema](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/xml/config_webview_packages.xml) y [es una aplicación del sistema o tiene una firma que coincide con una definida dentro del sistema](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java;l=688-705;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928).

La carga de ese `apk` se realiza [creando un nuevo `Context`, pasando las banderas `Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/webkit/WebViewFactory.java;l=521;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928).

Veamos ahora al llamador de ese método [(fuente del fragmento)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/webkit/WebViewFactory.java;l=541-563;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928).```java
// Overall snip: try/catch/finally, Trace, logging and timing measurement
webViewContext = getWebViewContextAndSetProvider();
if (android.content.res.Flags.registerResourcePaths()) {
    Resources.registerResourcePaths(webViewContext.getPackageName(),
            webViewContext.getApplicationInfo());
} else {
    // Snip: old resource update path, won't be used on latest Android builds
}
ClassLoader clazzLoader = webViewContext.getClassLoader();

getWebViewContextAndSetProvider() es un método que crea Context, Resources.registerResourcePaths() es nuestra única oportunidad de introducir un retraso para que podamos llamar a checkAndUpdateApkPaths() en otro hilo y cargar código Java arbitrario, ya que webViewContext.getClassLoader() es el final de la ventana donde hay un LoadedApk con mIncludeCode siendo true y mDefaultClassLoader siendo null

Resources.registerResourcePaths() llegará a appendLibAssetsLocked(), que iterará sobre el campo mResourceImpls del singleton, que contiene todos los recursos que se cargaron en este proceso y, por lo tanto, puede contener recursos construidos usando ApplicationInfo plantado a través de RemoteViews

Mi idea inicial era poner en ApplicationInfo un gran número de rutas de overlays, todas con el mismo hashCode(), lo que sería lento de deduplicar mediante la llamada a createNewResourceKeyIfNeeded() y, aunque al usar el intérprete (por ejemplo, al usar un depurador o dentro de una app recién instalada) esto era un retraso significativo, cuando el runtime había realizado optimizaciones el retraso introducido por las colisiones de hash no era significativo; sin embargo, apareció otra razón de ralentización: dado que estas rutas de overlays no apuntaban a archivos existentes, por cada overlay que fallaba al cargarse se imprimía un mensaje de log con el stack trace y eso en la práctica sí provocaba una ralentización, haciendo factible la explotación de esta condición de carrera

Entrando en SystemUI

Las apps pueden pasar RemoteViews a SystemUI en las Notificaciones. Mi app de explotación solicita el permiso POST_NOTIFICATIONS, que creo que es una interacción de usuario razonable, aunque podría ser posible que algunas notificaciones eviten ese requisito

Normalmente SystemUI no usa WebView y, de hecho, ni siquiera puede hacerlo, ya que SystemUI usa almacenamiento protegido por dispositivo (es decir, almacenamiento que no está protegido por la credencial de la pantalla de bloqueo) y la implementación de WebView no lo permite

Sin embargo, este exploit me permite modificar Resources, así que primero modifiqué el layout usado por SlicePermissionActivity para incluir un elemento <WebView />, y luego lancé esa Activity, lo que dispara la inicialización de WebView en el hilo principal de SystemUI

Luego tengo que hacer que se llame a RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths() en otro hilo para reemplazar la ruta usada para cargar el código

Normalmente, publicar una notificación implica varios hilos en el proceso de SystemUI, incluido el hilo principal (así que mientras WebView se está inicializando no puedo publicar otra notificación); sin embargo, la aplicación de RemoteViews ocurre en un hilo separado y puedo suspender esa operación teniendo un ImageView dentro del RemoteViews y pidiéndole que cargue una imagen desde mi ContentProvider

Ataques solo de recursos

Este exploit carga WebView reemplazando el layout usado en SlicePermissionActivity. Si actualizar el código no fuera posible, atacar SlicePermissionActivity podría seguir siendo valioso, ya que el atacante podría reemplazar el layout para ocultar por completo el mensaje original y, por ejemplo, mostrar un changelog, reemplazar el botón de permitir con "Entendido" y el botón de denegar con una cadena vacía, haciéndolo efectivamente invisible. Si bien el framework de Slices está obsoleto, todavía hay slices de Settings que pueden, por ejemplo, cambiar la configuración de datos móviles sin interacción del usuario

Otro aviso de permiso importante dentro de SystemUI es la confirmación de Media Projection; sin embargo, ese resultó no ser vulnerable, ya que usa el contexto de la aplicación para el Dialog

También son interesantes los fragmentos de Preference, ya que puedes definir un Intent para que lo lance Preference; sin embargo, en el caso de SystemUI, las únicas Activities de preferencias son las relacionadas con SystemUI Tuner y Demo Mode, pero estas se ejecutan en un proceso diferente al que muestra las Notificaciones

Descargar herramienta