
IPv6 के माध्यम से कर्नेल का दूरस्थ शोषण
IPv6 के माध्यम से कर्नेल का दूरस्थ शोषण
CVE-2024-38063 - IPv6 के माध्यम से कर्नेल का दूरस्थ शोषण Marcus Hutchins
13 अगस्त को नवीनतम विंडोज पैच आने के बाद से मैं tcpip.sys (TCP/IP पैकेट को संभालने वाला कर्नेल ड्राइवर) की गहराई में डूबा हुआ हूँ। विंडोज कर्नेल के सबसे आसानी से पहुंचने वाले हिस्से में 9.8 CVSS स्कोर वाली भेद्यता को मैं बस छोड़ नहीं सकता था। मैंने पहले कभी IPv6 (या इसे पार्स करने वाले ड्राइवरों) को वास्तव में नहीं देखा था, इसलिए मुझे पता था कि इस भेद्यता को रिवर्स इंजीनियर करने की कोशिश बेहद चुनौतीपूर्ण होगी, लेकिन यह एक अच्छा सीखने का अनुभव होगा।
अधिकांश भाग के लिए, tcpip.sys काफी हद तक अप्रलेखित है। मैं पुराने बगों के लिए कुछ एक्सप्लॉइट राइट-अप खोजने में सक्षम था: यहाँ, यहाँ, और यहाँ, लेकिन और कुछ नहीं। जब मेरी अंग्रेज़ी Google खोज का शीर्ष परिणाम चीनी भाषा में लिखा होता है, तो मुझे तुरंत पता चल जाता है कि मैं अपनी गहराई से बाहर हूँ और मुसीबत में हूँ, लेकिन सीखना तो हमें है। Google Translate के औसत दर्जे के काम के बावजूद, उस पोस्ट ने IPv6 विखंडन कैसे काम करता है, इस बारे में अविश्वसनीय रूप से विस्तृत जानकारी प्रदान की, और मुझे एक अच्छी शुरुआत दी।
बाद में, कुछ फ़ंक्शन नामों को गूगल करते समय, मुझे उसी 2021 की भेद्यता का एक और विश्लेषण मिला, जो Axel Souchet (उर्फ 0vercl0k) द्वारा लिखा गया था, जो tcpip.sys की आंतरिक कार्यप्रणाली में और भी गहराई तक गया और मुझे कई अप्रलेखित संरचनाओं को परिभाषित करने के लिए पर्याप्त जानकारी दी। सबसे आसान पैच विश्लेषण कभी
आमतौर पर, केवल पैच को रिवर्स इंजीनियर करने में भी यह पता लगाने में दिन या सप्ताह लग सकते हैं कि कौन सा कोड परिवर्तन भेद्यता से मेल खाता है, लेकिन इस मामले में यह तुरंत हो गया। यह इतना आसान था कि सोशल मीडिया पर कई लोगों ने मुझसे कहा कि मैं गलत हूँ और बग कहीं और है। क्या मैंने वास्तव में उनकी बात सुनी और फिर गलत ड्राइवर को रिवर्स करने में एक पूरा दिन बर्बाद कर दिया? हम कभी नहीं जान पाएंगे।
पैच स्थापित करने से पहले और बाद में tcpip.sys का एक bindiff अवलोकन।
पूरे ड्राइवर में केवल एक ही फ़ंक्शन संशोधित किया गया है। आमतौर पर, मैं यह पता लगाने के लिए 20+ विभिन्न फ़ंक्शन परिवर्तनों के माध्यम से एक पूरा दिन बिता सकता था कि कौन सा वह है जिसे मुझे देखना चाहिए, लेकिन इस बार ऐसा नहीं है।
पैच से पहले Ipv6pProcessOptions().
पैच के बाद Ipv6pProcessOptions().
अत्यधिक लंबे नाम वाला Feature_2660322619__private_IsEnabledDeviceUsage_3() फ़ंक्शन कुछ ऐसा है जिसे Microsoft कभी-कभी आंशिक पैच रोलबैक सक्षम करने के लिए जोड़ता है। कॉल एक ग्लोबल फ्लैग या रजिस्ट्री सेटिंग की उपस्थिति की जाँच करता है, जो सेट होने पर फ़ंक्शन को false लौटाने का कारण बनेगा, जिसके परिणामस्वरूप पैच किए गए संस्करण के बजाय मूल कोड निष्पादित होगा।
Microsoft ऐसा करने का कारण यह है कि सुरक्षा पैच कभी-कभी अनजाने में चीजों को तोड़ देते हैं, इसलिए यह सेटिंग एक व्यवस्थापक को पूरे मासिक पैच रोलअप को अनइंस्टॉल किए बिना और अपने सिस्टम सुरक्षा को काफी कमजोर किए बिना एकल भेद्यता को अनपैच करने में सक्षम बनाती है।
इसे ध्यान में रखते हुए, यह स्पष्ट है कि यह पैच केवल IppSendErrorList() के कॉल को IppSendError() से बदलता है, हमें एक सुराग देता है कि समस्या किसी प्रकार की सूची से है। अब तक का सबसे आसान पैच डिफ (या कम से कम मुझे ऐसा लगा)। भेद्यता वैकल्पिक, शोषण अनिवार्य
पैच को रिवर्स इंजीनियर करके परिवर्तित कोड ढूंढना चुनौती का केवल आधा हिस्सा है (या इस मामले में 0.1% से भी कम)। बाकी प्रक्रिया में कोडबेस के पर्याप्त हिस्से को रिवर्स इंजीनियर करना शामिल है ताकि यह समझा जा सके कि क्या हो रहा है, यह पता लगाना कि किस तरह की भेद्यता को पैच किया गया था, लक्ष्य कोड तक पहुंचने के लिए अनुरोध कैसे तैयार किया जाए, और कौन सी स्थिति शोषण योग्य स्थिति में परिणत होती है।
पहला भाग काफी आसान है। परिवर्तन Ipv6pProcessOptions() में है, जो हमें बताता है कि यह IPv6 है और इसमें विकल्पों का प्रसंस्करण शामिल है। तो, RFC पर एक त्वरित नज़र हमें बताती है कि IPv6 विकल्प क्या है और हम इसे कहाँ पा सकते हैं।
विकिपीडिया से डेस्टिनेशन ऑप्शन्स हेडर का लेआउट।
ठीक है, बढ़िया. हम जो खोज रहे हैं वह डेस्टिनेशन ऑप्शन्स हेडर प्रतीत होता है, जो मुख्य IPv6 हेडर के ठीक बाद आता है। चलिए एक परीक्षण IPv6 पैकेट तैयार करने के लिए Python लाइब्रेरी 'scapy' का उपयोग करते हैं।
नोट: DDoS हमलों को कम करने के लिए, Windows नकली IP पतों का उपयोग करके कच्चे IP पैकेट बनाने की क्षमता को प्रतिबंधित करता है। इस कारण से, मैंने अपने प्रूफ-ऑफ-कॉन्सेप्ट को विकसित करने के लिए Linux का उपयोग करना चुना। जबकि Linux उपयोगकर्ताओं को निर्माण और कच्चे लेयर 2 और लेयर 3 पैकेट भेजने की अनुमति देता है, इसके लिए Python स्क्रिप्ट को रूट के रूप में चलाना आवश्यक है।
import sys
import struct
from scapy.all import *
def send_ipv6_option_packet(dest_ip):
ethernet_header = Ether()
ip_header = IPv6(dst=dest_ip)
options_header = IPv6ExtHdrDestOpt()
sendp(ethernet_header / ip_header / options_header)
if len(sys.argv) < 2:
print('Use: python3 script.py <target_ipv6_address>')
exit(-1)
send_ipv6_option_packet(sys.argv[1])
tcpip!Ipv6pProcessOptions पर ब्रेकपॉइंट सेट करने के बाद, फिर स्क्रिप्ट चलाने पर, यह स्पष्ट था कि भेद्य फ़ंक्शन तक पहुंचने के लिए बस एक खाली विकल्प संरचना के साथ एक IPv6 पैकेट भेजना आवश्यक था। फिर मैंने IppSendErrorList() के कॉल तक पहुंचने के लिए संरचना में कुछ अमान्य विकल्प जोड़ने का प्रयास किया।
एक संक्षिप्त कोड समीक्षा ने संकेत दिया कि लगभग कोई भी अमान्य विकल्प स्वरूपण IppSendErrorList के कॉल को ट्रिगर कर सकता है। इसलिए, मैंने एक अमान्य लंबाई (65535 बाइट्स से कम) के साथ जंबो पैकेट विकल्प का उपयोग करने का निर्णय लिया।
options_header = IPv6ExtHdrDestOpt(options=[Jumbo(jumboplen=0x1337)])
तो, IppSendErrorList() वास्तव में क्या करता है? खैर, कोड काफी सरल है।
संपूर्ण IppSendErrorList फ़ंक्शन।
कोड एक लिंक्ड लिस्ट को iterates करता है और सूची में प्रत्येक आइटम पर IppSendError() को कॉल करता है। फिर से, तारे संरेखित हो गए हैं और अब तक चीजें आसान रही हैं। यदि IppSendErrorList केवल एक सूची में प्रत्येक आइटम के लिए IppSendError को कॉल करता है, और पैच IppSendErrorList के कॉल को IppSendError से बदल देता है, तो समस्या तब उत्पन्न होती है जब IppSendError को पहले के अलावा किसी अन्य सूची आइटम पर कॉल किया जाता है।
तो, यह किसकी सूची है, और हम एक कैसे बनाएं? वह एक सूची बना रहा है, वह इसे जाँच रहा है….52,567 बार
यह वह जगह है जहाँ चीजें स्पष्ट से असामान्य रूप से कठिन हो गईं, हालांकि मुझे लगता है कि इसका एक बड़ा हिस्सा मेरे दो उपलब्ध मस्तिष्क कोशिकाओं में से एक के खराब कोविड संक्रमण से लड़ने में व्यस्त होने के कारण था। मैंने कोड के कुछ हिस्सों को समझने, सो जाने, फिर भूल जाने में कुछ दिन खो दिए कि मैंने क्या पता लगाया था। पूरी प्रक्रिया में यह समझने के लिए tcpip.sys के कुछ हिस्सों को रिवर्स इंजीनियर करने में एक सप्ताह से अधिक का समय लगा कि क्या हो रहा था। लेकिन Axel का ब्लॉग पोस्ट बेहद मददगार था।
Axel द्वारा रिवर्स इंजीनियर किए गए फ़ंक्शन और संरचना को देखकर, और वे किन अन्य फ़ंक्शनों को पास किए जाते हैं, यह स्पष्ट है कि Ipv6pProcessOptions() को पारित किया गया एकमात्र तर्क लेख में परिभाषित समान packet_t संरचना है। मूलतः, Ipv6pProcessOptions को पास किया गया पॉइंटर, और IppSendErrorList द्वारा iterated, पैकेटों की एक लिंक्ड-लिस्ट है।
तो, मैंने Ipv6pProcessOptions() पर एक ब्रेकपॉइंट सेट किया और सूची का निरीक्षण किया।
सूची->Next प्रविष्टि NULL है।
हर बार जब मेरा ब्रेकपॉइंट हिट हुआ, सूची में केवल एक पैकेट था। मैंने यह पता लगाने में जितना मैं स्वीकार करना चाहूंगा उससे कहीं अधिक समय बिताया कि क्यों और कैसे मेरी सूची को वास्तव में एक सूची बनाया जाए। मेरा पहला विचार IPv6 विखंडन था: IPv6 प्रेषकों को बड़े पैकेटों को अलग-अलग छोटे पैकेटों में विभाजित करने की अनुमति देता है, जिन्हें एक सूची में एक साथ रखना समझ में आता है।
व्यापक रिवर्स इंजीनियरिंग के बाद, मैंने पुष्टि की कि मेरी धारणाएँ सही थीं, हालांकि खंड सूची उस सूची से संबंधित नहीं है जिससे हम यहाँ निपट रहे हैं।
मैं वास्तव में पूरी तरह से संयोग से उत्तर ढूंढने में समाप्त हुआ। कभी-कभी, सूची भर जाती थी, लेकिन कारण स्पष्ट नहीं था। बहुत चक्कर लगाने के बाद, मुझे एहसास हुआ कि जब मेरा कर्नेल ब्रेकपॉइंट ट्रिगर होता है, तो यह पूरे कर्नेल को रोक देता है, जिससे नेटवर्क एडाप्टर पैकेट जमा कर लेता है। जब कर्नेल फिर से शुरू होता है, तो ये पैकेट एक साफ-सुथरी सूची में स्टैक के नीचे tcpip.sys को पास कर दिए जाते हैं। यह केवल तब हुआ जब पैकेट कर्नेल के रुकने के दौरान भेजे गए थे, लेकिन अगले ब्रेकपॉइंट के हिट होने से पहले संसाधित नहीं हुए थे।