
CVE-2024-54756 के लिए अवधारणा का प्रमाण, एक भेद्यता जो मैंने GZDoom के ZScript स्क्रिप्टिंग इंजन में पाई।
GZDoom के (https://github.com/zdoom/gzdoom) ZScript कार्यक्षमता में मेरे द्वारा पाई गई एक मनमाना कोड निष्पादन भेद्यता का एक प्रमाण-अवधारणा। एक हमलावर एक दुर्भावनापूर्ण ZScript स्रोत फ़ाइल वाली PK3 फ़ाइल साझा कर सकता है और पीड़ित के पीसी तक पहुँच प्राप्त कर सकता है।
GZDoom डेव टीम के Rachael और Agent Ash को उनके त्वरित उत्तरों के लिए बहुत-बहुत धन्यवाद, और उन्हें तथा अन्य GZDoom डेवलपर्स को इस समस्या को शीघ्रता से संबोधित करने के लिए धन्यवाद!
4.13.0 और 4.13.1 के लिए काम करने की पुष्टि हुई है, और यह संभवतः पुराने संस्करणों के लिए भी काम करता है। किसी भी व्यक्ति से सावधान रहें जो आपको अपना WAD खेलने में सक्षम होने के लिए संस्करण 4.13.1 या उससे नीचे डाउनग्रेड करने के लिए कहता है।
यह PoC केवल लिनक्स पर काम करता है, लेकिन भेद्यता विंडोज़ पर भी मौजूद होने की संभावना है। ZDoom या LZDoom पर परीक्षण नहीं किया गया, लेकिन वहाँ भी भेद्यता मौजूद हो सकती है।
भेद्यता इस PoC के प्रकाशन से पहले डेवलपर्स को बताई गई थी और इसे संस्करण 4.13.2 में अब मौजूद नहीं होना चाहिए। मेरी जानकारी के अनुसार, इस संस्करण में कोई व्यावहारिक ब्रेकिंग परिवर्तन शामिल नहीं हैं।
यह PoC शैक्षिक उद्देश्यों के लिए बनाया और जारी किया गया है, ताकि गेम/स्क्रिप्टिंग इंजन डेवलपर्स समझ सकें कि भेद्यताएँ कैसे उत्पन्न हो सकती हैं और खिलाड़ी समझ सकें कि एक दुर्भावनापूर्ण गेम मॉड कैसा दिख सकता है। मैं इस PoC के किसी भी दुरुपयोग के लिए जिम्मेदार या उत्तरदायी नहीं हूँ। कृपया इसका उपयोग अपने साथी गेमर्स के पीसी से समझौता करने के लिए न करें; यह अवैध है (आपको मुझे यह बताने की आवश्यकता नहीं होनी चाहिए), और किसी के कंप्यूटर को वीडियो गेम के माध्यम से अपने कब्जे में लेना विशेष रूप से एक गंदा काम है।
इस PoC का उपयोग करने के लिए, इस रेपो को डाउनलोड करें और zscript.zs और MAPINFO युक्त एक PK3 फ़ाइल बनाएँ (जो वास्तव में .pk3 एक्सटेंशन वाली एक ज़िप फ़ाइल है):
git clone https://github.com/Chainmanner/GZDoom-Arbitrary-Code-Execution-via-ZScript-PoC
cd GZDoom-Arbitrary-Code-Execution-via-ZScript-PoC
zip PoC.pk3 zscript.zs MAPINFO
डिफ़ॉल्ट पेलोड पोर्ट 1337 पर लोकलहोस्ट पर एक रिवर्स शेल चलाना है। लिसनर शुरू करें:
nc -nvlp 1337
PoC को इस प्रकार चलाएँ:
gzdoom -iwad <your-doom-or-freedoom-wad> -file PoC.pk3
यदि यह काम करता है, तो अब आपके पास स्वयं के लिए एक रिवर्स शेल होना चाहिए।
यह PoC केवल लिनक्स के लिए है। यह पहले प्रयास में काम नहीं कर सकता है; बस इसे तब तक दोबारा आज़माएँ जब तक यह काम न करे।
नोट: यह मेरा पहला एक्सप्लॉइट राइटअप है और मैं अभी भी निम्न-स्तरीय राइटअप करने के अपने कौशल पर काम कर रहा हूँ। साथ ही, मैंने अपनी अधिकांश डिबगिंग GDB के साथ की, और दुर्भाग्य से मेरे पास अपनी व्याख्या को बेहतर ढंग से चित्रित करने के लिए कुछ मेमोरी डंप को सहेजने की अच्छी समझ नहीं थी। माफ़ करना! मेरा अगला राइटअप बेहतर होगा, मैं वादा करता हूँ।
GZDoom एक Doom सोर्स पोर्ट है जो प्रदर्शन और विस्तारशीलता के लिए डिज़ाइन किया गया है। इसकी शक्तिशाली विशेषताओं के कारण, कई शानदार WAD, मॉड और यहाँ तक कि व्यावसायिक टोटल कन्वर्ज़न भी बनाए गए हैं। दुर्भाग्य से, जहाँ जटिलता है, वहाँ भेद्यताओं का अवसर है, और इस मामले में ZScript स्क्रिप्टिंग इंजन में दो भेद्यताएँ मौजूद थीं जिन्होंने एक पूर्ण शोषण श्रृंखला को जन्म दिया।
यह हमला ASLR को पराजित करता है और स्टैक कैनरी को पराजित करने की आवश्यकता को टालता है। मुझे नहीं लगता कि Clang का CFI या शैडो स्टैक यहाँ मदद करता।
पहली और सबसे महत्वपूर्ण भेद्यता यह थी कि विशाल सारणियों को कैसे संभाला गया। यदि आप एक पर्याप्त छोटी सारणी आवंटित करते हैं, तो आवंटित मेमोरी का क्षेत्र शून्य से भरा होता है और अन्य वस्तुओं से ठीक से अलग होता है; अप्रारंभिक मेमोरी पढ़ने से कोई जानकारी प्राप्त नहीं की जा सकती है, और कोई वस्तु सारणी के साथ ओवरलैप नहीं होती है। हालाँकि, यदि आप एक विशाल सारणी आवंटित करते हैं - मान लीजिए, 1073741823 32-बिट शब्द या अधिक - तो आप सारणी के प्रारंभ बिंदु से 4 GiB तक संभावित रूप से अप्रारंभिक मेमोरी को पढ़ और लिख सकते हैं, जिससे हमलावर को अन्य वस्तुओं को सीधे संशोधित करने और ज्ञात ऑफसेट वाले पतों को ढूंढकर ASLR को पराजित करने की अनुमति मिलती है। इसके अतिरिक्त, इस बिंदु के बाद बनाई गई कोई भी अन्य सारणी बड़ी सारणी के साथ ओवरलैप होगी।
दूसरी भेद्यता मेमोरी मैप अनुमतियों में थी। तेज़ प्रदर्शन के लिए, ZScript कोड जब भी संभव हो x86 या x86-64 बाइटकोड में JIT-संकलित किया जाता है। इसके लिए, कोड को मेमोरी के एक क्षेत्र में लिखा जाना चाहिए, और मेमोरी के उस क्षेत्र को निष्पादित किया जाना चाहिए। हालाँकि, W^X नियम कहता है कि एक क्षेत्र या तो लिखने योग्य या निष्पादन योग्य होना चाहिए, लेकिन दोनों नहीं। यदि दोनों एक ही समय में लागू किए जाते हैं (क्षेत्र को लिखने योग्य बनाने, कोड लिखने, और फिर क्षेत्र को निष्पादन योग्य और अलिखनीय बनाने के बजाय), तो एक मनमाना लेखन प्रिमिटिव वाला हमलावर इसे मनमाना कोड निष्पादन तक बढ़ा सकता है; वे शेलकोड लिख सकते हैं और e.g. स्टैक पर रिटर्न पते को संशोधित करके उस पर कूद सकते हैं (यह मानते हुए कि हमलावर के पास मनमाना निष्पादन प्रिमिटिव नहीं है)। यदि आप GZDoom के चलने पर इसकी मेमोरी मैपिंग को देखते हैं, तो आप कई RWX क्षेत्र देख सकते हैं:
7fcd19700000-7fcd19800000 rwxp 00000000 00:00 0
7fcd1a100000-7fcd1a200000 rwxp 00000000 00:00 0
7fcd1eb00000-7fcd1ec00000 rwxp 00000000 00:00 0
इसलिए यदि मनमाना लेखन और मनमाना निष्पादन प्रिमिटिव उपलब्ध हैं, और हमलावर जानता है कि कोई RWX क्षेत्र कहाँ मौजूद है, तो वे मनमाना शेलकोड लिख और निष्पादित कर सकते हैं। JIT-संकलित कोड लिखते समय इन क्षेत्रों को RW- और फिर चलाने के लिए तैयार होने पर R-X बनाने से यह PoC रुक जाएगा, लेकिन यह किसी हमलावर को e.g. स्टैक (ROP) या हीप पर डेटा संशोधित करके कोड निष्पादन प्राप्त करने से नहीं रोकेगा।
इसके अतिरिक्त, एक उपयोगी गैजेट है। याद रखें कि विशाल सारणी आवंटित करने पर उसके बाद बनाई गई कोई भी अन्य सारणी ओवरलैप होगी? इसमें ऑब्जेक्ट पॉइंटर्स की सारणियाँ शामिल हैं। C++ ऑब्जेक्ट्स की तरह, ZScript ऑब्जेक्ट्स में चर और फ़ंक्शन पॉइंटर्स हो सकते हैं। मान लीजिए हमारे पास यह ऑब्जेक्ट है:
class WeirdObject
{
uint one;
uint two;
uint three;
uint four;
Function<clearscope void()> funcptr;
}
यदि हम एक WeirdObject इंस्टेंस के लिए एक पॉइंटर वाली सारणी बनाते हैं, तो हमलावर विशाल सारणी का उपयोग करके पॉइंटर को जहाँ चाहे बदल सकता है और ऑब्जेक्ट के फ़ील्ड तक पहुँच कर पॉइंट किए गए डेटा को बदल सकता है, जिससे हमें हीप से परे एक मनमाना पढ़ने/लिखने का प्रिमिटिव मिलता है। ZScript में पॉइंटर्स की जाँच यह सुनिश्चित करने के लिए की जाती है कि वे शून्य नहीं हैं, लेकिन यह सुनिश्चित करने के लिए नहीं कि वे समझदार हैं।
एक फ़ंक्शन पॉइंटर की उपस्थिति हमें एक मनमाना निष्पादन प्रिमिटिव भी देती है; हालाँकि, यह थोड़ा कम सीधा है, जिसमें वर्चुअल मशीन को संतुष्ट करने के लिए एक नकली VMFunction बनाने की आवश्यकता होती है। जैसे ही एक्सप्लॉइट कोड में किसी ZScript फ़ंक्शन का कॉल पेश किया जाता है, वह कोड अब JIT-संकलित नहीं होता है। फिर भी काम करता है, लेकिन यह डीबग और शोषण करने में थोड़ा अधिक सिरदर्द बन जाता है। इस भाग को करने का कोई बेहतर तरीका हो सकता है, लेकिन मैंने इसके बारे में जानने के लिए GZDoom के आंतरिक भागों का पर्याप्त अध्ययन नहीं किया।
एक बात ध्यान देने योग्य है: WeirdObject में विरासत में मिले सदस्य चर हैं, इसलिए पहला सदस्य ऑफसेट 0x28 पर शुरू होता है।
तो अब, हमारे पास निम्नलिखित उपकरण हैं:
हम एक्सप्लॉइट बनाने के लिए इन्हें कैसे श्रृंखलित करें?