
विंडोज़ कर्नेल पूल (clfs.sys) भ्रष्टाचार विशेषाधिकार वृद्धि
==============
विंडोज कर्नेल पूल (clfs.sys) भ्रष्टाचार विशेषाधिकार वृद्धि। (CVE-2023-36424)
इस रिपॉजिटरी में तकनीकी विश्लेषण और कार्यशील एक्सप्लॉइट शामिल है।
लेखक: Nassim Asrir (@p1k4l4) || https://www.linkedin.com/in/nassim-asrir-b73a57122/
================
clfs.sys मिनी फ़िल्टर ड्राइवर में एक पूल ओवरफ्लो है। इसके बारे में जानकारी यहाँ पढ़ी जा सकती है:
1 - https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html
2 - https://www.zerodayinitiative.com/blog/2021/7/19/cve-2021-31969-underflowing-in-the-clouds
इसका कारण यह है कि ड्राइवर NTFS रीपार्स पॉइंट से आने वाले डेटा की पर्याप्त जाँच नहीं करता है।
हम clfs.sys संस्करण 10.0.22621.2134 (Windows 11 22H2 22621.2215) पर विचार करेंगे।
फ़ंक्शन HsmFltProcessHSMControl क्लाउड फ़िल्टर FSCTLs को प्रोसेस करने के लिए जिम्मेदार है। कोड 0xC0000003 वाले ऑपरेशन के लिए, यह अंततः HsmFltProcessUpdatePlaceholder को कॉल करेगा।
कुछ प्रसंस्करण के बाद निष्पादन प्रवाह HsmiOpUpdatePlaceholderDirectory तक पहुँचेगा और फिर अंततः HsmpRpCommitNoLock तक:
__int64 __fastcall HsmpRpCommitNoLock(__int64 a1, __int64 a2, struct _FILE_OBJECT *a3, char a4, char a5)
{ .....
v26 = FileObject; LODWORD(v9) = HsmpRpReadBuffer(*(PFLT_INSTANCE *)(v164 + 32), FileObject, (unsigned __int16 **)&P); // [1*]
HsmDbgBreakOnStatus((unsigned int)v9);
if ( (_DWORD)v9 == -1073741195 ) .....
goto LABEL_55; }
if ( (v9 & 0x80000000) != 0i64 )
goto LABEL_9;}
if ( (*(_DWORD *)P & 0xFFFF0FFF) != dword_1C0027650 )// Is
Cloud Reparse Tag?
{
LODWORD(v9) = 0xC000CF0B;
.....
goto LABEL_54;
}
v32 = *((unsigned __int16 *)P + 2);
v9 = (unsigned int)HsmpRpValidateBuffer((__int64)P + 8, v32); [2*]
.....
Pool2 = ExAllocatePool2(0x100i64, 0x4000i64, 'pRsH'); // [3*]
v146 = (_DWORD *)Pool2;
v13 = (void *)Pool2;
if ( Pool2 )
{
v64 = v159_10;
v65 = Pool2 + 4;
if ( v8 && *((_WORD *)v8 + 7) > 0xAu )
v64 = *((_WORD *)v8 + 7);
v9 = Pool2 + 20;
v66 = (unsigned int *)(Pool2 + 12);
*(_OWORD *)v65 = 0i64;
*(_WORD *)(Pool2 + 16) = 0;
*(_WORD *)(Pool2 + 18) = v64;
*(_DWORD *)(Pool2 + 12) = 8 * v64 + 16;
*(_DWORD *)v65 = 'pReF';
memset((void *)(Pool2 + 20), 0, 8i64 * v64);
.....
if ( v8 )
{
v127 = 10;
if ( *((_WORD *)v8 + 7) > 0xAu ) // [4*]
{
if ( WPP_GLOBAL_Control !=
(PDEVICE_OBJECT)&WPP_GLOBAL_Control
&& (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) != 0
&& BYTE1(WPP_GLOBAL_Control->Timer) >= 4u )
{
WPP_SF_qiq(WPP_GLOBAL_Control->AttachedDevice, v86,
v87, a2, *(_QWORD *)(v156 + 32), FileObject);
}
while ( v127 < *((_WORD *)v8 + 7) )
{
*(_QWORD *)(v65 + 8i64 * v127 + 16) = *(_QWORD
*)&v8[8 * v127 + 16];
memmove(
(void *)(v65 + *v66),
&v8[*(unsigned int *)&v8[8 * v127 + 20]],
*(unsigned __int16 *)&v8[8 * v127 + 18]); //
[5*]
*(_DWORD *)(v65 + 8i64 * v127 + 20) = *v66;
*v66 += *(unsigned __int16 *)(v65 + 8i64 * v127++ +
18);
}
}
}
.....
}
HsmpRpReadBuffer [1*] रीपार्स पॉइंट डेटा प्राप्त करता है। इस डेटा में WORD-आकार का मान *((_WORD *)v8 + 7) होता है, जो संरचित आइटमों की संख्या निर्दिष्ट करता है। प्रत्येक आइटम में एक Type फ़ील्ड, Size और डेटा फ़ील्ड्स के लिए Offset होता है।
कौन सा आइटम प्रकार किस स्थान पर है, यह सख्ती से पूर्वनिर्धारित है। लेकिन केवल पहले 10 के लिए। उदाहरण के लिए, पहले आइटम का Type फ़ील्ड का मान 0x7 के बराबर होना चाहिए।
ड्राइवर प्राप्त डेटा को सत्यापित करने के लिए HsmpRpValidateBuffer [2*] निष्पादित करेगा। फिर [3*] निश्चित 0x4000 बाइट आकार के साथ पेज्ड पूल आवंटित किया जाएगा। और यदि रीपार्स पॉइंट डेटा में 10 से अधिक Count मान है, तो दसवें के बाद के आइटमों का डेटा बिना किसी अतिरिक्त जाँच के इस निश्चित-आकार के पूल [4*] में कॉपी किया जाएगा।
HsmpRpValidateBuffer के अंदर सत्यापन अपर्याप्त है, क्योंकि यह केवल पहले 10 रिकॉर्ड की जाँच करता है।
__int64 __fastcall HsmpRpValidateBuffer(__int64 pBuf, unsigned int a2)
{
.....
v2 = a2 - 4;
pBuf2 = pBuf + 4;
LOBYTE(v5) = 0;
v6 = 0i64;
if ( a2 <= 4 )
v2 = 0;
v7 = 0;
v8 = *(_DWORD *)pBuf & 0xF;
if ( !v8 )
{
.....
return IsReparseBufferSupported;
}
if ( v8 > 1 )
{
....
}
v9 = 0;
v66 = 0;
if ( v2 < 0x18 )
goto ERROR_EXIT;
v9 = 1;
if ( *(_DWORD *)pBuf2 != 'pReF' )
goto ERROR_EXIT;
v9 = 2;
v10 = (unsigned int *)(pBuf + 0xC);
if ( (*(_BYTE *)(pBuf + 16) & 2) != 0 && *(_DWORD *)(pBuf +
8) != RtlComputeCrc32(0, (PUCHAR)(pBuf + 0xC), v2 - 8) )
goto ERROR_EXIT;
v11 = *v10;
v9 = 3;
if ( v2 < (unsigned int)v11 )
goto ERROR_EXIT;
v12 = *(unsigned __int16 *)(pBuf2 + 0xE);
v9 = 4;
if ( !(_WORD)v12 )
goto ERROR_EXIT;
v13 = 8 * v12 + 16;
v9 = 5;
if ( v13 >= v11 )
goto ERROR_EXIT;
v9 = 0x10000;
for ( i = 0; ; ++i )
{
v15 = *(unsigned __int16 *)(pBuf2 + 0xE);
if ( (unsigned int)v12 >= 0xA ) // [1*]
v15 = 10;
if ( i >= v15 )
break;
}
जैसा कि हम देख सकते हैं [1*] कोड केवल पहले 10 आइटमों को सत्यापित करेगा और अधिक रिकॉर्ड होने पर उस स्थिति को अनदेखा करता है।
=================
कमजोर पूल का आकार 0x4000 है। आकार पृष्ठ का गुणक है और इसलिए सेगमेंट आवंटन का उपयोग किया जाएगा [3]।
शोषण के लिए यहाँ वर्णित तकनीक का उपयोग किया गया [4]। NtAlpcCreateResourceReserve को कॉल करने से बहुत सारे हैंडल बनेंगे और उनमें से एक को निर्मित फर्जी _KALPC_RESERVE ऑब्जेक्ट के पॉइंटर से ओवरराइट करने से हमें मनमाना कर्नेल पते पर लिखने की क्षमता मिलेगी।
मेमोरी तैयार करने के लिए, हम पाइप [5] का उपयोग करके अनुक्रमिक रूप से 0x4000 आकार के पूल आवंटित करते हैं। फिर हम हर दूसरे पूल को मुक्त करेंगे, कमजोर बफर के लिए जगह प्रदान करेंगे।

एक मनमाना कर्नेल पता पढ़ने के लिए, एक्सप्लॉइट ने पाइप का उपयोग किया। इस उद्देश्य के लिए हम PipeAttribute संरचना के AttributeValue पॉइंटर को ओवरराइट करेंगे।

और उसके बाद, हम लक्ष्य प्रक्रिया में टोकन को ओवरराइट करने के लिए सिस्टम टोकन चुरा सकते हैं।
पढ़ने के लिए धन्यवाद।