
CVE-2022-37969 का ट्यूटोरियल जिसमें कर्नेल शोषण की पद्धति पर ध्यान केंद्रित किया गया है, न कि CVE के आंतरिक कारणों पर
यह Windows शोषण से संबंधित सामान्य पहलुओं को स्पष्ट करने के लिए बनाया गया था। यह CVE-2022-37969 पर लागू बुनियादी अवधारणाओं की व्याख्या करता है। अंतिम परिणाम एक कार्यशील PoC है। यह CVE के हर पहलू को स्पष्ट नहीं करता है, लेकिन पुन: प्रयोज्य कोड के टुकड़े प्रदान करता है और ऐसे तंत्रों की व्याख्या करता है जो कई सामान्य शोषणों में पाए जा सकते हैं।
लक्षित उपयोगकर्ता एक शुरुआती रिवर्स इंजीनियर, शोषण डेवलपर होगा जो कार्यशील प्रूफ ऑफ कॉन्सेप्ट स्रोत कोड की तलाश में है ताकि बुनियादी Windows आंतरिक को परीक्षण और समझ सके। यह आगे सीखने के लिए एक संदर्भ बिंदु प्रदान करता है।
आवश्यकताएँ: बुनियादी कर्नेल डिबगिंग, बुनियादी रिवर्स इंजीनियरिंग, बुनियादी Windows आंतरिक, c/c++ प्रोग्रामिंग कौशल
एक प्रोग्राम कोड का एक टुकड़ा है जो मशीन पर चल रहा है। आम तौर पर एक प्रोग्राम डेटा (इनपुट) प्राप्त करता है, इनपुट का उपयोग करके गणना करता है और डेटा (आउटपुट) उत्पन्न करता है। अधिकांश प्रोग्राम मनुष्यों द्वारा लिखे जाते हैं, और इस प्रकार उनमें बग होते हैं। एक बग कुछ स्रोत कोड द्वारा उत्पन्न होता है जो सही ढंग से नहीं लिखा गया था (प्रोग्रामर इनपुट के साथ कुछ करना चाहता था, परिणामस्वरूप कोड इच्छित परिणाम से भिन्न था)। अधिकांश बग उत्पाद लॉन्च करने से पहले ठीक कर दिए जाते हैं, लेकिन कुछ रह जाते हैं। ऐसा इसलिए होता है क्योंकि विभिन्न प्रकार के बग होते हैं, कुछ को दूसरों की तुलना में पहचानना कठिन होता है।
Windows एक कंप्यूटर प्रोग्राम है, इसे मनुष्यों द्वारा लिखा गया था, और इस प्रकार इसमें बग हैं। यह महत्वपूर्ण क्यों है? क्योंकि Windows सिस्टम ऐसे प्रोग्राम चला सकते हैं जो संवेदनशील डेटा जैसे बैंक खाते, स्वास्थ्य सेवा डेटाबेस, और अन्य को संचालित करते हैं। कुछ बग का उपयोग अवैध रूप से प्रतिबंधित डेटा तक पहुँच प्राप्त करने के लिए किया जा सकता है (यह एक शोषण के लिए एक अच्छा उपयोग मामला है)
बग कई प्रकार के होते हैं, कुछ उपयोगी होते हैं कुछ नहीं। आम तौर पर बग प्रोग्राम में इनपुट के कारण उत्पन्न होते हैं जो गलत लिखे गए कोड की पंक्तियों के साथ मिलकर एक दोषपूर्ण आउटपुट या प्रोग्राम का व्यवहार उत्पन्न करते हैं। उस इनपुट को ढूँढना सुरक्षा विशेषज्ञ (या हैकर) का काम है। अगला कदम परिणामी दोषपूर्ण आउटपुट/व्यवहार का मूल्यांकन करना और प्रश्न का उत्तर देना है "क्या इसका उपयोग उपयोगी तरीके से किया जा सकता है?" यहीं पर बग को विभिन्न श्रेणियों में वर्गीकृत किया जाता है। उदाहरण के लिए एक बग ऐसा व्यवहार उत्पन्न कर सकता है जो कुछ डेटा संरचनाओं को दूषित करता है और लक्ष्य कंप्यूटर को पुनरारंभ करने का कारण बनता है। इसकी उपयोगिता सीमित है। एक बग के कारण इनपुट एक मेमोरी क्षेत्र में लिखा जा सकता है जो प्रतिबंधित फ़ाइलों तक पहुँच अनुमतियों को नियंत्रित करता है। इस प्रकार का बग अधिक उपयोगी है।
तो सभी संभावित बगों के सेट से हैकर उस उपसमूह की खोज करता है जो उसके उद्देश्य के लिए सबसे उपयोगी है। सामान्य शब्दों में समस्या यह है "क्या मैं लक्ष्य प्रोग्राम को एक विशेष रूप से तैयार किया गया इनपुट दे सकता हूँ ताकि मैं सिस्टम को तोड़े बिना अपने एक्सेस के स्तर को बढ़ा सकूँ और लाभ उठा सकूँ?"
इस गैर-तकनीकी परिचय के बाद ट्यूटोरियल का दायरा तैयार किया जा सकता है: क्या हम एक Windows प्रोग्राम ढूँढ सकते हैं जो दोषपूर्ण इनपुट स्वीकार करता है और गलत डेवलपर कोड के परिणामस्वरूप अवैध रूप से हमारी अनुमतियों को सामान्य उपयोगकर्ता से प्रशासक तक बढ़ा सकता है?
लक्ष्य प्रोग्राम: Windows CLFS (सामान्य लॉग फ़ाइल सिस्टम ड्राइवर)
शोषण का नाम: CVE-2022-37969
प्रकार: स्थानीय विशेषाधिकार उन्नयन
असुरक्षित ISO डाउनलोड: यहाँ से डाउनलोड करें
Windows पता स्थान मोटे तौर पर उपयोगकर्ता-स्थान (सामान्य प्रोग्राम चलाने) और कर्नेल-स्थान (स्वयं ऑपरेटिंग सिस्टम और हार्डवेयर घटक सॉफ़्टवेयर-->ड्राइवर चलाने) में विभाजित है। एक सामान्य उपयोगकर्ता को कर्नेल स्थान तक नहीं पहुँचना चाहिए, लेकिन ऐसे तंत्र हैं जिनके द्वारा सामान्य-उपयोगकर्ता-प्रोग्राम कर्नेल कोड (सिस्टम कॉल, ड्राइवर प्रक्रियाओं) के कुछ भागों तक पहुँच सकते हैं। हमें पहुँच की आवश्यकता क्यों है? OS डिज़ाइनरों द्वारा प्रदान किए गए सुरक्षित और नियंत्रित तरीके से OS के साथ संवाद करने के लिए।
कुछ ड्राइवर उपयोगकर्ता द्वारा प्रदान किए गए डेटा इनपुट का उपयोग कर्नेल-स्थान-डेटा-संरचनाओं पर संचालन करने के लिए करते हैं। यदि इनपुट एक बग उत्पन्न करता है, तो कर्नेल दूषित हो सकता है। एक मामला सामान्य लॉग फ़ाइल सिस्टम ड्राइवर है। कुछ विशेष इनपुट का उपयोग करके हम ड्राइवर को कर्नेल-डेटा-संरचनाओं को बदलने के लिए मजबूर कर सकते हैं जो उपयोगकर्ता के लिए विशेषाधिकार पहुँच स्तर रखते हैं और सामान्य उपयोगकर्ता को प्रशासक से अधिलेखित कर सकते हैं।
प्रशासक के लिए विशेषाधिकार बढ़ाने के लिए क्या संशोधित करने की आवश्यकता है?
हम अंतिम लक्ष्य को ध्यान में रखते हुए शुरू करते हैं। Windows सिस्टम पर चलने वाली प्रत्येक प्रक्रिया के लिए _EPROCESS नामक एक कर्नेल डेटा संरचना के अंदर जानकारी संग्रहीत करता है। _Eprocess का उदाहरण
एक महत्वपूर्ण फ़ील्ड struct _EX_FAST_REF Token है। यह एक और डेटा संरचना है जो आगे उस संबंधित प्रक्रिया के विशेषाधिकार स्तर को संदर्भित करने वाले डेटा की ओर इंगित करती है। निम्नलिखित चित्र में सिस्टम प्रक्रिया में एक सिस्टम टोकन है और एक्सप्लोरर प्रक्रिया में एक सामान्य उपयोगकर्ता टोकन है।

इसलिए Explorer.exe के विशेषाधिकार को बढ़ाने के लिए हमें सिस्टम के _EPROCESS-->Token से मान को कॉपी करके Explorer के _EPROCESS-->Token में डालना होगा। हम सिस्टम टोकन को अपने स्वयं के प्रोग्राम के टोकन में कॉपी करके और उन्नत प्रक्रिया से एक कमांड प्रॉम्प्ट लॉन्च करके (चाइल्ड प्रक्रियाएँ पिता प्रक्रिया का टोकन प्राप्त करती हैं) कुछ ऐसा ही पूरा करेंगे।
इन क्रियाओं को पूरा करने के लिए हमें निम्नलिखित तंत्रों की आवश्यकता है:
परिचय: वर्षों में Windows की प्रकृति: जैसे-जैसे नई कमज़ोरियों की खोज हुई, Windows को उन्हें कम करने के लिए पैचिंग की आवश्यकता थी। साथ ही नई तकनीक के उद्भव के साथ, प्रतिस्पर्धी बने रहने के लिए Windows को अपडेट की आवश्यकता थी। एक महत्वपूर्ण आवश्यकता पुराने संस्करणों के साथ पिछड़ी संगतता थी। और कभी-कभी सुरक्षा अस्पष्टता के माध्यम से प्राप्त की जाती थी। डेटा संरचनाएँ, फ़ंक्शन परिभाषाएँ मैनुअल से हटा दी गईं, लेकिन कार्यक्षमता अभी भी बनी रही। रिवर्स इंजीनियरिंग द्वारा, शोधकर्ता उन कार्यक्षमताओं का विभिन्न उद्देश्यों के लिए उपयोग करने में सक्षम थे।
_EPROCESS का कर्नेल पता खोजने के लिए हम एक अप्रलेखित फ़ंक्शन का उपयोग करेंगे: NtQuerySystemInformation (पैरामीटर के लिए लिंक देखें)। SystemInformationClass पैरामीटर का उपयोग करके हम निर्दिष्ट कर सकते हैं कि हम किस प्रकार की जानकारी प्राप्त करना चाहते हैं। हम SystemExtendedHandleInformation मान (#define SystemExtendedHandleInformation 0x40) निर्दिष्ट करके सामान्य प्रक्रिया जानकारी प्राप्त करेंगे।
NtQuerySystemInformation का उपयोग करने में एक चेतावनी यह है कि हम पहले से नहीं जानते कि लौटाए गए डेटा की लंबाई क्या है, लेकिन NtQuerySystemInformation में एक तंत्र है जो मदद करता है। यदि इसे आवश्यक डेटा के लिए गलत आकार की सरणी के साथ कॉल किया जाता है, तो यह ERROR लौटाता है और सही डेटा आकार जो अनुरोध किया जाना चाहिए था। इसका उपयोग निम्नलिखित तरीके से प्रक्रिया जानकारी को सही ढंग से पढ़ने के लिए किया जा सकता है:
NtQuerySystemInformation को डमी SystemInformationLength पैरामीटर के साथ कॉल करेंReturnLength पैरामीटर मान को पढ़ेंSystemInformationLength मान के साथ NtQuerySystemInformation को फिर से कॉल करेंलौटाई गई डेटा संरचना PSYSTEM_HANDLE_INFORMATION_EX प्रकार की है। यह एक अप्रलेखित डेटा संरचना है। (लिंक देखें) जो एक SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX डेटा संरचना की ओर ले जाती है, जो Object फ़ील्ड में संबंधित प्रक्रिया के _Eprocess डेटा संरचना का कर्नेल पता रखती है।
इसलिए तर्क सभी PSYSTEM_HANDLE_INFORMATION_EX तत्वों पर पुनरावृत्ति करना, SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX के UniqueProcessId फ़ील्ड की वांछित प्रक्रिया PID से तुलना करना और कर्नेल में इसके _Eprocess पते को खोजने के लिए संबंधित Object फ़ील्ड का चयन करना होगा।
कोड का एक स्निपेट:

NtQuerySystemInformation को एक फ़ंक्शन के पॉइंटर के रूप में घोषित किया गया है और इसका पता रन टाइम पर loadlibrary और getprocaddress के माध्यम से गतिशील रूप से प्राप्त किया जाता है।
typedef NTSTATUS(WINAPI* ptr_NtQuerySystemInformation)(int, PVOID, ULONG, PULONG);ntdll=LoadLibrary(L"ntdll.dll");MyNtQuerySystemInformation = (ptr_NtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");टोकन को पढ़ने और लिखने के लिए, हमें clfsw32.sys और नेम्ड पाइप्स के अंदर कमज़ोरियों पर निर्भर रहना होगा।
यह कुछ हद तक एक ब्लैक बॉक्स है, अन्य लेखों में विस्तृत है, लेकिन इस कार्यक्षमता के लिए न्यूनतम ज्ञान समझाया जाएगा ताकि शोषण प्रक्रिया की बुनियादी समझ प्राप्त हो सके। पाइप अंतर-प्रक्रिया संचार तंत्र हैं। प्रक्रियाएँ पाइप का उपयोग करके एक-दूसरे को जानकारी पास कर सकती हैं। पाइप को कर्नेल डेटा संरचनाओं के रूप में दर्शाया जाता है जिनमें कुछ फ़ील्ड होते हैं जिन्हें उपयोगकर्ता स्थान से आबाद किया जा सकता है। एक उदाहरण पाइप विशेषताएँ होंगी।
(बाद में हम इसे CLSF कमज़ोरी से जोड़ेंगे ताकि उपयोगकर्ता-स्थान से कर्नेल-स्थान में मनमाना पढ़/लिख प्राप्त हो सके)
आगे पढ़ने के लिए कृपया fengshui-spraying-big-kids-pool देखें। तंत्र का एक सरलीकृत स्पष्टीकरण होगा:
कर्नेल में हम जिस आकार को आवंटित करना चाहते हैं, उसके संबंध में मेमोरी आवंटित करने के दो तरीके हैं: छोटे-पूल <4KB+हेडर वाली वस्तुओं के लिए और बड़े-पूल >4KB+हेडर वाली वस्तुओं के लिए। बड़े-पूल पृष्ठ महत्वपूर्ण हैं क्योंकि इसे उपयोगकर्ता-स्थान से गणना किया जा सकता है। इसका मतलब है कि एक सामान्य उपयोगकर्ता सभी प्रारंभ कर्नेल पतों को ढूँढ सकता है जिनमें बिग-पूल पृष्ठ होता है।
कैसे? प्रत्येक बिग-पूल पृष्ठ में Tag नामक एक फ़ील्ड होता है (जिसका उपयोग वहाँ संग्रहीत डेटा के प्रकार के बारे में जानकारी प्राप्त करने के लिए किया जा सकता है)। सिस्टम में सभी बिग-पूल पृष्ठों को NtQuerySystemInformation का उपयोग करके SystemBigPoolInformation को SystemInformationClass पैरामीटर के मान के रूप में गणना किया जा सकता है। फिर सभी पृष्ठों में से हम Tag द्वारा फ़िल्टर कर सकते हैं और उन बिग-पूल पृष्ठों का पता प्राप्त कर सकते हैं जिनमें हमारी रुचि है। उदाहरण के लिए CLFS 'Clfs' टैग के साथ बिग-पूल पृष्ठों का उपयोग करता है। हम कर्नेल में सभी पृष्ठों का पता प्राप्त कर सकते हैं जहाँ CLFS ऑब्जेक्ट आवंटित हैं।

पाइप पर लौटते हुए, हम उसी तरह काम कर सकते हैं, बिग-पूल तंत्र का उपयोग करने के लिए पर्याप्त बड़ा एक पाइप आवंटित कर सकते हैं, बिग पूल पृष्ठों की गणना कर सकते हैं, पाइप के विशिष्ट Tag की खोज कर सकते हैं और उन पृष्ठों को फ़िल्टर कर सकते हैं।
इस प्रकार हम उपयोगकर्ता स्थान में उस स्थान को लीक कर सकते हैं जहाँ कर्नेल ने हमारे पाइप आवंटित किए। कर्नेल में जाने वाले डेटा को नियंत्रित करने और उसे पढ़ने के बारे में क्या?
इसके लिए हम पाइप विशेषताओं पर भरोसा करते हैं। बिग-पूल टैग की तरह ही पाइप विशेषता एक सरणी है जिसमें पाइप का वर्णन करने वाली जानकारी हो सकती है (उपयोगकर्ता द्वारा भरी गई) अप्रलेखित फ़ंक्शन NtFsControlFile का उपयोग करके हम पाइप-विशेषता डेटा संरचना को मनमाने ढंग से पढ़ और लिख सकते हैं। अप्रलेखित होने के कारण, केवल एक प्रूफ ऑफ कॉन्सेप्ट प्रदान किया गया है जो एक PipeAttribute वेक्टर सेट करता है और फिर उसे पढ़ता है। पढ़ने, लिखने के लिए प्रिमिटिव में एकमात्र संशोधन इनपुट/आउटपुट बफर और उनके आकार की सामग्री को नियंत्रित करना है।
यहाँ हम बिग-पूल पृष्ठों (0x2000) का उपयोग करने के लिए पर्याप्त बड़ा एक पाइप आवंटित करते हैं, एक इनपुट और आउटपुट बफर को नियंत्रित मान पर सेट करते हैं। सावधानी: काम करने के लिए इनपुट मान के पहले 2 बाइट्स 0x5a 0x00 होने चाहिए

यहाँ हम वापस पढ़ते हैं जो हमने पहले कर्नेल में लिखा था, फिर से केवल आउटपुट बफर बदलते हैं

और परिणाम:

यहाँ कर्नेल में पाइप बिग-पूल पृष्ठ की सामग्री है: बिग-पूल पृष्ठों की क्वेरी करके हमने कर्नेल में पाइप डेटा संरचना की शुरुआत पाई। पते pipe_begin+0x20 पर हमें अपने इनपुट बफर+0x2 का एक पॉइंटर मिलता है।

जब हम पाइप विशेषता पढ़ने वाले फ़ंक्शन को कॉल करते हैं, तो OS निम्नलिखित करेगा:
यह उपयोगी क्यों है? कल्पना करें यदि हम pipe_begin+0x20 पर पॉइंटर को सिस्टम की प्रक्रिया सुरक्षा टोकन के स्थान से बदल सकते हैं और पाइप विशेषता पढ़ने को कॉल कर सकते हैं। हम उपयोगकर्ता स्थान में इसका मान लीक कर देंगे। प्रतिस्थापन CLFS.SYS कमज़ोरी के माध्यम से पूरा किया जाता है।
इस तकनीक को समझना शोषण को समझने के लिए महत्वपूर्ण है।
अधिकांश शोषण प्रकृति में नियतात्मक नहीं बल्कि संभाव्य होते हैं। भले ही कमज़ोरी का लाभ उठाने वाला कोड सही हो, शोषण काम नहीं कर सकता है। लक्ष्य प्रोग्राम को अस्थिर स्थिति में डालकर, शोषण डेवलपर को यह सुनिश्चित करना होगा कि शोषण निष्पादित करने के बाद, सिस्टम क्रैश न हो। एक प्रोग्राम की कल्पना करें जो शोषित होने पर किसी पते से पढ़ने की क्षमता देता है जो प्रोग्राम के अंदर एक चर मान पर निर्भर करता है।
उदा.:
मान लें कि target=0x1000000+ var_1&0xff+ var_2&0xff00।
हैकर target या var_1 या var_2 को नियंत्रित नहीं कर सकता।
लेकिन शोषण पते target से पढ़ने की क्षमता देता है।
हम 0x1000000 और 0x100FFFF के बीच कहीं से भी पढ़ सकते हैं, जो कुछ मामलों में उपयोगी हो सकता है या नहीं।
उदा2:
मान लें कि एक शोषण एक निश्चित पैटर्न से मेल खाने वाले पते से QWORD पढ़ने और सामग्री को अपने सुरक्षा टोकन मान में डालने की क्षमता देता है। मान लें कि हमने पहले System.exe के लिए सुरक्षा टोकन मान प्राप्त कर लिया है। हम विशेषाधिकार बढ़ाने के लिए शोषण का लाभ कैसे उठा सकते हैं?
read_addr=0x1000000+alfa&0xFFFF00
हम alfa पैरामीटर को नियंत्रित नहीं कर सकते
विषय से बाहर लेकिन बहुत महत्वपूर्ण: कर्नेल उस समय कर्नेल कोड चलाने वाली प्रक्रिया के अनुरूप उपयोगकर्ता स्थान तक पहुँच सकता है।
हम किस पते से पढ़ सकते हैं? ठीक है 0x1000000, 0x1000100(alfa=1),0x1000200(alfa=2),....,0x1FFFF00(alfa=ffff00).
शोषण के सफल होने के लिए प्रोग्रामर को निम्नलिखित करना चाहिए:
memory=virtualalloc(dest=0x1000000,size=0x1000000,....)for (i=0x1000000;i<0x2000000;i+=0x100) ((QWORD*)i)[0]=system_token_value;यह वास्तव में मेमोरी स्प्रेइंग है। एक विशिष्ट पैटर्न के बाद मेमोरी हेरफेर जो एक शोषण के परिणामस्वरूप होने वाली संभाव्य अभिव्यक्ति के सभी संभावित मानों से मेल खाता है।
हमारे मामले में इसके काम करने के लिए एक आवश्यकता यह है कि मेमोरी को पते 0x1000000 पर आवंटित किया जा सकता है। यह एक सीमा है, जिसके साथ हमें वास्तविक शोषण के मामले में भी काम करना होगा।
कुछ तकनीकी पहलुओं को स्पष्ट करने के बाद, अब CLFS पर बुनियादी समझ की आवश्यकता है। Microsoft लिंक. CLFS का उपयोग एप्लिकेशन लॉग, डेटा बेस लॉग, लेन-देन आदि के लिए किया जाता है।
शोषणों को आम तौर पर कार्य करने के लिए एक निश्चित मेमोरी लेआउट की आवश्यकता होती है। लेआउट का रूप शोषण के सटीक समय पर प्रोग्राम के अंदर चर मानों द्वारा निर्धारित किया जाता है।
यह ट्यूटोरियल कमज़ोर कोड, या लॉग फ़ाइल सिस्टम के प्रारूप के बारे में विस्तार से नहीं बताएगा। यह शोषण उत्पन्न करने वाली प्रक्रियाओं की बुनियादी समझ प्रदान करेगा।
लॉग फ़ाइलें एक विशेष प्रकार की फ़ाइलें होती हैं जिनका एक निश्चित प्रारूप होता है और CLSF ड्राइवर और dll API द्वारा उनके साथ बातचीत की जा सकती है। इस प्रणाली के भीतर लॉग कंटेनरों की भी अवधारणा है। लॉग कंटेनर भी लॉग फ़ाइलें हैं, लेकिन मुख्य लॉग फ़ाइल (जिस लॉग फ़ाइल में उन्हें जोड़ा जाता है) से मेमोरी में जुड़ी होती हैं।
यह निर्माण निम्नानुसार काम करता है:
यह ऑपरेशन मुख्य लॉग फ़ाइल के भीतर नई मेमोरी स्पेस आवंटित करने के लिए मजबूर करता है, और मुख्य लॉग फ़ाइल के मेमोरी लेआउट के अंदर विभिन्न वस्तुओं को संशोधित करेगा। मेमोरी लेआउट का संशोधन, अवधारणात्मक रूप से इस तरह दिखेगा:

हर महत्वपूर्ण डेटा संरचना/फ़ाइल की तरह, इसका उपयोग करने से पहले CLFS ड्राइवर लॉग फ़ाइल के प्रारूप के विरुद्ध सुरक्षा जाँच करता है:
दूसरा बिंदु कमज़ोरी का प्रारंभिक बिंदु है। हमलावर पहले से बनाई गई लॉग फ़ाइल को संशोधित करने, उसके हैश की पुनर्गणना करने, और ड्राइवर को अखंडता परीक्षण पास करने के लिए हैश फ़ील्ड को संपादित करने में सक्षम है। संशोधन फ़ाइल हेडर लंबाई से संबंधित हैं। सावधानीपूर्वक तैयार किए गए मानों का एक सेट एक रेंज-लंबाई जाँच पर पास निर्धारित करता है जो अन्यथा विफल हो जाता और एक त्रुटि फेंकता। इस प्रकार ड्राइवर नकली लंबाई को वैध मानता है, और कोड निष्पादन सामान्य रूप से जारी रखता है। नकली लंबाई का उपयोग फ़ाइल के भीतर एक ऑफ़सेट की गणना करने के लिए किया जाता है जहाँ एक हार्डकोडेड पता लिखा जाएगा। यह हमलावर को उस पते को नियंत्रित करने की क्षमता देता है जहाँ पिछला लिखने का ऑपरेशन होगा।
इस कमज़ोरी को शोषण को पूरा करने के लिए पाइप-कर्नेल-रीड-राइट के साथ जोड़ा जाना चाहिए। दृश्य प्रमाण:

पिछले चित्र में हम CLFS.sys के अंदर AllocSymbol फ़ंक्शन देखते हैं जो नकली रेंज चेक के लिए जिम्मेदार है। कमज़ोर लंबाई जाँच वह है जो 0xC0000023 त्रुटि कोड लौटाएगी। (BaseLogRecord + v9 + Size_1 + 0x1338 > v8 + *(0x68)) इसकी स्थिति में इनपुट आंशिक रूप से हमलावर द्वारा नियंत्रित होता है। सूत्र में एक मान इंजेक्ट करना संभव है जो सामान्य रूप से विफल होने पर भी जाँच को पास करने का कारण बनता है। v8 और v9 को नियंत्रित करके हम स्थिति के सत्य मान को FALSE में बदल सकते हैं, त्रुटि लौटने को रोक सकते हैं और हमलावर नियंत्रित v9 मान का उपयोग करके v10 चर के मनमाने मान की गणना कर सकते हैं।
V10 का उपयोग आगे memset में सेट किए जाने वाले मान के रूप में किया जाता है। इसलिए हमलावर मनमाने ढंग से मेमोरी को 0 पर सेट करने की क्षमता प्राप्त करता है। यह सब नहीं है। अंतिम रिटर्न से पहले *a3=v10। A3 एक पैरामीटर है जो AllocSymbol फ़ंक्शन को पते द्वारा भेजा जाता है। AllocSymbol का कॉलर इस प्रकार AllocSymbol से लौटने के बाद V10 का मान प्राप्त करता है।
AllocSymbol का कॉलर FindSymbol है।

FindSymbol के अंदर V33 वह पैरामीटर है जिसे AllocSymbol में a3 नाम दिया गया है। इसलिए v33 को v10 का मान मिलता है। फिर v33 पते पर मान को एक स्थिरांक (0xc1fdf006) पर सेट किया जाता है और आगे 0x30 मान से उपसर्ग किया जाता है।

इसलिए हमलावर एक स्थिरांक के साथ एक मनमाना मेमोरी स्थान सेट करने की क्षमता प्राप्त करता है।
यह महत्वपूर्ण क्यों होगा? यदि हम किसी फ़ंक्शन पॉइंटर के डिफ़ॉल्ट मान को उपयोगकर्ता स्थान और कर्नेल स्थान दोनों से सुलभ एक स्थिर पते से अधिलेखित कर सकते हैं, और गारंटी है कि संबंधित पॉइंटर को कॉल किया जाएगा, तो हम कोड निष्पादन नियंत्रण प्राप्त करते हैं। हालांकि यह सामान्य विचार है, तकनीकी बाधाएँ हैं जिन्हें आगे समझाया जाएगा।
CClfsContainer* pContainer; नामक एक पॉइंटर होता है। जब संबंधित कंटेनर को डीलोकेट किया जाता है, तो पैरेंट फ़ाइल में सफाई कार्रवाई होती है और pContainer को डीरेफ़रेंस किया जाता है और [[pContainer]+0x18] तथा [[pContainer]+0x8] के मानों को फ़ंक्शन पॉइंटर के रूप में उपयोग किया जाता है।इसलिए pContainer को नियंत्रित करने और जोड़ने और हटाने के लिए कंटेनर-विशिष्ट API को निष्पादित करने की क्षमता नियंत्रण प्रवाह पुनर्निर्देशन सुनिश्चित करती है।
pContainer कहाँ स्थित है?
CLFS फ़ाइल संरचना अप्रलेखित है, लेकिन फ़ाइल संरचना को उलटने के कुछ व्यक्तिगत प्रयास मौजूद हैं।
एक लॉग फ़ाइल संरचना का पक्षी-दृश्य:

और आधार ब्लॉक का विस्तृत दृश्य:

संरचना और मेमोरी दृश्य के बीच अंतर:
CLFS फ़ाइलें Clfs टैग मान के साथ बिग-पूल पृष्ठों में आवंटित की जाती हैं। जब उपयोगकर्ता स्थान में किसी clfs के लॉग पते का पता लगाया जाता है (वही विधि जो पाइप ऑब्जेक्ट का कर्नेल स्पेस पता प्राप्त करने में उपयोग की जाती है), तो कर्नेल वह पता लौटाएगा जहाँ आधार ब्लॉक शुरू होता है। (पिछले चित्र में ऑफ़सेट 0x800 पर)। ऑफ़सेट 0xb98 पर हम regContainers नामक एक डेटास्ट्रक्चर देखते हैं। यह 32-बिट मानों की एक सरणी है। प्रत्येक मान एक कंटेनर से संबंधित है और 0x870 से गिनती करते हुए उस ऑफ़सेट का प्रतिनिधित्व करता है जहाँ कंटेनर की आंतरिक डेटा संरचनाएँ आधार ब्लॉक फ़ाइल में स्थित हैं।
एक कंटेनर फ़ाइल की मेमोरी लेआउट इस प्रकार है:
CLFS_CONTAINER_CONTEXT संरचना
CLFS_CONTAINER_CONTEXT संरचना में ऑफ़सेट 0x18 पर हमारा लक्ष्य pContainer है।
जाहिर है, pContainer का ऑफ़सेट स्थिर है, जो regContainers सरणी का पहला तत्व है। और इसका मान 0x1468 है।
pContainer को फ़ंक्शन पॉइंटर के रूप में डीरेफ़रेंस करना और एक्सेस करना इस शोषण का मुख्य बिंदु है। यह CLFS.SYS ड्राइवर में Remove container फ़ंक्शन के अंदर होता है। इसलिए शोषण तब ट्रिगर होगा जब कंटेनर हटा दिया जाता है।
प्रमाण:

आइए एक कंटेनर को हटाने के चरणों की समीक्षा करें ताकि यह दिखाया जा सके कि वास्तव में पॉइंटर pContainer को एक फ़ंक्शन के पॉइंटर के रूप में एक्सेस किया जाता है
GetBaseLogRecord API का कॉल है जो आधार ब्लॉक + 0x70 का कर्नेल पता लौटाता है (यह फ़ाइल पॉइंटर को हेडर से आगे ले जाता है)BaseRecord_1) की एक प्रति प्रारंभ की जाती हैa4 की दूसरी प्रति v10 के रूप में प्रारंभ की जाती हैLODWORD(a4) = *( (_DWORD*)BaseLogRecord_1 + StartingIndex_1 + 0xCA); यदि फ़ाइल में एक एकल कंटेनर जोड़ा गया है, तो StartingIndex_1 शून्य है। GetBaseLogRecord द्वारा लौटाया गया मान DWORD (32-बिट) वेक्टर में डाला जाता है और 0xCA तत्वों द्वारा अनुक्रमित किया जाता है। ध्यान दें कि (_DWORD*)BaseLogRecord_1 + 0 + 0xCA केवल BaseLogRecord_1 में 0xCA मान नहीं जोड़ता है। कास्ट के कारण यह BaseLogRecord_1[0xCA] के बराबर है। यह इसी के अनुरूप डिस्सेम्बल कोड में परिलक्षित होता है, mov eax, [rdi+r12*4+328h] जहाँ rdi आधार है, r12 StartingIndex_1 है (ध्यान दें कि इसे 4 से गुणा किया जाता है जो 32-बिट आकार के तत्व (DWORD) की लंबाई है) और 0x328 जोड़ा गया है जो 0xCA*0x4 है।a4 का मान अंततः BaseBlock+0x70+0+328=(BaseBlock+0x398) है जो हमें RgConrainers के पहले मान (0x398+0x800=0xB98) पर ले जाएगा।
_CLFS_CONTAINER_CONTEXT प्रकार का है। इसलिए v10 संरचना का तीसरा क्षेत्र है जो pContainer से मेल खाता है।82 और 83 पर pContainer को डीरेफ़रेंस किया जाता है, 0x18 और 0x8 जोड़ा जाता है, फ़ंक्शन के पॉइंटर के रूप में व्याख्या किया जाता है और निष्पादित किया जाता है। (call cs:__guard_dispatch_icall_fptr वास्तव में jmp eax निर्देश का कॉल है)यह साबित करता है कि pConainter मान को फिर से लिखकर हम ड्राइवर के नियंत्रण प्रवाह को बदल सकते हैं और इसे हमलावर द्वारा नियंत्रित पतों पर इंगित कर सकते हैं।
पहले यह समझाया गया था कि कभी-कभी आपको शोषण के सफल निष्पादन की गारंटी देने के लिए मेमोरी के एक टुकड़े को मानों के पूर्व निर्धारित पैटर्न से भरने की आवश्यकता होती है। ऐसा इसलिए है क्योंकि हैकर शोषण के समय प्रोग्राम की स्थिति बनाने वाले चरों के केवल एक उपसमुच्चय को नियंत्रित कर सकता है। पहले मेमोरी स्प्रे के सरल उदाहरण में हमने एक निश्चित शर्त को संतुष्ट करने के लिए केवल एक वेक्टर में लिखे गए मानों का उपयोग किया था।
वास्तविक मामले में स्थितियाँ अधिक जटिल हैं। हमें कर्नेल मेमोरी में लॉग फ़ाइलों को एक निश्चित क्रम में व्यवस्थित करने की आवश्यकता है जिसमें उनके बीच ज्ञात ऑफ़सेट हो।
सबसे पहले, आइए एक लूप में कई लॉग फ़ाइलें बनाएं और अध्ययन करें कि OS उनके लिए मेमोरी कैसे आवंटित करता है। प्रयोग निम्नलिखित पैटर्न का उपयोग करेगा:
निम्नलिखित कोड का टुकड़ा इसे पूरा करता है:

और परिणाम:

आइए पतों को क्रमबद्ध करें:

पतों के बीच ऑफ़सेट का निरीक्षण करके, आवंटन के संबंध में एक छद्म पैटर्न: ऐसे सतत आवंटन हैं जिनके बीच स्थिर ऑफ़सेट है। उदाहरण के लिए ffffd80faf444000 से ffffd80faf4ee000 तक दो क्रमिक आवंटनों के बीच ऑफ़सेट 0x11000 है।
यह धारणा शोषण के काम करने के लिए महत्वपूर्ण है। इस पर भरोसा किया जाएगा।
एक और धारणा: आइए कुछ पृष्ठ लें जो 0x11000 अलग हैं। उदाहरण के लिए: ffffd80faf444000,ffffd80faf455000,ffffd80faf466000,ffffd80faf477000,ffffd80faf488000,ffffd80faf499000। सभी पृष्ठ खोली गई CLFS फ़ाइलों के अनुरूप हैं। यदि हम एक फ़ाइल बंद करते हैं तो पृष्ठ डीलोकेट हो जाएगा और संबंधित पते पर मेमोरी मुक्त हो जाएगी।
यह मेमोरी में इस तरह दिखेगा (मान लीजिए हम ffffd80faf466000 पर पृष्ठ के अनुरूप फ़ाइल बंद करते हैं):
ffffd80faf444000,ffffd80faf455000,xxxxxxxxxxxxxxxx,ffffd80faf477000,ffffd80faf488000,ffffd80faf499000
यदि हम फ़ाइल को फिर से खोलते हैं तो OS उच्च संभावना के साथ उसी पते (ffffd80faf466000) पर एक पृष्ठ आवंटित करेगा ताकि छेद भर सके और मेमोरी को सतत बना सके। --> यह भी शोषण के निष्पादन के लिए महत्वपूर्ण है।
अब हम उस रणनीति का एक योजनाबद्ध चित्र प्रस्तुत करते हैं जिसका उपयोग हम यह सुनिश्चित करने के लिए करेंगे कि हम *pConainter पॉइंटर को उपयोगकर्ता स्थान में हमारे नियंत्रण वाले पते से ओवरराइट कर दें।


पिछले चित्र में start(aux1)+0x11000=start(A),start(A)+0x11000=start(B),start(B)+0x11000=start(aux2)
हम Logfile A का उपयोग Logfile B के *pcontainer पॉइंटर को ओवरराइट करने के लिए करेंगे, और Logfile B को बंद करके शोषण को ट्रिगर करेंगे, जिसमें एक विशेष बंद करने के बाद हटाएँ कोड का उपयोग किया जाएगा।
Logfile B में एक लॉग कंटेनर जोड़ें, ताकि उन फ़ील्ड को आवंटित और अपडेट किया जा सके जो संकेत देते हैं कि B के पास एक सही कंटेनर फ़ाइल है। हम aux2 को B के कंटेनर के रूप में जोड़ सकते हैं।
LogfileA को बंद करें ताकि हम इसे डिस्क पर संपादित कर सकें। A और B को 0x11000 स्थान वाले अधिकतम अनुक्रम के बीच में चुनना महत्वपूर्ण है, ताकि मेमोरी में एक छेद बनाया जा सके जिसे OS भरने को प्राथमिकता देगा। यदि ऐसा नहीं होता है, तो शोषण विफल हो जाएगा।
A के हैश की पुनर्गणना करें, अखंडता बनाए रखने के लिए इसके हैश फ़ील्ड को संपादित करें और A को फिर से खोलें और आशा करें कि कर्नेल इसे उसी पते पर रखेगा अन्यथा शोषण विफल हो जाएगा।
A पर सामान्य लॉग फ़ाइल के साथ AddLogContainer को कॉल करें ताकि B के *pContainer पॉइंटर को ओवरराइट किया जा सके
B फ़ाइल को हटाएँ ताकि RemoveConainter को कॉल किया जाए और निष्पादन B के *pContainer पर स्थानांतरित हो जाए जो अब दूषित हो गया है और उपयोगकर्ता द्वारा आवंटित कोड की ओर इशारा करता है।
निम्नलिखित चित्र पहले बताए गए चरणों को दर्शाता है।

हम पहले 50 CLFS फ़ाइलों को आवंटित करके शुरू करते हैं। प्रत्येक आवंटन के बाद, हम कर्नेल मेमोरी में सभी CLFS पृष्ठों को क्वेरी करते हैं और प्रत्येक फ़ाइल के लिए आवंटित सभी पतों की एक सूची बनाते हैं। इसके बाद, हम 2 फ़ाइलों की पहचान करते हैं जो एक दूसरे से 0x11000 दूर हैं (योजना पर A और B)। (और इसे first, second चर में संग्रहीत करें)

पतों की पहचान करने के बाद, दो फ़ाइलों की खोज करें जो 0x11000 दूर हैं A->first , B->second

इसके बाद A (first) को बंद करें और इसे डिस्क पर संपादित करें (इसके हेडर को खराब करें) और इसे फिर से खोलें।

इस चरण को और स्पष्टीकरण की आवश्यकता है क्योंकि हमें कुछ सटीक मानों की गणना करने की आवश्यकता है, जो AllocSymbol में शोषण योग्य कोड को ट्रिगर करने पर B के *pConainter पॉइंटर को ओवरराइट करने की ओर ले जाएंगे।
AllocSymbol और FindSymbol फ़ंक्शन पर पिछले विश्लेषण से हम जानते हैं कि हमें A फ़ाइल को इस प्रकार संशोधित करना होगा कि यह IF स्थिति के तार्किक मान को FALSE पर ओवरराइट कर दे और v9 चर में एक मान इंजेक्ट करे जो B के *pContainer स्थान पॉइंटर पर लिखने की ओर ले जाएगा।
पहले v9 को किस मान पर सेट किया जाना चाहिए?
AllocSymbol v10 (लेखन का लक्ष्य पता) की गणना इस प्रकार करता है:
v10=BaseLogRecord + v9 + 0x1338। यह A पता स्थान के संदर्भ में गणना की जाती है। इसलिए BaseLogRecord है: A का कर्नेल पृष्ठ पता + 0x70। v9 हमलावर द्वारा नियंत्रित होता है और 0x1338 स्थिर है।
B का *pContainer अपने कर्नेल पृष्ठ पते के सापेक्ष कहाँ है?
यह पहले विस्तृत किया गया था, इसलिए B के कर्नेल पृष्ठ पते से हमें B के regContainter वेक्टर तक पहुँचने के लिए 0x398 जोड़ने की आवश्यकता है और पहले CONTAINER_CONTEXT संरचना का ऑफ़सेट प्राप्त करने के लिए पहले तत्व द्वारा अनुक्रमण करें। जैसा कि पहले कहा गया था, पहले कंटेनर के लिए इंडेक्स 0x1468 (B BaseBlock +0x70 से गिनती करते हुए) निर्धारित किया गया था।
इसलिए B के *pContainer का स्थान है B का कर्नेल_पता + 0x70 + 0x1468 + 0x18 (CONTAINER_CONTEXT संरचना में तीसरा तत्व)
B का कर्नेल_पता=A का कर्नेल पता+0x11000 (क्योंकि हमने मेमोरी को इस प्रकार बनाया है)
A का BaseRecordAddress=A का कर्नेल पता+0x70
V10=A का कर्नेल पता+0x70+v9+0x1338
v10 को B के *pContainer को ओवरराइट करने की आवश्यकता है
V10 को होना चाहिए: v10=B का कर्नेल_पता + 0x70 + 0x1468 + 0x18 (CONTAINER_CONTEXT संरचना में तीसरा तत्व)
B के कर्नेल_पते को प्रतिस्थापित करना: v10=A का कर्नेल पता+0x110000+x70 + 0x1468 + 0x18
v10 को कम करना: A का कर्नेल पता+0x70+v9+0x1338=A का कर्नेल पता+0x11000+x70 + 0x1468 + 0x18
v9 = 0x11000+0x70+0x1468+0x18-0x1338-0x70=0x11148 के लिए हल करना
अब A में किन फ़ील्ड को ओवरराइट करने की आवश्यकता है? --> फ़ील्ड की 2 श्रेणियाँ हैं:
AllocSymbol में IF स्थिति को FALSE पर मूल्यांकन करने के लिए बाध्य करने वाले फ़ील्डश्रेणी 1 के फ़ील्ड का विवरण नहीं दिया जाएगा। V9 A फ़ाइल के अंदर डिस्क पर ऑफ़सेट 0x1b98 पर स्थित फ़ील्ड से मेल खाता है। इस बारे में अधिक विवरण नहीं होंगे कि ये फ़ील्ड शोषण को क्यों ट्रिगर करते हैं, यदि रुचि हो तो पाठक को इसके बारे में पढ़ना चाहिए।
यहाँ वे मान हैं जिन्हें संशोधित करने की आवश्यकता है:

ध्यान दें कि (लिटिल एंडियन), उपयोग किया गया मान 0x11149 है न कि 0x11148। ऐसा इसलिए है क्योंकि हमें *pContainer में उच्च क्रम के ऑक्टेट को नियंत्रित करने की आवश्यकता है। सबसे कम महत्वपूर्ण ऑक्टेट X0 (10 या 20 या 30...) के रूप का होगा। हम इस स्थिति को ध्यान में रखने के लिए प्रत्येक मान पर मेमोरी स्प्रे कर सकते हैं।
ध्यान दें कि v10 का उपयोग len 0xa0 के मेमोरी क्षेत्र को 00 से memset करने के लिए भी किया जाएगा

फिर उसी पते पर स्थिरांक के साथ ओवरराइट करें

स्थिर मान पर ध्यान दें जो 0x30c1fdf006X0 रूप का है
यह तब होता है जब हम A फ़ाइल में एक लॉग कंटेनर जोड़ते हैं, लेकिन हमने A के फ़ील्ड के संशोधन के बाद हैश पुनर्गणना प्रक्रिया को कवर नहीं किया।
डिस्क पर लॉग फ़ाइल का दृश्य मेमोरी में दृश्य से कुछ अलग है। हम केवल बेस ब्लॉक पर सामग्री को संशोधित कर रहे हैं जो लंबाई में 0x7a00 है और डिस्क पर फ़ाइल ऑफ़सेट 0x800 पर शुरू होता है। बेस ब्लॉक को हैश करने के लिए उपयोग किया जाने वाला हैश एल्गोरिदम CRC32 है। हैश का मान रखने वाला फ़ील्ड भी बेस ब्लॉक के अंदर ऑफ़सेट 0x80c पर संग्रहीत है।
हैश की पुनर्गणना करने की प्रक्रिया:
0x80c पर पुराने CRC32 मान को शून्य करें0x800 से शुरू होने वाले बेस ब्लॉक के लिए लंबाई 0x7a00 के साथ CRC32 की गणना करेंकोड निष्पादन को उपयोगकर्ता स्थान पर रूट करने के लिए हमें यह करना होगा:
*pConainter मान पर 0x30c1fdf006X0 रूप के स्थिर मान के साथ ओवरराइट ट्रिगर होगा।RemoveContainer API ट्रिगर नहीं होगा।
*pContainer का उपयोग करके निष्पादन प्रवाह को पुनर्निर्देशित करने के लक्ष्य वाली उपयोगकर्ता मेमोरी पर कुछ शर्तें लगाई गई हैं। हम कर्नेल मोड में प्रोग्राम के साथ उपयोगकर्ता स्थान से बेतरतीब ढंग से निर्देश निष्पादित करना शुरू नहीं कर सकते।
इस खंड का उद्देश्य SystemToken के मान को उपयोगकर्ता स्थान में लीक करना है, बिना सिस्टम क्रैश (BSOD) पैदा किए।
आप कर्नेल में एक पते से कैसे पढ़ सकते हैं और परिणाम को उपयोगकर्ता स्थान में संग्रहीत कर सकते हैं? ट्यूटोरियल के पाइप अनुभाग पर एक त्वरित अनुस्मारक:

यहाँ हम:
पाइप ऑब्जेक्ट और सिस्टम टोकन मान प्राप्त करने के हमारे लक्ष्य को जोड़ने का विचार है कि NtFsControlFile PipeReadAttribute का उपयोग करके सिस्टम टोकन के पते से पढ़ा जाए, न कि उस बफर की शुरुआत से जिसमें हमने पाइप राइट एट्रिब्यूट का उपयोग करके कर्नेल को दी गई जानकारी है।
इसका मतलब है कि हमें पते PIPE_BEGIN+=0x20 पर पॉइंटर के मान को संशोधित करना होगा ताकि वह उस पते को धारण करे जहाँ सिस्टम टोकन स्थित है।
पता ज्ञात है, ट्यूटोरियल के पिछले चरणों में प्राप्त किया गया था, जब हमने EPROCESS संरचना का पता लगाया और पार्स किया था।
यहाँ हमें एक ऐसा तंत्र खोजने की आवश्यकता है जो हमें एक निश्चित स्थान पर पाइप संरचना में लिखने की अनुमति देता है।
इसके लिए हम CLFS AddLogConainer फ़ंक्शन का शोषण करके प्राप्त कोड पुनर्निर्देशन का उपयोग करते हैं। कोई सोच सकता है कि एक शेलकोड लिखना पर्याप्त है जो कच्चा प्रतिस्थापन करता है, लेकिन (हालाँकि इसका परीक्षण नहीं किया गया था) यह निश्चित रूप से काम नहीं करेगा। ऐसा इसलिए है क्योंकि ड्राइवर कर्नेल संदर्भ में चलता है और उपयोगकर्ता प्रक्रिया क्षेत्र से कोड निष्पादित करता है।
इस सीमा को दरकिनार करने के लिए हमें कर्नेल ROPs ढूँढ़ने होंगे जो एक मनमाना मान पर लिखने का कार्य पूरा करते हैं। यानी कर्नेल कोड के कुछ टुकड़े खोजें जो किसी फ़ंक्शन के अंत में हों और ret निर्देश के साथ समाप्त हों, और उनके पतों को पुनर्निर्देशन के लक्ष्य के रूप में प्रदान करें। इस तरह कोड अभी भी कर्नेल द्वारा निष्पादित होता है।
यह मुख्य विचार है लेकिन सीमाएँ उस तरीके से उत्पन्न होती हैं जिसमें CLFS ड्राइवर *pConainer के अंदर कोड को कॉल करता है:

उपयोगी मेमोरी पैटर्न बनाने के लिए हमें उस तरीके का अध्ययन करना होगा जिसमें RemoveContiner फ़ंक्शन *pContainer कोड तक पहुँचता है।
पिछले चित्र में हम कोड के उन हिस्सों को उजागर करते हैं जो *pContainer को डीरेफ़रेंस करते हैं और इसे फ़ंक्शन पॉइंटर के रूप में उपयोग करते हैं।
RDI वह स्थिरांक है जो AllocSymbol का शोषण करके *pcontainer के अंदर लिखा गया था। जैसा कि आप देखते हैं, स्थिरांक मान में पूरी तरह से स्थिर नहीं है, यह पहले बाइट में भिन्न होता है (रूप 6X0 है, X कुछ भी हो सकता है)।
mov rax, [rdi] स्थिरांक मान को डीरेफ़रेंस करता है। अमान्य पढ़ने (और BSOD) का कारण न बनने के लिए हमें यह सुनिश्चित करना होगा कि rdi एक वैध पते पर पॉइंटर रखता है। ऐसा करने के लिए हमें उपयोगकर्ता मेमोरी को 0x30C1FDF00000 से कम से कम 0x30C1FDF006FF तक स्प्रे करना होगा। यह VirtualAlloc के साथ मेमोरी का एक हिस्सा आवंटित करके किया जाता है। बेशक यदि सिस्टम किसी भी कारण से मेमोरी आवंटित नहीं कर सकता है, तो शोषण विफल हो जाता है।
LPVOID dirtySpray = VirtualAlloc((LPVOID)0x000030c1fdf006d0, 0x1000000, 0x3000, 0x4);
फिर X के किसी भी संभावित मान के लिए हम 0x30C1FDF000X0 से 0x30C1FDF00FX0 पर एक और मान संग्रहीत करते हैं जो वैध उपयोगकर्ता मेमोरी से मेल खाता है, मान लीजिए 0x5000000 का एक मनमाना मान चुनें। इस तरह हम सुनिश्चित करते हैं कि 0x30C1FDF006X0 रूप के किसी भी rdi के लिए rax 0x5000000 के बराबर होगा।
इस चरण के बाद हम दो और डीरेफ़रेंसिंग ऑपरेशन देखते हैं: mov rax, [rax+0x18] और mov rax ,[rax+0x8]
यह सुनिश्चित करने के लिए कि इन पतों पर मेमोरी अभी भी सुसंगत है, हमें 0x5000008 और 0x5000018 पर कर्नेल फ़ंक्शंस (ROP1 और ROP2, भविष्य में गणना की जाएगी) के पॉइंटर्स संग्रहीत करने होंगे।
यह स्प्रे पैटर्न उत्पन्न करने का एल्गोरिथ्म है।

नोट: पैटर्न तब विकसित होगा जब हम ROPs प्रस्तुत करेंगे क्योंकि उनके भी पैरामीटर होंगे जिन्हें ध्यान में रखा जाएगा, लेकिन अमान्य मेमोरी एड्रेसिंग को रोकने के लिए अब तक यह न्यूनतम है।
डिबगर में पहले चरण की स्प्रेड मेमोरी इस प्रकार दिखती है (ध्यान दें कि मान 0x5000000 कहाँ रखा गया है --> X0 संरेखित):

0x5000000 पर मेमोरी इस प्रकार दिखती है (वे ROPs जिनका विवरण दिया जाएगा)

और कर्नेल डिबगर में कोड पुनर्निर्देशन की प्रक्रिया:

डिबगर में RDI रजिस्टर और इंस्ट्रक्शन पॉइंटर के मान पर ध्यान दें। RDI को एक बार डीरेफ़रेंस करने पर 0x5000000 मिलता है और RAX में संग्रहीत होता है। [RAX+0x18] दूसरा ROP है, और आगे [RAX+0x8] पहले ROP का पता होगा।
इस चरण में, हमारे पास शोषण के निम्नलिखित टुकड़े हैं जिन्हें हमें एक साथ जोड़ना है:
इस बिंदु पर उद्देश्य कोड पुनर्निर्देशन को कोड के एक टुकड़े से जोड़ना है जो पाइप के विशेषता बफर के पॉइंटर को सिस्टम टोकन के पते से ओवरराइट करेगा और पाइप रीड एट्रिब्यूट का उपयोग करके जानकारी वापस उपयोगकर्ता स्थान में प्राप्त करेगा।जैसा कि हमने कहा, रीरूट किया गया कोड भी कर्नेल में होना चाहिए। इसके अलावा हमारे पास उपयोग करने के लिए केवल 2 फ़ंक्शन हैं। ट्यूटोरियल यह नहीं बताएगा कि ये 2 विशिष्ट फ़ंक्शन कैसे पाए गए, लेकिन संभवतः ROP उम्मीदवारों की एक सूची है जो अक्सर उपयोग की जाती है।
हम 2 फ़ंक्शन का अध्ययन करेंगे:
SeSetAccessStateGenericMapping in ntoskrnl.exe दूसरा कॉल किया गया ([rax+0x8])ClfsEarlierLsn in CLFS.SYS पहले कॉल किया गया ([rax+0x18])ClfsEarlierLSn का विश्लेषण:

इस फ़ंक्शन की एकमात्र भूमिका जब गलत पैरामीटर के साथ कॉल किया जाता है तो EDX को 0xFFFFFFFF पर सेट करना और वापस लौटना है। दूसरे ROP का विश्लेषण करते समय यह स्पष्ट हो जाएगा कि इसकी आवश्यकता क्यों है।
SeSetAccessStateGenericMapping का विश्लेषण:

इस पर पंक्ति दर पंक्ति विस्तार से चर्चा करने की आवश्यकता है।
इनपुट: इस फ़ंक्शन में प्रवेश करते समय:
0x30C1FDF006X0 स्थिरांक है0x30C1FDF00YX8 रूप का है, हमेशा 0x8 संरेखित होता है और 0x30C1FDF006X0 से टकराएगा नहीं, जो 0 संरेखित है और इसमें 0x5000000 है।फ़ंक्शन कोड निष्पादन:
mov rax, [rcx+48h] 0x30C1FDF00YX8 से डीरेफ़रेंस करेगा और मान को RAX में ले जाएगाmovups xmm0, xmmword ptr [rdx] RDX से 16 बाइट XMM0 रजिस्टर में ले जाएगा। याद रखें यहाँ ClfsEarlierLSn से RDX 0xFFFFFFFF हैmovdqu xmmword ptr [rax+8], xmm0 XMMO के मान को [RAX+0x8] पर ले जाएगाअनिवार्य रूप से यह एक पते से पढ़ता है और कर्नेल के भीतर संग्रहीत करता है। इसे इस प्रकार समझा जा सकता है:

इसलिए पाइप के एट्रीब्यूट बफ़र पॉइंटर को सिस्टम टोकन के पॉइंटर से ओवरराइट करने के लिए हमें 0xFFFFFFFF पर 16 ऑक्टेट आवंटित करने होंगे और वहां सिस्टम टोकन का पता संग्रहीत करना होगा। फिर हमें डेस्टिनेशन एड्रेस -0x8 के मान के साथ 0x30C1FDF00YX8 पर मेमोरी को बार-बार स्प्रे करना होगा। यह पाइप एट्रीब्यूट बफ़र के ऑफ़सेट -0x8 का मान है। अर्थात PIPE_KERNEL_PAGE_ADDRESS+0x20-0x8।
यहाँ बताया गया है कि वास्तविक कोड में यह कैसे प्राप्त किया जाता है:

एक और पहलू जो नहीं समझाया गया: आप उपयोगकर्ता स्पेस में ROP के कर्नेल पते कैसे प्राप्त करते हैं?
यह एक ट्रिक है। सबसे पहले आप NtQuerySystemInformation का उपयोग करके किसी भी कर्नेल मॉड्यूल का बेस एड्रेस प्राप्त कर सकते हैं, जिसमें निम्नलिखित पैरामीटर हों: NtQuerySystemInformation(SystemModuleInformation, (HANDLE)ModuleInfo, ModuleInfoSize, &retlen)
फिर आप आवश्यक लाइब्रेरी से मेल खाने के लिए ModuleInfo->Modules[i].Name मान द्वारा फ़िल्टर करते हैं। इससे कर्नेल में बेस एड्रेस प्राप्त होता है।
फिर हम इस तथ्य का लाभ उठाते हैं कि बेस एड्रेस और एक्सपोर्ट एड्रेस के बीच ऑफ़सेट स्थिर है, भले ही एक्ज़ीक्यूटेबल किसी भी एड्रेस स्पेस में लोड किया गया हो।
हम LoadLibrary का उपयोग करके मॉड्यूल को यूज़रस्पेस में लोड करते हैं (आप ऐसा कर सकते हैं) और Getprocaddress का उपयोग करके एक्सपोर्ट का पता प्राप्त करते हैं। एक्सपोर्ट और यूज़र बेस के बीच डेल्टा की गणना करें और इसे कर्नेल बेस (जो NtQuerySystemInformation का उपयोग करके पाया गया) में जोड़ें। इस प्रकार हम यूज़रस्पेस में एक्सपोर्ट का पता प्राप्त करते हैं जो कर्नेलस्पेस में लोड है।
एक बार एट्रीब्यूट के पॉइंटर को दूषित कर दिया जाए और सिस्टम टोकन के स्थान की ओर इंगित करने के लिए सेट कर दिया जाए, तो MyNtFsControlFile को सही पैरामीटर के साथ कॉल करने से पते से पढ़ना चाहिए और सिस्टम टोकन मान को उपयोगकर्ता स्पेस में लीक करना चाहिए।

सिस्टम टोकन का सही मान प्राप्त करने के बाद, एक्सप्लॉइट को पूरा करने के लिए हमें इसे अपनी प्रक्रिया के स्थान पर लिखना होगा। अर्थात एक्सप्लॉइट को निष्पादित करने वाली प्रक्रिया के टोकन को सिस्टम टोकन के मान से ओवरराइट करना होगा।
यह परिवर्तन करने के लिए आवश्यक कदमों में कोई नई तकनीक या पद्धति शामिल नहीं है, बल्कि पिछले कोड के पुन: उपयोग पर निर्भर करते हैं। अपने स्वयं के टोकन मान को ओवरराइट करने के एल्गोरिदम में एक निश्चित पते पर कर्नेल में एक लेखन संचालन शामिल होता है। यह ROP फ़ंक्शन SeSetAccessStateGenericMapping और ClfsEarlierLSn द्वारा प्राप्त किया जाता है। इसका अर्थ है कि हमें CLFS एक्सप्लॉइट को दूसरी बार ट्रिगर करने की आवश्यकता है। यह सही है, हमें दूसरी बार कंटेनर आवंटन और मेमोरी स्प्रे करना होगा और उम्मीद करनी होगी कि OS क्रैश न हो।
अपने स्वयं के टोकन मान को ओवरराइट करने के चरण:
चूंकि दूसरी बार हमें कर्नेल से पढ़ने की आवश्यकता नहीं है, इस बार हमारे पास पाइप का कोई उपयोग नहीं है।
चूंकि चरणों के लिए अतिरिक्त ज्ञान की आवश्यकता नहीं है, ट्यूटोरियल यहाँ समाप्त हो सकता है, जिसमें nt authority\system के साथ cmd.exe का अंतिम प्रमाण हो।

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