
VirtualBox E1000 गेस्ट-टू-होस्ट एस्केप
मुझे VirtualBox पसंद है और इसका इस बात से कोई लेना-देना नहीं है कि मैं एक 0day कमजोरी क्यों प्रकाशित कर रहा हूँ। इसका कारण समकालीन इन्फोसेक की स्थिति, विशेष रूप से सुरक्षा अनुसंधान और बग बाउंटी से मेरी असहमति है:
मैं पहले दो से थक गया हूँ, इसलिए मेरी चाल पूर्ण प्रकटीकरण (full disclosure) है। इन्फोसेक, कृपया आगे बढ़ें।
असुरक्षित सॉफ्टवेयर: VirtualBox 5.2.20 और पिछले संस्करण।
होस्ट OS: कोई भी, बग एक साझा कोड बेस में है।
गेस्ट OS: कोई भी।
VM कॉन्फ़िगरेशन: डिफ़ॉल्ट (एकमात्र आवश्यकता यह है कि नेटवर्क कार्ड Intel PRO/1000 MT Desktop (82540EM) हो और मोड NAT हो)।
जब तक पैच किया गया VirtualBox बिल्ड जारी नहीं होता, आप अपनी वर्चुअल मशीनों के नेटवर्क कार्ड को PCnet (दोनों में से कोई भी) या Paravirtualized Network में बदल सकते हैं। यदि आप ऐसा नहीं कर सकते, तो मोड को NAT से दूसरे में बदलें। पहला तरीका अधिक सुरक्षित है।
एक डिफ़ॉल्ट VirtualBox वर्चुअल नेटवर्क डिवाइस Intel PRO/1000 MT Desktop (82540EM) है और डिफ़ॉल्ट नेटवर्क मोड NAT है। हम इसे E1000 के रूप में संदर्भित करेंगे।
E1000 में एक कमजोरी है जो गेस्ट में रूट/एडमिनिस्ट्रेटर विशेषाधिकार वाले हमलावर को होस्ट ring3 पर भागने की अनुमति देती है। फिर हमलावर /dev/vboxdrv के माध्यम से विशेषाधिकारों को ring 0 तक बढ़ाने के लिए मौजूदा तकनीकों का उपयोग कर सकता है।
नेटवर्क पैकेट भेजने के लिए गेस्ट वही करता है जो एक सामान्य PC करता है: वह नेटवर्क कार्ड को कॉन्फ़िगर करता है और उसे नेटवर्क पैकेट प्रदान करता है। पैकेट डेटा लिंक लेयर फ्रेम और अन्य, अधिक उच्च स्तरीय हेडर के होते हैं। एडॉप्टर को प्रदान किए गए पैकेट Tx डिस्क्रिप्टर (Tx का अर्थ ट्रांसमिट है) में लिपटे होते हैं। Tx डिस्क्रिप्टर 82540EM डेटाशीट (317453006EN.PDF, रेविज़न 4.0) में वर्णित डेटा संरचना है। यह पैकेट आकार, VLAN टैग, TCP/IP सेगमेंटेशन सक्षम फ़्लैग इत्यादि जैसी मेटाजानकारी संग्रहीत करता है।
82540EM डेटाशीट तीन Tx डिस्क्रिप्टर प्रकार प्रदान करती है: legacy, context, data। मेरा मानना है कि Legacy पदावनत (deprecated) है। अन्य दो का एक साथ उपयोग किया जाता है। हम केवल इसकी परवाह करते हैं कि context डिस्क्रिप्टर अधिकतम पैकेट आकार सेट करते हैं और TCP/IP सेगमेंटेशन को स्विच करते हैं, और data डिस्क्रिप्टर नेटवर्क पैकेट के भौतिक पते और उनके आकार रखते हैं। data डिस्क्रिप्टर का पैकेट आकार context डिस्क्रिप्टर के अधिकतम पैकेट आकार से छोटा होना चाहिए। आमतौर पर context डिस्क्रिप्टर नेटवर्क कार्ड को data डिस्क्रिप्टर से पहले प्रदान किए जाते हैं।
नेटवर्क कार्ड को Tx डिस्क्रिप्टर प्रदान करने के लिए गेस्ट उन्हें Tx Ring में लिखता है। यह एक रिंग बफर है जो एक पूर्वनिर्धारित पते पर भौतिक मेमोरी में स्थित होता है। जब सभी डिस्क्रिप्टर Tx Ring में लिख दिए जाते हैं, तो गेस्ट E1000 MMIO TDT रजिस्टर (Transmit Descriptor Tail) को अपडेट करता है ताकि होस्ट को बताया जा सके कि संभालने के लिए नए डिस्क्रिप्टर हैं।
निम्नलिखित Tx डिस्क्रिप्टर की सरणी पर विचार करें:``` [context_1, data_2, data_3, context_4, data_5]
आइए उनके संरचना क्षेत्रों को इस प्रकार निर्दिष्ट करें (क्षेत्र नाम काल्पनिक हैं जो मानव-पठनीय हों लेकिन सीधे 82540EM विनिर्देश में मैप होते हैं):```
context_1.header_length = 0
context_1.maximum_segment_size = 0x3010
context_1.tcp_segmentation_enabled = true
data_2.data_length = 0x10
data_2.end_of_packet = false
data_2.tcp_segmentation_enabled = true
data_3.data_length = 0
data_3.end_of_packet = true
data_3.tcp_segmentation_enabled = true
context_4.header_length = 0
context_4.maximum_segment_size = 0xF
context_4.tcp_segmentation_enabled = true
data_5.data_length = 0x4188
data_5.end_of_packet = true
data_5.tcp_segmentation_enabled = true
हम अपने चरण-दर-चरण विश्लेषण में जानेंगे कि वे ऐसे क्यों होने चाहिए।
मान लें कि उपरोक्त डिस्क्रिप्टर निर्दिष्ट क्रम में Tx Ring में लिखे गए हैं और TDT रजिस्टर को अतिथि द्वारा अपडेट किया गया है। अब होस्ट src/VBox/Devices/Network/DevE1000.cpp फ़ाइल में e1kXmitPending फ़ंक्शन निष्पादित करेगा (पठनीयता के लिए अधिकांश टिप्पणियाँ हटा दी गई हैं और हटा दी जाएँगी):```c static int e1kXmitPending(PE1KSTATE pThis, bool fOnWorkerThread) { ... while (!pThis->fLocked && e1kTxDLazyLoad(pThis)) { while (e1kLocateTxPacket(pThis)) { fIncomplete = false; rc = e1kXmitAllocBuf(pThis, pThis->fGSO); if (RT_FAILURE(rc)) goto out; rc = e1kXmitPacket(pThis, fOnWorkerThread); if (RT_FAILURE(rc)) goto out; }
e1kTxDLazyLoad टीएक्स रिंग से सभी 5 टीएक्स डिस्क्रिप्टर पढ़ेगा। फिर e1kLocateTxPacket को पहली बार कॉल किया जाता है। यह फ़ंक्शन प्रारंभिक अवस्था स्थापित करने के लिए सभी डिस्क्रिप्टर पर पुनरावृत्ति करता है लेकिन वास्तव में उन्हें संभालता नहीं है। हमारे मामले में e1kLocateTxPacket का पहला कॉल context_1, data_2, और data_3 डिस्क्रिप्टर को संभालेगा। शेष दो डिस्क्रिप्टर, context_4 और data_5, while लूप के दूसरे पुनरावृत्ति पर संभाले जाएंगे (हम अगले भाग में दूसरे पुनरावृत्ति को कवर करेंगे)। यह दो-भागीय सरणी विभाजन कमजोरी को ट्रिगर करने के लिए महत्वपूर्ण है, तो आइए जानें क्यों।
e1kLocateTxPacket इस प्रकार दिखता है:```c
static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
...
for (int i = pThis->iTxDCurrent; i < pThis->nTxDFetched; ++i)
{
E1KTXDESC *pDesc = &pThis->aTxDescriptors[i];
switch (e1kGetDescType(pDesc))
{
case E1K_DTYP_CONTEXT:
e1kUpdateTxContext(pThis, pDesc);
continue;
case E1K_DTYP_LEGACY:
...
break;
case E1K_DTYP_DATA:
if (!pDesc->data.u64BufAddr || !pDesc->data.cmd.u20DTALEN)
break;
...
break;
default:
AssertMsgFailed(("Impossible descriptor type!"));
}
पहला डिस्क्रिप्टर (context_1) E1K_DTYP_CONTEXT प्रकार का है, इसलिए e1kUpdateTxContext फ़ंक्शन कॉल किया जाता है। यह फ़ंक्शन TCP Segmentation Context को अपडेट करता है यदि डिस्क्रिप्टर के लिए TCP Segmentation सक्षम है। context_1 के लिए यह सत्य है, इसलिए TCP Segmentation Context अपडेट होगा। (वास्तव में TCP Segmentation Context Update क्या है, यह महत्वपूर्ण नहीं है, और हम इसका उपयोग केवल नीचे दिए गए कोड को संदर्भित करने के लिए करेंगे।)
दूसरा डिस्क्रिप्टर (data_2) E1K_DTYP_DATA प्रकार का है, इसलिए चर्चा के लिए अनावश्यक कई क्रियाएं की जाएंगी।
तीसरा डिस्क्रिप्टर (data_3) भी E1K_DTYP_DATA प्रकार का है, लेकिन चूंकि data_3.data_length == 0 है, इसलिए कोई क्रिया नहीं की जाती।