
كتابة وتحليل استغلال لثغرة CVE-2025-22441: تصعيد الامتيازات من تطبيق مثبت إلى عملية SystemUI على أندرويد بسبب تمرير ApplicationInfo غير موثوق إلى LoadedApk
تم إصلاح هذه المشكلة في CVE-2025-22441: النشرة التصحيح المتابعة
ApplicationInfo حول النظامApplicationInfo هو بنية تُعرّف معلومات متنوعة حول التطبيق المثبّت، وأبرزها مسار ملف apk الذي يتم تحميل الموارد والكود منه
عادةً يتم تمريره من النظام إلى التطبيقات، ومع ذلك توجد حالات قد يمرر فيها مُستدعي غير تابع للنظام كائنًا خاصًا به. على سبيل المثال، في الماضي كانت هناك ثغرة في طريقة bindBackupAgent()، حيث يمكن للمهاجم تمرير كائن ApplicationInfo خاص به في المعامل مع قيم uid وsourceDir دون التحقق منها مقابل التطبيقات المثبّتة فعليًا في النظام، لأن تلك الطريقة كان من المفترض أن تُستدعى داخليًا بواسطة system_server، لكنها كانت مكشوفة لـ adb shell
لكن هذه المرة، نظرت عن كثب إلى حقل ApplicationInfo داخل RemoteViews
RemoteViews هو كائن يصف عرضًا يمكن أن يأتي من عملية أخرى. يُستخدم هذا بشكل أبرز لأدوات الشاشة الرئيسية، حيث يقوم التطبيق الذي يوفر الأداة ببناء RemoteViews ثم يتم "تطبيقه" داخل عملية الشاشة الرئيسية
الأماكن الأخرى التي تُستخدم فيها RemoteViews هي الإشعارات (يتم تطبيقها بواسطة عملية SystemUI) وحوارات الملء التلقائي (يتم توفيرها بواسطة خدمة الملء التلقائي، وتُطبق بواسطة system_server)
حقل RemoteViews.mApplication يتم تسلسله عبر Parcel وبالتالي قد يأتي من عمليات بعيدة، وكلما تم تطبيق RemoteViews يُستخدم بواسطة الطريقة التالية (مصدر المقتطف):```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;
}
الأكثر إثارة للاهتمام هنا هو استدعاء `LoadedApk.checkAndUpdateApkPaths()`، حيث أن هذه طريقة ثابتة وستقوم بتعديل بعض الحالة العامة [(مصدر المقتطف)](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);
}
لنناقش ما يحدث في هذه الدوال
أولاً، نستدعي الدالة checkAndUpdateApkPaths ذات الثلاث معاملات مع تعيين cacheWithCode على كل من true و false. يمكن إنشاء كائن LoadedApk في وضعين، إما مع تعيين mIncludeCode على true أو false، وهو ما يُرتبط على مستوى SDK بـ Context مع تعيين علم CONTEXT_INCLUDE_CODE أو عدم تعيينه
ستستخدم تلك الدالة أولاً ActivityThread.peekPackageInfo()، والتي ستعيد مثيل LoadedApk المخزن مؤقتاً بالفعل، لذلك إذا لم يقم التطبيق سابقاً بإنشاء LoadedApk مع تطابق packageName و includeCode، فستعيد peekPackageInfo() قيمة null ولن تقوم checkAndUpdateApkPaths() بأي شيء
بالعودة إلى RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths()، لدينا context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED) هناك، ولم يتم تحديد علم CONTEXT_INCLUDE_CODE (وكذلك الحال مع السياقات المُمررة كوسيط لتلك الدالة)، وبالتالي ستستخدم تلك الدالة دائماً LoadedApk مع mIncludeCode=false
لذلك، بالنسبة لإصدار RemoteViews، فإن تحديث الإصدار مع الكود غير ضروري، لأن RemoteViews تستخدم دائماً Context بدون كود. رسالة الالتزام تقول فقط أن هناك مكانين قد يتم فيهما تخزين ApplicationInfo مؤقتاً وأعتقد أن إزالة تحديث الإصدار مع الكود لن تسبب مشاكل هنا (حيث أن checkAndUpdateApkPaths() تُستخدم فقط بواسطة AppWidgetHostView و RemoteViews). ومع ذلك، فإن الحجة المضادة لذلك هي تجنب احتمالية عدم تزامن المخابئ المختلفة، وبينما أن إزالة الاستدعاء مع cacheWithCode=true يزيل التأثير الأشد خطورة لهذه الثغرة، فإنه لا يصلح المشكلة بالكامل، لأن تعديل الموارد فقط (بدلاً من الكود) قد يظل ذا قيمة للمهاجم
LoadedApk.updateApplicationInfo()حتى الآن، الكود المعروض يُستخدم فقط لـ RemoteViews (الأدوات، الإشعارات، إلخ)، لكننا الآن سندخل إلى LoadedApk.updateApplicationInfo() والتي تُستخدم أيضاً لتحديث عمليات التطبيق الجارية بعد تثبيت split جديد (على سبيل المثال عند استخدام Play Feature Delivery للتسليم عند الطلب)