
CVE-2022-37969 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, एक Windows कॉमन लॉग फ़ाइल सिस्टम ड्राइवर स्थानीय विशेषाधिकार वृद्धि। यह हीप स्प्रे, टोकन स्टीलिंग और आर्बिट्ररी कर्नेल राइट का प्रदर्शन करके SYSTEM विशेषाधिकार प्राप्त करता है।
authors: Ricardo Narvaja & Daniel Kazimirow (Solid)
केवल प्रदर्शन उद्देश्यों के लिए। पूर्ण एक्सप्लॉइट कमजोर Windows 11 21H2 सिस्टम पर काम करता है।
पहले Zscaler द्वारा प्रकाशित जानकारी पर आधारित कार्यात्मक PoC
व्याख्या लेख देखें Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation।
एक्सप्लॉइटेशन वॉकथ्रू:
यहाँ उपयोग किया गया परिदृश्य Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918 था
पहला चरण MyLog.blf नामक एक फ़ाइल को पब्लिक फ़ोल्डर (%public%) में बनाना है, CreateLogFile() फ़ंक्शन का उपयोग करके:



फिर यह एक Loop का उपयोग करके रैंडम नामों के साथ कई लॉग फ़ाइलें बनाता है।
और लूप के भीतर, यह हमारे getBigPoolInfo() फ़ंक्शन को कॉल करता है:

यह NtQuerySystemInformation() को कॉल करता है, पहले तर्क के रूप में 0x42 (66 दशमलव) के साथ, यह v5 में बिगपूल में किए गए raids के बारे में जानकारी लौटाएगा, जिसकी संरचना SYSTEM_BIGPOOL_INFORMATION प्रकार की होती है।

हमें इस फ़ंक्शन को दो बार कॉल करना होता है। पहली बार यह एक त्रुटि लौटाएगा, लेकिन यह हमें दूसरी बार कॉल करने के लिए बफर का सही आकार देगा ताकि वांछित जानकारी प्राप्त हो सके।

v5 को SYSTEM_BIG_POOL_INFORMATION संरचना की जानकारी प्राप्त होगी।

बिगपूल में आवंटनों की संख्या, Count नामक पहले फ़ील्ड में संग्रहीत होती है, दूसरे फ़ील्ड में SYSTEM_BIGPOOL_ENTRY संरचनाओं की एक सरणी होती है।

फिर हम सभी संरचनाओं में "Clfs" टैग और आकार 0x7a00 के लिए खोज करेंगे।

यह kernelAddrArray नामक एक सरणी में VirtualAddress संग्रहीत करता है जो प्रत्येक संरचना का पहला फ़ील्ड है जिसमें CLFS टैग और आकार 0x7a00 होता है। अब से, जो पूल दोनों शर्तों को पूरा करते हैं उन्हें "right pools" कहा जाएगा।

प्रत्येक right pool को सरणी में संग्रहीत करने के अलावा, यह अंतिम पाए गए right pool को a2 चर की सामग्री में संग्रहीत करता है, जिसका उपयोग फ़ंक्शन के तर्क के रूप में किया जाता है।

इस प्रकार a2 हमेशा CLFS टैग और आकार 0x7a00 वाले अंतिम बनाए गए right pool की ओर इंगित करता है।
चर v26 हमेशा पिछला पाया गया right pool संग्रहीत करता है क्योंकि getBigPoolinfo() को कॉल करने से पहले यह v24 के बराबर होता है (v26=v24), लेकिन इस कॉल से बाहर निकलते समय v24 को अंतिम पाए गए right pool के साथ अद्यतन किया जाता है, और v26 पिछले पाए गए right pool के साथ रहता है।

फिर यह दोनों पतों को घटाता है, और यदि परिणाम नकारात्मक है, तो संकार्यों को उलट देता है ताकि यह हमेशा सकारात्मक रहे।

इस प्रकार v32 में अंतिम दो पाए गए right pool के VirtualAddress के बीच का अंतर संग्रहीत होगा।
फिर यह कुछ ऐसा ही करता है, इस मामले में v23 शुरू में शून्य होता है इसलिए पहली बार v23=v32 होता है।

अगली बार लूप में v23 का मान अभी भी समान होता है और शून्य नहीं होता, इसलिए यह टूट जाता है और यहाँ आता है।

V32 में अंतिम अंतर होता है और v23 में पिछला अंतर होता है, यदि वे बराबर हैं, तो यह बाहर आता है और एक बढ़ाता है, लेकिन काउंटर को शून्य पर रीसेट कर देता है।
विचार यह है कि CLFS टैग और आकार 0x7a00 के 6 लगातार तुलनाओं को खोजा जाए जिनके अंतर समान हों, और वह अंतर 0x11000 होगा। निष्पादित करते समय हम देखेंगे कि जब यह 6 (क्योंकि यह शुरू से शुरू होता है) समान दूरियों वाले लगातार पाता है तो यह उनके बीच का अंतर मान देगा।


वहाँ हम देखते हैं कि उसे 6 लगातार मिले और वह लॉग फ़ाइलें बनाने के लूप से बाहर निकल गया।
"public" फ़ोल्डर में हम बनाई गई फ़ाइलें देख सकते हैं

हमारा craftFile() फ़ंक्शन मूल फ़ाइल (MyLog.blf) खोलता है और बग को ट्रिगर करने के लिए उसे संशोधित करता है।

फ़ाइल को संशोधित करने के बाद, CRC32 बदलना आवश्यक है, अन्यथा हमें एक दूषित फ़ाइल त्रुटि मिलेगी
यह मान फ़ाइल के ऑफसेट 0x80C पर स्थित है।

इसके बाद, यह एक HeapSpray करता है, मेमोरी आवंटित करने के लिए VirtualAlloc() फ़ंक्शन का उपयोग करते हुए, क्रमशः मनमाने पतों 0x10000 और 0x5000000 पर, और दूसरे आवंटन (0x10000) में मान 0x5000000 को हर 0x10 बाइट्स पर सहेजता है।

यह एक अनाम पाइप बनाने के लिए CreatePipe() का उपयोग करता है और एक विशेषता जोड़ने के लिए तर्क के रूप में 0x11003c का उपयोग करके NtFsControlFile() को कॉल करता है, बाद में आप इसे पढ़ने के लिए 0x110038 तर्क के साथ इसी फ़ंक्शन को कॉल कर सकते हैं।
इस विधि के अधिक विवरण यहाँ पाए जा सकते हैं

वहाँ हम इनपुट बफर देखते हैं जो वह विशेषता है जिसे हम जोड़ रहे हैं, यदि हम तर्क 0x11038 के साथ NtFsControlFile() को फिर से कॉल करते हैं तो आउटपुट में यह वही विशेषता लौटानी चाहिए।

बनाई गई विशेषता के टैग (NpAt) के लिए पूल खोजें


और जब यह इसे पाता है, तो यह इस पूल के VirtualAddress को v30.Pointer में सहेजता है।
V30.pointer+24 कर्नेल पूल में AttributeValueSize की ओर इंगित करता है और इसे उन HeapSprays में से एक में सहेजता है जो हमने पहले बनाए थे।

विचार यह है कि उस कर्नेल पते+8 पर लिखा जाए, ताकि AttributeValue को अधिलेखित किया जा सके।


PipeAttribute संरचना के पहले फ़ील्ड के रूप में एक LIST_ENTRY होता है जिसका आकार 16 बाइट्स होता है, फिर विशेषता के नाम की ओर एक पॉइंटर होता है जिसका आकार 8 बाइट्स होता है और फिर 0x18 (24 दशमलव) पर AttributeValueSize फ़ील्ड आता है जिसे हम HeapSpray में संग्रहीत कर रहे हैं।
उसके बाद, हम usermode में CLFS.sys और ntoskrnl लोड करते हैं, और GetProcAddress() का उपयोग करके ClfsEarlierLsn() और SeSetAccessStateGenericMapping() फ़ंक्शनों के पते ढूंढते हैं।

फिर हम FindKernelModulesBase() फ़ंक्शन को कॉल करते हैं जो NtquerySystemInformation() का उपयोग करके दोनों समान मॉड्यूल का कर्नेल आधार ढूंढेगा, इस बार SystemModuleInformation तर्क के साथ ताकि सभी मॉड्यूल के बारे में जानकारी लौटाई जा सके।

इस प्रकार, हम प्रत्येक फ़ंक्शन के ऑफसेट की गणना कर सकते हैं, और फिर उन्हें कर्नेल में प्राप्त कर सकते हैं

pipeArbitraryWrite() फ़ंक्शन को दो बार कॉल किया जाता है, एक फ्लैग होता है जो पहली कॉल के लिए शुरू में शून्य होता है और जब दूसरी कॉल में इसका मान 1 होता है, तो यह HeapSpray के मानों को बदल देगा।

पहली कॉल में 0x5000000 मेमोरी पते पर निम्नलिखित मान स्थित होते हैं

याद रखें कि यह मान उस दिशा में आवंटित होने के अलावा, हमारे HeapSpray में संग्रहीत होता है।

पहली कॉल के बाद मेमोरी इस प्रकार होती है, जैसा कि हमने कहा, लगभग 0x5000000 के पते पर

और मेमोरी 0x10000 से HeapSpray में यह हर 0x10 बाइट्स पर AttributeValueSize का पॉइंटर संग्रहीत करेगा, साथ ही 0x5000000 का पॉइंटर भी।

यह अनुक्रम बग को ट्रिगर करेगा:

तैयार की गई फ़ाइल पर और एक अन्य रैंडम नाम वाली फ़ाइल पर CreateLogFile() फिर से कॉल किया जाता है।
फिर उन फ़ाइलों के हैंडल का उपयोग करके AddLogContainer() कॉल किया जाता है।

NtSetinformationFile() कॉल किया जाता है, और हैंडल बंद कर दिए जाते हैं जिससे पॉइंटर दूषित हो जाता है (इसे बाद में समझाया जाएगा)

HeapSpray, इस बिंदु पर BSOD होने से रोकता है:

वहाँ एक ब्रेकपॉइंट सेट करके, हम देख सकते हैं कि पॉइंटर दूषित है और हमारे HeapSpray की ओर इंगित करता है, जिससे हम vtable के अगले दो फ़ंक्शन कॉलों को संभाल सकते हैं।


RAX मान 0x5000000 लेता है और पहले 0x5000000+18 पर स्थित फ़ंक्शन पर जाता है और फिर 0x5000000+8 पर।


तो पहले fnClfsEarlierLsn() पर कूदें और फिर fnSeSetAccessStateGenericMapping() पर।
हम ब्रेकपॉइंट से ट्रेस करते हैं और देखते हैं कि यह CLFS!ClfsEarlierLsn() तक पहुँचता है।

यह फ़ंक्शन विशेष रूप से इसलिए कॉल किया जाता है क्योंकि जब यह लौटता है, तो यह EDX को 0xFFFFFFFF पर सेट करता है

पते 0xFFFFFFFF पर हमने SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000 का परिणाम संग्रहीत किया था

जैसा कि हमने उल्लेख किया, CLFS!ClfsEarlierLsn() से लौटते समय, RDX का मान 0x00000000FFFFFFFF होता है

हम दूसरे फ़ंक्शन nt!SeSetAccessStateGenericMapping() पर आते हैं

यह फ़ंक्शन उपयोगी है, क्योंकि RCX हमारे HeapSpray की ओर इंगित करता है, और RDX का मान 0xFFFFFFFF है, जिसकी सामग्री हम नियंत्रित करते हैं


RCX+0x48 की सामग्री में AttributeValueSize का पॉइंटर होता है जिसे v30.Pointer+24 में संग्रहीत किया गया था



AttributeValueSize का वह पॉइंटर मान RAX में ले जाया जाता है, फिर पते 0xFFFFFFFF की सामग्री पढ़ी जाती है जहाँ हमने SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000 का पता संग्रहीत किया था।

फिर RAX+8 पर अगले फ़ील्ड को अधिलेखित करता है जो AttributeValue() है


बेशक, AttributeValue सामान्य रूप से कर्नेल में उस विशेषता की ओर इंगित करेगा जिसे हमने जोड़ा था।

और अब हम इसे system _EPROCESS & 0xFFFFFFFFFFFFFFF00 के परिणाम के पॉइंटर के साथ अधिलेखित करेंगे।
इसका मतलब होगा कि जब हम NtFsControlFile() फ़ंक्शन को फिर से कॉल करते हैं, इस बार विशेषता पढ़ने के लिए 0x110038 तर्क के साथ, तो उन "A" को लौटाने के बजाय जो AttributeValue पॉइंटर द्वारा इंगित किए गए थे, यह अब _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 से अनुरोधित बाइट्स की संख्या पढ़ेगा और उन्हें आउटपुट बफर में लौटाएगा, जिससे हम पहली कॉल में SYSTEM TOKEN का मान प्राप्त कर सकते हैं।

v9b Output Buffer का प्रारंभिक पता है जहाँ System EPROCESS & 0xFFFFFFFFFFFFFFF000 के परिणाम की सामग्री कॉपी की गई थी।
इसमें वह v14 जोड़ता है जो System EPROCESS के अंतिम 3 बाइट्स हैं और फिर 0x4b8 जोड़ता है जो Windows 11 के इस संस्करण के लिए Token का ऑफसेट है, फिर उस पते की सामग्री ढूंढता है जिसमें System Token का मान सहेजा गया होगा।



याद रखें कि अंतिम 4 बिट बदले गए थे, यह महत्वपूर्ण नहीं है, इसलिए मान अभी भी मेल खाता है।
दूसरी कॉल में Flag का मान 1 है क्योंकि पहली कॉल के अंत में इसे बढ़ाया गया था।

वहाँ हम वह क्रम देखते हैं जिसमें मान संग्रहीत होते हैं

पता 0xFFFFFFFF उस मान के साथ जो हमने अभी पाया है System Process Token का।


और HeapSpray में मेरी प्रक्रिया के Token पते का मान है जिसमें से मैं 8 घटाता हूँ। यह मान प्लस आठ लक्ष्य के रूप में उपयोग किया जाएगा, याद रखें कि आपने RAX+8 द्वारा इंगित पते पर लिखा था।


0x5000000 से शुरू होने वाले मेमोरी पते में

हम यह भी देखते हैं कि यह दूसरे कंटेनर का नाम उपयोग करता है, क्योंकि पिछला कंटेनर सिस्टम प्रक्रिया द्वारा उपयोग किया जा रहा है और इसे फिर से नहीं खोला जा सकता या हटाया नहीं जा सकता।

फिर बग दूसरी बार उसी तरह ट्रिगर होता है जैसे पहले प्रयास में हुआ था।

यह फिर से CLFS!ClfsEarlierLsn() पर आता है।

RDX को 0xFFFFFFFF पर सेट करते हुए

फिर यह nt!SeSetAccessStateGenericMapping() पर आता है

मेरी प्रक्रिया के Token के पते को माइनस 8 पढ़ता है जहाँ लिखा जाना है

फिर SYSTEM TOKEN पढ़ता है

और मेरी प्रक्रिया के Token के पते पर (वह 8 जोड़ता है), System Token लिखता है

और इस प्रकार मेरी प्रक्रिया System Token के साथ होती है

टोकन लिखे जाने के बाद, हम विशेषाधिकारों की जाँच करने के लिए एक प्रक्रिया शुरू करते हैं, इस मामले में, हम Notepad.exe लॉन्च करते हैं



याद रखें कि यह POC केवल Windows 11 में काम करता है, Windows 10 में यह BSOD उत्पन्न करेगा, इसलिए सही ढंग से काम करने के लिए आपको कुछ संशोधन करने होंगे, इस ब्लॉगपोस्ट में इसकी व्याख्या नहीं की गई है।
संरचनाओं का विश्लेषण
संरचनाएं और CLFS फ़ाइल प्रारूप पर अधिकांश दस्तावेज़ीकरण, हमने IONESCU के CLFS Internals पर उत्कृष्ट कार्य से लिया है।
हम देख सकते हैं कि ClfsBaseFilePersisted::LoadContainerQ फ़ंक्शन में एक जाँच जोड़ी गई है

जो मान जोड़ (addition) करते हैं, वे _CLFS_BASE_RECORD_HEADER संरचना से संबंधित हैं।

ध्यान दें कि Base Block फ़ाइल के ऑफसेट 0x800 से शुरू होता है, और ऑफसेट 0x71FF पर समाप्त होता है, पहले 0x70 बाइट्स Log Block Header के अनुरूप होते हैं
एक अच्छे अभ्यास के रूप में, हम IDA पर _CLFS_LOG_BLOCK_HEADER संरचना जोड़ सकते हैं
struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
char ClientId;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[16];
ULONG SignaturesOffset;
};
फिर हमारे पास Base Record Header (_CLFS_BASE_RECORD_HEADER) है जो फ़ाइल की शुरुआत से ऑफसेट 0x870 पर शुरू होता है और 0x1338 बाइट्स लंबा होता है।

यदि आप इसे IDA में आयात करना चाहते हैं, तो पहले आपको निम्नलिखित प्रकार और लापता संरचनाएँ जोड़नी होंगी
typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;
struct _CLFS_METADATA_RECORD_HEADER
{
ULONGLONG ullDumpCount;
};
अब इसे जोड़ने के लिए तैयार है:
typedef struct _CLFS_BASE_RECORD_HEADER
{
CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
CLFS_LOG_ID cidLog;
ULONGLONG rgClientSymTbl[0x0b];
ULONGLONG rgContainerSymTbl[0x0b];
ULONGLONG rgSecuritySymTbl[0x0b];
ULONG cNextContainer;
CLFS_CLIENT_ID cNextClient;
ULONG cFreeContainers;
ULONG cActiveContainers;
ULONG cbFreeContainers;
ULONG cbBusyContainers;
ULONG rgClients[0x7c];
ULONG rgContainers[0x400];
ULONG cbSymbolZone;
ULONG cbSector;
USHORT bUnused;
CLFS_LOG_STATE eLogState;
UCHAR cUsn;
UCHAR cClients;
} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

संरचनाओं को शामिल करने के बाद, हम देखते हैं कि यह cbSymbolZone और उस पते के बीच एक जोड़ करता है जहाँ _CLFS_BASE_RECORD_HEADER समाप्त होता है। (start + 1338h)
याद रखें कि cbSymbolZone को तैयार किए गए log फ़ाइल में 0x000000F8 से 0x0001114B में संशोधित किया गया था।
(फ़ाइल का ऑफसेट 0x1b98)
0x800 (बेस ब्लॉक की शुरुआत का ऑफसेट) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)
0x800+0x70+0x1328 = 0x1b98
MyLog.blf फ़ाइल पर तैयार किया गया cbsymbolZone:


चूंकि पैच CClfsBaseFilePersisted::LoadContainerQ फ़ंक्शन में है, हमें CClfsBaseFilePersisted ऑब्जेक्ट पर एक नज़र डालनी होगी।
CLFS!CClfsBaseFilePersisted::LoadContainerQ में ब्रेकपॉइंट सेट करें और जब CreateLogFile को तैयार फ़ाइल के हैंडल के साथ कॉल किया जाता है तो यह टूट जाएगा।

Base Log Record (_CLFS_BASE_RECORD_HEADER) का पता प्राप्त करने के लिए CClfsBaseFile::GetBaseLogRecord फ़ंक्शन को कॉल करें

RAX _CLFS_BASE_RECORD_HEADER पते की ओर इशारा करेगा

मेमोरी में _CLFS_BASE_RECORD_HEADER संरचना और cbsymbolZone फ़ील्ड 0x1328 बाइट्स आगे पर ध्यान दें


r14 "this" के अनुरूप संरचना को संग्रहीत करता है, जो कि CClfsBaseFilePersisted है क्योंकि यह CClfsBaseFilePersisted::LoadContainerQ. फ़ंक्शन का this है।

मेमोरी में CClfsBaseFilePersisted संरचना:

तो, चलिए इसके फ़ील्ड्स को पूरा करने के लिए लंबाई 0x21c0 वाली एक संरचना बनाते हैं जब तक हम इसे रिवर्स करते हैं (यह एक अनिर्देशित संरचना है) हम इसे struct_CClfsBaseFilePersisted कहेंगे

फ़ंक्शन CClfsBaseFile::GetBaseLogRecord() के अंदर _CLFS_BASE_RECORD_HEADER का पॉइंटर मिलता है। और हम जानते हैं कि उस फ़ंक्शन में "this" संरचना है: struct_CClfsBaseFilePersisted।

दो फ़ील्ड्स पढ़ें (ऑफसेट 0x28 और 0x30)

फ़ील्ड 0x28 एक word है और इसका मान 6 है, इसलिए हम संरचना में प्रकार को word में बदलते हैं।



अभी के लिए, हम इसे स्थिरांक 6 (const_6) में बदलते हैं


दस्तावेज़ीकरण के अनुसार, 6 ब्लॉकों की संख्या CLFS_METADATA_BLOCK_COUNT होगी। फ़ील्ड इस मान को संदर्भित कर सकता है।
और वह पॉइंटर ऑफसेट 0x30 पर है।

ध्यान दें कि वहाँ दिखाया गया आकार लंबाई 0x10 वाले हेडर को शामिल करता है


जब ExAllocatePoolWithTag फ़ंक्शन को कॉल किया जाता है, तो कुछ बाइट्स का अनुरोध किया जाता है, लेकिन हेडर शामिल नहीं होता है, इसलिए, कॉल में 0x90 बाइट्स (0xa0 – 0x10) का अनुरोध किया जाएगा।
+30h] टेक्स्ट द्वारा खोज करने पर, ऑफसेट 0x30 पर लिखने वाले निर्देशों की एक लंबी सूची मिलती है, लेकिन सूची को CClfsBaseFilePersisted ऑब्जेक्ट के प्रकार द्वारा फ़िल्टर करने पर कुछ ही परिणाम बचते हैं और तुरंत पता चलता है कि वह आकार कहाँ आवंटित किया गया है, और वही टैग। (टिप: Create और Initialize फ़ंक्शन नाम, हमेशा देखने वाले पहले होते हैं)


चूंकि हम अभी भी नाम नहीं जानते हैं, हम इसे pool_0x90 रखेंगे, जो एक और अनिर्देशित संरचना है, और हम उस आकार की एक संरचना बनाएंगे।


मेमोरी में pool_0x90 के अपने ऑफसेट 0x30 पर एक और पॉइंटर है।

यह अन्य पॉइंटर फ़ाइल में बेस ब्लॉक की ओर इशारा करता है (बेस ब्लॉक ऑफसेट 0x800 पर शुरू होता है)


छवि Zscaler ब्लॉगपोस्ट से ली गई है:

आवंटन बहुत बड़ा है, क्योंकि इसमें पूरा बेस ब्लॉक होता है।



इसलिए, हम आकार 0x7a00 की एक नई संरचना बनाएंगे और इसे BASE_BLOCK कहेंगे

पहले 70 बाइट्स जिन्हें हम पहले से जानते थे, _CLFS_LOG_BLOCK_HEADER के अनुरूप हैं और अगले 0x1338 _CLFS_BASE_RECORD_HEADER के अनुरूप हैं।

इसलिए, बेस ब्लॉक की शुरुआत को अगले रिकॉर्ड के ऑफसेट (जो 0x70 है) के साथ जोड़ने पर, हमें _CLFS_BASE_RECORD_HEADER मिलता है

मेमोरी पर _CLFS_BASE_RECORD_HEADER।

उसी CClfsBaseFilePersisted ऑब्जेक्ट के अन्य तरीकों को देखते हुए, CClfsBaseFilePersisted::AddContainer में आपको CClfsBaseFile::GetBaseLogRecord के साथ _CLFS_BASE_RECORD_HEADER का पता भी मिलता है।

इसके बाद, cbOffset का उपयोग करके CClfsBaseFile::OffsetToAddr को कॉल करें, यह _CLFS_CONTAINER_CONTEXT का पता प्राप्त करता है, और cboffset को rgbcontainers सरणी में संग्रहीत करता है जो _CLFS_BASE_RECORD_HEADER के ऑफसेट 0x328 पर है।

CClfsBaseFile::OffsetToAddr फ़ंक्शन का उपयोग ऑफसेट से संरचनाओं के पते खोजने के लिए किया जाता है

इस बिंदु पर, कंटेनर ऑफसेट जो 0x328 पर संग्रहीत किया जाएगा वह अभी भी 0 है, क्योंकि हमने अभी तक एक कंटेनर नहीं जोड़ा है।

PoC CreateLogFile को दो बार कॉल करता है, पहली बार malformed फ़ाइल MyLog.blf के साथ और दूसरी बार सामान्य MyLogxxx.blf फ़ाइल के साथ, इसलिए हमें उपरोक्त सभी स्थानों पर डिबगिंग को दो बार रोकना होगा और दोनों फ़ाइलों के लिए उपरोक्त संरचनाओं के पते नोटपैड में नोट करने होंगे।

आइए उस पर ब्रेकपॉइंट सेट करके और वहाँ चलाकर CLFS!CClfsLogFcbPhysical::AllocContainer तक थोड़ा आगे बढ़ते हैं।
जब POC पर AddLogContainer() पहुँच जाता है, तो हम ब्रेकपॉइंट पर रुक जाते हैं।

चलिए CClfsBaseFilePersisted::AddContainer+176 पर भी एक ब्रेकपॉइंट सेट करते हैं, जहाँ हमने पहले देखा था कि _CLFS_CONTAINER_CONTEXT संरचना का ऑफसेट और पॉइंटर मिलेगा।


जब डीबगर टूटता है, तो हम देख सकते हैं कि ऑफसेट 0x1468 है।

RAX में _CLFS_CONTAINER_CONTEXT संरचना का पता लौटेगा।

संरचना अभी भी खाली है क्योंकि कंटेनर अभी तक नहीं जोड़ा गया था।

ध्यान दें कि SignatureOffset=0x50 मान जो हमने malformed फ़ाइल में ऑफसेट 0x868 पर लिखा था, बेस ब्लॉक की शुरुआत से 0x800 घटाने पर, ऑफसेट 0x68 पर _CLFS_LOG_BLOCK_HEADER संरचना में होगा।


जब PoC malformed फ़ाइल का उपयोग करके AddLogContainer() फ़ंक्शन को कॉल करता है, तो _CLFS_LOG_BLOCK_HEADER के ऑफसेट 0x68 पर, वहाँ लिखे गए 0x50 मान के बजाय, मेमोरी में वर्तमान में 0xFFFF0050 है।

किसी बिंदु पर, वह मान प्रोग्राम द्वारा बदल दिया गया था, यह देखने के लिए कि यह कब हुआ, अगले निष्पादन में, हम राइट पर मेमोरी ब्रेकपॉइंट सेट करेंगे।
ऑफसेट r15 + 0x328 पर संग्रहीत है (r15 _CLFS_BASE_RECORD_HEADER संरचना की ओर इशारा करता है)


RBX ऑफसेट 0x1468 संग्रहीत करता है।

इसलिए, बेस ब्लॉक पते + 0x70 + ऑफसेट 0x1468 में जो हमें पता चला, वहाँ CLFS_CONTAINER_CONTEXT कंटेनर का पता होगा।

ऑफसेट 0x18 पर CLFS_CONTAINER_CONTEXT संरचना में pContainer पॉइंटर होगा जो वहाँ संग्रहीत किया जाएगा, हम राइट पर ब्रेकपॉइंट सेट कर सकते हैं और देख सकते हैं कि यह कब लिखा जाता है।


यह वह पॉइंटर है जिसे हमें करप्ट करना होगा क्योंकि जिस फ़ंक्शन में कमजोरी है, वह पहले CLFS_CONTAINER_CONTEXT पढ़ता है, फिर इसे r15 में ले जाता है और फिर r15+18 का मान पढ़ता है, जो यह पॉइंटर है जिस पर हमने अभी राइट पर ब्रेकपॉइंट सेट किया है।


यह pContainer को struct_CClfsBaseFilePersisted संरचना के ऑफसेट 0x1c0 पर संग्रहीत करता है।

कई बार रुकने के बाद, हम उस क्षण पर पहुँचते हैं जहाँ यह करप्ट हो जाता है। पॉइंटर पते का शीर्ष FFs से शून्य में बदल दिया गया है।

यह तब होता है जब malformed फ़ाइल का दूसरा AddLogContainer() कॉल किया जाता है, पिछले MyLogxxx का पॉइंटर करप्ट हो जाता है।
समस्या इसलिए होती है क्योंकि SignaturesOffset, जो 0x50 होना चाहिए, अब 0xFFFF0050 है, इसलिए यह बाद में आने वाले memset में सीमा से बाहर लिखने की अनुमति देता है।


memset() फ़ंक्शन नीचे स्थित _CLFS_CONTAINER_CONTEXT संरचना को करप्ट करने जा रहा है, यह संरचना MyLogxxx फ़ाइल के अनुरूप है, क्योंकि जब बनाई गई थीं, तो यह उन्हें एक दूसरे से 0x11000 बाइट्स की दूरी पर स्थित करता था।
इस तरह, यह ठीक से गणना करता है कि अगली संरचना में कहाँ लिखना है और पॉइंटर के शीर्ष को शून्य कर देता है, ताकि यह उपयोगकर्ता हीप की ओर इशारा करे जहाँ HeapSspray बनाया गया था।
malformed फ़ाइल की बेस ब्लॉक संरचना MyLogxxx फ़ाइल की तुलना में केवल 0x11000 पहले है।
Malformed:

MyLogxxx


RCX, RDX से छोटा है क्योंकि 0xFFFF0050 जोड़ा गया था, 0x50 के बजाय जैसा कि होना चाहिए।

और हम memset() फ़ंक्शन पर पहुँच गए, जो 0xb0 बाइट्स की मात्रा को शून्य से सेट करने के लिए है, RCX MyLogxxx फ़ाइल की CLFS_CONTAINER_CONTEXT संरचना की ओर इशारा करता है, विशेष रूप से pContainer के पाँच उच्च बाइट्स की ओर।

यह पॉइंटर पहले बाइट्स को अधिलेखित करके करप्ट हो जाएगा:

पहले HeapSpray के माध्यम से हमारे द्वारा नियंत्रित मेमोरी पते की ओर इशारा करते हुए रहता है


फिर, MyLogxxx फ़ाइल का हैंडल बंद हो जाएगा, और CClfsBaseFilePersisted::RemoveContainer पर पहुँचता है, अंततः कमजोरी ट्रिगर होती है।

अब जब हमारे पास अधिक जानकारी है, तो हम देखते हैं कि यहाँ यह Base_Block.LOG_BLOCK_HEADER.SignaturesOffset और Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount पढ़ता है
पैच के पहले भाग में SignaturesOffset 0x7a00 से अधिक नहीं होना चाहिए, हमारे मामले में यह मूल रूप से 0x50 था, अगर यह 0x7a00 से अधिक मान के साथ आता है तो यह हमें बाहर निकाल देगा।

पैच की गई मशीन में PoC चलाने पर, यह 0x50 की तुलना 0x7a00 से करता है और चूंकि यह छोटा है, यह जारी रहता है।

निम्नलिखित ब्लॉक में, malformed cbSymbolZone को _CLFS_BASE_RECORD_HEADER के अंतिम पते के मान में जोड़ा जाता है और यह योग result_1 में संग्रहीत किया जाता है।

फिर, Base_Block का पता SignatureOffset मान के साथ जोड़ा जाता है, जो सामान्य फ़ाइल में 0x7980 है।

base_block का अधिकतम पता 0x7a00 है, अब SymbolZone को सीमा से पहले 0x80 तक अनुमति है।
यह इसे result_2 में संग्रहीत करेगा, यानी यह base block के अंदर SymbolZone के लिए अधिकतम सीमा होगी, फिर यह दोनों परिणामों की तुलना करता है, यदि पहला दूसरे से बड़ा है, तो इसका मतलब है कि यह सीमा से बाहर चला गया।


जाहिर है पहला सदस्य दूसरे से बड़ा होगा और यह जारी नहीं रहेगा, क्योंकि cbSymbolZone + _CLFS_BASE_RECORD_HEADER के अंतिम पते का पहला योग सीमा (जो result_2 है) से अधिक है और "आउट ऑफ बाउंड्स" की ओर ले जाता है।

आखिरी चीज़ जो हमें समझनी होगी वह यह है कि SignatureOffset का मान 0x50 कहाँ 0xFFFF0050 बन जाता है।तो, चलिए फिर से शुरू करते हैं, रीबूट करते हैं और CLFS!CClfsBaseFilePersisted::LoadContainerQ पर रुकते हैं, जहाँ मान अभी तक मेमोरी में बदला नहीं गया है और अभी भी 0x50 है।
SignatureOffset के ऑफसेट 0x68 पर एक एक्सेस ब्रेकपॉइंट सेट करें।

और कई बार रुकने के बाद, हम सही क्षण का पता लगाते हैं जब यह ClfsEncodeBlockPrivate में मान को संशोधित करता है।

यह फ़ंक्शन पैच नहीं किया गया है, इसलिए यह 0x50 के कम मान और बाकी मानों के हेरफेर किए जाने के कारण उत्पन्न व्यवहार हो सकता है।
तैयार किए गए मानों में, हम ccoffsetArray मान देख सकते हैं, जिसका नाम _CLFS_BASE_RECORD_HEADER संरचना में rgClients है और यह उन ऑफसेट्स की सरणी को दर्शाता है जो Client Context ऑब्जेक्ट की ओर इंगित करते हैं।
rgClients फ़ील्ड _CLFS_BASE_RECORD_HEADER संरचना के ऑफसेट 0x138 (0x9a8-0x800-0x70) पर स्थित है।


PoC में, यह मान एक नकली क्लाइंट कॉन्टेक्स्ट ऑब्जेक्ट को इंगित करने के लिए विकृत किया गया है, जिसे FakeClientContext कहा जाता है।

यह Client Context संरचना _CLFS_CLIENT_CONTEXT है।
struct _CLFS_CLIENT_CONTEXT
{
CLFS_NODE_ID cidNode;
CLFS_CLIENT_ID cidClient;
USHORT fAttributes;
ULONG cbFlushThreshold;
ULONG cShadowSectors;
ULONGLONG cbUndoCommitment;
LARGE_INTEGER llCreateTime;
LARGE_INTEGER llAccessTime;
LARGE_INTEGER llWriteTime;
CLFS_LSN lsnOwnerPage;
CLFS_LSN lsnArchiveTail;
CLFS_LSN lsnBase;
CLFS_LSN lsnLast;
CLFS_LSN lsnRestart;
CLFS_LSN lsnPhysicalBase;
CLFS_LSN lsnUnused1;
CLFS_LSN lsnUnused2;
CLFS_LOG_STATE eState;
union
{
HANDLE hSecurityContext;
ULONGLONG ullAlignment;
};
};
eState मान संरचना की शुरुआत से ऑफसेट 0x78 पर है, तैयार की गई फ़ाइल में 0x23a0+0x78 पर।


यह मान लॉग की स्थिति दर्शाता है।
typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;
यह मान CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20 पर सेट है।
दूसरा विकृत मान fAttributes है, जो बेस लॉग फ़ाइल से जुड़े FILE_ATTRIBUTE फ़्लैग्स के सेट से संबंधित है (जैसे System और Hidden)।


चूँकि फ़ील्ड एक बाइट पहले 0xa पर शुरू होती है और दो बाइट्स तक फैली हुई है, fAttributes का मान 0x100 है।


अंत में, blocknameoffset मान है जो ऑफसेट 0x1bb8 की ओर इंगित करता है, मेरा मतलब है, 0x78 और 0x800 जोड़ने पर यह फ़ाइल के ऑफसेट 0x2428 की ओर इंगित करता है।


ध्यान दें कि Client Context का ऑफसेट 0x1b30 है।

तो, Client Context ऑफसेट 0x23a0 पर है।


और ठीक 0x10 पहले, यह blocknameoffset के अनुरूप मान है।


जो नाम वाली स्ट्रिंग की ओर इंगित करेगा।
आखिरी मान blockattributeoffset है, जो 0x2394 पर Client Context से 0xC पहले है।

ये अंतिम दो मान Client Context से पहले 0x30 बाइट लंबी संरचना से संबंधित हैं, जिसे _CLFSHASHSYM कहा जाता है।
typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;


वे _CLFSHASHSYM संरचना की शुरुआत से 0x20 और 0x24 बाइट्स पर हैं, इसलिए _CLFSHASHSYM संरचना में PoC में blockNameOffset नामक मान cbSymName फ़ील्ड है और blockAttributteoffset cbOffset फ़ील्ड है।


ये विकृत मान हैं, अब हमें यह देखना है कि ये हमारे SignaturesOffset को 0x50 मान से 0xFFFF0050 में बदलने को कैसे प्रभावित करते हैं।
आइए CClfsBaseFile::AcquireClientContext() फ़ंक्शन पर एक नज़र डालते हैं, जिसे क्लाइंट कॉन्टेक्स्ट लौटाना चाहिए।

यह CClfsBaseFile::GetSymbol को चौथे आर्ग्युमेंट के साथ कॉल करता है, जो _CLFS_CLIENT_CONTEXT ** होगा, जहाँ यह Client Context का पॉइंटर संग्रहीत करेगा।

CClfsBaseFile::GetSymbol फ़ंक्शन के अंदर हम विकृत ccoffsetArray ऑफसेट को CClfsBaseFile::OffsetToAddr में पास करते हैं और क्लाइंट कॉन्टेक्स्ट का पता प्राप्त करते हैं, आइए वहाँ एक ब्रेकपॉइंट सेट करें ताकि यह CreatelogFile . के साथ बनाई गई फ़ाइल को कॉल करते समय रुक जाए।

वहाँ यह ccoffsetArray तैयार किए गए आर्ग्युमेंट के साथ रुका हुआ है।


CClfsBaseFile::OffsetToAddr फ़ंक्शन गलत Client Context लौटाता है।

और जाँचता है कि cbOffset का मान शून्य नहीं है, क्योंकि RAX में मौजूद _CLFS_CLIENT_CONTEXT संरचना से पहले 0xC पाया जाता है।


फिर यह cbOffset की तुलना **ccoffsetArray (**जो RSI में है) से करता है, उन्हें बराबर होना चाहिए, अन्यथा हमें एक त्रुटि मिलेगी।

यह यह भी जाँचता है कि cbSymName, cbOffset+0x88, के बराबर हो, अन्यथा हमें भी एक त्रुटि मिलेगी।

और अंत में, यह cidClient बाइट की तुलना शून्य से करता है।

यदि ये सभी जाँचें सफल होती हैं, तो client context सहेज लिया जाएगा।

फ़ंक्शन r14 का आउटपुट Client Context की ओर इंगित करता है।

CClfsLogFcbPhysical::Initialize से बाहर निकलते समय हमारे पास CLFS_CLIENT_CONTEXT का पता होगा।

अब यह fAttributes (0x100) का मान पढ़ता है।

यह फ़ंक्शन CClfsLogFcbPhysical क्लास से संबंधित है।


जिसे यहाँ आवंटित किया गया था, और इसका आकार 0x15d0 है और इसका टैग “ClfC” है।

आइए हम जो रिवर्स कर रहे हैं उसे संग्रहीत करने के लिए एक संरचना बनाते हैं, हम इसे: struct_CClfsLogFcbPhysical. कहेंगे।

ध्यान दें कि 0x2b0 पर यह CClfsBaseFilePersisted संरचना का पता सहेजता है।

संरचना में कई मान सहेजने के बाद, यह एक महत्वपूर्ण भाग पर जाता है, यह eState को 0x20 के साथ जाँचता है।


चूँकि तैयार किया गया मान 0x20 था, परीक्षण 1 लौटाएगा।


हम देखते हैं कि कंस्ट्रक्टर में vtable में है।

यह जाँचेगा कि फ़ाइल multiplexed है या नहीं।

तो, यह वांछित मार्ग से जाता है, CClfsLogFcbPhysical::ResetLog तक पहुँचता है।


कई फ़ील्ड शून्य पर आरंभ किए जाते हैं, एक को छोड़कर जो 0xFFFFFFFF00000000 पर आरंभ किया जाता है।

यहाँ Client Context प्राप्त करता है।

यह मान 0xFFFFFFFF00000000 संग्रहीत करता है।



यह 0xFFFFFFFF को ऑफसेट 0x5c पर लिखता है, जो CLFS_LSN lsnRestart.ullOffset का उच्च भाग है।



अब हम ClfsEncodeBlockPrivate() फ़ंक्शन निष्पादित करते हैं, जो जैसा हमने पहले देखा है, 0x50 को 0xFFFF0050 से अधिलेखित करने के लिए ज़िम्मेदार है।
वहाँ यह SignatureOffset = 0x50 का मान पढ़ता है, जो अभी भी वैसा ही है जैसा हमने विकृत फ़ाइल में रखा था, और इसे CLFS_LOG_BLOCK_HEADER की शुरुआत में जोड़ता है।

यह एक लूप है जो 2 बाइट्स लिख रहा है, जैसे SignatureOffset किसी सही मान की ओर इंगित करने के बजाय, जो सामान्य फ़ाइल में एक उच्च मान होता है, उदाहरण के लिए 0x3f8 जो इसे आगे लिखने के लिए प्रेरित करता है, यहाँ यह उसी CLFS_LOG_BLOCK_HEADER में लिखेगा।
विचार लिखने के गंतव्य को बदलकर SignatureOffset मान को भ्रष्ट करने का प्रयास करना है।
सामान्य फ़ाइल

इस बिंदु पर, यह लूप करना शुरू कर देगा और दो बाइट्स लिखेगा।

लूप से बाहर निकलने के लिए काउंटर को 0x3d मान तक पहुँचना चाहिए।

RCX 0x200 से बढ़ रहा है, हम पहले से ही तीसरे चक्र में हैं, और इसका मान 0x600 है।

पुनरावृत्ति 0xe में, RCX 0x1a00 है।


वहीं उसने 0xFFFFFFFF000000 लिखा था।


यह अंतिम दो बाइट्स FFFF पढ़ रहा है।

और फिर इसे R8 में कॉपी करेगा।


जैसा कि हमने देखा है, यह मान महत्वपूर्ण है क्योंकि यह आपको जाँच को बायपास करने और memset() के बाद आने वाली फ़ाइल के pContainer पॉइंटर को भ्रष्ट करने के लिए सीमा से बाहर लिखने की अनुमति देता है, और शीर्ष पर शून्य लिखकर इसे हमारे नियंत्रित मेमोरी (HeapSpray) की ओर इंगित करता हुआ छोड़ देता है।
CClfsBaseFilePersisted::AllocSymbol में, वही योग जो memset के गंतव्य को प्राप्त करने वाला है, जो cbSymbolZone + CLFS_BASE_RECORD_HEADER का अंतिम पता है, इसकी तुलना पहले Base_block + 0xFFFF0050 से करता है, इसलिए समीकरण के दोनों ओर भ्रष्ट मान हैं।
CbSymbolZone= 0x1114B
यह विकृत मान है जो CLFS_BASE_RECORD_HEADER के अंतिम पते में जोड़े जाने पर इसे सीमा से बाहर लिखने के लिए प्रेरित करेगा, और तुलना का दूसरा सदस्य जो Base Block + SignatureOffset का पता होना चाहिए, वह SignatureOffset =0xFFFF0050 बना रहता है, जो इस जाँच को पास करने और memset() में सीमा से बाहर लिखने तथा पॉइंटर के शीर्ष को शून्य करने की अनुमति देता है, जो हमारे HeapSpray की ओर इंगित करता रहेगा।

चूँकि RCX, RDX से छोटा है।

जैसा कि हमने पहले देखा है। (मान भिन्न हो सकते हैं क्योंकि वे पिछले निष्पादन से संबंधित हैं।)
यह पॉइंटर को भ्रष्ट कर देगा, उच्चतम बाइट्स को 0 पर सेट कर देगा।

इसे एक मेमोरी क्षेत्र की ओर इंगित करता हुआ छोड़ देता है जिसे हम HeapSpray के माध्यम से नियंत्रित करते हैं।


तो, जब कमज़ोरी ट्रिगर होती है, हम CClfsBaseFilePersisted::RemoveContainer तक पहुँचते हैं।

वहाँ पहले से भ्रष्ट पॉइंटर होगा और जैसा हमने पहले देखा, इसका शोषण किया जा सकता है।

इस बिंदु पर हमने बग का शोषण कर लिया है, यह उन फ़ंक्शनों को नियंत्रित करने की ओर ले जाता है जो SYSTEM टोकन को पढ़ने और अपनी प्रक्रिया में लिखने की अनुमति देते हैं, ताकि स्थानीय विशेषाधिकार वृद्धि प्राप्त की जा सके।
हमें उम्मीद है कि यह आपको उपयोगी लगेगा, यदि आपको कोई संदेह है तो आप हमसे [email protected] और [email protected] पर संपर्क कर सकते हैं।
आनंद लीजिए!