
ثغرة حرجة في Windows OLE بدون نقرة. هذا إثبات مفهوم لـ CVE-2025-21298 - ثغرة تنفيذ التعليمات البرمجية عن بُعد في Windows OLE (CVSS 9.8). هذا PoC لفساد الذاكرة
هذا إثبات مفهوم لـ CVE-2025-21298 - ثغرة تنفيذ التعليمات البرمجية عن بُعد في OLE لنظام Windows (CVSS 9.8). إنه إثبات مفهوم لفساد الذاكرة، وليس استغلالاً.
تقع الثغرة في ole32.dll!UtOlePresStmToContentsStm. الغرض من الدالة هو تحويل البيانات الموجودة في تدفق "OlePres" داخل مخزن OLE إلى بيانات منسقة بشكل مناسب وإدراجها في تدفق "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;
// إنشاء تدفق "CONTENTS" في المخزن وتخزينه في pstmContents
if ( (lpVtbl->CreateStream)(pstg, L"CONTENTS", 18LL, 0LL, 0, &pstmContents) )
return 0LL;
// إطلاق pstmContents فوراً، لن نستخدمه الآن
(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);// الخيار الثاني للفشل -> لا يوجد تدفق OlePres
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);
// إطلاق pstmContents إذا كان لا يزال موجوداً، نحتاج للتنظيف
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 (مثل Outlook). قد يكون الاستغلال ممكنًا من خلال تنسيقات أخرى تُضمّن كائنات OLE، لم أجرب ذلك.
الفيديو: