
Writeup et exploit pour CVE-2025-22441 : élévation de privilèges d'une application installée vers le processus SystemUI sur Android en raison du passage d'un ApplicationInfo non fiable à LoadedApk
Le correctif pour ce problème est apparu sous la référence CVE-2025-22441 : bulletin correctif suivi
ApplicationInfoApplicationInfo est une structure définissant diverses informations sur une application installée, notamment le chemin vers le fichier APK à partir duquel les ressources et le code sont chargés
Habituellement, elle est transmise du système aux applications, mais il existe parfois des cas où un appelant non-système peut fournir la sienne. Par exemple, dans le passé, il y a eu une vulnérabilité dans la méthode bindBackupAgent(), où un attaquant pouvait passer en paramètre son propre objet ApplicationInfo avec des valeurs uid et sourceDir qui n'étaient pas vérifiées par rapport aux applications réellement installées sur le système, car cette méthode était destinée à être appelée en interne par system_server, mais était exposée à adb shell
Cette fois-ci, cependant, j'ai examiné de près le champ ApplicationInfo dans RemoteViews
RemoteViews est un objet décrivant une vue pouvant provenir d'un autre processus. Il est notamment utilisé pour les widgets de l'écran d'accueil, où l'application fournissant le widget construit un RemoteViews qui est ensuite "appliqué" dans le processus de l'écran d'accueil
Les autres endroits où RemoteViews sont utilisés sont les notifications (appliquées par le processus SystemUI) et les boîtes de dialogue d'autofill (fournies par le service d'autofill, appliquées par system_server)
Le champ RemoteViews.mApplication est sérialisé via Parcel et peut donc provenir de processus distants, et à chaque fois que RemoteViews sont appliqués, il est utilisé par la méthode suivante (extrait de code source) :```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");
}
}
return context;
}
La partie la plus intéressante ici est l’appel à `LoadedApk.checkAndUpdateApkPaths()`, car il s’agit d’une méthode statique qui va modifier un certain état global [(source de l’extrait)](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);
}
Discutons de ce qui se passe dans ces méthodes
Tout d'abord, nous appelons checkAndUpdateApkPaths avec 3 paramètres, cacheWithCode étant défini à la fois sur true et false. L'objet LoadedApk peut être construit selon deux modes, soit avec mIncludeCode défini sur true ou false, ce qui, du point de vue du SDK, correspond à un Context avec le drapeau CONTEXT_INCLUDE_CODE défini ou non
Cette méthode utilisera d'abord ActivityThread.peekPackageInfo(), qui renverra l'instance LoadedApk déjà mise en cache. Par conséquent, si l'application n'a pas précédemment construit de LoadedApk avec un packageName et un includeCode correspondants, peekPackageInfo() renverra null et checkAndUpdateApkPaths() ne fera rien
En revenant sur RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths(), nous avons context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED) là, le drapeau CONTEXT_INCLUDE_CODE n'a pas été spécifié (il en va de même pour les contextes passés en argument à cette méthode) et donc cette méthode utilisera toujours un LoadedApk avec mIncludeCode=false