Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2023-28252 — Analisi tecnica e exploit proof-of-concept per CVE-2023-28252, una vulnerabilità di escalation dei privilegi nel driver Windows Common Log File System (CLFS) utilizzata negli attacchi ransomware Nokoyawa. | Kitploit
Strumenti/GitHubGitHub/fortra/cve-2023-28252
Escalation di PrivilegiMemory ForensicsAnalisi delle VulnerabilitàExploitReverse EngineeringDebuggerBinary Exploitation
GitHubfortra/cve-2023-28252

CVE-2023-28252

Analisi tecnica e exploit proof-of-concept per CVE-2023-28252, una vulnerabilità di escalation dei privilegi nel driver Windows Common Log File System (CLFS) utilizzata negli attacchi ransomware Nokoyawa.

Vedi Repository
1844423 anni faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Da febbraio 2022 è stato segnalato un nuovo ransomware che sembra sfruttare una vulnerabilità 0-day di Windows, secondo la ricerca condotta da Trend Micro.
Maggiori informazioni su questo ransomware si trovano a questo
link.
Secondo l'analisi di Kaspersky, il gruppo ransomware Nokoyawa ha utilizzato altri exploit che colpiscono il driver Common Log File System (CLFS) da giugno 2022, con caratteristiche simili ma distinte, tutti collegati a un singolo sviluppatore di exploit.
Nell'aprile 2023, quando Microsoft ha rilasciato la patch, è stato assegnato
CVE-2023-28252.
In precedenza, nel 2022, un bug simile nello stesso componente era stato studiato da noi e documentato in questo
blogpost

Formato del file Common Log File System (CLFS):

Per affrontare l'analisi, è necessario conoscere il formato del file .blf, gestito dal driver vulnerabile Common Log File System chiamato CLFS.sys e che si trova nella cartella dei driver all'interno di system32.

Maggiori informazioni su questo tipo di file si trovano nei link seguenti:

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

https://github.com/ionescu007/clfs-docs/blob/main/README.md

https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe

La vulnerabilità:

Questa analisi è fatta per Windows 11 21H2, clfs.sys versione 10.0.22000.1574 sebbene funzioni anche su Windows 10 21H2, Windows 10 22H2, Windows 11 22H2 e Windows server 2022.

Nelle versioni precedenti di Windows, è necessario regolare alcuni valori, altrimenti si produrrebbe un BSOD.

Microsoft Patch Tuesday aprile 2023.

A screenshot of a computer Description automatically generated with medium confidenceÈ possibile controllare la versione del driver come mostrato

Quando la vulnerabilità è stata pubblicata, nell'aprile 2023 ho iniziato con Esteban Kazimirow a eseguire il reverse del driver CLFS.sys, sebbene in questo caso, analizzare solo la patch fosse molto difficile per dedurre dove fosse il bug e come innescarlo, poiché lo sfruttamento è molto complesso.

Successivamente, è uscito un blogpost il cui autore, a partire da un campione di malware, ha mostrato alcune parti del codice decompilato da HexRays e alcune informazioni che hanno guidato su dove doveva essere affrontato lo sfruttamento.

Ovviamente le informazioni fornite non erano complete, ma senza questo aiuto sarebbe stato improbabile riuscire a costruire il PoC e poi un exploit funzionante.

Per facilitare la comprensione, spiegheremo prima come costruire il PoC e poi faremo l'analisi della vulnerabilità.

Questo blogpost contiene due sezioni:

Costruzione del PoC:

1-Ottenere gli indirizzi kernel necessari per lo sfruttamento

2-Preparare il percorso per creare i file .blf:

3-Creare il file "trigger blf" usando la funzione CreateLogFile()

4-Costruire il file "trigger blf"

5-Ottenere l'indirizzo kernel del BASE BLOCK di trigger blf

6-Chiamare AddLogContainer con l'handle di trigger blf

7-Preparare i file spray blf

8-Preparare la memoria per eseguire lo spray

9-Innescare il bug

Debugging:

1-Controllare lo spray di memoria

2-Osservare RecordOffset[12] di trigger blf

3-Osservare il valore iFlushBlock nel file spray blf

4-Perché legge da BLOCK 1 SHADOW invece di BLOCK 0 CONTROL ?

5-Perché il checksum è uguale a zero nei file spray blf ?

6-Concludere lo sfruttamento.

7-La vera patch

Costruzione del PoC:

1-Ottenere gli indirizzi kernel necessari per lo sfruttamento

Creerò una funzione chiamata InitEnvironment per ottenere alcuni indirizzi Kernel necessari.

Ottengo l'indirizzo EPROCESS del mio processo e lo memorizzo nella variabile g_EProcessAddress, poi l'indirizzo EPROCESS del processo SYSTEM e lo memorizzo in system_EPROCESS, poi l'indirizzo EHTREAD del thread principale del mio processo e lo memorizzo in g_EThreadAddress e infine l'indirizzo di PREVIOUS MODE che in questa versione del PoC non verrà utilizzato.

A screenshot of a computer code Description automatically generated with low confidence

Questo metodo è ben noto, la funzione GetObjectKernelAddress chiama NtQuerySystemInformation due volte con il primo argomento SystemExtendedHandleInformation, la prima chiamata passa una dimensione errata e restituisce errore, ma restituisce anche la dimensione corretta che viene utilizzata nella seconda chiamata e ottiene le informazioni di tutti gli handle, quindi scorrendo in un ciclo le informazioni di ogni handle e nel campo Object del handleinfo corretto si ottiene l'indirizzo cercato nel kernel.

A picture containing text, screenshot, font, line Description automatically generated

Ho anche bisogno degli indirizzi kernel delle seguenti funzioni esportate da CLFS.sys:

• ClfsEarlierLsn

• ClfsMgmtDeregisterManagedClient

E le funzioni esportate da NTOSKRNL.exe

• RtlClearBit/PoFxProcessorNotification

• SeSetAccessStateGenericMapping

Per ottenere questi indirizzi si usa un metodo simile a quello usato per ottenere la base kernel di entrambi i moduli, chiamando NtQuerySystemInformation due volte, ma in questo caso il primo argomento sarà SYSTEM_INFORMATION_CLASS (nel PoC usiamo la funzione FindKernelModulesBase per questo scopo).

A picture containing text, font, screenshot, line Description automatically generatedSuccessivamente carica CLFS.sys e NTOSKRNL.exe come modelli normali in modalità utente chiamando LoadLibrary, ottiene gli indirizzi in modalità utente con GetProcAddress e poi sottrae l'imagebase da ciascuno, ottenendo così l'offset della funzione e infine aggiunge ciascun offset alle corrispondenti basi kernel e in questo modo ottiene gli indirizzi kernel di tutte le funzioni necessarie.

A picture containing text, font, line, screenshot Description automatically generated

2-Preparare il percorso per creare i file .blf:

Creo una funzione chiamata createInitialTriggerBlfFile che genererà e scriverà un file .blf.

Il percorso utilizzato come argomento in CreateLogFile è diverso da un percorso normale; per esempio, per aprire il file 1280.blf situato nella cartella C:\Users\Public, dobbiamo impostare il percorso LOG:C:\Users\Public\1280. Questo verrà salvato nella variabile stored_name_CreateLog.

Faccio questo usando wsprintfW() poiché stored_env memorizza il percorso C:\Users\Public, precedentemente ottenuto dalle variabili d'ambiente. A questa stringa anteporrò la stringa LOG: e un nome casuale alla fine, senza l'estensione .blf.

A screenshot of a computer Description automatically generated with medium confidence

Questo sarà il percorso del mio file iniziale che chiamerò "trigger blf". Naturalmente, devo anche salvare il percorso normale dello stesso file senza il LOG: davanti e con l'estensione BLF per aprirlo e modificarlo con CreateFile(), WriteFile() come qualsiasi altro file; questo percorso sarà, per esempio: C:\Users\Public\1280.blf, e sarà memorizzato nella variabile stored_name_fopen.**

A picture containing text, font, line, screenshot Description automatically generated

Naturalmente, entrambi i percorsi corrispondono allo stesso file, e devo usarli uno o l'altro a seconda del caso.

3-Creare il file "trigger blf" usando la funzione CreateLogFile().

La funzione CreateLogFile svolge una funzione abbastanza simile a CreateFile() (crea nuovi file o apre file esistenti e ottiene il loro handle), anche alcuni argomenti sono simili, ma CreateLogFile() funziona solo con file blf.

Inoltre, quando apre un file esistente, verifica che il formato sia corretto, persino che ogni blocco abbia un checksum e se questo non è corretto restituirà un errore.

Creerò 2 tipi di file BLF:

  1. Il Trigger blf

  2. Lo Spray blf

Entrambi sono file blf ma modificati in modo diverso.

A close-up of a computer code Description automatically generated with low confidenceIn questo modo il PoC crea prima il file "trigger blf", usando CreateLogFile, con il percorso per esempio: LOG:C:\Users\Public\1280 che ho impostato in precedenza, ed era memorizzato nella variabile stored_name_CreateLog.

Il quinto argomento fCreateDisposition, come in CreateFileA(), può assumere i seguenti valori:

A picture containing text, font, line, receipt Description automatically generated

In questo caso userò l'argomento OPEN_ALWAYS, quindi il file verrà creato se non esiste e se esiste verrà aperto. Poiché il file non esiste ancora, verrà creato con un nome casuale.

logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);

CreateLogFile() creerà il nostro file "trigger blf" con i suoi 6 blocchi e i relativi checksum e restituirà l'handle che sarà memorizzato nella variabile logFile.

A picture containing text, screenshot, font, number Description automatically generated

Ogni blocco avrà dall'offset mostrato nella colonna sinistra un'intestazione la cui dimensione è 0x70 byte.

Quindi, per esempio, l'intestazione del CONTROL BLOCK va dall'offset 0x0 a 0x70.

A screenshot of a computer Description automatically generated with low confidence

Tutte le intestazioni di tutti i blocchi hanno la stessa struttura chiamata _CLFS_LOG_BLOCK_HEADER.

Questa è la struttura dell'intestazione:

A screenshot of a computer Description automatically generated with medium confidence

All'offset 0xC dell'intestazione trovo il checksum, quindi poiché il CONTROL BLOCK inizia all'offset 0, il checksum sarà all'offset 0xC del file e così ogni blocco avrà il suo checksum a 0xC dall'inizio del suo blocco.

A screenshot of a computer Description automatically generated with low confidence

4-Costruire il file "trigger blf":

Per modificare il file trigger blf, devo aprirlo come un file normale usando CreateFileA o fopen e poi modificarlo con WriteFile o fwrite rispettivamente; eseguo questa operazione all'inizio della funzione fun_prepare del PoC.

Ricorda che il percorso normale è memorizzato nella variabile stored_name_fopen, quindi lo uso per aprire il file con wfopen_s (che è una variante di fopen che supporta stringhe Unicode).

Il file viene modificato nella funzione craftTriggerBlfFile chiamata da fun_prepare.

A picture containing text, line, font, screenshot Description automatically generated

Poi chiamo fseek per puntare all'offset da modificare e poi con fwrite il file viene modificato.

A screenshot of a computer Description automatically generated with medium confidenceLe modifiche da apportare al file "trigger blf" sono le seguenti:

Dopo aver apportato queste modifiche, viene chiamata FixCRCFile per calcolare il nuovo checksum e correggere i checksum dei primi 4 blocchi. I due blocchi successivi non hanno modifiche, quindi non è necessario ricalcolare i loro checksum.

A picture containing text, font, screenshot, number Description automatically generated

5-Ottenere l'indirizzo kernel del BASE BLOCK di trigger blf:

Il driver CLFS.sys legge i sei blocchi del file e per memorizzare il loro contenuto effettua un'allocazione nel pool del Kernel.

A picture containing text, screenshot, font, number Description automatically generated

C'è una struttura molto importante di dimensione 0x90 che nel precedente blogpost su CVE-2022-37969, attraverso il reverse ho trovato alcuni campi e l'ho chiamata pool_0x90. Dopo molto più reverse, ora so che il suo vero nome è m_rgBlocks e mentre il controller alloca memoria per copiare dal file il contenuto di ogni blocco, lì salva la dimensione di ogni blocco, l'offset di inizio e l'indirizzo kernel dove è stato memorizzato.

A picture containing text, screenshot, font Description automatically generated

Ha sei strutture CLFS_METADATA_BLOCK che corrispondono a ciascun blocco per numero.

Ogni struttura CLFS_METADATA_BLOCK è lunga 0x18 byte. (0x18*6=0x90)

A picture containing text, font, line, number Description automatically generatedAll'offset 0 c'è una union, ma almeno in questo exploit viene utilizzato solo il campo pbImage, quindi semplificando sarebbe:

L'allocazione di questa struttura può avvenire da due punti diversi del driver CLFS.sys, a seconda della creazione di un nuovo file o dell'apertura di uno esistente. Nel caso in cui un nuovo file viene creato, il driver alloca i 0x90 byte da CClfsBaseFilePersisted::CreateImage+28A, mentre nel caso di un file esistente li alloca da CClfsBaseFilePersisted::ReadImage+6E.

Successivamente, ottengo l'indirizzo di inizio del blocco 2 che corrisponde al file trigger blf, chiamato BASE BLOCK che inizia all'offset 0x800 e la sua lunghezza è 0x7a00.

A picture containing text, screenshot, font, number Description automatically generated

All'interno della funzione fun_prepare di seguito, questo indirizzo verrà trovato nel kernel usando questo pezzo di codice.

A screenshot of a computer code Description automatically generated with low confidence

Prima, la funzione getBigPoolInfo trova tutte le allocazioni nel pool che hanno il tag "Clfs" e una dimensione di 0x7a00, poi le memorizza in un array.

Successivamente riapre il file trigger blf precedentemente modificato usando CreateLogFile con l'argomento OPEN_EXISTING, quindi apre un file esistente; questo eseguirà l'allocazione del suo BASE BLOCK.

Quando getBigPoolInfo viene chiamato di nuovo, ci sarà una nuova pool "Clfs" di dimensione 0x7a00, e il suo indirizzo viene recuperato chiamando NtQuerySystemInformation due volte.

L'indirizzo del BASE BLOCK del file trigger blf è memorizzato nella variabile CLFS_kernelAddrArray.

Nota che se il file trigger blf modificato non ha il checksum corretto, la funzione CreateLogFile() fallirà.

6-Chiamare AddLogContainer con l'handle di trigger blf:

L'ultima parte della funzione fun_prepare chiama l'API AddLogContainer usando l'handle del file trigger blf.

A close-up of a computer code Description automatically generated with low confidence

7-Preparare i file spray blf:

Nell'ultima funzione del PoC chiamata to_trigger verrà creato un secondo tipo di file blf,

Lo chiamerò spray blf.

Questo tipo di file verrà utilizzato per riempire uno spazio di memoria (spray); sono necessari 10 file uguali di questo tipo, ma inizialmente ne viene creato solo uno. A picture containing text, font, line, screenshot Description automatically generated

Verranno creati tre array per memorizzare i nomi casuali di questi file:

stored_log_arrays: memorizza dieci nuovi nomi casuali di file .blf che verranno utilizzati con CreateLogFile.

stored_container_arrays: memorizza nomi casuali per creare dieci nuovi file contenitore.

stored_fopen_arrays: memorizza i nomi dei file di log del primo array (variabile stored_log_arrays), ma con il loro percorso normale (senza la stringa "LOG:") e con l'estensione .blf.

A screenshot of a computer code Description automatically generated with medium confidence

Ad ogni iterazione il file blf viene copiato usando CopyFileW, vengono assegnati i nomi memorizzati negli array.

La funzione fun_trigger chiama craftSprayBlfFile dove vengono apportate modifiche a ciascun file e FixCRCFile correggerà i CRC.

A screenshot of a computer program Description automatically generated with medium confidence

Riassumendo, ho creato 10 file simili (spray blf) con nomi casuali con le seguenti modifiche:

A screenshot of a computer Description automatically generated with medium confidence

L'ultima modifica è copiare l'intero blocco 0 (CONTROL BLOCK) nel blocco 1 (CONTROL BLOCK SHADOW)

A screen shot of a computer Description automatically generated with low confidence

L'effetto di queste modifiche, insieme a quelle apportate al file trigger blf, verrà spiegato più avanti nel capitolo di debug.

Alcune di queste modifiche sono quelle che producono la vulnerabilità, mentre altre sono necessarie solo per bypassare i controlli del driver.

A questo punto i file sono già creati e modificati, pronti per eseguire lo spray; poi quando vengono aperti con CreateLogFile, verranno posizionati nell'area di memoria che desideriamo, come mostreremo in seguito.

8-Preparare la memoria per eseguire lo spray

Nella funzione to_trigger viene creato un array di 12 elementi, contenente l'indirizzo del BASE BLOCK del file trigger blf più 0x30.

Poi, nella funzione fun_pipeSpray, la memoria viene riempita con uno spray di pipe; al suo interno c'è un ciclo che chiama CreatePipe e crea il numero di pipe passato come primo argomento; il secondo argomento è un array che memorizzerà gli handle di tutte le pipe create.

Dentro un ciclo, chiama CreatePipe creando pipe di lettura-scrittura.

In questo modo vengono prima create 0x5000 pipe e poi chiamate di nuovo per creare altre 0x4000 pipe.

Poi usa WriteFile per scrivere nelle prime 5000 pipe l'array appena creato con gli indirizzi di BASE BLOCK + 0x30 del file trigger blf.

A screenshot of a computer code Description automatically generated with low confidence

Ora abbiamo già un blocco compatto creato in memoria; verranno rilasciate 0x667 pipe dal numero 0x2000 fino a 0x2667, poiché in memoria le pipe non sono nello stesso ordine in cui sono state create; ciò che accadrà è che ci saranno spazi liberi in questo blocco di memoria.

A picture containing text, screenshot, font, software Description automatically generatedNota che le allocazioni delle pipe hanno dimensione utente di 0x90 byte, quindi quando vengono rilasciate avremo

Libera gli spazi di memoria di dimensione 0x90 tra la memoria piena di pipe.
Poi itera per chiamare CreateLogFile con i 10 file spray blf.
Quando CreateLogFile viene chiamato per aprire file esistenti, viene eseguita l'allocazione di 0x90 byte per m_rgBlocks, una per ogni file spray blf, quindi queste allocazioni occuperanno i vuoti lasciati quando sono state rilasciate le pipe, poiché hanno la stessa dimensione.

A screenshot of a computer code Description automatically generated with medium confidence

Poi ripete il processo di scrittura nelle ultime 0x4000 pipe dell'array che contiene l'indirizzo di BASE BLOCK +0x30 di trigger blf.

9-Innescare il bug

Tutte queste manipolazioni creano uno spazio di memoria controllato; mostrerò come appare quando viene eseguito il debug, ma l'idea è che m_rgBlocks di ogni file spray blf occupa i vuoti di 0x90 byte che sono stati rilasciati.

Poi, già nella parte finale, il bug viene innescato all'interno di un while( 1 ) usando una chiamata a AddLogContainer per i file spray blf.Una foto contenente testo, carattere, linea, numero Descrizione
generata automaticamente

All'interno di questo while il bug viene attivato:

Uno screenshot di un programma per computer Descrizione generata automaticamente
con bassa confidenza

Questo while uscirà quando trova il token di Sistema, utilizzando la funzione NtFsControlFile che leggerà gli attributi delle pipe.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

Quindi, usando CreateLogFile, sovrascrive nuovamente il token del nostro processo con il token di Sistema appena trovato e in questo modo otteniamo l'elevazione dei privilegi.

Una foto contenente testo, carattere, linea, screenshot Descrizione
generata automaticamente

Poi ripristina alcuni valori, chiude gli handle delle pipe e dei file blf, ed esegue un Blocco note come Sistema per verificare che abbiamo effettuato l'elevazione correttamente.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Nota i file blf creati nella cartella PUBBLICA. Ricorda che se vuoi riprovare, devi prima eliminare i file creati. alcuni saranno bloccati e non potranno essere cancellati, ma il PoC funzionerà comunque.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Debug:

1- Verifica dello spray di memoria

Prima di iniziare con l'effetto delle modifiche per innescare blf e spruzzare blf file per eseguire lo sfruttamento, devo verificare che m_rgBlocks di spray blf file si trovino nei buchi che si verificano nella distribuzione della memoria, dopo aver eseguito lo spray di pipe e il successivo rilascio di un numero fisso di pipe.

Al termine di questa procedura, una pipe dovrebbe trovarsi sotto i 0x90 byte di m_rgBlocks, quindi quando m_rgBlocks viene utilizzato, si verificherà un OUT OF BOUNDS e leggerà da quella pipe che si trova sotto.

Il PoC ha un punto ideale per posizionare un breakpoint:

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

A questo punto, l'apertura dei file spray blf è completa e la funzione AddLogContainer non è ancora stata chiamata.

Per eseguire il debug in modalità utente, userò x64dbg e per la modalità kernel, IDA con il plugin Windbg.

Uno screenshot di un computer Descrizione generata automaticamente con
bassa confidenza

A questo punto la memoria dovrebbe essere già preparata, e posso vedere la distribuzione.

Metterò in pausa IDA per trovare un punto interessante dove posizionare un breakpoint.

Imposterò un breakpoint su CClfsBaseFilePersisted::AddContainer, che viene chiamato da AddLogContainer e all'inizio, ha il registro RCX che punta alla struttura CClfsBaseFilePersisted e all'offset 0x30 c'è un puntatore a m_rgBlocks.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Quando il breakpoint viene raggiunto, controllo nello stack delle chiamate che AddLogContainer viene chiamato dal mio PoC.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

il registro RCX punta a:

Uno screenshot di un computer Descrizione generata automaticamente con
bassa confidenza Uno screenshot di un computer
Descrizione generata automaticamente con bassa
confidenza

Il primo campo è il puntatore a una vtable (CLFS! CClfsBaseFilePersisted::'vftable') e all'offset 0x30 c'è il puntatore a m_rgBlocks.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con confidenza media

I blocchi 0, 1, 4 e 5 non hanno ancora salvato pbImage, mentre i blocchi 2 (BASE BLOCK) e 3 (SHADOW BLOCK) lo hanno.

Ogni blocco nella tabella m_rgBlocks ha il suo cbOffset che è l'offset dove il blocco inizia nel file, cbImage è la dimensione del blocco, e eBlockType è il tipo di blocco.

Se lo spray è corretto, sotto m_rgBlocks dovrebbe esserci una pipe e al suo interno, i puntatori a BASE BLOCK + 0x30 di trigger blf.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

Il comando "!pool" su windbg mostra la distribuzione della memoria:

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Ogni m_rgBlocks ha un tag "Clfs" e la sua dimensione è 0xa0 perché è la dimensione utente 0x90 più 0x10 di header e sotto c'è una pipe con il tag "NpFr" che ha la stessa dimensione utente 0x90

  • 0x10 header.

Poiché la distribuzione non è una scienza esatta, alcuni "Clfs" sono stati posizionati in modo continuo, il che è indesiderabile, ma quello con cui sto lavorando è posizionato correttamente seguito da una pipe.

2- Osservazione del RecordOffset[12] di trigger blf

Una delle prime modifiche che influiscono è quella fatta nel file trigger blf all'offset 0x858, dove viene memorizzato il valore 0x369.

Una foto ravvicinata di un segno Descrizione generata automaticamente con
bassa confidenza

Il BASE BLOCK inizia all'offset 0x800 nel file.

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamente

All'interno del _CLFS_LOG_BLOCK_HEADER all'offset 0x800+0x58 (0x58 dall'inizio dell'header di BASE BLOCK).

Una foto contenente testo, screenshot, carattere, display Descrizione
generata automaticamente

All'offset 0x28 inizia l'array RecordOffsets (DWORD).

Spostandosi in avanti di 0x30 byte, all'offset 0x58 (0x828+0x30=0x858 dall'inizio), si trova il campo 12 di RecordOffsets.

Una foto contenente testo, carattere, screenshot, grafica Descrizione
generata automaticamente

Eseguo il PoC fino a CreateLogFile come mostrato nell'immagine qui sotto:

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza Uno screenshot di un codice
di computer Descrizione generata automaticamente con bassa
confidenza

Uno screenshot di un computer Descrizione generata
automaticamente

Prima di entrare in CreateLogFile imposterò un breakpoint in un punto in cui il valore 0x369 non è ancora stato utilizzato.

Nel caso in cui CreateLogFile apra un file esistente, la struttura m_rgBlocks viene allocata qui:

CClfsBaseFilePersisted::ReadImage+6E

Quindi, imposterò un breakpoint su IDA proprio qui:

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Quando il breakpoint viene attivato**:**

Una foto contenente testo, screenshot, carattere, numero Descrizione
generata automaticamente

In m_rgBlocks c'è ancora della spazzatura perché non è ancora inizializzato, ma non appena pbImage del blocco 2 viene allocato, l'indirizzo verrà salvato all'offset 0x30 dall'inizio, poiché il primo campo all'interno di ogni CLFS_METADATA_BLOCK è pbImage.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamente

Ora imposto un breakpoint hardware in scrittura: ba w1 ffffd003'7f5bea30

Dopo l'inizializzazione a zero, si ferma quando salva pbImage.

L'analisi dice che corrisponde a blocco0, perché non considera la costante r14*8 che è 0x30 successivamente, di conseguenza sta realmente scrivendo il pbImage del blocco 2.

Una foto contenente testo, screenshot, carattere Descrizione generata
automaticamente

Nota che CClfsBaseFilePersisted::ReadMetadataBlock viene utilizzato per allocare uno qualsiasi dei blocchi, utilizzando la dimensione passata come argomento.

Una foto contenente testo, carattere, screenshot, linea Descrizione
generata automaticamente

Ora imposta un breakpoint lettura/scrittura a 0x58 dal blocco base, per vedere quando usa il valore 0x369.

ba r1 FFFF978A'16ECF000+0x58

Quando il breakpoint viene raggiunto, legge il valore 0x369 situato nel RecordOffset[12], lo aggiunge a un puntatore strano su r14 e incrementa il contenuto di RAX+r14.

Poche righe sopra nel codice, ESI ha il valore 0x13 e moltiplica per 0x18, che è la dimensione di ogni blocco in m_rgBlocks.

WINDBG>? 0x18*0x13

Espressione valutata: 456 = 00000000'000001c8

Se aggiungo il valore di r8= 0x1c8 che è maggiore di 0x90, all'indirizzo iniziale di m_rgBlocks, leggerà OUT OF BOUNDS.

Uno screenshot di un computer Descrizione generata
automaticamente

Sotto m_rgBlocks, c'è la pipe con il puntatore a BASE BLOCK + 0x30, legge questo puntatore che è stato posizionato strategicamente all'interno della pipe.

Uno screenshot di un programma per computer Descrizione generata automaticamente
con bassa confidenzaLa posizione corrente nel codice è stata chiamata dall'istruzione while(1) del modulo principale.

All'interno del file spray blf ho posizionato strategicamente il valore 0x13 all'offset 0x48a (iFlushBlock).

Una foto contenente testo, carattere, linea, screenshot Descrizione
generata automaticamente

3- Osservazione del valore iFlushBlock nel file spray blf.

All'offset 0x8a del file spray blf, si trova iFlushBlock di BLOCK 0, il cui valore è 4, mentre l'offset 0x48a appartiene a iFlushBlock di BLOCK 1, e il suo valore è 0x13

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Ora devo scoprire perché legge iFlushBlock = 0x13 da BLOCK 1 invece di iFlushBlock = 4 da BLOCK 0.

4- Perché legge da BLOCK 1 SHADOW invece di BLOCK 0 CONTROL?

Se guardo indietro per scoprire da dove viene 0x13, vedo sullo stack delle chiamate che WriteMetadataBlock viene chiamato da CClfsBaseFilePersisted::ExtendMetadataBlock+416, lì il secondo argomento iFlushBlock è EDX=0x13, che proviene da r9w.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamente

Uno screenshot di un programma per computer Descrizione generata automaticamente
con bassa confidenzaPoche righe prima, CClfsBaseFile::GetControlRecord è stata chiamata per recuperare l'indirizzo di BLOCK 0, forse il problema è qui, quindi riavvierò e imposterò un breakpoint su di essa.

GetControlRecord chiama CClfsBaseFile::AcquireMetadataBlock che dovrebbe riempire la tabella m_rgBlocks con l'indirizzo di block 0, quando passo oltre questa funzione ottiene l'indirizzo di block 1, quindi, il problema si verifica all'interno di CClfsBaseFile::AcquireMetadataBlock.

Aggiungendo 0x8A all'indirizzo recuperato, posso confermare che il valore 0x13 che appartiene a BLOCK 1 è presente.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con confidenza media

Riavvierò e imposterò un breakpoint lì:

Uno screenshot di un programma per computer Descrizione generata automaticamente
con bassa confidenza

CClfsBaseFile::GetControlRecord+27 chiama CClfsBaseFile::AcquireMetadataBlock

Uno screenshot di un computer Descrizione generata
automaticamente

Il secondo argomento passato a AcquireMetadataBlock è zero, corrisponde a block 0, sta per copiare dal file e memorizzare il suo indirizzo in m_rgBlocks Uno screenshot di un computer Descrizione generata
automaticamente con confidenza media.

Nell'enumerazione del tipo di blocco _CLFS_METADATA_BLOCK_TYPE hanno nomi diversi da quelli che ho usato, ma sono gli stessi 6 blocchi.

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamente

Dopo aver verificato che il tipo di blocco è inferiore al massimo m_cBlocks=6, salva un valore di riferimento per evitare di leggere lo stesso blocco due volte.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

ReadMetadataBlock viene chiamato, il problema di leggere block 1 invece di block 0 sarebbe all'interno di questa funzione.

Una foto contenente testo, carattere, numero, linea Descrizione
generata automaticamente

Se tutto è a posto, alloca usando cbImage come dimensione e memorizza l'indirizzo nel campo block 0-> pbImage in m_rgBlocks.

Una foto contenente testo, carattere, linea, screenshot Descrizione
generata automaticamente

Il comando !pool mostra il tag e la dimensione allocata.

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamente

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamenteQuindi, ho già l'indirizzo di pbImage di block 0 memorizzato in m_rgBlocks, quindi devo vedere perché copia i byte di block 1 lì invece dei byte di block 0.

Arrivo a una chiamata a CClfsContainer::ReadSector dove viene passato un puntatore a una variabile contenente pbImage, per scrivere i byte.

Nota le modifiche apportate al contenuto di pbImage quando si passa oltre ReadSector.

Aggiungendo 0x8a a pbImage posso trovare il valore 4 che è il valore corretto, invece di 0x13, quindi il problema deve verificarsi più tardi.

Dopo aver chiamato ClfsDecodeBlock restituisce un errore 0x0C01A000A.

CClfsBaseFilePersisted::ReadMetadataBlock+153 chiama ClfsDecodeBlock

Dopo questo errore, aggiunge 1 al tipo e chiama CClfsBaseFilePersisted::ReadMetadataBlock di nuovo ma con tipo 1 per leggere il blocco 1.

Uno screenshot di un computer Descrizione generata
automaticamente

In CClfsBaseFilePersisted::ReadMetadataBlock Alloca e memorizza un nuovo pbImage in m_rgBlocks per block 1.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

Blocks 0 e 1 hanno indirizzi diversi, ora se aggiungo 0x8a all'indirizzo di block 1 il suo valore è 0x13.

Forse dal momento che block 0 ha restituito un errore, usa block 1 e lo restituisce a GetControlRecord come Control Block.

Come mostrato in precedenza, quando utilizza il valore 0x13 invece di 4, esce dai limiti di m_rgBlocks e legge i valori dello spray di pipe controllati da me.

Una foto contenente testo, screenshot, carattere Descrizione generata
automaticamentePoi libera pbImage da block 0 e copia il puntatore da block 1 a block 0.

Uno screenshot di un computer Descrizione generata
automaticamente

Sarebbe necessario trovare il valore che causa l'errore 0x0C01A000A all'interno di ClfsDecodeBlock.

All'interno di ClfsDecodeBlock il checksum del primo blocco è zero, questo è l'errore 0xC01A000A.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

5- Perché il checksum è uguale a zero nei file blf spray?

Prima di chiamare AddLogContainer, aprendo qualsiasi file spray blf con un editor esadecimale, il checksum è stato cambiato in zero.

Uno screenshot di un computer Descrizione generata
automaticamente

dovrebbe essere stato cambiato prima quando è stato aperto con CreateLogFile.

Una foto contenente testo, screenshot, display, carattere Descrizione
generata automaticamente

Per qualche motivo i file spray blf terminano dopo l'uscita da CreateLogFile con checksum di block 0 uguale a 0 e restituiscono un handle valido, vediamo perché accade.

Mi fermo su CreateLogFile prima di aprire qualche file spray blf.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Nota che prima di chiamare CreateLogFile, spray files hanno il checksum corretto in block 0 e dopo aver completato la funzione, il valore del checksum cambia in zero.

Uno screenshot di un codice di computer Descrizione generata automaticamente
con bassa confidenza

Quindi, imposto un breakpoint su CClfsBaseFile::GetControlRecord, per guardare all'interno.

Una foto contenente testo, screenshot, carattere, linea Descrizione
generata automaticamenteDopo aver passato CClfsContainer::ReadSector il checksum non è zero.

Prima di entrare per calcolare il CRC32, mette il campo checksum a zero in memoria per calcolare il CRC, e il risultato è corretto.

Una foto contenente testo, carattere, screenshot Descrizione generata
automaticamente

Una foto contenente testo, carattere, linea, screenshot Descrizione
generata automaticamente

Poi controlla il valore di eExtendState =2 e va a WriteMetadataBlock.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Qui il checksum è ancora zero in memoria, devo solo vedere quando questo valore viene scritto nel file.

Una foto contenente testo, carattere, linea, screenshot Descrizione
generata automaticamente

Controlla alcuni valori che sono realizzati nel file blf spray per raggiungere CClfsBaseFilePersisted::ExtendMetadataBlock.

Uno screenshot di un programma per computer Descrizione generata automaticamente
con confidenza media

Dopo un ciclo per leggere i blocchi che non sono stati ancora letti, block 0 continua con checksum = 0.

Un rettangolo bianco con testo nero Descrizione generata automaticamente
con bassa confidenza

Arrivando a WriteMetadataBlock.

Poiché sto eseguendo prima che sostituisca block 0 con 1, il valore iFlushBlock del file blf spray è ancora 4 il valore corretto.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Ora sta lavorando con block 4, e scriverà block 4 nel file, qui non è ancora il problema.

Poi arriva a CClfsBaseFilePersisted::FlushControlRecord

Uno screenshot di un programma per computer Descrizione generata automaticamente
con confidenza media

All'interno raggiunge WriteMetadataBlock, ma con argomento 0, per scrivere block 0 nel file**.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Poi ClfsEncodeBlock restituisce l'errore 0xC01A000A, sebbene scriverà il file con il block 0 errato in CClfsContainer::WriteSector, appena sotto.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

La variabile var_54 memorizza il valore di errore 0xC01A000A e verrà controllata prima di uscire dalla funzione.

Uno screenshot di un computer Descrizione generata automaticamente con
confidenza media

Ma dopo aver chiamato CClfsContainer::WriteSector che non restituisce errori, il contenuto di var_54 viene sovrascritto con zero.

Quindi, la funzione restituisce zero senza errori e continua a funzionare poiché CreateLogFile restituirà un handle invece di un valore di errore.

Uno screenshot di un computer Descrizione generata
automaticamente

6- Conclusione dello sfruttamento.

Il valore 0x13 in iFlushBlock lo fa uscire dai limiti e leggerà il puntatore che si trova nelle pipe che punta al Base Block +30 di trigger blf.

Uno screenshot di un computer Descrizione generata
automaticamenteThen it adds 0x28 to that pointer, ( 0x58 from the beginning of the base block of the trigger blf) that has the value 0x369.

A screenshot of a computer program Description automatically generated
with medium confidence

L'istruzione INC aumenterà il valore 0x14 di 1 e si ripete 4 volte, quindi 0x14 termina a 0x18.

WINDBG>db r14+369

ffffcb82'091e7397 14 00 00 00

Successivamente, viene chiamato CreateLogFile e legge il valore 0x1858.

A screenshot of a computer Description automatically generated with
medium confidence

A close-up of a card Description automatically generated with low
confidenceGetSymbol controlla se il blocco falso creato in precedenza in trigger blf, puntato dall'offset 0x1858, ha i valori corretti.

A picture containing text, font, line, screenshot Description
automatically generated

se il puntatore non fosse stato incrementato più volte, avrebbe il valore originale 0x1458 e punterebbe al blocco giusto.

Dopo l'uscita da GetSymbol, utilizzerà quel blocco falso qui.

A screenshot of a computer Description automatically generated with
medium confidence

Poi leggerà il valore dell'offset 0x18 del blocco falso dove ho inserito 0x05000000 e salterà al contenuto di ciò che c'è.

WINDBG>dps 0x5000000

00000000'05000000 00000000'05001000

Legge il contenuto di 0x05000000 e il suo 0x05001000 e lì c'è ClfsEarlierLsn.

A screenshot of a computer program Description automatically generated
with low confidence

Questa funzione è utilizzata per restituire il valore 0xFFFFFFFF in RDX sebbene questa prima volta quel valore non venga utilizzato.

La seconda chiamata avviene qui, chiama PoFxProcessorNotification che era in 0x501000 +8

A picture containing text, font, screenshot, line Description
automatically generated A picture containing
text, font, screenshot, line Description automatically
generated

WINDBG>dps 00000000**'05001000**

00000000'05001000 fffff805'7ab13220 CLFS! ClfsEarlierLsn

00000000'05001008 fffff805'769dc3b0 nt! PoFxProcessorNotification

A screenshot of a computer screen Description automatically generated
with low confidence

in questa funzione RCX = 0x05000000 , controlla che 0x40 byte dopo sia diverso da zero

WINDBG>dps rcx+40

00000000'05000040 00000000'05000000

L'indirizzo a cui saltare sarà 0x68 dopo.

WINDBG>dps rcx+68

00000000'05000068 fffff805'7ab2bfb0 CLFS! ClfsMgmtDeregisterManagedClient

E l'argomento sarà 0x48 byte dopo.

WINDBG>dps rcx+48

00000000'05000048 00000000'05000400

La funzione ClfsMgmtDeregisterManagedClient è comoda perché posso controllare l'argomento e ho anche due salti a funzioni controllate da me.

A screenshot of a computer Description automatically
generated

La prima chiamata è di nuovo a ClfsEarlierLsn che ha restituito RDX=0xFFFFFFFF.

A screenshot of a computer Description automatically generated with
medium confidence

A screen shot of a computer Description automatically generated with
medium confidence

prenderà la sorgente da scrivere dal contenuto di RDX=0xFFFFFFFF.

WINDBG>dps rdx

00000000'ffffffff ffff8005'3a4ee000

All'indirizzo 0xFFFFFFFF avevo memorizzato system_EPROCESS & 0xfffffffffffff000.

A picture containing text, font, line, number Description
automatically generated

La destinazione è il puntatore situato in 0x5000400 +0x48

*(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18;

A screenshot of a computer Description automatically
generated

Il puntatore PipeAttribute nel kernel che punta a un buffer riempito con "A" verrà sovrascritto con la parte alta del puntatore SYSTEM EPROCESS.

Questo puntatore è stato creato quando ho chiamato in precedenza _NtFsControlFile con un buffer pieno di “A” .

A screenshot of a computer program Description automatically generated
with medium confidence

Il contenuto di quell'attributo può essere letto usando NtFsControlFile.

A screenshot of a computer screen Description automatically generated
with medium confidence

Ora l'attributo pipe non punta più al buffer con “A” ma a system_EPROCESS & 0xffffffffffffff000.

A screenshot of a computer program Description automatically generated
with low confidence

Questo codice verrà ripetuto fino a quando non viene recuperato il token di sistema.

A screenshot of a computer code Description automatically generated
with medium confidence

A screenshot of a computer Description automatically
generated

Su Windows 11 il token di sistema si trova all'offset 0x4b8 della struttura EPROCESS letta di recente.

A screenshot of a computer Description automatically
generated

Devo solo scrivere quel token di sistema nel mio processo chiamando CreateLogFile.

A picture containing text, font, line, screenshot Description
automatically generated

Per fare questo lavoro, basta ripetere il passaggio usato per leggere il token di sistema.

A screenshot of a computer Description automatically generated with
medium confidence

Nella doppia chiamata, prima chiama ClfsEarlierLsn per restituire 0xFFFFFFFF in RDX e poi chiama nt_SeSetAccessStateGenericMapping.

A picture containing text, screenshot, font, line Description
automatically generated

Controllo che il valore puntato da RDX sia il Token di Sistema.

A picture containing text, screenshot, font Description automatically
generated

Il token del mio processo è:

Scriverà lì.

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'f601c06c

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'ef841919

Ora il mio processo è System posso eseguire un Blocco note per verificare.

A picture containing text, screenshot, font, line Description
automatically generated

A screenshot of a computer Description automatically
generated

A screenshot of a computer Description automatically
generated

7-La vera patch

BINDIFF mostra molte funzioni modificate

A screenshot of a computer Description automatically
generated

La funzione vulnerabile è qui:

A screenshot of a computer Description automatically generated with
medium confidence

A screenshot of a computer Description automatically
generated

La primaria è la versione patchata, la secondaria è la versione vulnerabile.

La patch testa il valore di ritorno di CflsEncodeBlock, che è 0xC01A000A, lo memorizza nella variabile var_54, e poiché è negativo, lo controlla ed evita la WriteSector.

La patch, oltre a non scrivere il file, la funzione restituisce correttamente 0xc01a000a, con cui CreateLogFile non restituisce alcun handle e lo sfruttamento non può continuare.

A screenshot of a computer Description automatically generated with
medium confidence

A screenshot of a computer Description automatically
generated

Solo se ClfsDecodeBlock non è negativo, va a WriteSector ma lascia restituire il valore negativo 0xC01A000A.

A screenshot of a computer Description automatically generated with
medium confidenceQuesta è la vera patch che impedisce lo sfruttamento utilizzando il PoC che ho appena allegato.

A questo punto abbiamo spiegato come è stato sfruttato il bug, porta a controllare le funzioni che ci permettono di leggere il token SYSTEM e scriverlo nel nostro processo per ottenere l'elevazione dei privilegi locali. Potete trovare il PoC funzionante su GitHub di Fortra.

Speriamo vi sia utile, se avete dubbi potete contattarci:

[email protected] 
@ricnar456

 [email protected]
@solidclt

Scarica lo strumento