
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 को पास कर दिए जाते हैं। यह केवल तब हुआ जब पैकेट कर्नेल के रुकने के दौरान भेजे गए थे, लेकिन अगले ब्रेकपॉइंट के हिट होने से पहले संसाधित नहीं हुए थे।
यह व्यवहार संभवतः एक प्रदर्शन अनुकूलन है, जहाँ कम थ्रूपुट पर, कर्नेल पैकेटों को व्यक्तिगत रूप से संसाधित करता है, लेकिन अधिक मात्रा में, पैकेटों को सूचियों में व्यवस्थित किया जाता है और बैचों में संसाधित किया जाता है। सबसे अधिक संभावना है कि प्रसंस्करण को गति देने के लिए सूचियाँ प्रोटोकॉल और स्रोत पते जैसे कारकों के आधार पर अलग की जाती हैं, इसलिए हमारी सूची में हमारे द्वारा भेजे गए केवल IPv6 पैकेट होने चाहिए। अरे दोस्त, मैंने सुना तुम्हें DoS पसंद है
अब जब हम जानते हैं कि उच्च थ्रूपुट के दौरान पैकेट सूचियों में समाहित हो जाते हैं, तो यह स्पष्ट है कि सबसे आसान विकल्प क्या होगा। हमारा DoS PoC विडंबनापूर्ण रूप से DoS स्थिति को ट्रिगर करने के लिए DoS का उपयोग करने वाला है। यदि हम सिस्टम को IPv6 पैकेटों के फटाकों से भर देते हैं, तो हमें IppSendErrorList() को एक अच्छी बड़ी सूची पास करने में सक्षम होना चाहिए।
पहले तो, मैंने कितने भी पैकेट भेजे, फिर भी मैं केवल कर्नेल को रोकने पर ही सूची को n > 1 बना सकता था। लेकिन… चूँकि हम Python (दर्दनाक रूप से धीमा) का उपयोग कर रहे हैं, एक VM में (दोगुना दर्दनाक रूप से धीमा), हमें शायद कुछ सेटिंग्स बदलने की आवश्यकता होगी। अपने अटैक सिस्टम पर हो रहे VM-सेप्शन का मुकाबला करने के लिए, मैंने लक्ष्य VM को केवल एक CPU कोर का उपयोग करने के लिए पुन: कॉन्फ़िगर करने का निर्णय लिया।
बढ़िया! पैकेट सूची अब बहुत सारी प्रविष्टियों वाली एक सूची है!
तो, पता चला कि VM के अंदर VM DoS के लिए सबसे अच्छा विकल्प नहीं है, किसने सोचा था? लेकिन हमने अंत में इसे काम करने में सफलता पाई। अब, हमें बस यह पता लगाने की आवश्यकता है कि IppSendError() क्या करता है और समस्या किस भाग में है। और अधिक रिवर्सिंग…फिर से…हमेशा के लिए…
कुछ व्यापक रिवर्स इंजीनियरिंग के बाद, यह बहुत स्पष्ट हो गया कि IppSendError क्या करता है। सामान्य परिस्थितियों में, यह net_buffer_list->Status को 0xC000021B (STATUS_DATA_NOT_ACCEPTED) पर सेट करके पैकेट को अक्षम कर देता है। फिर, यह त्रुटिपूर्ण पैकेट के बारे में जानकारी वाला एक ICMP त्रुटि प्रेषक को वापस भेजता है।
IppSendError के दो प्रासंगिक भाग।
मेरा पहला कदम यह देखना था कि क्या tcpip.sys में कोई ऐसे फ़ंक्शन हैं जो net_buffer_list->Status मान को अनदेखा करते हैं। इसके परिणामस्वरूप ड्राइवर उन पैकेटों को संसाधित करेगा जो अपरिभाषित या अप्रत्याशित अवस्थाओं में हैं, उम्मीद है कि एक शोषण स्थिति की ओर ले जाएगा।
पैकेटों को संसाधित करने के लिए जिम्मेदार मुख्य लूप।
चूँकि सभी पार्सिंग फ़ंक्शन को कॉल करने वाला लूप एक त्रुटि जाँच में लपेटा गया है (जिसका अर्थ है कि एक बार त्रुटि कोड सेट होने के बाद हम कहीं नहीं जा सकते), मैंने सोचा कि यह गलत खरगोश का बिल था। इसके बजाय, मैंने IppSendError पर वापस जाने का निर्णय लिया और देखा कि क्या कोई कोड पथ हैं जो त्रुटि कोड सेट करने से पहले पैकेट की स्थिति को संशोधित करते हैं, जो एक रेस कंडीशन का कारण बन सकता है।
बहुत अधिक रिवर्स इंजीनियरिंग के बाद, मुझे IppSendError के बिल्कुल नीचे निम्नलिखित कोड मिला।
IppSendError में एक कोड पथ जो packet_size को शून्य पर सेट करता है।
जब IppSendErrorList, और इस प्रकार IppSendError, को always_send_icmp तर्क true पर सेट करके कॉल किया जाता है, तो ऐसा प्रतीत होता है कि यह सूची में प्रत्येक पैकेट को ICMP त्रुटि भेजने का प्रयास करता है।
फिर, शायद केवल ईश्वर को ज्ञात कारणों से, यह कोड के एक ब्लॉक तक पहुँचता है जहाँ packet->packet_size फ़ील्ड शून्य पर सेट हो जाती है।
always_send_icmp को true सेट करने के लिए, हमें बस विकल्प हेडर प्रसंस्करण में एक विशिष्ट त्रुटि उत्पन्न करने की आवश्यकता है, जो 'Option Type' मान को 0x80 से बड़ी किसी भी संख्या पर सेट करके किया जा सकता है।
def build_malicious_option(next_header, header_length, option_type, option_length):
dest_options_header = 60
options_header = struct.pack('BBBB', next_header, header_length, option_type, option_length) + b'1337'
return Ether(dst=mac_addr) / IPv6(dst=ip_addr, nh=dest_options_header) / raw(options_header)
packet = build_malicious_option(next_header=59, header_length=0, option_type=0x81, option_length=0)
sendp(packet)
लेकिन, क्या packet_size को शून्य पर सेट करने से पार्सर नहीं टूटना चाहिए?
पैकेटों को संसाधित करने के लिए जिम्मेदार मुख्य लूप का एक अंश।
पैकेट हैंडलर केवल packet->next_header मान के आधार पर एक VTable फ़ंक्शन को कॉल करता है, जो प्री-पार्सिंग के दौरान सेट होने के बाद से अपरिवर्तित रहता है। यह पैकेट प्रसंस्करण को जारी रखने की अनुमति देता है और हमें यह भी नियंत्रण देता है कि कौन सा प्रसंस्करण होता है।
चूँकि packet->next_header मान IPv6 पैकेट के 'Next Header' फ़ील्ड से प्राप्त होता है, हम इसे किसी भी मान्य IPv6 हेडर मान पर सेट कर सकते हैं, और लूप संबंधित पार्सर को कॉल करेगा। यह हमें हमले की बहुत अधिक संभावित सतह देता है।
IPv6 पैकेट प्रारूप.
बस इतना करना बाकी है कि IPv6 पार्सर का एक सुलभ भाग ढूंढा जाए जो packet_size फ़ील्ड के साथ कुछ बेवकूफी करता हो। विखंडन पर वापस
पहली जगह जहाँ मैंने देखने का फैसला किया, वह IPv6 खंड पार्सर था, क्योंकि वहाँ पुरानी cve-2021-24086 भेद्यता थी, इसलिए यह और अधिक विचित्र कोड खोजने के लिए एक अच्छी जगह लग रही थी।
एह… यह बहुत करीब है, लेकिन उतना ही दूर भी।
हमारे पास यहाँ एक भेद्यता है, लेकिन यह RCE नहीं है।
मूलतः, अधिकांश CPU पर, रजिस्टर चक्रीय होते हैं। यदि आप किसी रजिस्टर को उसके अधिकतम संभव मान से आगे बढ़ाते हैं, तो वह वापस शून्य पर आ जाता है। इसी प्रकार, यदि आप इसे अपने न्यूनतम संभव मान से नीचे घटाते हैं, तो यह उच्चतम संभव मान पर घूम जाता है। इन्हें क्रमशः इंटीजर ओवरफ्लो और इंटीजर अंडरफ्लो कहा जाता है। यह व्यवहार हस्ताक्षरित पूर्णांकों के लिए थोड़ा अलग है, लेकिन हम यहाँ उनसे निपट नहीं रहे हैं।
पहली पंक्ति, fragment_size = LOWORD(packet->packet_size) - 0x30, में निम्नलिखित ASM कोड शामिल है:
movzx eax, word ptr [rdi+0A8h]
movzx ecx, ax
sub ecx, 30h
mov edx, ecx
AX, EAX रजिस्टर के निचले 16 बिट है। हालाँकि EAX रजिस्टर 32-बिट है, AX अपने स्वयं के 16-बिट रजिस्टर की तरह काम करता है, इसलिए कोई भी ओवरफ्लो या अंडरफ्लो AX तक ही सीमित रहता है, और EAX रजिस्टर के बाकी हिस्सों को प्रभावित नहीं करेगा। यह अविश्वसनीय रूप से सुविधाजनक है क्योंकि EAX रजिस्टर पर अंडरफ्लो के परिणामस्वरूप 4 बिलियन का मान होगा, जिसके परिणामस्वरूप 4GB मेमोरी आवंटित करने का प्रयास होगा, जो संभवतः विफल हो जाएगा।
चूँकि packet->packet_size का मान शून्य है, यह कोड ax को शून्य पर सेट करता है, फिर इसमें से 0x30 घटाता है।
सामान्य परिस्थितियों में पैकेट हेडर 0x30 बाइट होता है, इसलिए packet_size - 0x30 खंड डेटा का आकार है।
हमारे मामले में, packet->packet_size 0 है, इसलिए इसमें से 1 भी घटाने पर रजिस्टर अधिकतम संभव 16-बिट पूर्णांक मान (0xFFFF) पर घूम जाएगा। चूँकि हम 0x30 घटा रहे हैं, AX का मान अंडरफ्लो हो जाएगा और MAX_VALUE - 0x2F, या 0xFFD0 हो जाएगा, जो 65,488 है।
दुर्भाग्य से, चूँकि समान गणना का उपयोग मेमोरी आवंटन और डेटा कॉपी करने दोनों के लिए किया जाता है, हमें बफर ओवरफ्लो नहीं मिलता है। मेरा मानना है कि RtlCopyMdlToBuffer() स्रोत बफर पर बाउंड्स चेकिंग भी करता है, इसलिए हमें आउट-ऑफ-बाउंड्स रीड भी नहीं मिलती है। हालाँकि, हम पूरी तरह से खाली हाथ नहीं आते हैं।
चूँकि ExAllocatePoolWithTagPriority() आवंटित मेमोरी को शून्य नहीं करता है, और RtlCopyMdlToBuffer() केवल उपलब्ध डेटा की वास्तविक मात्रा को कॉपी करता है, हमें लगभग 65kb की अप्रारंभीकृत कर्नेल मेमोरी मिलती है। चूँकि मेमोरी पते डीलोकेशन के बाद पुनर्चक्रित हो जाते हैं, बफर में संभवतः वही भरा होगा जो पुन: आवंटन से पहले पते पर संग्रहीत था। यदि हम विखंडन का उपयोग करके एक ऐसा पैकेट बना सकते हैं जो हमें वापस भेजा जाता है, जैसे ICMP Echo अनुरोध, तो हम संभावित रूप से यादृच्छिक कर्नेल मेमोरी को लीक कर सकते हैं, जिससे ASLR बाईपास हो सकता है।
इसके अलावा, कोड reassembly->fragment_size को अंडरफ्लो हुए 16-बिट पूर्णांक (65,488) पर भी सेट करता है, इसलिए अब हमारे पास दो अलग-अलग चर हैं जिनका उपयोग हम संभावित रूप से बफर ओवरफ्लो का कारण बनने के लिए कर सकते हैं। पराजित, लेकिन हार नहीं मानी
दुर्भाग्य से (या सौभाग्य से, क्योंकि इसने शायद मेरा बहुत समय बचाया), किसी ने मुझसे पहले ही यह कर लिया। इससे पहले कि मैं बफर ओवरफ्लो को ट्रिगर करने के लिए अंडरफ्लो हुए पूर्णांकों में से किसी एक का उपयोग करने के लिए कोई जगह ढूंढ पाता, @ynwarcs को उत्तर मिल गया और उसने एक PoC प्रकाशित किया। यह मेरी पहेली के अंतिम टुकड़े को हल करता है।
समाधान (या उनमें से कम से कम एक) Ipv6pReassemblyTimeout() है। जबकि हम प्रारंभिक खंड हैंडलिंग में ओवरफ्लो नहीं कर सकते, हम स्पष्ट रूप से सफाई के दौरान कर सकते हैं।
IPv6 खंड मेमोरी में तब तक बने रहेंगे जब तक तीन स्थितियों में से एक नहीं होती:
हम अपने विखंडन को इतना बुरी तरह से गड़बड़ कर देते हैं कि सिस्टम हमें बताता है कि अब रुकने का समय है।
हम 'More' फ़ील्ड को 0 पर सेट करके एक खंड भेजते हैं, जो इंगित करता है कि यह अंतिम खंड है, और सिस्टम पुन: संयोजन शुरू करेगा।
हम टाइमआउट अवधि (60 सेकंड) समाप्त होने से पहले अंतिम खंड नहीं भेजते हैं, और सिस्टम खंडों को छोड़ देता है।
Ipv6pReassemblyTimeout() को स्थिति 3 के तहत कॉल किया जाता है, तो आइए देखें कि इसका शोषण कैसे किया जा सकता है।
यह बिल्कुल वही है जो हमें चाहिए!
पहले, हमारी समस्या यह थी कि कोड ने मेमोरी आवंटन और कॉपी ऑपरेशन दोनों के लिए बिल्कुल समान गणना का उपयोग किया था। दूसरी ओर, यह कोड ऐसा नहीं करता है। आइए ASM पर गहराई से नज़र डालें कि यह कैसे शोषण योग्य है।
आवंटन आकार की गणना के लिए जिम्मेदार असेंबली कोड।
जैसा कि आप यहाँ देख सकते हैं, गणना का पहला भाग (fragment_list->net_buffer_length + reassembly->packet_length + 8) 16-बिट DX रजिस्टर का उपयोग करके किया जाता है।
यदि आपको पहले से याद हो, हमने reassembly->packet_length को 0xFFD0 पर अंडरफ्लो कर दिया था। तो DX रजिस्टर, 8 बाइट जोड़ने के बाद, 0xFFD8 है। यदि fragment_list->net_buffer_length 0x27 (39 बाइट) से बड़ा है, तो DX ओवरफ्लो हो जाएगा और शून्य पर रीसेट हो जाएगा।
fragment_list->net_buffer_length लगभग 0x38 बाइट होना चाहिए, इसलिए इसके परिणामस्वरूप DX रजिस्टर 8 पर ओवरफ्लो हो जाएगा। 0x28 बाइट जोड़ने के बाद, हमें सिर्फ 48 बाइट का मेमोरी आवंटन मिलेगा।
चूँकि बाद के memmove() कॉल आकार के लिए बिना छेड़छाड़ किए गए reassembly->packet_length मान का उपयोग करते हैं, इसके परिणामस्वरूप reassembly->payload से 30 बाइट बफर में 65,488 बाइट कॉपी हो जाएंगे। एक बड़ा अतिरिक्त बोनस यह है कि कॉपी किया गया अधिकांश डेटा खंड पेलोड से आता है, जिसे हम नियंत्रित करते हैं, और किसी भी प्रारूप का मनमाना डेटा हो सकता है, इसलिए हमें एक अच्छा काफी नियंत्रणीय कर्नेल पूल आधारित बफर ओवरफ्लो मिलता है।
भेद्यता को ट्रिगर करने का मौका पाने के लिए, हमें IppSendErrorList कॉल किए जाने के समय लिंक्ड लिस्ट में दोषपूर्ण विकल्प पैकेट के बाद एक या अधिक खंड पैकेट रखने की आवश्यकता है। हालाँकि, मेरे परीक्षण से ऐसा प्रतीत नहीं होता है कि यह शोषण की गारंटी देता है। मेरा मानना है कि कुछ अन्य शर्तें भी हैं जिन्हें पूरा करने की आवश्यकता है। मुझे संदेह है, लेकिन पुष्टि नहीं हुई है, कि IppSendError में सिंक्रनाइज़ेशन कोड का अर्थ है कि हमें एक रेस कंडीशन भी जीतनी होगी।