
CVE-2024-30051 के लिए विस्तृत तकनीकी विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जो Windows DWM Core Library में हीप-आधारित बफर ओवरफ्लो है, जो इंटीग्रिटी सिस्टम स्तर तक स्थानीय विशेषाधिकार वृद्धि को सक्षम बनाता है।
इस ब्लॉग पोस्ट में, मैं Microsoft Windows DWM कोर लाइब्रेरी में एक भेद्यता के बारे में बताऊंगा जिसका मैंने विश्लेषण तब किया जब Core Impact के लिए एक्सप्लॉइट विकसित किया जा रहा था। यह एक अन-प्रिविलेज्ड हमलावर को इंटीग्रिटी सिस्टम विशेषाधिकारों के साथ DWM उपयोगकर्ता के रूप में कोड निष्पादित करने की अनुमति देता है (CVE-2024-30051)।
चूँकि उस समय एक्सप्लॉइट विकसित करने के लिए पर्याप्त सार्वजनिक जानकारी नहीं थी, मुझे बहुत अधिक रिवर्स करना पड़ा, इसलिए यहाँ मैं दिखाऊंगा कि IDA PRO का उपयोग करके Windows 23H2 के लिए KB5037771 पैच को कैसे रिवर्स किया जाए। मैं dwmcore.dll संस्करण 10.0.22621.3447 और संस्करण 10.0.22621.3593 के बीच बाइनरी डिफिंग करने के लिए BINDIFF का उपयोग करूंगा, दिखाऊंगा कि हीप ओवरफ्लो कैसे उत्पन्न होता है, और फिर विशेषाधिकारों को बढ़ाकर इसका शोषण करूंगा, अंत में एक कार्यशील PoC बनाऊंगा।
अनुक्रमणिका:
[Windows DWM कोर लाइब्रेरी विशेषाधिकार वृद्धि भेद्यता (CVE-2024-30051) 1](#windows-dwm-core-library-elevation-of-privilege-vulnerability-cve-2024-30051)
[भेद्यता विवरण: 2](#vulnerability-details)
[बग खोजने के लिए डिफिंग: 3](#diffing-to-find-the-bug)
[CVE-2024-30051 का शोषण करने वाले PoC का विश्लेषण: 8](#analysis-of-the-poc-exploiting-cve-2024-30051)
[1)प्रारंभिकरण 8](#initialization)
[2)हुकिंग 8](#hooking)
[3)विंडो बनाना 16](#creating-the-window)
[4)डिवाइस बनाएं 16](#create-device)
[5) फैक्ट्री बनाएं 22](#create-factory)
[6) डिवाइस कॉन्टेक्स्ट बनाएं 28](#create-a-device-context)
[7) कंपोज़िशन डिवाइस बनाएं 29](#create-a-composition-device)
[8) hook3 फ़ंक्शन को कॉल करना 31](#calling-dcompositioncreatedevice-function)
[9) HWND के लिए टार्गेट बनाना 32](#creating-a-target-for-handle-hwnd)
[10)सतह बनाना 33](#creating-surface)
[11)BeginDraw, EndDraw, और CreateVisual को कॉल करना 34](#calling-begindraw-enddraw-and-createvisual)
[11)Visual SetContent को कॉल करना 36](#calling-visual-setcontent)
[12)ऑब्जेक्ट रिलीज़ करें 38](#release-objects)
[13)कंपोज़िशन डिवाइस को कमिट करें 38](#commit-composition-device)
[14)hook2 को कॉल करना 39](#calling-hook2)
[15)hook को कॉल करना 39](#remember-that-the-vulnerable-function-can-be-reached-using-some-methods-of-the-cprimitivegroup-class.-at-this-point-it-creates-a-heap-then-hook2-captures-and-saves-the-corresponding-heaphandle.)
[16)hook4 को कॉल करना 41](#calling-the-function-hook4)
[17)हीप स्प्रे करना 49](#performing-heap-spray)
[18)भेजने से पहले बेस चंक को संशोधित करना 51](#modifying-the-base-chunk-before-send)
[19)DWM प्रक्रिया को डीबग करना 52](#debugging-the-dwm-process)
[20)इंटीग्रिटी सिस्टम स्तर तक विशेषाधिकार बढ़ाना 62](#elevating-privileges-to-integrity-system-level)
Windows DWM कोर लाइब्रेरी विशेषाधिकार वृद्धि भेद्यता CVE-2024-30051
जारी: 14 मई, 2024
CNA निर्दिष्ट करना: Microsoft CVE-2024-30051
प्रभाव: विशेषाधिकार वृद्धि
अधिकतम गंभीरता: महत्वपूर्ण
कमजोरी:
CWE-122: हीप-आधारित बफर ओवरफ्लो
CVSS: 3.1 7.8 / 7.2
यह भेद्यता dwmcore.dll नामक मुख्य Windows DWM लाइब्रेरी में पूर्णांक विभाजन के भीतर आकार की गणना में त्रुटि के कारण मौजूद है। एक स्थानीय उपयोगकर्ता dwmcore.dll में CCommandBuffer::Initialize विधि में हीप पर बफर ओवरफ्लो उत्पन्न कर सकता है और इंटीग्रिटी सिस्टम विशेषाधिकारों के साथ DWM उपयोगकर्ता के रूप में मनमाना कोड निष्पादित कर सकता है। एक्सप्लॉइट मेमोरी तैयार करने के लिए DWM प्रक्रिया में हीप स्प्रे करेगा और अंततः dwmcore.dll में एक हीप ओवरफ्लो उत्पन्न करेगा, जो हीप स्प्रे के कुछ हिस्सों को रिलीज़ करने पर ट्रिगर होगा।
एक्सप्लॉइट सफल होने के बाद, DWM प्रक्रिया हमारे तैयार किए गए DLL को लोड करेगी जो हमारे कोड या हमारे निष्पादन योग्य (हमारे मामले में एक CMD) को DWM उपयोगकर्ता के रूप में निष्पादित करेगा, जिसके पास इंटीग्रिटी सिस्टम विशेषाधिकार हैं।

आइए इस भेद्यता के माध्यम से चलें और देखें कि यह हमें इंटीग्रिटी स्तर SYSTEM के साथ DWM उपयोगकर्ता के रूप में चलाने की अनुमति कैसे देता है। ध्यान दें कि चूँकि यह व्यवस्थापक समूह से संबंधित उपयोगकर्ता नहीं है, इसलिए इसमें कुछ विशेषाधिकार प्रतिबंध हैं।
Windows 11 23H2 के लिए पैच यहाँ से डाउनलोड किया जा सकता है:
https://www.catalog.update.microsoft.com/Search.aspx?q=KB5037771
windows11.0-kb5037771-x64_19a3f100fb8437d059d7ee2b879fe8e48a1bae42.msu
dwmcore.dll का कमजोर संस्करण है: 10.0.22621.3447
dwmcore.dll का पैच किया गया संस्करण है: 10.0.22621.3593
परिवर्तित फ़ंक्शनों का विश्लेषण करने पर, यह स्पष्ट है कि CCommandBuffer::Initialize के पैच किए गए संस्करण में कई ब्लॉक जोड़े गए हैं, जिससे यह अपैच किए गए संस्करण से काफी अलग दिखता है।

उस फ़ंक्शन को स्थैतिक रूप से रिवर्स करने के बाद, CD2DSharedBuffer::GetBufferSize के दो कॉल हैं।
पहला कॉल new में आवंटित करने के लिए size प्राप्त करता है और दूसरा कॉल memcpy के लिए समान आकार प्राप्त करता है।

शुरुआत में सब कुछ सही लगता है। हालाँकि, आवंटन से पहले, यह आकार के साथ कुछ ऑपरेशन करता है।

यह समान CD2DSharedBuffer::GetBufferSize फ़ंक्शन को कॉल करके buffer_size और buffer_size2 प्राप्त करता है, दोनों समान मान लौटाते हैं। लेकिन new में यह एक पूर्व-ऑपरेशन करता है, buffer_size का 0x90 से पूर्णांक विभाजन और फिर 0x90 से गुणा, जबकि memcpy में यह लौटाए गए buffer_size2 का उपयोग बिना उस पर ऑपरेशन किए करता है।
इन ऑपरेशनों के साथ, मैंने पाया कि new और memcpy में अंततः उपयोग किया गया आकार भिन्न हो सकता है।
buffer_size = buffer_size2 (लौटाए गए आकार)
size_new= buffer_size/0x90 x 0x90
size_memcpy=buffer_size2
उदाहरण के लिए, यदि buffer_size 0x91 है
buffer_size = buffer_size2=0x91
size_new= buffer_size/0x90 x 0x90 =0x90
size_memcpy= buffer_size2= 0x91
यह उदाहरण साबित करता है कि एक हीप ओवरफ्लो है। यह आवंटित बाइट्स से अधिक बाइट्स कॉपी कर रहा है, और आकार नियंत्रणीय है।
उदाहरण के लिए, यदि buffer_size 0x23f है जैसा कि POC में उपयोग किया गया है।
buffer_size = buffer_size2=0x23F
size_new= buffer_size/0x90 x 0x90 =0x1b0
size_memcpy== buffer_size2=0x23f
कमजोर फ़ंक्शन का विश्लेषण करने के बाद, मैं देखना चाहता था कि कमजोर फ़ंक्शन CCommandBuffer::Initialize तक कैसे पहुँचा जाए। यह वह जगह है जहाँ चीजें जटिल होने लगती हैं।
इस फ़ंक्शन के संदर्भों को वापस देखने पर, ऐसा लगता है कि यह CPrimitiveGroup वर्ग के तरीकों से पहुँचा जा सकता है:

ऐसे तरीकों को CPrimitiveGroup ऑब्जेक्ट्स के vftable से एक्सेस किया जा सकता है:
इसका अपना कन्स्ट्रक्टर है:

और यह इस तरह पहुँचा जाता है:

जब मैं शुरू में इस प्रक्रिया से गुजरा, तो मैंने PDF "द लॉस्ट वर्ल्ड ऑफ डायरेक्टकंपोज़िशन: हंटिंग विंडोज डेस्कटॉप विंडो मैनेजर बग्स" पढ़ने का समय लिया और डायरेक्ट कंपोज़िशन की दुनिया में उतर गया। इससे मुझे अपना पहला PoC बनाने में मदद मिली।
साथ ही, मुझे win32ksys को रिवर्स करने की आवश्यकता थी और निम्नलिखित फ़ंक्शनों के माध्यम से पैकेज भेजने का प्रयास किया:
NtDCompositionCreateChannel
NtDCompositionProcessChannelBatchBuffer
NtDCompositionCommitChannel

मेरा पहला PoC CPrimitiveGroup कन्स्ट्रक्टर तक पहुँच गया। हालाँकि, बहुत अधिक रिवर्सिंग के बाद मुझे इन फ़ंक्शनों का उपयोग करके ALPC कॉल के माध्यम से सीधे कमजोर फ़ंक्शन तक पहुँचने के लिए vftable विधियों को संभालने का कोई तरीका नहीं मिला।
मैंने कुछ जटिल रिवर्सिंग करने में बहुत समय बिताया। इस प्रक्रिया के दौरान, मुझे उस मैलवेयर का नमूना मिला जो भेद्यता का शोषण करता था, जो अत्यंत सहायक था क्योंकि शोषण विधि मेरे शुरुआती विचार से कहीं अधिक जटिल है। इसमें कई सिस्टम API के हुकिंग भी शामिल हैं और यह ऐसी विधियों का उपयोग करता है जो शायद थोड़ी संदिग्ध हैं। लेकिन युद्ध और एक्सप्लॉइट में कुछ भी वैध है, इसलिए मैंने मैलवेयर का विश्लेषण करना शुरू किया और उस विश्लेषण से मैंने अपना अंतिम PoC बनाया जो अंततः भेद्यता का शोषण करता है, जिसे मैं नीचे समझाऊंगा।
सबसे पहले, मैं यह स्पष्ट करना चाहता हूं कि मैलवेयर केवल CVE-2024-30051 भेद्यता का शोषण नहीं करता है जो हमारी प्रक्रिया को इंटीग्रिटी सिस्टम स्तर तक बढ़ाती है, बल्कि यह एक दूसरा भाग भी करता है जो वहाँ से सभी विशेषाधिकारों के साथ एक SYSTEM उपयोगकर्ता को बढ़ाने में समाप्त होता है, जो पहले से ही बताए गए CVE से परे है।
इसके अतिरिक्त, यह ध्यान रखना महत्वपूर्ण है कि मैलवेयर मेरे PoC की तुलना में कहीं अधिक जटिल है जो कोड को कम करने की कोशिश करता है। मैलवेयर विश्वसनीयता सुनिश्चित करने के लिए कई और जाँचें करता है और इसीलिए यह पहले प्रयास में काम करता है। मैंने सरलीकरण के लिए उन सभी जाँचों को हटा दिया और खुद को शुद्ध शोषण के लिए समर्पित कर दिया, शायद शोषण प्राप्त करने के लिए PoC को दो या तीन बार चलाना पड़े।
निष्पादन योग्य PoC का लिंक है https://github.com/fortra/CVE-2024-30051
सबसे पहले, PoC अपने चल रहे OS संस्करण को प्राप्त करने के लिए GetVersion को कॉल करता है और उसके अनुसार यह कुछ वैश्विक चरों के विभिन्न प्रारंभिकरण करता है। मेरे PoC का परीक्षण Windows 11 23H2 और Windows 11 22h2 पर किया गया था। अन्य सिस्टम भी कमजोर हैं और उनका शोषण करने के लिए मान जोड़े गए हैं।
यह चार सिस्टम फ़ंक्शनों को हुक करता है और उन्हें हुक किए बिना यह शोषण प्राप्त नहीं कर सकता। ये सिस्टम हैं: RtlAllocateHeap, RtlCreateHeap, NtDCompositionCreateChannel और NtDCompositionCommitChannel।

इन फ़ंक्शनों में यह पहले 5 बाइट्स को पैच करेगा ताकि यह अपने स्वयं के कोड पर कूद जाए। निश्चित रूप से, कोड बहुत दूर नहीं हो सकता क्योंकि 5-बाइट जंप सभी मेमोरी को कवर नहीं करता है और उसके पास होना चाहिए।
इसे बनाने के लिए, मैलवेयर एक बहुत लंबे कोड का उपयोग करता है, यह तय करने के लिए मेमोरी मैप का विश्लेषण करता है कि वह अपने स्वयं के कोड का आवंटन कहाँ कर सकता है। चूँकि कोड जटिल है, मैंने इसे दो सरल पंक्तियाँ बनाने पर ध्यान केंद्रित किया:
base_ntdll = GetModuleHandleW(L"ntdll.dll");
global4_ = (char *)VirtualAlloc((LPVOID)(base_ntdll-0x2000), 0x1000uLL, 0x3000u, 0x40u);
मैंने ntdll base से 0x2000 घटाया और उस पते को VirtualAlloc में पास कर दिया ताकि वहाँ आवंटन हो सके।
64-बिट DLL मेमोरी में एक दूसरे से काफी अलग मैप किए जाते हैं, उनके बीच खाली स्थान होते हैं।
आइए देखें कि हुक कैसे काम करते हैं:

यह एक हुकिंग फ़ंक्शन को कॉल करता है, जो वह है जो RtlAllocateHeap API की हुकिंग करेगा, जिसमें तीन तर्क हैं, पहला पैच किए जाने वाले API का पता है, जिसे sym_RtlAllocateHeap कहा जाता है।
पैच करने से पहले, यह API की शुरुआत की ओर इंगित करता है:

यहाँ RtlAllocateHeap फ़ंक्शन है:

दूसरा तर्क hook नामक रूटीन है जो API के पूरी तरह से पैच होने पर निष्पादित होगा:


hook फ़ंक्शन my_RtlAllocateHeap को कॉल करता है।
हुकिंग फ़ंक्शन API के पहले 5 बाइट्स को पैच करेगा ताकि यह hook पर कूद जाए।
यह आवंटित क्षेत्र में कोड को कॉल करेगा जहाँ यह API के पहले निर्देश को निष्पादित करेगा जो 5 बाइट्स के साथ स्टेप किया गया था और फिर पैच किए गए बाइट्स के तुरंत बाद RtlAllocateHeap+5 पर कूद जाएगा:

हुक के बाद API इस तरह दिखेगा। पहले 5 बाइट्स बदल गए हैं ताकि यह hook पर कूद जाए। यह my_RtlAllocateHeap को कॉल करेगा, वह कोड जो ठीक ऊपर है, जो बैंगनी रंग में चिह्नित क्षेत्र में लौटकर API के निष्पादन को जारी रखेगा:

जब API निष्पादन समाप्त कर लेता है तो यह hook पर लौट आएगा। वहाँ से यह वैश्विक चर heap_base (जो शुरू में शून्य है) की तुलना RtlAllocateHeap को पारित पहले तर्क से करेगा:

उसके बाद कोड एक निश्चित विशेष आवंटन की प्रतीक्षा करता है, जिसमें एक विशिष्ट HeapHandle होता है। शुरुआत में यह चर शून्य है और जब तक यह शून्य है, यह छोड़ देगा और सामान्य RtlAllocateheap की तरह काम करेगा:

पैरामीटर HeapHandle RtlCreateHeap के अंदर प्राप्त होता है, जो संयोगवश दूसरा हुक किया गया API है।
वैश्विक चर heap_base के संदर्भों को देखते हुए, यह केवल hook2 फ़ंक्शन में अपना मान बदलता है, जो RtlCreateHeap को हुक करने के बाद निष्पादित होता है:


तो, विचार एक निश्चित HeapHandle को कैप्चर करना और उसे heap_base में सहेजना है। चूँकि यह अब शून्य से भिन्न है, hook फ़ंक्शन प्रत्येक आवंटन की तुलना करना शुरू कर देगा। इसलिए, PoC उस मेमोरी पते को सहेजेगा जिसका HeapHandle पहले संग्रहीत के समान है।
जब ऐसा होता है, तो यह आवंटन की दिशा को base नामक चर में सहेज देगा:
ये पहले दो हुक अब श्रृंखलाबद्ध हैं। जब hook2 HeapHandle के अपेक्षित मान को सहेजता है, तो यह hook फ़ंक्शन को सक्रिय करता है जो आवंटन पते को सहेजेगा जो समान HeapHandle का उपयोग करता है।
तीसरा हुक NtDCompositionCreateChannel को प्रस्तुत किया गया है। पहली बार जब इसे कॉल किया जाता है तो यह MappedAddress को सहेजेगा, जो तीसरे तर्क की सामग्री है। वहाँ से यह hooked_flag को 1 में बदल देगा ताकि अब से यह और न सहेजे और सामान्य रूप से काम करे।


चर base में सहेजा गया पता बाद में तीन बार पढ़ा जाएगा। उनमें से दो अंतिम हुक में होंगे, जिसे hook4 कहा जाता है:

NtDCompositionCommitChannel के लिए hook4 फ़ंक्शन का विश्लेषण बाद में किया जाएगा क्योंकि यह काफी जटिल और बहुत महत्वपूर्ण है।
चारों हुक पूरे होने के बाद, यह विंडो बनाना शुरू करने के लिए मुख्य फ़ंक्शन पर लौटता है। यह RegisterClassExW को कॉल करके किया जाता है। हालाँकि, बाद में उपयोग के लिए विंडो क्लास पंजीकृत करने के लिए, इसे CreateWindowExW फ़ंक्शन के साथ कॉल किया जाना चाहिए।

यह कॉलिंग थ्रेड द्वारा उपयोग के लिए CoInitializeEx को कॉल करके COM लाइब्रेरी को प्रारंभ करता है:

यह वांछित आकार के आधार पर विंडो आयत के आवश्यक आकार की गणना करता है:

CreateWindowExW फ़ंक्शन एक विंडो बनाने के लिए कॉल किया जाता है जिसे खींचा जाएगा:

वहाँ से, डिस्प्ले एडेप्टर का प्रतिनिधित्व करने वाला डिवाइस या DirectX डिवाइस बनाने के लिए D3D11CreateDevice को कॉल करें:


मेरे PoC में ppDevice का नाम d3dDevice और ppInmediateContext का नाम d3dContext है:

तर्क flags को 0x20 पर सेट करने की आवश्यकता है:

फिर AddRef को कॉल करें:

यह COM ऑब्जेक्ट के लिए इंटरफ़ेस पॉइंटर के संदर्भ काउंटर को बढ़ाता है:


THIS से मान 0x10 घटाया जाता है:

ID3D11Device-0x10 से ऑफसेट 0xf8 पर TComObject का पॉइंटर है:



यह नया THIS होगा और अंततः TComObject::AddRef पर कूद जाता है:

और यह TComObject के ऑफसेट 8 पर स्थित ऑब्जेक्ट काउंटर में एक जोड़कर समाप्त होता है:

फिर, AddRef D3D11CreateDevice में बनाए गए अन्य ऑब्जेक्ट प्रकार का काउंटर बढ़ाएगा, जो ID3D11DeviceContext प्रकार है:

इस मामले में, नया THIS खोजने के लिए, यह 0x108 घटाता है:


यह यहाँ कूदता है जहाँ ऑफसेट 0x98 पर नया THIS है:

यह काउंटर है। इस उदाहरण में, यह एक QWORD है:

PoC Direct2D का उपयोग करने के लिए D2D1CreateFactory को कॉल करता है, और ID2D1Factory इंटरफ़ेस बनाने के लिए जिसका उपयोग अन्य Direct2D संसाधनों को बनाने के लिए किया जाता है जिनका उपयोग आकृतियों को खींचने या वर्णन करने के लिए किया जा सकता है:

riid तर्क वही है जो Microsoft पृष्ठ द्वारा सुझाया गया है:
https://learn.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-d2d1createfactory

मैलवेयर इनका उपयोग करता है:

ID2D1Factory के लिए सही यहाँ पाया जा सकता है**:**
https://github.com/apitrace/dxsdk/blob/master/Include/d2d1_1.h

चूँकि मैं डायरेक्ट कंपोज़िशन में विशेषज्ञ नहीं हूँ, मैंने फिर मैलवेयर के समान चरणों का उपयोग किया:
लौटने वाली नई फैक्ट्री कोई विस्तृत प्रकार प्रदान नहीं करती है। यह कहता है void *, जिसका अर्थ है कि यह आधिकारिक रूप से प्रलेखित नहीं है:

जैसा कि मैं इस मामले में एक ऑब्जेक्ट प्रकार नहीं जानता, मैंने एक निष्पादन योग्य विकसित किया जो इसका उपयोग करता है ताकि इसे मेमोरी में आसानी से देखा जा सके:

चार hook फ़ंक्शनों में ब्रेकपॉइंट जोड़ें। इस मामले में, hook2 में एक ब्रेकपॉइंट दिखाएगा कि यह HeapHandle को कब कैप्चर करता है:

जब वांछित chunk कैप्चर हो जाए तो hook को रोक दिया जाना चाहिए:

अन्य दो hooks में ब्रेकपॉइंट लगाएं:

फिर यह QueryInterface को कॉल करना जारी रखता है:

https://help.solidworks.com/2020/english/api/sldworksapi/queryinterface_example_cplusplus_com.htm
https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/dxgi.idl

यह एक प्रकार का डायनामिक कास्टिंग करने का प्रयास करता है। यदि ID3D11Device प्रकार का ऑब्जेक्ट IDXGIDevice के इंटरफ़ेस (विधियों, आदि) को स्वीकार कर सकता है, तो यह मूल ऑब्जेक्ट की एक प्रति बनाता है जो नए प्रकार को स्वीकार करता है, उसके बाद उसका पॉइंटर लौटाता है। इस मामले में, चर d3dContext1 प्रकार IDXGIDevice का होगा:

दोनों ऑब्जेक्ट CLayeredObject<Cdevice> से विरासत में मिलते हैं
मूल ID3D11Device है**:**

उस पॉइंटर को लौटाने वाले की तरह।

फिर यह CreateDevice फ़ंक्शन के साथ एक ID2D1Device ऑब्जेक्ट बनाता है:

value2 में यह ID2D1Device प्रकार का ऑब्जेक्ट लौटाता है।
https://learn.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1device-createdevicecontext

यहाँ इसे PoC में लागू किया गया है:


फिर DCompositionCreateDevice को कॉल करें
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatedevice


IID _IDCompositionDevice से संबंधित है


जिस समय फ़ंक्शन DCompositionCreateDevice पर ट्रेस किया जाता है, यह hook3 पर रुकता है, जब यह NtDCompositionCreateChannel: को कॉल करता है।

इस तरह यह MappedAddress को कैप्चर करता है जिसे सिस्टम आंतरिक रूप से तब उपयोग करता है जब DCompositionCreateDevice कॉल किया गया था:
यह अब तक का कॉल स्टैक है:

यह वह बिंदु है जहाँ dcomp मॉड्यूल NtDCompositionCreateChannel फ़ंक्शन को कॉल करता है:

पिछले चरण से लौटने के बाद, MappedAddress को सेव करें। ALPC का उपयोग करके, यह DWM प्रोसेस से कनेक्ट होगा और फिर CreateTargetForHwnd को कॉल करेगा।

यह बनाई गई विंडो के HWND हैंडल का उपयोग करता है। यह उस डिवाइस से संबंधित है जिसे मैंने अभी बनाया है, और जो इस मेथड का THIS है:

फिर CreateSurface को कॉल करें
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createsurface

फिर BeginDraw, EndDraw को कॉल करें, और CreateVisual पर पहुँचें।

यह BeginDraw को कॉल करता है
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-begindraw
यह IID _IDXGISurface: का उपयोग करता है


फिर यह EndDraw: का उपयोग करता है:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-enddraw


अंत में, यह CreateVisual: को कॉल करता है:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createvisual

इसके बाद, यह IDCompositionVisual::SetContent: को कॉल करता है:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionvisual-setcontent


और यह SetRoot: को कॉल करता है:

दस्तावेज़ीकरण में यह निर्दिष्ट नहीं है कि BeginDraw में प्राप्त updateObject किस प्रकार का है।

इसके बाद, यह पहले बनाए गए ऑब्जेक्ट्स को रिलीज़ करता है:

और अब IDCompositionDevice प्रकार के उसी dcompDevice ऑब्जेक्ट का उपयोग करते हुए, यह Commit मेथड को कॉल करता है:


उस Commit मेथड को कॉल करना hook2 पर रुकता है, जो desired HeapHandle को कैप्चर करता है:

अब कॉल स्टैक यह है:

मुख्य पर लौटने से पहले, यह RtlAllocateHeap का उपयोग करके एक चंक भी बनाता है। फिर इसे hook फ़ंक्शन के अंदर base वेरिएबल में कैप्चर और स्टोर किया जाता है:


Create और Allocate के कॉल एक के बाद एक किए जाते हैं:

दोनों (Allocate और Create) DirectComposition::Cdevice::Commit: से कॉल किए जाते हैं:

उसके बाद, जब NtDCompositionCommitChannel कॉल किया जाता है, यह hook4 पर रुकता है:

NtDCompositionCommitChannel यहाँ से कॉल किया जाता है:

इसे DirectComposition::Cdevice::Commit से भी कॉल किया जाता है:

यह उल्लेख करने योग्य है कि सिस्टम ने ALPC द्वारा DWM को भेजने के लिए कमांड्स को पहले ही बैच कर दिया है। उसके बाद यह NtDCompositionCommitChannel. का उपयोग करके कमांड्स भेजता है।
hook4 फ़ंक्शन NtDCompositionCommitChannel कॉल्स को इंटरसेप्ट करता है और इस बिंदु पर बैच में और कमांड्स जोड़े जाएँगे।
देखते हैं कि hook4 क्या करता है:
base द्वारा इंगित चंक में एक लूप चलाया जाता है।
यह लूप से तब बाहर निकलता है जब उसे चंक के अंदर 0x120 मान मिलता है:

यह उस पते और ऑफसेट को स्टोर करता है जहाँ 0x120 मान स्थित था:

यह 0x120 मान को value4 से ओवरराइट करता है, जो 0x1b0 + 0x8f = 0x23f. के बराबर है। यह वह आकार है जिसका उपयोग यह memcpy में ओवरफ्लो के समय करेगा:



यह उस एड्रेस पॉइंटर में 0xbc + 0x90 जोड़ता है जहाँ 0x120 स्थित था:


याद रखें कि base से 0x48 ऑफसेट पर 0x120 आकार था। इसे 0x23f द्वारा ओवरराइट किया गया था, इसलिए मूल चंक का आकार 0x120: होना चाहिए।
स्रोत 0x23f + 0x2c: का पॉइंटर एड्रेस है:

इसने शुरू में 0x90 जोड़ा था लेकिन अब यह फिर से 0x90 घटा रहा है।
गंतव्य pointer to 0x120 + 0xbc का एड्रेस होगा:

यह इस पर लिखने जा रहा है:

सभी लिखाए गए मान चंक के अंदर होंगे:

यह लूप को 3 बार दोहराएगा, जो 0x1b0/0x90: के पूर्णांक विभाजन का परिणाम है:

उसके बाद, चूँकि ArgChannelHandle चैनल वही है जिसका उपयोग MappedAddress कैप्चर होने पर किया गया था। PoC NtDCompositionProcessChannelBatchBuffer. का उपयोग करके batch में कमांड्स जोड़ेगा। इन्हें सिस्टम द्वारा पहले जोड़े गए कमांड्स के साथ संसाधित किया जाएगा**।**
batch उन्हें इकट्ठा करता है और फिर सभी कमांड्स NtDCompositionCommitChannel: का उपयोग करके एक साथ भेजे जाते हैं:


भेजे गए कमांड का मान 8 है, जो 4 अलग-अलग ट्रैकर्स (1,2,3, और 4) के लिए SetResourceIntegerProperty से मेल खाता है।
जब PoC मुख्य फ़ंक्शन पर लौटता है, तो यह HeapSpray करने के लिए एक अलग चैनल बनाता है।
यह 0x10000 कमांड्स को बैच करता है, जिन्हें _NtDCompositionCommitChannel: के साथ भेजा जाता है:

यह CreateResource=1 मान और उस प्रकार का उपयोग करता है जो CHolographicInteropTextureMarshaler = 0x50: के अनुरूप है:

आवंटन नीचे दिए गए कोड में किए जाते हैं। स्प्रे करने के लिए बनाए गए ऑब्जेक्ट्स का आकार 0x1b0: है:

फिर यह पिछले चरण में बनाए गए ऑब्जेक्ट्स को रिलीज़ करने के लिए एक लूप चलाता है और अब मेमोरी वितरण में छेद बनाता है।
वेरिएबल counter2 0x3000 से शुरू होता है और 0x7000 से कम होने तक 0x20 के चरण जोड़ता है:

यह base + 0x48 + 44 + 0x1b0 वाले चंक की दिशा से 0x41 लिखता है।
अर्थात, यह ऐसे मान लिख रहा है जिनका उपयोग बाद में किया जाएगा—जब यह आसन्न चंक को ओवरफ्लो करता है:
वह pvalue7 base से 0x224 एड्रेस पर स्थित है:

फिर यह “escribe” फ़ंक्शन पर जाता है:

यह pKernelCallbacktable और 0x388, LoadLibraryA एड्रेस और लोड होने वाली DLL का पथ लिखता है। इस मामले में, मैंने इसे s11.dll. नाम दिया।

अब, हीप ओवरफ्लो होने पर कमजोर फ़ंक्शन पर रुकने के लिए एक कर्नेल डीबगर की आवश्यकता होती है। ऐसा इसलिए है क्योंकि DWM प्रोसेस को user mode डीबगर से डीबग नहीं किया जा सकता।
टारगेट को रिमोटली डीबग करने के लिए IDA PRO का उपयोग करते हुए, एक कंडीशनल ब्रेकपॉइंट सेट करें ताकि यह तब रुके जब आकार 0x1b0: के बराबर हो:
print ("VALUE1 %x" % ((cpu.rax)))
return cpu.rax==0x1b0.
चूँकि एक user mode प्रोग्राम को कर्नेल से डीबग किया जा रहा है, ब्रेकपॉइंट लगाने के लिए DWM प्रोसेस कॉन्टेक्स्ट में स्विच करने की आवश्यकता होती है। उपयोगकर्ता प्रतीकों को पुनः लोड करें:
. reload /user
कर्नेल वाले प्रतीकों को पुनः लोड करें:
. reload /f

यह तब रुकेगा जब ShowWindow को स्टेप ओवर किया जाएगा**:**

यह 0x1b0 आकार के साथ आवंटित करता है और 0x23f, आकार के साथ कॉपी करता है, जिससे हीप ओवरफ्लो उत्पन्न होता है:

इस बिंदु पर कॉल स्टैक इस प्रकार दिखता है:
ओवरफ्लो बनाने के लिए, DWM नीचे दिए गए कोड में मान प्राप्त करता है:
मेरे PoC से भेजे गए base में तैयार किए गए मान dwmcore.dll: मॉड्यूल में DWM प्रोसेस से MapViewofFile का उपयोग करके पढ़े जाते हैं:

पिछला फ़ंक्शन यहाँ से कॉल किया जाता है:


जब इसे hook4 से ALPC का उपयोग करके destination_copy (NtDCompositionCommitChannel) के माध्यम से भेजा जाता है, तो यह रुकता है:

याद रखें कि hook4 में, बैच में कमांड्स जोड़े गए थे। हालाँकि, सिस्टम ने भी पहले से बैच में कुछ कमांड्स जोड़ दिए थे, जिनमें base और तैयार किया गया डेटा शामिल है:



इस मामले में यह एक मेमोरी क्षेत्र साझा करता है जो 000001cd'178d0000. से शुरू होता है। जब इसे memcpy करने के लिए स्रोत के रूप में उपयोग किया जाता है, तो यह उसी मेमोरी क्षेत्र में 0x794 बाइट्स बाद होगा।
साझा मेमोरी क्षेत्र का आकार 0x4000: है:

यह तब रुकेगा जब आवंटित किया जाने वाला आकार 0x1b0, होगा, और 0x23f बाइट्स कॉपी करने के लिए memcpy तक पहुँचेगा:

मेमोरी में 0x1b0 से आगे वह कोड है जो आसन्न ब्लॉक को ओवरराइट करके ओवरफ्लो करेगा:
जब PoC से चंक्स रिलीज़ किए जाते हैं, तो यह LoadLibraryA , पर जंप करके समाप्त होता है, जो तैयार की गई लाइब्रेरी को लोड करता है:

वह यहाँ से आता है:


हीप स्प्रे 0x1b0 आकार के CHolographicInteropTexture. प्रकार के ऑब्जेक्ट्स के साथ किया गया था।
चूँकि मैंने मेमोरी वितरण में छेद बनाए थे, यह कुछ ऑब्जेक्ट्स को रिलीज़ करता है। चूँकि जो ब्लॉक ओवरफ्लो होने वाला है उसका आकार भी 0x1b0 है, इसलिए उसके हीप स्प्रे के छेदों में स्थित होने की उच्च संभावना है।
memcpy के गंतव्य पर ब्लॉक हर 0x1b0 बाइट्स पर स्थित होते हैं:

एक vftable का पॉइंटर LoadLibrary: के पॉइंटर द्वारा ओवरराइट हो जाता है:
ओवरराइट करने से पहले:

ओवरराइट करने के बाद:

याद रखें कि यह [R11+50], पर जंप करके समाप्त हुआ, जो LoadLibraryA का पॉइंटर है।
PoC निष्पादित करते समय, DLL को उसी पथ पर कॉपी करें जो PoC में दर्शाया गया है:
PoC निष्पादित करने के बाद, DWM उपयोगकर्ता Integrity System Level विशेषाधिकारों के साथ एक CMD प्रोसेस निष्पादित होती है:

संदर्भ:
PoC at Fortra GitHub: https://github.com/fortra/CVE-2024-30051
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-30051
https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2024-30051
यह PoC को पूरा करता है। याद रखें कि यदि आप इसे कई बार निष्पादित करते हैं तो हीप अस्थिर स्थिति में रहेगा, इसलिए इसे फिर से काम करने के लिए आपको मशीन को रीस्टार्ट करने की आवश्यकता हो सकती है। साथ ही, हालाँकि यह हमेशा पहले प्रयास में काम नहीं कर सकता है, यह आमतौर पर दूसरे या तीसरे प्रयास के दौरान सही ढंग से काम करेगा। जैसा कि आप देख सकते हैं, रिवर्सिंग कठिन हो सकती है, इसलिए यदि आपके कोई प्रश्न हैं, तो आप मुझसे परामर्श कर सकते हैं।
Mail: [email protected]
X: @ricnar456