Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
ResourcePoison — Writeup ed exploit per CVE-2025-22441: escalatione dei privilegi da app installata al processo SystemUI su Android dovuta al passaggio di ApplicationInfo non attendibile a LoadedApk | Kitploit
Strumenti/GitHubGitHub/michalbednarski/resourcepoison
Sicurezza AndroidEscalation di PrivilegiAnalisi delle VulnerabilitàExploitSicurezza MobilePaper e Ricerca
GitHubmichalbednarski/resourcepoison

ResourcePoison

Writeup ed exploit per CVE-2025-22441: escalatione dei privilegi da app installata al processo SystemUI su Android dovuta al passaggio di ApplicationInfo non attendibile a LoadedApk

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Vedi Repository
107284911 mesi faRevisionato da Kitploit

La correzione per questo problema è apparsa come CVE-2025-22441: bollettino patch follow up

Passare ApplicationInfo in giro

ApplicationInfo è una struttura che definisce varie informazioni sull'app installata, in particolare il percorso del file apk da cui vengono caricati risorse e codice

Di solito viene passato dal sistema alle applicazioni, tuttavia a volte ci sono casi in cui un chiamante non di sistema potrebbe fornirne uno proprio. Ad esempio in passato c'è stata una vulnerabilità nel metodo bindBackupAgent(), dove un attaccante poteva passare come parametro un proprio oggetto ApplicationInfo con valori uid e sourceDir che non venivano verificati rispetto alle app realmente installate nel sistema, perché quel metodo era pensato per essere chiamato internamente da system_server, ma era esposto a adb shell

Questa volta, però, ho esaminato attentamente il campo ApplicationInfo all'interno di RemoteViews

RemoteViews è un oggetto che descrive una vista che può provenire da un altro processo. Questo è soprattutto usato per i widget della schermata home, dove l'app che fornisce il widget costruisce RemoteViews e poi viene "applicato" all'interno del processo della schermata home

Altri punti in cui vengono usati RemoteViews sono le notifiche (applicate dal processo SystemUI) e i dialoghi di autofill (forniti dal servizio di autofill, applicati da system_server)

Il campo RemoteViews.mApplication viene serializzato tramite Parcel e quindi può provenire da processi remoti e ogni volta che RemoteViews vengono applicati viene usato dal seguente metodo (snippet sorgente):```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:~
La parte più interessante qui è la chiamata a `LoadedApk.checkAndUpdateApkPaths()`, poiché si tratta di un metodo statico che modificherà alcuni stati globali [(snippet source)](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);
}

Parliamo di cosa succede in questi metodi

Prima chiamiamo checkAndUpdateApkPaths con 3 parametri, con cacheWithCode impostato sia su true che su false. L'oggetto LoadedApk può essere costruito in due modalità, con mIncludeCode impostato su true o false, che a livello di SDK corrisponde a un Context con il flag CONTEXT_INCLUDE_CODE impostato o meno

Questo metodo userà prima ActivityThread.peekPackageInfo(), che restituirà l'istanza LoadedApk già memorizzata nella cache, quindi se l'app non ha precedentemente costruito un LoadedApk con packageName e includeCode corrispondenti, peekPackageInfo() restituirà null e checkAndUpdateApkPaths() non farà nulla

Tornando a RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths(), abbiamo context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED) lì, il flag CONTEXT_INCLUDE_CODE non è stato specificato (lo stesso vale per i contesti passati come argomento a quel metodo) e quindi quel metodo userà sempre LoadedApk con mIncludeCode=false

Pertanto, per RemoteViews l'aggiornamento della versione con il codice non è necessario, poiché RemoteViews usa sempre Context senza codice. Il messaggio del commit dice solo che ci sono due punti in cui ApplicationInfo può essere memorizzato nella cache e penso che rimuovere l'aggiornamento della versione con il codice non causerebbe problemi qui (poiché checkAndUpdateApkPaths() è usato solo da AppWidgetHostView e RemoteViews). Tuttavia, la controargomentazione è evitare la possibilità di avere varie cache fuori sincronia, e sebbene rimuovere la chiamata con cacheWithCode=true elimini l'impatto più grave di questo bug, non risolve completamente il problema, poiché la modifica delle sole risorse (invece del codice) potrebbe comunque essere preziosa per un attaccante

LoadedApk.updateApplicationInfo()

Finora il codice presentato è usato solo per RemoteViews (widget, notifiche, ecc.), ma ora entreremo in LoadedApk.updateApplicationInfo(), che viene usato anche per aggiornare i processi dell'app in esecuzione dopo che è stato installato un nuovo split (ad esempio quando si usa la consegna on-demand di Play Feature Delivery)

Ora l'oggetto ApplicationInfo non attendibile proveniente da RemoteViews verrà passato a updateApplicationInfo(), diamo un'occhiata a cosa fa quel metodo (fonte del frammento)```java public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List oldPaths) { if (!setApplicationInfo(aInfo)) { return; }

root@kitploit:~
Diamo un'occhiata a quel `setApplicationInfo()` [(sorgente del frammento)](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;
}

Il campo createTimestamp è normalmente impostato su SystemClock.uptimeMillis(), ma poiché questo oggetto proviene dall'attaccante, ciò significa che l'attaccante può fornire un valore futuro per impedire l'esecuzione di ulteriori chiamate a updateApplicationInfo()

Tornando a updateApplicationInfo() (fonte del frammento)```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:~
Elenco di `addedPaths` viene costruito: in caso di installazione di una nuova split, `oldPaths` conterrebbe l'elenco dei percorsi utilizzati prima dell'installazione della nuova split e `addedPaths` conterrebbe l'elenco dei file `.apk` che devono essere aggiunti al `ClassLoader` esistente, tuttavia nel caso di `checkAndUpdateApkPaths()` `oldPaths` verrà costruito dallo stesso identico oggetto `ApplicationInfo` e quindi `addedPaths` sarà vuoto e l'attaccante non potrà aggiungere nuovi percorsi al `ClassLoader` esistente qui

`createOrUpdateClassLoaderLocked()` è un metodo lungo, ma solo poche cose sono interessanti qui:

* [Se `mIncludeCode` è `false`, l'unica cosa fatta è creare un `ClassLoader` senza fare riferimento ad alcun file `apk`/`dex`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=968-990;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)
* La cosa più pericolosa è creare un nuovo `ClassLoader` usando i percorsi appena impostati tramite un `ApplicationInfo` controllato dall'attaccante, tuttavia ciò accadrà solo se [`mDefaultClassLoader` è `null`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1005;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), il che significa che questa è la prima chiamata a `createOrUpdateClassLoaderLocked()` su quell'istanza di `LoadedApk` (il campo `mDefaultClassLoader` si riferisce all'istanza di `ClassLoader` usata prima di applicare [`AppComponentFactory.instantiateClassLoader()`](https://developer.android.com/reference/android/app/AppComponentFactory#instantiateClassLoader(java.lang.ClassLoader,%20android.content.pm.ApplicationInfo)))
* C'è anche [l'aggiunta di nuovi percorsi al percorso di ricerca delle librerie native di quel `ClassLoader`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1050-1058;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), tuttavia per sfruttare ciò l'app vittima dovrebbe eseguire `System.loadLibrary()` che normalmente non è presente
* E c'è [l'aggiunta dei percorsi `apk`/`dex` dall'argomento `addedPaths` al `ClassLoader` esistente](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1060-1065;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), tuttavia sul percorso di codice da `RemoteViews` `addedPaths` sarà vuoto

Tornando a `updateApplicationInfo()` di nuovo, c'è ancora una cosa rilevante che questo metodo fa, [sostituire `LoadedApk.mResources` con un'istanza che usa il nuovo valore di `mResDir`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=382;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1). Va notato che questa è una nuova istanza, non un aggiornamento delle istanze già presenti. I `Context` appena creati restituiranno/utilizzeranno questa nuova istanza di `Resources`, tuttavia qualsiasi `Context` già creato continuerà a usare le vecchie `Resources`

# Riepilogo dell'impatto

Per riassumere le sezioni precedenti, ogni volta che il processo vittima applica un `RemoteViews`, questa vulnerabilità consente di:

* Sostituire le [`Resources` (stringhe di localizzazione, layout, ecc.)](https://developer.android.com/guide/topics/resources/providing-resources) delle Activity appena create all'interno di quel processo
* Aggiungere al percorso di ricerca delle librerie native usato da `System.loadLibrary()`, tuttavia sfruttare ciò richiederebbe che la vittima chiami `System.loadLibrary()` passando il nome di una libreria normalmente assente, il che è improbabile
* Caricare codice Java arbitrario se il processo vittima ha usato `createPackageContext(CONTEXT_INCLUDE_CODE)`, ma non ha chiamato [`getClassLoader()` su quel `Context`](https://developer.android.com/reference/android/content/Context#getClassLoader()), tuttavia poiché il caricamento del codice è il motivo per usare il flag `CONTEXT_INCLUDE_CODE`, anche questo è improbabile che accada naturalmente

Ora, mentre la possibilità di caricare codice Java in questo caso è improbabile che accada naturalmente, sono riuscito a innescarla

# Caricamento della `WebView`

Sulle versioni moderne di Android, la `WebView` non fa parte del sistema, ma viene caricata da un normale `apk` definito nella [configurazione di sistema](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/xml/config_webview_packages.xml) e [è un'app di sistema o ha una firma che corrisponde a quella definita all'interno 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)

Il caricamento di quel `apk` viene eseguito [creando un nuovo `Context`, passando i flag `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)

Diamo ora un'occhiata al chiamante di quel metodo [(sorgente del frammento)](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() è un metodo che crea Context, Resources.registerResourcePaths() è la nostra unica possibilità di introdurre un ritardo per poter chiamare checkAndUpdateApkPaths() in un altro thread e caricare codice Java arbitrario, poiché webViewContext.getClassLoader() è la fine della finestra in cui esiste LoadedApk con mIncludeCode impostato su true e mDefaultClassLoader impostato su null

Resources.registerResourcePaths() raggiungerà appendLibAssetsLocked(), che itera sul campo mResourceImpls del singleton, che contiene tutte le risorse caricate in questo processo e quindi può contenere risorse costruite utilizzando ApplicationInfo inserito tramite RemoteViews

La mia idea iniziale era di inserire in ApplicationInfo un gran numero di percorsi di overlay tutti con lo stesso hashCode(), il che sarebbe stato lento da deduplicare tramite la chiamata a createNewResourceKeyIfNeeded() e mentre usando l'interprete (ad esempio quando si usa un debugger o all'interno di un'app appena installata) questo era un ritardo significativo, quando il runtime aveva eseguito le ottimizzazioni il ritardo introdotto dalle collisioni di hash non era significativo, tuttavia è emersa un'altra causa di rallentamento: poiché questi percorsi di overlay non puntavano a file esistenti, per ogni overlay che non riusciva a caricarsi veniva stampato un messaggio di log con stack trace e questo in pratica ha portato a un rallentamento, rendendo fattibile lo sfruttamento di questa race condition

Entrare in SystemUI

Le app possono passare RemoteViews a SystemUI nelle Notifiche. La mia app di exploit richiede il permesso POST_NOTIFICATIONS, che ritengo sia una ragionevole interazione con l'utente, sebbene potrebbe essere possibile per alcune notifiche bypassare tale requisito

Normalmente SystemUI non usa WebView e in realtà non può nemmeno farlo, poiché SystemUI usa lo storage protetto dal dispositivo (cioè storage non protetto dalle credenziali della schermata di blocco) e l'implementazione di WebView lo vieta

Questo exploit mi permette però di modificare Resources, quindi prima ho modificato il layout usato da SlicePermissionActivity per includere un elemento <WebView />, poi ho avviato quella Activity, che attiva l'inizializzazione di WebView sul thread principale di SystemUI

Poi devo far chiamare RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths() su un altro thread per sostituire il percorso usato per caricare il codice

Normalmente pubblicare una notifica coinvolge più thread nel processo SystemUI, incluso il thread principale (quindi mentre WebView viene inizializzato non posso pubblicare un'altra notifica), tuttavia l'applicazione di RemoteViews avviene su un thread separato e posso sospendere quell'operazione avendo un ImageView all'interno di RemoteViews e chiedendogli di caricare un'immagine dal mio ContentProvider

Attacchi solo-risorse

Questo exploit carica WebView sostituendo il layout usato in SlicePermissionActivity. Se l'aggiornamento del codice non fosse possibile, attaccare SlicePermissionActivity potrebbe comunque essere prezioso, poiché l'attaccante potrebbe sostituire il layout per nascondere completamente il messaggio originale e ad esempio mostrare un changelog, sostituire il pulsante di consenso con "Ho capito" e il pulsante di rifiuto con una stringa vuota rendendolo di fatto invisibile. Sebbene il framework Slices sia deprecato, esistono ancora Settings slices che possono ad esempio modificare l'impostazione dei dati mobili senza interazione dell'utente

Un altro importante prompt di permesso all'interno di SystemUI è la conferma di Media Projection, tuttavia quello non è risultato vulnerabile poiché usa il contesto dell'applicazione per Dialog

Interessanti sono anche i fragment di Preference, poiché puoi definire un Intent da avviare tramite Preference, tuttavia nel caso di SystemUI le uniche Activity di preference sono quelle relative a SystemUI Tuner e Demo Mode, ma queste vengono eseguite in un processo diverso da quello che mostra le Notifiche

Scarica lo strumento