
पाइइंस्टॉलर CVE-2019-16783 के लिए एक प्रूफ ऑफ कॉन्सेप्ट एक्सप्लॉइट
यह विंडोज PyInstaller संस्करण < 3.6 में --onefile विकल्प के लिए मौजूद कमजोरी का मेरा POC है। एक हमलावर पायथन इंटरप्रेटर DLL द्वारा आयातित एक DLL को हाईजैक करके कमांड निष्पादन और संभावित LPE प्राप्त कर सकता है, जिस DLL का उपयोग pyinstaller बाइनरी करता है। कमजोरी और शोषण प्रक्रिया का एक संक्षिप्त विवरण नीचे दिया गया है। कमजोरी खोजने और PagedOut #3 (पीडीएफ में पेज 55) पर अपनी खोज के बारे में लिखने के लिए Alter Solutions का धन्यवाद।
यह कमजोरी निष्पादित बाइनरी के रनटाइम के दौरान PyInstaller द्वारा एक निर्देशिका के कमजोर निर्माण के कारण हुई थी। PyInstaller उपयोगकर्ता के अस्थायी निर्देशिका में एक _MEIPIDX निर्देशिका बनाता है, और उसमें अलग-अलग चीजें डालता है, जैसे कि पायथन कोड को PE एक्जीक्यूटेबल में पैक करने के लिए उपयोग किया जाने वाला पायथन इंटरप्रेटर dll।
इस प्रक्रिया में समस्या यह थी कि NT AUTHORITY\SYSTEM के लिए बनाई गई निर्देशिका C:\Windows\Temp थी, जिससे कोई भी इसका अनुमान लगा सकता था और उसके अंदर लिख सकता था। इसलिए एक DLL हाईजैकिंग संभव होगी, उदाहरण के लिए जब पायथन इंटरप्रेटर निष्पादित होता है। यहाँ कमजोरी को ठीक करने वाला कमिट है। केवल कुछ मानक API फ़ंक्शन पर निर्भर रहने के बजाय, डेवलपर्स ने निर्देशिका निर्माण पर अधिक नियंत्रण रखने के लिए अपना स्वयं का फ़ंक्शन लागू किया।
चूंकि यह मेरा पहला POC था, मैं रास्ते में आई कुछ समस्याओं पर चर्चा करूंगा।
सबसे पहले, इस POC के लिए वातावरण स्थापित करना विशेष रूप से कठिन नहीं था, क्योंकि आपको केवल सही पैकेज संस्करण की आवश्यकता थी। हालाँकि जब मैंने इसे स्थापित किया, तो यह क्रैश हो गया
Traceback (most recent call last):
File "c:\users\ckrielle\appdata\local\programs\python\python38\lib\runpy.py", line 194, in _run_module_as_main
return _run_code(code, main_globals, None,
...
File "c:\users\ckrielle\appdata\local\programs\python\python38\lib\site-packages\PyInstaller\building\utils.py", line 653, in <genexpr>
strip_paths_in_code(const_co, new_filename)
File "c:\users\ckrielle\appdata\local\programs\python\python38\lib\site-packages\PyInstaller\building\utils.py", line 660, in strip_paths_in_code
return code_func(co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize,
TypeError: an integer is required (got type bytes)
थोड़ी खोजबीन के बाद, मैंने देखा कि मेरा पायथन संस्करण दोषपूर्ण था (मैं 3.8.10 का उपयोग कर रहा हूं)। मेरे पायथन संस्करण ने किसी भी अन्य 3.8.x संस्करण को स्थापित करना मुश्किल बना दिया, क्योंकि स्थापना पर इंस्टॉलर मेरा Python38 संस्करण ढूंढ लेगा और एक त्रुटि लौटाएगा। मैंने एक और 3.8 संस्करण बनाने का भी प्रयास किया, हालाँकि मैं नहीं कर सका क्योंकि इसके लिए विजुअल स्टूडियो 2015 की आवश्यकता थी, जबकि मेरे पास 2022 है। अंत में, मैंने पायथन 3.7.5 डाउनलोड करना चुना, जिसने पूरी तरह से काम किया। इसलिए मैंने 3.7 संस्करण के लिए एक आभासी वातावरण बनाया।
मैं समझ गया कि वातावरण स्थापित करना पहले से सेटअप होने से लेकर सही तरीके से सेटअप करने में संभावित रूप से बहुत समय लग सकता है। हालाँकि यह महत्वपूर्ण है, लेकिन यह संभावित रूप से हमारे लक्ष्य के वास्तविक शोषण के मज़े को कम कर सकता है।
हमारी शोषण प्रक्रिया में दो चरण हैं: PyInstaller पैकेज्ड प्रक्रिया की निर्देशिका ढूंढना, और वहां हमारा शोषण DLL लिखना। पहले भाग के लिए, हम मानक WINAPI फ़ंक्शन (CreateToolhelp32Snapshot, Process32First, Process32Next) के माध्यमից PID पा सकते हैं। यह सुचारू रूप से काम किया। उसके बाद, हमें _MEI निर्देशिका की अंतिम संख्या ढूंढनी होगी। मूल शोषण केवल GetFileAttributesA फ़ंक्शन का उपयोग करने और यह जांचने का सुझाव देता है कि वापस आया स्थिति कोड FILE_ATTRIBUTE_DIRECTORY है या नहीं। हालाँकि यह मेरे लिए काम नहीं किया। मेरा समाधान प्रत्येक निर्देशिका उम्मीदवार में एक फ़ाइल बनाना था, और यदि फ़ाइल सफलतापूर्वक बनाई गई, तो निर्देशिका मौजूद थी। किसी कारण से GetFileAttributesA से फ़ाइल के लिए लौटाई गई स्थिति FILE_ATTRIBUTE_ARCHIVE थी, इसलिए मैं इसके लिए जांच करता हूं। आगे बढ़ने से पहले, PID की खोज करते समय लूप इसलिए है क्योंकि हम अपने DLL को तब इंजेक्ट करना चाहते हैं जब लक्ष्य प्रक्रिया निष्पादित हो, ताकि लोड होने से पहले वे तैयार रहें।
DLL के लिए, हमें एक DLL को हाईजैक करना होगा जिसे पायथन इंटरप्रेटर (python37.dll) आयात करता है। इंटरप्रेटर द्वारा आयातित DLL को प्राप्त करने के लिए, हम PE-Bear के साथ DLL खोल सकते हैं। आयातित सिस्टम DLL में से एक version.dll है। इसलिए हम अपना स्वयं का DLL बना सकते हैं, और विंडोज लोडर के खोज क्रम का दुरुपयोग करके इसे PyInstaller पैकेज्ड प्रक्रिया की अस्थायी निर्देशिका में रख सकते हैं। इस तरह, जब वह DLL आयात करना चाहता है, तो वह सही DLL के बजाय हमारा दुर्भावनापूर्ण DLL आयात करेगा।

हालाँकि ऐसा करने से काम नहीं चलेगा। इसका कारण यह है कि पायथन इंटरप्रेटर version.dll से जिस फ़ंक्शन को कॉल करता है (विशेष रूप से छवि से VerQueryValueW) का आयात हल नहीं होता है। इसलिए प्रोग्राम क्रैश हो जाता है। इस समस्या को हल करने के लिए, हमें DLL प्रॉक्सीइंग करने की आवश्यकता है। संक्षेप में, हम अपने दुर्भावनापूर्ण DLL को मूल DLL के फ़ंक्शन को निर्यात करने के लिए कॉन्फ़िगर करते हैं, और हम मूल DLL का नाम बदलकर लाते हैं ताकि वह उसे लोड कर सके और वहां से फ़ंक्शन को कॉल कर सके। तो हमारे शोषण के लिए, हम एक DllMain के साथ एक दुर्भावनापूर्ण DLL संकलित करेंगे जो हमें अपना कोड निष्पादन प्राप्त करने देगा। हम सभी version.dll फ़ंक्शन को निर्यात करेंगे, और मूल सिस्टम version2.dll को कॉपी करके उसी निर्देशिका में रखेंगे। इस तरह, version.dll फ़ंक्शन कॉल को version2.dll को अग्रेषित कर सकता है। और एक बार जब हमारे पास ये DLL हों, तो हमें बस उन्हें प्रक्रिया की निर्देशिका में कॉपी करना होगा और कोड निष्पादन प्राप्त करने की प्रतीक्षा करनी होगी। बेहतर DLL प्रॉक्सीइंग/हाईजैकिंग स्पष्टीकरण के लिए, लिंक किया गया लेख पढ़ें।

हालाँकि पीछे मुड़कर देखने पर ये सभी कदम आसान और सीधे हैं, लेकिन POC विकसित करते समय ऐसा नहीं था। कुछ समय पहले, शोषण की प्रक्रिया को समझने में थोड़ा समय लगा, और एक बार जब मैंने कोडिंग शुरू की तो मैं इसे और अधिक समझने लगा। जहां तक DLL का सवाल है, मैंने इसे उस तरीके से स्वयं संकलित करने का प्रयास किया जैसा कि Alter Solutions POC रिपॉजिटरी में निहित था। उनके पास कोड निष्पादन के लिए एक अलग DLL है, और प्रॉक्सीइंग के लिए एक अलग, जो रनटाइम पर payload.dll लोड करता है (DllMain लोड होने पर कॉल किया जाता है)। हालाँकि मैं इसे ठीक से संकलित नहीं कर सका। अंत में उपरोक्त लेख पढ़ने के बाद, मैंने DLLProxyProject रिपॉजिटरी के साथ जाने का फैसला किया, जिसने मेरे इच्छित DLL को संकलित किया, और सामान्य रूप से प्रॉक्सीइंग उद्देश्यों के लिए DLL का उत्पादन करने के लिए इस्तेमाल किया जा सकता है। मैंने इसकी DLLMain.cpp फ़ाइल को कॉपी करने और इसे exports.h हेडर फ़ाइल के साथ संकलित करने का प्रयास किया। और यद्यपि यह चला, इसने एक त्रुटि उत्पन्न की:
Fatal Python error: init_sys_streams: can't initialize sys standard streams
OSError: [WinError 6] The handle is invalid
Current thread 0x00002ea8 (most recent call first):
यह त्रुटि Utils.cpp फ़ाइल के साथ टाली जा सकती है, इसलिए मैंने अपने DLL संकलन के लिए उस प्रोजेक्ट को रखने का फैसला किया (हालाँकि यह अच्छा होता अगर मैं इसे पूरी तरह से स्वयं करता)।
वातावरण स्थापित करने के बाद (NT AUTHORITY\SYSTEM शेल प्राप्त करने के लिए psexec का उपयोग किया), मैंने शोषण चलाया, लक्ष्य बाइनरी चलाई, और व्यवस्थापक के रूप में कोड निष्पादन प्राप्त किया।

यह एक बहुत अच्छा अनुभव था, और मुझे खुशी है कि मैं इससे गुज़रा। मैं वास्तव में अन्य CVE के लिए और POC बनाना चाहता हूं, और यह एक आदर्श पहला लक्ष्य था। कमजोरी का विवरण पढ़ना दिलचस्प था, क्योंकि मैं समझ गया कि स्वयं POC लागू करने की मुख्य कठिनाई उन चीजों के लिए रिक्त स्थान भरना है जो लेखक ने समझाई नहीं (चाहे जानबूझकर या नहीं), और यह समझना कि आप क्या पढ़ रहे हैं। यह एक सरल कमजोरी थी, इसलिए मैंने इसे समझने में बहुत अधिक प्रयास नहीं किया। निकट भविष्य में कुछ और करने के बाद, मैं एक मेमोरी करप्शन कमजोरी के लिए POC करने का प्रयास करूंगा।