
CVE-2023-45777 के लिए राइटअप और एक्सप्लॉइट, Android 13 पर AccountManagerService के अंदर Intent सत्यापन को बायपास करना, "Lazy Bundle" शमन के बावजूद
इस बार शुरुआत करते हैं उस पैच से जो Android Security Bulletin में CVE-2023-45777 के फिक्स के रूप में सामने आया था:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
if (intent != null && intent.getClass() != Intent.class) {
return false;
}
कुछ लोग इससे इतने उलझन में थे कि उन्होंने मुझसे पूछा, पहले मैंने उन्हें कुछ संकेत दिए थे और अब मैं इस मुद्दे के लिए पूरा विवरण प्रकाशित कर रहा हूँ
लेकिन पहले आइए कुछ संदर्भ प्रदान करें कि इस पैच में क्या हो रहा है
यह [`checkKeyIntent()` विधि](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d) में बदलाव है। यह विधि कई जाँचें करती है ताकि यह सुनिश्चित किया जा सके कि एप्लिकेशन द्वारा प्रदान किया गया `Intent` सिस्टम के लिए (सिस्टम के विशेषाधिकारों का उपयोग करके) लॉन्च करने के लिए सुरक्षित है
सबसे पहले, यह विधि `checkKeyIntentParceledCorrectly()` का उपयोग करती है जो उस `Bundle` को फिर से सीरियलाइज़ और डीसीरियलाइज़ करती है जिसे हम जाँच रहे हैं और यह जाँचती है कि उससे पहले `Bundle` से लिया गया `Intent` ऐसे चक्र के बाद `Bundle` से प्राप्त `Intent` से मेल खाता है या नहीं। चूँकि `Intent` का लॉन्च उस प्रक्रिया के अलावा अन्य सिस्टम ऐप प्रक्रियाओं में होता है जो सत्यापन करती है, इसलिए पहले [ऐसे `Bundle`-s का निर्माण संभव था जो `AccountManagerService` के अंदर सत्यापन के दौरान सुरक्षित दिखते थे, लेकिन अगली प्रक्रिया में भेजे जाने के बाद उनमें अलग `Intent` होता था](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482)। यह ऐसी स्थितियों का पता लगाने के लिए `Bundle` को अगली प्रक्रिया में भेजने का अनुकरण करता है
`checkKeyIntentParceledCorrectly()` के बाद हमारे पास `bundle.getParcelable()` कॉल है, जिसे यह पैच पुराने संस्करण से बदल देता है जो किसी भी ऑब्जेक्ट का निर्माण कर सकता था, उस संस्करण से जो यह सत्यापित करता है कि जो ऑब्जेक्ट डीसीरियलाइज़ होने वाला है वह उस प्रकार का है जो दूसरे पैरामीटर में निर्दिष्ट किया गया था
टाइप पैरामीटर वाला वह संस्करण Android 13 में, बड़े `Parcel`/`Bundle` सख्तीकरण के हिस्से के रूप में पेश किया गया था। विशेष रूप से, Android 13 से पहले जब `Bundle` प्रक्रियाओं के बीच भेजा जाता था, तो यह पूरे सीरियलाइज़्ड डेटा की कच्ची प्रति तब तक रखता था जब तक कि किसी आइटम तक पहुँच नहीं होती थी, जिस बिंदु पर हर मान डीसीरियलाइज़ हो जाता था। अब जब `Bundle` प्राप्त होने के बाद किसी मान तक पहली बार पहुँचा जाता है, तो केवल `String` कुंजियाँ और आदिम प्रकारों के मान डीसीरियलाइज़ होते हैं, जबकि गैर-आदिम मान `LazyValue`-s के रूप में छोड़ दिए जाते हैं, जिनकी लंबाई सीरियलाइज़्ड डेटा के हिस्से के रूप में संग्रहीत होती है ताकि यह सुनिश्चित किया जा सके कि भले ही सीरियलाइज़ेशन/डीसीरियलाइज़ेशन तर्क मेल न खाता हो, ऐसी असंगतियाँ अन्य प्रविष्टियों को प्रभावित न करें
गहराई में जाने से पहले, आइए `LazyValue` पर एक नज़र डालें: इसके स्रोत कोड में [हमारे पास एक अच्छी टिप्पणी है जो इसकी डेटा संरचना की व्याख्या करती है](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
| 4B | 4B |
mSource = Parcel{... | type | length | object | ...}
a b c d
length = d - c
mPosition = a
mLength = d - a
mPosition और mLength मूल Parcel में पूरे LazyValue डेटा के स्थान का वर्णन करते हैं, जिसमें type और length शामिल हैं। "length" (शुरुआत में "m" के बिना) Parcel में लिखे गए length मान को संदर्भित करता है और हेडर (type और length) को बाहर रखता है
यदि LazyValue युक्त Bundle को किसी अन्य प्रक्रिया में अग्रेषित किया जा रहा है, तो type और length फ़ील्ड सहित पूरा LazyValue Bundle.mParcelledData से गंतव्य Parcel में शब्दशः कॉपी किया जाता है
जब LazyValue द्वारा प्रदर्शित Bundle आइटम तक पहुँचा जाता है, तो Parcel को mPosition पर रिवाइंड किया जाता है और readValue() को कॉल किया जाता है। यदि bundle.getParcelable() को type तर्क पारित किया जाता है, तो इसे readValue() तक प्रचारित किया जाता है जो यह सुनिश्चित करेगा कि अनपार्सल होने वाला type अपेक्षित है, साथ ही अनपार्सल करने के बाद सत्यापित करेगा कि अनपार्सल किया गया मान type अपेक्षित है। अनपार्सल करने के बाद LazyValue को बदल दिया जाता है ताकि अगली बार जब Bundle को Parcel में लिखा जाए, तो मान को writeValue() के माध्यम से फिर से क्रमबद्ध किया जाए
टाइप किए गए Bundle.get*()/Parcel.read*() पैरामीटर का उपयोग मुख्य रूप से Parcel.readParcelableList() जैसी विधियों के लिए प्रासंगिक है, जो ArrayList लौटाता है और Java Type Erasure के कारण भले ही आपने List<SomeParcelableType> field = parcel.readParcelableList(); जैसा कुछ किया हो, <SomeParcelableType> भाग रनटाइम पर लागू नहीं किया गया था और ऐसी List में सिस्टम में उपलब्ध कोई भी Parcelable क्लास हो सकती थी, और इसलिए सिस्टम में उपलब्ध सभी createFromParcel/writeToParcel ऐसी List वाले type के क्रमबद्धीकरण/विक्रमबद्धीकरण के भाग के रूप में उपयोग किए जा सकते थे
आप Android Security and Privacy टीम की इन तंत्रों की शुरुआत के बारे में प्रस्तुति (स्लाइड्स, वीडियो) भी देख सकते हैं
हालाँकि यहाँ टाइप किए गए संस्करण का उपयोग अनावश्यक प्रतीत होता है, क्योंकि हम लौटाई गई वस्तु के type की भी स्पष्ट रूप से जाँच करते हैं। तो यहाँ क्या हो रहा है और यहाँ किस कमजोरी को ठीक किया जा रहा है?
शुरुआत से पैच को फिर से देखें
"intent" कुंजी के अंतर्गत विपार्सल किया गया मान एक Intent है
Intent ऑब्जेक्ट से बेमेल ट्रिगर किया जा सकता है तो हमारे पास बहुत बड़ी समस्या होगीIntent नहीं है
Bundle के अंदर एक Intent रखने की आवश्यकता होगी, लेकिन Parcelable का type किसी भी संभावित बेमेल से पहले के ऑफसेट पर सहेजा जाता है और LazyValue की length-उपसर्ग प्रणाली हमें writeToParcel/createFromParcel बेमेल के मामले में अगले कुंजी-मान जोड़े को संशोधित करने से रोकती हैतो, type तर्क के बिना bundle.getParcelable(AccountManager.KEY_INTENT) का कॉल यहाँ क्या खतरनाक काम कर सकता है?
[उत्तर अगले पैराग्राफ में है, पढ़ने से पहले अनुमान लगाने का प्रयास करें। यदि मेरे पास एक फ़र्सोना होता तो यह कुछ कला के लिए स्थान होता]
उत्तर एक असंबंधित createFromParcel() को कॉल करना है जो वास्तव में LazyValue के कच्चे डेटा को संशोधित करता है जो एक अलग कुंजी के अंतर्गत संग्रहीत है और अगली प्रक्रिया में शब्दशः पारित किया जाएगा
हमारे पास एक createFromParcel() कार्यान्वयन है जो वास्तव में प्रदान किए गए Parcel पर writeInt() को कॉल कर सकता है
लेकिन writeInt के गलती से रखे जाने के कारण नहीं, बल्कि अप्रतिबंधित रिफ्लेक्शन के कारण। विशेष रूप से PackageParser के अंदर हमारे पास निम्नलिखित कोड है:```java
final Class cls = (Class) Class.forName(componentName);
final Constructor cons = cls.getConstructor(Parcel.class);
intentsList = new ArrayList<>(N); for (int i = 0; i < N; ++i) { intentsList.add(cons.newInstance(in)); }
हमारे पास `Parcel` ऑब्जेक्ट हो सकता है जिसे `createFromParcel` में पास किया गया था, और इसे सिस्टम में उपलब्ध किसी भी `public` कंस्ट्रक्टर को पास किया जा सकता है जो एकल `Parcel` तर्क स्वीकार करता है
और फिर [हमारे पास निम्नलिखित कोड है](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=51-56;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb):```java
public PooledStringWriter(Parcel out) {
mOut = out;
mPool = new HashMap<>();
mStart = out.dataPosition();
out.writeInt(0); // reserve space for final pool size.
}
हमारे पास एक कंस्ट्रक्टर है जो दिए गए Parcel पर writeInt(0) कॉल करता है, हालाँकि कुछ चीज़ें हैं जो एक्सप्लॉइटेशन को जटिल बनाती हैं
सबसे पहले, हालाँकि यह सोर्स कोड में सीधे दिखाई नहीं देता है, newInstance() कॉल होते ही एक कास्ट किया जाता है और ClassCastException फेंक दी जाती है
मुझे ऐसी चीज़ चाहिए थी जो createFromParcel के दौरान किसी अन्य क्लास के createFromParcel को try ब्लॉक के अंदर कॉल करे और फिर पकड़ी गई Exception को आगे प्रचारित करने में विफल हो जाए
यह वह हिस्सा है जहाँ एक्सप्लॉइट वास्तव में शुद्ध AOSP पर काम नहीं करता है, मैंने Samsung विशिष्ट क्लास का उपयोग किया है
मैंने उस क्लास के प्रासंगिक भागों की प्रति इस रिपो में शामिल की है
इस रिपो में एक स्क्रिप्ट भी शामिल है जो इसे AOSP में एकीकृत करती है, इसलिए परीक्षण के लिए आप इसे चला सकते हैं (अपने AOSP चेकआउट का पथ तर्क के रूप में पास करें, जैसे ./make-aosp-buggy.sh /path/to/aosp), राइटअप की शुरुआत में वर्णित परिवर्तन को वापस करें और इस एक्सप्लॉइट को अपने AOSP बिल्ड के विरुद्ध चलाएँ
मैंने पहले Exception निगलने के लिए AOSP की OutputConfiguration क्लास का उपयोग किया था, Android 13 से पहले createFromParcel() में Exception को निगलना, अन्य Parcelable-स के निर्माण की अनुमति देने के साथ मिलकर, अपने आप में एक भेद्यता है, हालाँकि SemImageClipData के मामले में इन Android संस्करणों पर Exception निगलना मौजूद नहीं था
हालाँकि SemImageClipData और पहले उपयोग की गई OutputConfiguration के बीच एक महत्वपूर्ण अंतर है: भले ही SemImageClipData एक Exception को पकड़ता है, फिर भी यह गैर-शून्य ऑब्जेक्ट लौटाता है और यदि इसे बाद में किसी अन्य प्रकार में कास्ट किया जाता है, तो यह ClassCastException ट्रिगर करेगा जिससे हम बचने की कोशिश कर रहे हैं
Java Type Erasure का मतलब है कि जेनेरिक विधियाँ वास्तव में कॉलर द्वारा उपयोग किए गए जेनेरिक प्रकार के बारे में नहीं जानती हैं। यह आमतौर पर एक्सप्लॉइटेशन में मदद करता था```java // When we read some List, this actually didn't check if list contains only SomeParcelableType List myList = sourceParcel.readParcelableList();
// Above is why Android 13 has introduced typed methods that enforce type at runtime List myList = sourceParcel.readParcelableList(SomeParcelableType.class);
// If untyped method was used when reading, list can contain non-SomeParcelableType // items and they would be written without errors targetParcel.writeParcelableList(myList, 0);
// However if List contains non-SomeParcelableType item, this would throw during item access // (That however commonly didn't happen if we used Parcelable object only as container in gadget chain) SomeParcelableType myItem = myList.get(0);
इस बार टाइप इरेज़र हमारे पक्ष में काम नहीं आया। पहले हमारे पास एक मेथड थी जो वास्तव में रिफ्लेक्शन के माध्यम से कंस्ट्रक्टर को इनवोक करती थी```java
private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
// ...
final ArrayList<T> intentsList;
// ...
intentsList.add(cons.newInstance(in));
// ...
return intentsList;
}
यह विधि जेनेरिक पैरामीटर T रखती है। इससे कोई फर्क नहीं पड़ता कि कॉलर ने किस पैरामीटर प्रकार का उपयोग किया था, हालांकि इस विधि की घोषणा में <T extends IntentInfo> होने के कारण, newInstance() कॉल वाली पंक्ति intentsList.add((IntentInfo) cons.newInstance(in)); बन जाती है, भले ही newInstance() Object लौटाता है और ArrayList.add() तर्क के रूप में Object स्वीकार करता है। इसने उस कॉल को किसी ऐसे Parcelable के साथ लपेटने की आवश्यकता पैदा की जो Exception को निगल लेता है
फिर हमारे पास bundle.getParcelable() कॉल है```java
@Deprecated
@Nullable
public T getParcelable(@Nullable String key) {
unparcel();
Object o = getValue(key);
if (o == null) {
return null;
}
try {
return (T) o;
} catch (ClassCastException e) {
typeWarning(key, o, "Parcelable", e);
return null;
}
}
डीसेरियलाइज़ेशन प्रक्रिया `getValue()` कॉल द्वारा निष्पादित की जाती है, जो वास्तव में `createFromParcel()` कॉल की ओर ले जाती है। यदि वहाँ `ClassCastException` होता है तो इसे पकड़ा नहीं जाएगा। `getValue()` अब वही मान लौटाता है जो इस कुंजी के लिए [`parcel.readValue()`](https://developer.android.com/reference/android/os/Parcel#readValue(java.lang.ClassLoader)) के माध्यम से डीसेरियलाइज़ किया गया था।
हालाँकि यदि हम `SemImageClipData` को मान के रूप में रखते हैं, तो `try`-`catch` के अंतर्गत हम `T` में कास्ट करने का प्रयास करेंगे, जो इस मामले में `Parcelable` है जैसा कि मेथड की जेनेरिक घोषणा में बताया गया है। कॉलर इस मेथड को जेनेरिक के रूप में उपयोग करता है जिसमें `T` एक `Intent` है, हालाँकि `getParcelable()` यह नहीं जानता और `Intent` में कास्ट कॉलर में होती है और इसलिए `ClassCastException` `try` के बाहर फेंकी जाती है।
हम हालाँकि अपने `SemImageClipData` को `Parcelable[]` ऐरे के अंदर लपेट सकते हैं, फिर `getParcelable()` के भीतर `T` में कास्ट `Parcelable[]` को `Parcelable` में कास्ट करने में विफल होगी और `try` के अंतर्गत `ClassCastException` फेंकेगी, वह `Exception` लॉग होगी और `null` लौटाया जाएगा और फिर `checkKeyIntent()` द्वारा स्वीकार किया जाएगा।
# लेआउट
तो अब हमें `Bundle` के भीतर चीज़ों को संरेखित करने की आवश्यकता है ताकि `writeToParcel`/`createFromParcel` चक्र के बाद इसकी सामग्री वही हो जो हमने तैयार की थी।
लेकिन विशिष्ट "`Bundle` फेंगशुई" के विपरीत जहाँ ट्रिगर यह होता है कि `createFromParcel` पहले `writeToParcel` द्वारा किए गए डेटा से अधिक या कम पढ़ता है, यहाँ हमारे पास `writeInt(0)` है जो गैर-डीसेरियलाइज़्ड `LazyValue` के हिस्से को अधिलेखित कर रहा है।
तो यहाँ बताया गया है कि `Bundle.mParcelledData` कैसा दिखता है जब इसे पहली बार `AccountManagerService` द्वारा अनपार्सल किया जाता है (ऑफ़सेट `system_server` से जुड़े डीबगर के माध्यम से `dataPosition()` कॉल करके लिए गए हैं)।
<table>
<tr><th>ऑफ़सेट</th><th>मान</th><th>नोट</th></tr>
<tr><td>0</td><td>3</td><td>कुंजी-मान जोड़ों की संख्या</td></tr>
<tr><td>4</td><td>"intent"</td><td><code>Bundle</code> में पहली कुंजी, वह जो <code>getParcelable(AccountManager.KEY_INTENT)</code> द्वारा एक्सेस की जाएगी</td></tr>
<tr><td>24</td><td>16</td><td>पहला <code>LazyValue</code> यहाँ शुरू होता है, प्रकार <code>VAL_PARCELABLEARRAY</code> है</td></tr>
<tr><td>28</td><td>340</td><td><code>LazyValue</code> की घोषित लंबाई, <code>Bundle</code> में अगली कुंजी खोजने के लिए उपयोग की जाती है। हमारे <code>LazyValue</code> की वास्तव में पढ़े जाने के बाद यह आकार नहीं होगा, लेकिन <code>LazyValue.apply</code> <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/Parcel.java;l=4501-4505;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">इसे <code>Slog.wtfStack()</code> के माध्यम से रिपोर्ट करता है</a> जो <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Slog.java;l=230-235;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">फेंकता नहीं है</a></td></tr>
<tr><td>32</td><td>1</td><td><code>Parcelable[]</code> ऐरे की लंबाई, ऐरे में केवल एक आइटम है और यह मौजूद है ताकि <code>ClassCastException</code> उस <code>try</code> ब्लॉक के अंदर हो जो <code>bundle.getParcelable()</code> में है</td></tr>
<tr><td>36</td><td>"com.samsung.android.<br>content.clipboard.data.<br>SemImageClipData"</td><td><code>Parcelable</code> वर्ग का नाम, यह रैपर वर्ग है जो Exception को निगल जाएगा</td></tr>
<tr><td>160</td><td>2</td><td><code>createClipBoardData()</code> द्वारा उपयोग किया जाने वाला प्रकार टैग</td></tr>
<tr><td>164</td><td></td><td>वे आइटम जो <code>SemImageClipData</code> सुपरक्लास कंस्ट्रक्टर द्वारा पढ़े जाते हैं (जिसमें <code>readParcelable()</code> कॉल शामिल है, हालाँकि वह <code>try</code> ब्लॉक के बाहर होता है)। वास्तव में प्रासंगिक नहीं, लेकिन <code>createFromParcel()</code> के दिलचस्प हिस्से तक पहुँचने से पहले हमें उनसे गुजरना होगा</td></tr>
<tr><td>252</td><td></td><td><code>SemImageClipData.readFromSource()</code> द्वारा पढ़ा गया डेटा</td></tr>
<tr><td>272</td><td>"android.content.pm.<br>PackageParser$Activity"</td><td><code>mExtraParcelFd = in.readParcelable()</code> द्वारा पढ़े गए <code>Parcelable</code> का नाम। प्रकार मेल नहीं खाता, हालाँकि कास्ट होने से पहले एक Exception वैसे भी फेंकी जाएगी</td></tr>
<tr><td>360</td><td></td><td><code>PackageParser$Component</code> के <code>className</code> और <code>metadata</code> फ़ील्ड</td></tr>
<tr><td>368</td><td>1</td><td><code>createIntentsList()</code> में आइटमों की संख्या</td></tr>
<tr><td>372</td><td>"android.os.<br>PooledStringWriter"</td><td>उस वर्ग का नाम जिसे हम <code>Class.forName().getConstructor(Parcel.class).newInstance()</code> के माध्यम से इंस्टेंटिएट करेंगे। इस स्थिति में पहला <code>LazyValue</code> समाप्त होता है, हालाँकि इसका पार्सिंग जारी रहता है क्योंकि <code>readValue()</code> अंत तक नहीं पहुँचा। प्रारंभिक <code>unparcel()</code> के दौरान <code>Bundle</code> में दूसरी कुंजी के रूप में भी व्याख्या की गई</td></tr>
<tr><td>436</td><td>4</td><td>दूसरा <code>LazyValue</code> यहाँ शुरू होता है, यह 4 <code>VAL_PARCELABLE</code> है जिसके लिए <code>Parcel.isLengthPrefixed()</code> <code>true</code> लौटाएगा। यह मान बाद में <code>PooledStringWriter</code> कंस्ट्रक्टर द्वारा अधिलेखित कर दिया जाता है, जिसके बाद एक Exception फेंकी जाती है और <code>getParcelable(AccountManager.KEY_INTENT)</code> समाप्त होता है</td></tr>
<tr><td>440</td><td>240</td><td><code>LazyValue</code> की लंबाई जिसका प्रकार <code>VAL_PARCELABLE</code> घोषित किया गया था, इसका उपयोग अगली प्रविष्टि की स्थिति निर्धारित करने और पुनः-सीरियलाइज़ेशन के दौरान लक्ष्य <code>Bundle</code> में कितना डेटा कॉपी करने की आवश्यकता है, यह निर्धारित करने के लिए किया जाता है। यह <code>LazyValue</code> वास्तव में अनपार्सल नहीं किया जाता और कच्चे डेटा कंटेनर के रूप में उपयोग किया जाता है</td></tr>
<tr><td>684</td><td>"1&y~pw"</td><td rowspan="2">तीसरी कुंजी/मान जोड़ी, कुंजी यादृच्छिक रूप से उत्पन्न होती है ताकि Java <code>hashCode()</code> पहले उपयोग किए गए लोगों से ऊपर हो ( <code>ArrayMap</code> में संग्रहीत आइटम कुंजी के आरोही <code>hashCode()</code> द्वारा क्रमबद्ध होते हैं और यही वह क्रम है जिसमें <code>Bundle</code> के आइटम <code>Parcel</code> में लिखे जाएंगे)। यह कुंजी-मान जोड़ी यहाँ केवल लिखे गए जोड़ों की कुल संख्या बढ़ाने के लिए मौजूद है, क्योंकि यही पढ़े जाने वाले जोड़ों की संख्या होगी, भले ही यह जोड़ी वास्तव में पढ़ी नहीं जाएगी</td></tr>
<tr><td>704</td><td>-1 (<code>VAL_NULL</code>)</td></tr>
</table>
फिर, जब Bundle को फिर से सीरियलाइज़ किया जाता है, तो यह ऐसा दिखता है:
<table>
<tr><th>ऑफ़सेट</th><th>मान</th><th>नोट</th></tr>
<tr><td>0</td><td>3</td><td>कुंजी-मान जोड़ों की संख्या</td></tr>
<tr><td>4</td><td>"intent"</td><td><code>Bundle</code> में पहली कुंजी</td></tr>
<tr><td>24</td><td>16</td><td><code>VAL_PARCELABLEARRAY</code>, पहले डीसेरियलाइज़ किया गया <code>Parcelable[]</code> ऐरे अब फिर से सीरियलाइज़ किया जा रहा है</td></tr>
<tr><td>28</td><td>196</td><td><code>LazyValue</code> की लंबाई, यानी हमारा लपेटा हुआ <code>SemImageClipData</code> ऑब्जेक्ट। यह लंबाई मेरे मॉक <code>SemImageClipData</code> के साथ निष्पादन से ली गई है और इसलिए इस बिंदु से प्रस्तुत ऑफ़सेट वास्तविक Samsung डिवाइस पर दिखाई देने वाले ऑफ़सेट से मेल नहीं खाएंगे, हालाँकि यह <code>LazyValue</code> फिर से डीसेरियलाइज़ नहीं होगा इसलिए एक्सप्लॉइट निष्पादन के लिए यह मायने नहीं रखता</td></tr>
<tr><td>224</td><td>"android.os.<br>PooledStringWriter"</td><td><code>Bundle</code> में दूसरी कुंजी</td></tr>
<tr><td>288</td><td>0</td><td>दूसरा <code>LazyValue</code> यहाँ शुरू होता है, <code>"android.os.PooledStringWriter"</code> कुंजी के अंतर्गत आइटम एक्सेस नहीं किया गया था, इसलिए यह <code>LazyValue</code> मूल डेटा से कॉपी किया जा रहा है, हालाँकि प्रकार टैग <code>PooledStringWriter</code> कंस्ट्रक्टर द्वारा किए गए <code>writeInt(0)</code> कॉल द्वारा अधिलेखित कर दिया गया था और लक्ष्य प्रक्रिया तक पहुँचने पर इसे अब <code>LazyValue</code> के रूप में व्याख्या नहीं किया जाता</td></tr>
<tr><td>292</td><td>240</td><td>यह दूसरे <code>LazyValue</code> की लंबाई थी जो मूल <code>Bundle</code> से कॉपी की गई थी, हालाँकि चूँकि प्रकार टैग <code>writeInt(0)</code> के साथ अधिलेखित कर दिया गया था, जो <code>VAL_STRING</code> है, यह मान अब <code>readString()</code> के माध्यम से पढ़ा जा रहा है। पहले, <code>LazyValue</code> के लिए, लंबाई बाइट्स में व्यक्त की गई थी, लेकिन अब, <code>String</code> के लिए, यह दो-बाइट वर्णों में व्यक्त की गई है। स्रोत <code>Parcel</code> में उसके लिए पर्याप्त डेटा नहीं है, इसलिए <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/Parcel.cpp;l=2221-2226;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">नेटिव <code>parcel->readString16Inplace()</code> लंबाई पढ़ने के बाद विफल हो जाता है</a>, हालाँकि यह Java पक्ष पर Exception नहीं पैदा करता</td></tr>
<tr><td>296</td><td>"intent"</td><td><code>Bundle</code> में "तीसरी" कुंजी। वास्तव में पहली कुंजी को अधिलेखित करती है: चूँकि <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=651-659;drc=584140c83a456b5de99880b440c2d5dfc3c70506">"intent" का <code>hashCode()</code> पहले देखी गई कुंजी से छोटा है, <code>ArrayMap.append()</code> मेथड <code>put()</code> का उपयोग करती है जो मानों को बदलने की अनुमति देती है</a>, अन्यथा हमारे पास <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=667-675;drc=584140c83a456b5de99880b440c2d5dfc3c70506">डुप्लिकेट कुंजी होती जिसे बाद में <code>validate()</code> द्वारा अस्वीकार कर दिया जाता</a></td></tr>
<tr><td>316</td><td>4</td><td><code>VAL_PARCELABLE</code>, यहाँ वास्तविक <code>Intent</code> युक्त <code>LazyValue</code> शुरू होता है जो प्रारंभ किया जाएगा</td></tr>
<tr><td>536</td><td>"1&y~pw"</td><td>पैडिंग आइटम जो लिखा गया था लेकिन पढ़ा नहीं जाता क्योंकि सभी 3 कुंजी-मान जोड़े पहले ही पढ़े जा चुके हैं। <a href="https://cs.android.com/android/_/android/platform/system/tools/aidl/+/96a02f50fdfa4d20aa46ae2dde927257eac46d4a">AIDL इंटरफेस के विपरीत</a>, <code>Bundle</code> पर कोई <code>enforceNoDataAvail()</code> जाँच नहीं की जाती (लेकिन भले ही होती, यह <a href="https://github.com/michalbednarski/ReparcelBug2/issues/3">अपेक्षित लंबाई निर्दिष्ट करने वाली डमी प्रविष्टि डालकर बायपास किया जा सकता था</a>)</td></tr>
</table>
# यह दो बार कैसे हुआ
आइए अब चार पैच पर चर्चा करें, जिनमें से दो उस भेद्यता को ठीक करते हैं जिसके बारे में यह राइटअप है।
* CVE-2023-20944 ([बुलेटिन](https://source.android.com/docs/security/bulletin/2023-02-01#framework), [पैच](https://android.googlesource.com/platform/frameworks/base/+/d0bc9026e2e62e09fa88c1bcbf1dc1c3fb001375%5E%21/)): यह मेरे द्वारा पाई गई एक और भेद्यता है। इसी तरह, पैच यह स्पष्ट नहीं करता कि इसका शोषण कैसे किया जाएगा, लेकिन <a href="https://konata.github.io/posts/creator-mismatch/">ऐसा लगता है कि किसी और ने इसे समझ लिया है (चीनी में ब्लॉग पोस्ट)</a>
* CVE-2023-21098 ([बुलेटिन](https://source.android.com/docs/security/bulletin/2023-04-01#framework), [पैच](https://android.googlesource.com/platform/frameworks/base/+/107e6377328486fca55131ea06ca9d6a3c1585e0%5E%21/)): यह पहली बार है जब मैंने यहाँ प्रस्तुत एक्सप्लॉइट की रिपोर्ट की। वह पैच Android 13 से पहले के संस्करणों पर लागू होने वाले <code>checkKeyIntentParceledCorrectly()</code> बायपास के लिए भी फिक्स पेश करता है।
* CVE-2023-35669 ([बुलेटिन](https://source.android.com/docs/security/bulletin/2023-09-01#framework), [पैच](https://android.googlesource.com/platform/frameworks/base/+/f810d81839af38ee121c446105ca67cb12992fc6%5E%21/)): यह मेरी रिपोर्ट के जवाब में नहीं है, लेकिन मुझे लगता है कि यह उसी समस्या को ठीक करने के लिए बनाया गया था जिसके बारे में CVE-2023-20944 था, लेकिन उन मामलों के लिए जहाँ <code>AccountManager.KEY_INTENT</code> को <code>ChooseTypeAndAccountActivity</code> के अलावा अन्य Activities द्वारा लॉन्च किया जाता है (उदाहरण के लिए <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java;l=95-107;drc=32813a2bef49b172aed89122b4eb50bf14026ddc"><code>AddAccountSettings</code></a>, जिसे मैंने पहली बार बग रिपोर्ट करते समय चूक गया था)। इस परिवर्तन ने टाइप किए गए <code>bundle.getParcelable()</code> के उपयोग को बिना टाइप वाले और मैन्युअल <code>getClass() != Intent.class</code> जाँच से बदल दिया, जिसने वास्तव में CVE-2023-21098 के फिक्स को वापस कर दिया।
* CVE-2023-45777 ([बुलेटिन](https://source.android.com/docs/security/bulletin/2023-12-01#framework), [पैच](https://android.googlesource.com/platform/frameworks/base/+/f4644b55d36a549710ba35b6fb797ba744807da6%5E%21/)): यह दूसरी बार है जब मैंने इस एक्सप्लॉइट की रिपोर्ट की। पैच ने मैन्युअल <code>getClass() != Intent.class</code> जाँच को बनाए रखा, लेकिन उसके अतिरिक्त टाइप किए गए <code>bundle.getParcelable()</code> के उपयोग को वापस लाया, जो दोनों समस्याओं को ठीक करने का अच्छा तरीका है।
जबकि एक ही एक्सप्लॉइट CVE-2023-21098 और CVE-2023-45777 दोनों के लिए काम करता है, <code>checkKeyIntentParceledCorrectly()</code> को बायपास करने का तरीका अलग है।
CVE-2023-21098 के मामले में, <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=3519-3521;drc=cdd30b5c040ba7ebd0a1cc6009183ff602434fc0"><code>checkKeyIntent()</code> वास्तव में कॉल नहीं किया गया था यदि जाँचे गए <code>Bundle</code> में <code>Intent</code> नहीं था</a>। चूँकि <code>checkKeyIntent()</code> ही वह है जो <code>checkKeyIntentParceledCorrectly()</code> को कॉल करता है, उस स्थिति में जब मूल <code>Bundle</code> में <code>Intent</code> नहीं दिखता था, पुनः-सीरियलाइज़ेशन के बाद <code>Bundle</code> की जाँच नहीं की गई थी।
CVE-2023-45777 के मामले में, <code>checkKeyIntentParceledCorrectly()</code> को सही ढंग से कॉल किया गया था, हालाँकि <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4921-4929;drc=b0f6558fb36eb76df35c516ec5a65030a34a8734"><code>writeBundle()</code> वहाँ बिना प्रकार तर्क के <code>getParcelable()</code> कॉल से पहले हुआ (जब तक <code>Bundle</code> ने अपनी सामग्री नहीं बदली)</a>।