
Proof of concept & details for CVE-2025-21298
यह CVE-2025-21298 - विंडोज OLE रिमोट कोड निष्पादन भेद्यता (CVSS 9.8) के लिए एक प्रूफ-ऑफ-कॉन्सेप्ट है। यह एक मेमोरी करप्शन PoC है, कोई एक्सप्लॉइट नहीं।
पूर्ण पैच डिफ ghidriff के माध्यम से: लिंक
भेद्यता ole32.dll!UtOlePresStmToContentsStm में स्थित है। फ़ंक्शन का उद्देश्य OLE स्टोरेज के भीतर "OlePres" स्ट्रीम में डेटा को उपयुक्त रूप से स्वरूपित डेटा में परिवर्तित करना और उसे उसी स्टोरेज में "CONTENTS" स्ट्रीम में सम्मिलित करना है। यह एक स्टोरेज ऑब्जेक्ट के लिए IStorage पॉइंटर और तीन महत्वहीन तर्क प्राप्त करता है।
नीचे हम जनवरी 2025 पैच के डिफ के साथ फ़ंक्शन का कार्यान्वयन देख सकते हैं:
__int64 __fastcall UtOlePresStmToContentsStm(IStorage *pstg, wchar_t *puiStatus, __int64 a3, unsigned int *lpszPresStm)
{
struct IStorageVtbl *lpVtbl; // rax
int v7; // r14d
+ bool IsEnabled; // al
IStream *v10; // rcx
bool v11; // zf
struct IStorageVtbl *v12; // rax
int v13; // ebx
HRESULT v14; // eax
const wchar_t *v15; // rdx
IStream *pstmContents; // [rsp+40h] [rbp-19h] BYREF
IStream *pstmOlePres; // [rsp+48h] [rbp-11h] BYREF
tagFORMATETC foretc; // [rsp+50h] [rbp-9h] BYREF
tagHDIBFILEHDR hdfh; // [rsp+70h] [rbp+17h] BYREF
*lpszPresStm = 0;
lpVtbl = pstg->lpVtbl;
pstmContents = 0LL;
v7 = 1;
// Create a "CONTENTS" stream in the storage and store it into pstmContents
if ( (lpVtbl->CreateStream)(pstg, L"CONTENTS", 18LL, 0LL, 0, &pstmContents) )
return 0LL;
// Immediately release pstmContents, we're not going to be using it right now
(pstmContents->lpVtbl->Release)(pstmContents);
+ IsEnabled = wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
+ v10 = pstmContents;
+ v11 = !IsEnabled;
v12 = pstg->lpVtbl;
+ if ( !v11 )
+ v10 = 0LL;
+ pstmContents = v10;
(v12->DestroyElement)(pstg, L"CONTENTS");
v13 = (pstg->lpVtbl->OpenStream)(pstg, &OlePres, 0LL, 16LL, 0, &pstmOlePres);// 2nd option to fail -> no OlePres stream
if ( v13 )
{
*lpszPresStm |= 1u;
if ( (pstg->lpVtbl->OpenStream)(pstg, L"CONTENTS", 0LL, 16LL, 0, &pstmContents) )
{
*lpszPresStm |= 2u;
}
else
{
(pstmContents->lpVtbl->Release)(pstmContents);
+ wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
}
return v13;
}
foretc.ptd = 0LL;
v13 = UtReadOlePresStmHeader(pstmOlePres, &foretc, 0LL, 0LL);
if ( v13 >= 0 )
{
v13 = (pstmOlePres->lpVtbl->Read)(pstmOlePres, &hdfh, 16LL);
if ( v13 >= 0 )
{
v13 = OpenOrCreateStream(pstg, L"CONTENTS", &pstmContents);
if ( v13 < 0 )
{
*lpszPresStm |= 2u;
goto $errRtn_197;
}
if ( foretc.dwAspect == 4 )
{
*lpszPresStm |= 4u;
v7 = 0;
v13 = 0;
goto $errRtn_197;
}
if ( foretc.cfFormat == 8 )
{
v14 = UtDIBStmToDIBFileStm(pstmOlePres, hdfh.dwSize, pstmContents);
LABEL_19:
v13 = v14;
goto $errRtn_197;
}
if ( foretc.cfFormat == 3 )
{
v14 = UtMFStmToPlaceableMFStm(pstmOlePres, hdfh.dwSize, hdfh.dwWidth, hdfh.dwHeight, pstmContents);
goto LABEL_19;
}
v13 = -2147221398;
}
}
$errRtn_197:
if ( pstmOlePres )
(pstmOlePres->lpVtbl->Release)(pstmOlePres);
// Release pstmContents if it still exists, we need to clean up
if ( pstmContents )
(pstmContents->lpVtbl->Release)(pstmContents);
if ( foretc.ptd )
CoTaskMemFree(foretc.ptd);
if ( v13 )
{
v15 = L"CONTENTS";
goto LABEL_31;
}
if ( v7 )
{
v15 = &OlePres;
LABEL_31:
(pstg->lpVtbl->DestroyElement)(pstg, v15);
}
return v13;
}
समस्या pstmContents वेरिएबल में है। प्रारंभ में इसका उपयोग फ़ंक्शन की शुरुआत में बनाए गए "CONTENTS" स्ट्रीम ऑब्जेक्ट के पॉइंटर को संग्रहीत करने के लिए किया जाता है। स्ट्रीम बनने के तुरंत बाद नष्ट हो जाती है और pstmContents में संग्रहीत पॉइंटर रिलीज़ हो जाता है (जो इसे coml2.dll!ExposedStream::~ExposedStream में मुक्त करता है)। हालांकि, वेरिएबल में अभी भी मुक्त किया गया पॉइंटर होता है। फ़ंक्शन में आगे, वेरिएबल का पुन: उपयोग "CONTENTS" स्ट्रीम के पॉइंटर को फिर से संग्रहीत करने के लिए किया जा सकता है - इस वजह से, फ़ंक्शन के अंत में क्लीनअप कोड होता है जो पॉइंटर को रिलीज़ करता है यदि वह वेरिएबल में संग्रहीत है। कोड इस तथ्य पर ध्यान नहीं देता कि UtReadOlePresStmHeader विफल हो सकता है - यदि ऐसा होता है, तो pstmContents अभी भी मुक्त किए गए पॉइंटर की ओर इशारा करेगा और हम क्लीनअप कोड में आ जाएंगे, जो पॉइंटर को फिर से रिलीज़ करेगा। इस प्रकार, डबल-फ्री की स्थिति उत्पन्न होगी।
जैसा कि पैच डिफ में देखा जा सकता है, Microsoft ने प्रारंभ में शामिल पॉइंटर के रिलीज़ होने के बाद pstmContents को शून्य पर सेट करके समस्या को ठीक किया।
रिपॉजिटरी में एक rtf फ़ाइल है जो भेद्यता को पुन: उत्पन्न करती है। मैंने MS Word में फ़ाइल खोलकर परीक्षण किया लेकिन आप RTF डेटा पार्स करने वाले अन्य एप्लिकेशन (जैसे आउटलुक) के साथ भी इसका परीक्षण कर सकते हैं। OLE ऑब्जेक्ट एम्बेड करने वाले अन्य प्रारूपों के माध्यम से शोषण संभव हो सकता है, मैंने प्रयास नहीं किया।
वीडियो:
मैं बाद में विवरण प्रकाशित करूंगा।