
CVE-2024-38063 - IPv6 के माध्यम से कर्नेल का दूरस्थ शोषण
पूरे ड्राइवर फ़ाइल में केवल एक ही बदलाव किया गया था, जो अंततः वास्तव में बग निकला।
पैच स्थापित करने से पहले और बाद में tcpip.sys का एक bindiff अवलोकन।
पूरे ड्राइवर में केवल एक फ़ंक्शन को संशोधित किया गया है। आमतौर पर, मैं यह पता लगाने में पूरा दिन बिता सकता हूँ कि कौन सा फ़ंक्शन मुझे देखना चाहिए, लेकिन इस बार ऐसा नहीं है।

Ipv6pProcessOptions() पैच से पहले।
Ipv6pProcessOptions() . पैच के बाद।
न केवल एक ही फ़ंक्शन बदला गया था, बल्कि कोड की एक ही पंक्ति बदली गई थी।
अत्यधिक लंबे नाम वाला Feature_2660322619__private_IsEnabledDeviceUsage_3() फ़ंक्शन कुछ ऐसा है जिसे Microsoft कभी-कभी आंशिक पैच रोलबैक सक्षम करने के लिए जोड़ता है। यह कॉल वैश्विक फ़्लैग या रजिस्ट्री सेटिंग की उपस्थिति की जाँच करता है, जो सेट होने पर फ़ंक्शन को false लौटाने का कारण बनेगा, जिसके परिणामस्वरूप पैच किए गए संस्करण के बजाय मूल कोड निष्पादित किया जाएगा।
Microsoft ऐसा इसलिए करता है क्योंकि सुरक्षा पैच कभी-कभी अनजाने में चीजों को तोड़ देते हैं, इसलिए यह सेटिंग एक प्रशासक को पूरे मासिक पैच रोलअप को अनइंस्टॉल किए बिना और अपने सिस्टम सुरक्षा को काफी कमजोर किए बिना एकल भेद्यता को अनपैच करने में सक्षम बनाती है।
इसे ध्यान में रखते हुए, यह स्पष्ट है कि यह पैच केवल IppSendErrorList() के कॉल को IppSendError() से बदलता है, जिससे हमें संकेत मिलता है कि समस्या किसी प्रकार की सूची के साथ है। अब तक का सबसे आसान पैच अंतर (या मैंने ऐसा सोचा था)
पैच को रिवर्स इंजीनियर करके परिवर्तित कोड ढूँढना चुनौती का केवल आधा हिस्सा है (या इस मामले में 0.1% से भी कम)। शेष प्रक्रिया में कोडबेस को पर्याप्त रूप से रिवर्स इंजीनियर करना शामिल है ताकि यह समझा जा सके कि वास्तव में क्या चल रहा है, यह पता लगाना कि किस प्रकार की भेद्यता को पैच किया गया था, लक्ष्य कोड तक पहुँचने के लिए अनुरोध कैसे तैयार किया जाए, और कौन सी स्थिति शोषण योग्य स्थिति उत्पन्न करती है।
पहला भाग काफी आसान है। परिवर्तन Ipv6pProcessOptions() में है, जो हमें बताता है कि यह IPv6 है और इसमें विकल्पों को संसाधित करना शामिल है। तो, RFC पर एक त्वरित नज़र हमें बताती है कि IPv6 विकल्प क्या है और हम इसे कहाँ पा सकते हैं।
विकिपीडिया से गंतव्य विकल्प हेडर लेआउट।
ठीक है, बढ़िया। हम जो खोज रहे हैं वह गंतव्य विकल्प हेडर प्रतीत होता है, जो मुख्य IPv6 हेडर के ठीक बाद आता है। आइए एक परीक्षण IPv6 पैकेट तैयार करने के लिए Python लाइब्रेरी 'scapy' का उपयोग करें।
नोट: स्पूफ़ किए गए IP पतों का उपयोग करके DDoS हमलों को कम करने के लिए, Windows कच्चे 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 फ़ंक्शन।
कोड एक लिंक्ड सूची को पुनरावृत्त करता है और सूची में प्रत्येक आइटम पर IppSendError() को कॉल करता है। फिर से, सितारे संरेखित हो गए हैं और अब तक चीजें आसान रही हैं। यदि IppSendErrorList सूची में प्रत्येक आइटम के लिए IppSendError को कॉल करता है, और पैच IppSendErrorList के कॉल को IppSendError से बदल देता है, तो समस्या तब होती है जब IppSendError को पहले आइटम के अलावा किसी अन्य सूची आइटम पर कॉल किया जाता है।
यह वह जगह है जहाँ चीजें स्पष्ट से असामान्य रूप से कठिन हो गईं, हालाँकि मुझे लगता है कि इसका एक बड़ा हिस्सा मेरे दो उपलब्ध मस्तिष्क कोशिकाओं में से एक के खराब कोविड संक्रमण से लड़ने में व्यस्त होने के कारण था। मैंने कोड के कुछ हिस्सों को समझने, सो जाने, फिर यह भूल जाने में कुछ दिन खो दिए कि मैंने क्या पता लगाया था। पूरी प्रक्रिया में यह पता लगाने के लिए tcpip.sys के कुछ हिस्सों को रिवर्स इंजीनियर करने में एक सप्ताह से अधिक का समय लगा कि क्या चल रहा था। लेकिन Axel का ब्लॉग पोस्ट बेहद मददगार था।
Axel द्वारा रिवर्स इंजीनियर किए गए फ़ंक्शन और संरचना को देखकर, और वे किन अन्य फ़ंक्शनों को पास किए जाते हैं, यह स्पष्ट है कि Ipv6pProcessOptions() में पारित एकमात्र तर्क लेख में परिभाषित वही packet_t संरचना है। अनिवार्य रूप से, Ipv6pProcessOptions को पारित किया गया पॉइंटर, और IppSendErrorList द्वारा पुनरावृत्त किया गया, पैकेटों की एक लिंक्ड-सूची है।
इसलिए, मैंने Ipv6pProcessOptions() पर एक ब्रेकपॉइंट सेट किया और सूची का निरीक्षण किया।

सूची->अगली प्रविष्टि NULL है।
हर बार जब मेरा ब्रेकपॉइंट हिट हुआ, सूची में केवल एक पैकेट था। मैंने यह पता लगाने में जितना स्वीकार करना चाहूँगा उससे कहीं अधिक समय बिताया कि क्यों और कैसे मेरी सूची को वास्तव में एक सूची बनाया जाए। मेरा पहला विचार IPv6 विखंडन था: IPv6 प्रेषकों को बड़े पैकेटों को अलग-अलग छोटे पैकेटों में विभाजित करने की अनुमति देता है, जिसे एक सूची में एक साथ रखना समझ में आता है।
व्यापक रिवर्स इंजीनियरिंग के बाद, मैंने पुष्टि की कि मेरी धारणाएँ सही थीं, हालाँकि खंड सूची उस सूची से संबंधित नहीं है जिसके साथ हम यहाँ काम कर रहे हैं।