
CVE-2025-22441 के लिए राइटअप और एक्सप्लॉइट: एंड्रॉइड पर इंस्टॉल किए गए ऐप से SystemUI प्रोसेस तक विशेषाधिकार वृद्धि, जो LoadedApk को अविश्वसनीय ApplicationInfo पास करने के कारण होती है
इस समस्या का समाधान CVE-2025-22441 के रूप में सामने आया है: बुलेटिन पैच अनुवर्ती
ApplicationInfo को इधर-उधर पास करनाApplicationInfo एक संरचना है जो इंस्टॉल किए गए ऐप के बारे में विभिन्न जानकारी परिभाषित करती है, विशेष रूप से apk फ़ाइल का पथ जिससे संसाधन और कोड लोड किए जाते हैं
आमतौर पर इसे सिस्टम से एप्लिकेशन तक पास किया जाता है, हालांकि कभी-कभी ऐसे मामले होते हैं जहां गैर-सिस्टम कॉलर अपना स्वयं का प्रदान कर सकता है। उदाहरण के लिए, अतीत में bindBackupAgent() विधि में यह भेद्यता थी, जहां हमलावर पैरामीटर में अपना स्वयं का ApplicationInfo ऑब्जेक्ट uid और sourceDir मानों के साथ पास कर सकता था और उन्हें सिस्टम में वास्तव में इंस्टॉल किए गए ऐप्स के विरुद्ध जांचा नहीं जाता था, क्योंकि उस विधि को system_server द्वारा आंतरिक रूप से कॉल किए जाने का इरादा था, लेकिन यह adb shell के लिए उजागर थी
इस बार, हालांकि, मैंने RemoteViews के भीतर ApplicationInfo फ़ील्ड को बारीकी से देखा
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);
}
आइए इन मेथड्स में क्या हो रहा है, इस पर चर्चा करें
पहले हम 3-पैरामीटर वाले checkAndUpdateApkPaths को कॉल कर रहे हैं, जिसमें cacheWithCode को true और false दोनों पर सेट किया गया है। LoadedApk ऑब्जेक्ट को दो मोड में बनाया जा सकता है, या तो mIncludeCode के true या false होने के साथ, जो SDK के दृष्टिकोण से CONTEXT_INCLUDE_CODE फ्लैग के सेट होने या न होने के साथ Context से मेल खाता है
वह मेथड पहले ActivityThread.peekPackageInfo() का उपयोग करेगी, जो पहले से कैश किया गया LoadedApk इंस्टेंस लौटाएगी, इसलिए यदि ऐप ने पहले मेल खाते packageName और includeCode के साथ LoadedApk नहीं बनाया है, तो peekPackageInfo() null लौटाएगी और checkAndUpdateApkPaths() कुछ नहीं करेगी
RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths() पर वापस देखते हुए, हमारे पास वहाँ context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED) है, CONTEXT_INCLUDE_CODE फ्लैग निर्दिष्ट नहीं किया गया था (उस मेथड को तर्क में पारित किए गए कॉन्टेक्स्ट के मामले में भी ऐसा ही है) और इसलिए वह मेथड हमेशा mIncludeCode=false के साथ LoadedApk का उपयोग करेगी
इसलिए, RemoteViews के लिए कोड के साथ वर्जन अपडेट करना अनावश्यक है, क्योंकि RemoteViews हमेशा बिना कोड के Context का उपयोग करते हैं। कमिट संदेश केवल यह कहता है कि दो स्थान हैं जहाँ ApplicationInfo कैश किया जा सकता है और मुझे लगता है कि कोड के साथ वर्जन अपडेट करने को हटाने से यहाँ समस्याएँ नहीं होंगी (क्योंकि checkAndUpdateApkPaths() केवल AppWidgetHostView और RemoteViews द्वारा उपयोग किया जाता है)। हालाँकि इसका प्रतिवाद यह है कि विभिन्न कैश को सिंक से बाहर होने की संभावना से बचा जाए और जबकि cacheWithCode=true के साथ कॉल को हटाना इस बग के सबसे गंभीर प्रभाव को समाप्त करता है, यह समस्या को पूरी तरह से ठीक नहीं करता है क्योंकि केवल संसाधनों (कोड के बजाय) का संशोधन अभी भी हमलावर के लिए मूल्यवान हो सकता है
LoadedApk.updateApplicationInfo()अब तक प्रस्तुत कोड केवल RemoteViews (विजेट्स, नोटिफिकेशन, आदि) के लिए उपयोग किया जाता है, लेकिन अब हम LoadedApk.updateApplicationInfo() में प्रवेश करेंगे जो नया स्प्लिट इंस्टॉल होने के बाद चल रहे ऐप प्रोसेस को अपडेट करने के लिए भी उपयोग किया जाता है (उदाहरण के लिए Play Feature Delivery ऑन-डिमांड डिलीवरी का उपयोग करते समय)
अब RemoteViews से अविश्वसनीय ApplicationInfo ऑब्जेक्ट updateApplicationInfo() को पारित किया जाएगा, आइए देखें कि वह मेथड क्या करती है (स्निपेट स्रोत)```java
public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
@Nullable List oldPaths) {
if (!setApplicationInfo(aInfo)) {
return;
}
आइए उस `setApplicationInfo()` [(स्निपेट स्रोत)](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;
}
createTimestamp फ़ील्ड सामान्यतः SystemClock.uptimeMillis() पर सेट होता है, लेकिन चूंकि यह ऑब्जेक्ट हमलावर से आ रहा है, इसका मतलब है कि हमलावर आगे के updateApplicationInfo() कॉल्स को निष्पादित होने से रोकने के लिए भविष्य का मान प्रदान कर सकता है
updateApplicationInfo() पर वापस लौटते हुए (स्निपेट स्रोत)```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);
`addedPaths` की सूची बनाई जाती है: नए split की स्थापना के मामले में `oldPaths` में उन paths की सूची होगी जो नए split की स्थापना से पहले उपयोग में थे और `addedPaths` में उन `.apk` फाइलों की सूची होगी जिन्हें मौजूदा `ClassLoader` में जोड़ा जाना है, हालाँकि `checkAndUpdateApkPaths()` के मामले में `oldPaths` बिल्कुल उसी `ApplicationInfo` ऑब्जेक्ट से बनाया जाएगा और इसलिए `addedPaths` खाली होगा और हमलावर यहाँ मौजूदा `ClassLoader` में नए paths नहीं जोड़ पाएगा
`createOrUpdateClassLoaderLocked()` एक लंबी विधि है, लेकिन यहाँ केवल कुछ चीज़ें दिलचस्प हैं:
* [यदि `mIncludeCode` `false` है, तो केवल `ClassLoader` बनाना है बिना किसी `apk`/`dex` फाइल का संदर्भ दिए](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=968-990;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)
* सबसे खतरनाक चीज़ उन paths का उपयोग करके नया `ClassLoader` बनाना है जो अभी-अभी हमलावर-नियंत्रित `ApplicationInfo` का उपयोग करके सेट किए गए थे, हालाँकि ऐसा केवल तभी होगा जब [`mDefaultClassLoader` `null` हो](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1005;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), जिसका मतलब है कि यह उस `LoadedApk` इंस्टेंस पर पहला `createOrUpdateClassLoaderLocked()` है (`mDefaultClassLoader` फ़ील्ड [`AppComponentFactory.instantiateClassLoader()`](https://developer.android.com/reference/android/app/AppComponentFactory#instantiateClassLoader(java.lang.ClassLoader,%20android.content.pm.ApplicationInfo)) लागू करने से पहले उपयोग किए गए `ClassLoader` के इंस्टेंस को संदर्भित करता है)
* उस `ClassLoader` के native library search path में नए paths जोड़ना भी है [](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1050-1058;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), हालाँकि उसका शोषण करने के लिए पीड़ित ऐप को `System.loadLibrary()` करना होगा जो सामान्यतः मौजूद नहीं होता
* और `addedPaths` तर्क से मौजूदा `ClassLoader` में `apk`/`dex` paths जोड़ना भी है [](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1060-1065;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1), हालाँकि `RemoteViews` से कोड पथ पर `addedPaths` खाली होगा
`updateApplicationInfo()` पर फिर से लौटते हुए, अभी भी एक प्रासंगिक चीज़ है जो यह विधि करती है, [नए `mResDir` मान का उपयोग करने वाले इंस्टेंस के साथ `LoadedApk.mResources` को बदलना](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=382;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)। यह ध्यान दिया जाना चाहिए कि यह नया इंस्टेंस है, पहले से मौजूद इंस्टेंस को अपडेट नहीं करता। नवनिर्मित `Context`s इस नए `Resources` इंस्टेंस को लौटाएंगे/उपयोग करेंगे, हालाँकि पहले से बनाए गए कोई भी `Context`s पुराने `Resources` का उपयोग करते रहेंगे
# प्रभाव सारांश
उपरोक्त अनुभागों को सारांशित करने के लिए, जब भी पीड़ित प्रक्रिया `RemoteViews` लागू करती है, यह भेद्यता अनुमति देती है:
* उस प्रक्रिया के भीतर नवनिर्मित Activities के [`Resources` (स्थानीयकरण स्ट्रिंग्स, लेआउट, आदि)](https://developer.android.com/guide/topics/resources/providing-resources) को बदलना
* `System.loadLibrary()` द्वारा उपयोग किए जाने वाले native library search path में जोड़ना, हालाँकि उसका शोषण करने के लिए पीड़ित को `System.loadLibrary()` को उस लाइब्रेरी का नाम पास करके कॉल करना होगा जो सामान्यतः अनुपस्थित है, जो संभावना नहीं है
* मनमाना Java कोड लोड करना यदि पीड़ित प्रक्रिया ने `createPackageContext(CONTEXT_INCLUDE_CODE)` का उपयोग किया है, लेकिन उस `Context` पर [`getClassLoader()`](https://developer.android.com/reference/android/content/Context#getClassLoader()) को कॉल नहीं किया है, हालाँकि चूँकि कोड लोड करना `CONTEXT_INCLUDE_CODE` फ्लैग का उपयोग करने का कारण है, यह भी स्वाभाविक रूप से होने की संभावना नहीं है
अब, जबकि इस मामले में Java कोड लोड करने का मौका स्वाभाविक रूप से होने की संभावना नहीं है, मैं इसे ट्रिगर करने में सक्षम था
# `WebView` लोड करना
आधुनिक Android संस्करणों पर, `WebView` सिस्टम का हिस्सा नहीं है, बल्कि सामान्य `apk` से लोड किया जाता है जो [सिस्टम कॉन्फ़िगरेशन](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/xml/config_webview_packages.xml) में परिभाषित है और [या तो सिस्टम ऐप है या सिस्टम के भीतर परिभाषित हस्ताक्षर से मेल खाने वाला हस्ताक्षर है](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)
उस `apk` को लोड करना [नया `Context` बनाकर, `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)
आइए अब उस विधि के कॉलर को देखें [(स्निपेट स्रोत)](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() एक विधि है जो Context बनाती है, Resources.registerResourcePaths() हमारे लिए देरी शुरू करने का एकमात्र अवसर है ताकि हम checkAndUpdateApkPaths() को दूसरे थ्रेड में कॉल कर सकें और मनमाना Java कोड लोड कर सकें, क्योंकि webViewContext.getClassLoader() उस विंडो का अंत है जहाँ LoadedApk मौजूद है जिसमें mIncludeCode true है और mDefaultClassLoader null है
Resources.registerResourcePaths() appendLibAssetsLocked() तक पहुँचेगा, जो सिंगलटन के mResourceImpls फ़ील्ड पर पुनरावृत्ति करेगा, जिसमें वे सभी संसाधन शामिल हैं जो इस प्रक्रिया में लोड किए गए थे और इसलिए इसमें RemoteViews के माध्यम से लगाए गए ApplicationInfo का उपयोग करके बनाए गए संसाधन शामिल हो सकते हैं
मेरा प्रारंभिक विचार ApplicationInfo में बड़ी संख्या में ओवरले पथ डालना था जिन सभी का hashCode() समान हो, जो createNewResourceKeyIfNeeded() कॉल द्वारा डीडुप्लिकेशन को धीमा कर देगा और जबकि इंटरप्रेटर का उपयोग करते समय (उदाहरण के लिए डीबगर का उपयोग करते समय या नए इंस्टॉल किए गए ऐप के भीतर) यह महत्वपूर्ण देरी थी, जब रनटाइम ने अनुकूलन किए थे तो हैश टकराव से उत्पन्न देरी महत्वपूर्ण नहीं थी, हालाँकि एक और धीमा होने का कारण सामने आया: चूँकि ये ओवरले पथ मौजूदा फ़ाइलों की ओर इशारा नहीं करते थे, हर उस ओवरले के लिए जो लोड होने में विफल रहा, स्टैक ट्रेस के साथ एक लॉग संदेश मुद्रित किया गया था और व्यवहार में इससे धीमापन आया, जिससे इस रेस कंडीशन का शोषण संभव हो गया
ऐप्स नोटिफिकेशन में RemoteViews को SystemUI में पास कर सकते हैं। मेरा शोषण ऐप POST_NOTIFICATIONS अनुमति का अनुरोध करता है, जो मुझे लगता है कि उचित उपयोगकर्ता इंटरैक्शन है, हालाँकि कुछ नोटिफिकेशन के लिए उस आवश्यकता को दरकिनार करना संभव हो सकता है
सामान्य रूप से SystemUI WebView का उपयोग नहीं करता है और वास्तव में वह ऐसा कर भी नहीं सकता है, क्योंकि SystemUI डिवाइस-संरक्षित स्टोरेज का उपयोग करता है (अर्थात, वह स्टोरेज जो लॉक स्क्रीन क्रेडेंशियल द्वारा संरक्षित नहीं है) और WebView कार्यान्वयन उसे अस्वीकार करता है
हालाँकि, यह शोषण मुझे Resources को संशोधित करने की अनुमति देता है, इसलिए पहले मैंने SlicePermissionActivity द्वारा उपयोग किए जाने वाले लेआउट को संशोधित करके उसमें <WebView /> तत्व शामिल किया, फिर उस Activity को लॉन्च किया, जो SystemUI मुख्य थ्रेड पर WebView आरंभीकरण को ट्रिगर करता है
फिर मुझे दूसरे थ्रेड पर RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths() को कॉल करवाना होगा ताकि कोड लोड करने के लिए उपयोग किए जाने वाले पथ को बदला जा सके
सामान्य रूप से नोटिफिकेशन पोस्ट करने में SystemUI प्रक्रिया में कई थ्रेड शामिल होते हैं, जिसमें मुख्य थ्रेड भी शामिल है (इसलिए जब WebView आरंभीकृत हो रहा होता है तो मैं दूसरा नोटिफिकेशन पोस्ट नहीं कर सकता), हालाँकि RemoteViews का अनुप्रयोग अलग थ्रेड पर होता है और मैं उस ऑपरेशन को निलंबित कर सकता हूँ क्योंकि RemoteViews के भीतर ImageView है और उसे मेरे ContentProvider से छवि लोड करने के लिए कहा जाता है
यह शोषण SlicePermissionActivity में उपयोग किए गए लेआउट को बदलकर WebView लोड करता है। यदि कोड अपडेट करना संभव नहीं होता, तो SlicePermissionActivity पर हमला करना अभी भी मूल्यवान हो सकता था, क्योंकि हमलावर मूल संदेश को पूरी तरह से छिपाने के लिए लेआउट को बदल सकता था और उदाहरण के लिए चेंजलॉग दिखा सकता था, अनुमति बटन को "समझ गया" से बदल सकता था और अस्वीकार बटन को खाली स्ट्रिंग से बदल सकता था जिससे वह प्रभावी रूप से अदृश्य हो जाता। जबकि Slices फ्रेमवर्क अप्रचलित है, फिर भी Settings slices मौजूद हैं जो उदाहरण के लिए उपयोगकर्ता इंटरैक्शन के बिना मोबाइल डेटा सेटिंग बदल सकते हैं
SystemUI के भीतर एक अन्य महत्वपूर्ण अनुमति संकेत Media Projection पुष्टिकरण है, हालाँकि वह कमजोर नहीं निकला क्योंकि यह Dialog के लिए एप्लिकेशन संदर्भ का उपयोग करता है
Preference फ्रैगमेंट भी दिलचस्प हैं, क्योंकि आप Preference द्वारा लॉन्च किए जाने वाले Intent को परिभाषित कर सकते हैं, हालाँकि SystemUI के मामले में केवल preference Activities SystemUI Tuner और Demo Mode से संबंधित हैं, लेकिन ये नोटिफिकेशन दिखाने वाले प्रोसेस से अलग प्रोसेस में चलते हैं