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-2022-37969 — Exploit proof-of-concept per CVE-2022-37969, un'escalation di privilegi locale del driver Common Log File System di Windows. Dimostra heap spray, furto di token e scrittura arbitraria nel kernel per ottenere privilegi SYSTEM. | Kitploit
Strumenti/GitHubGitHub/fortra/cve-2022-37969
Escalation di PrivilegiMemory ForensicsAnalisi delle VulnerabilitàExploitBinary Exploitation
GitHubfortra/cve-2022-37969

CVE-2022-37969

Exploit proof-of-concept per CVE-2022-37969, un'escalation di privilegi locale del driver Common Log File System di Windows. Dimostra heap spray, furto di token e scrittura arbitraria nel kernel per ottenere privilegi SYSTEM.

Vedi Repository
1353813 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

CVE-2022-37969 PoC per l'elevazione dei privilegi locale di Windows

autori: Ricardo Narvaja & Daniel Kazimirow (Solid)

Solo a scopo dimostrativo. L'exploit completo funziona su sistemi Windows 11 21H2 vulnerabili.

PoC funzionante basato su informazioni pubblicate in precedenza da Zscaler

Consulta l'analisi Capire la vulnerabilità CVE-2022-37969 di elevazione dei privilegi locale nel driver Windows Common Log File System.

Utilizzo

Capire la vulnerabilità CVE-2022-37969 di elevazione dei privilegi locale nel driver Windows Common Log File System.

Procedura dettagliata dello sfruttamento:

  • Creazione del file di log BLF iniziale
    • Creazione di più file di log BLF casuali
    • Costruzione del file di log iniziale
    • Esecuzione di un Heap Spray controllato
    • Preparazione dei metodi CreatePipe() / NtFsControlFile()
    • Una volta preparata la memoria, verrà attivata la vulnerabilità
    • Lettura del System Token
    • Validazione del token
    • Sovrascrittura del token del nostro processo con quello di sistema
    • Esecuzione di un processo come sistema
    • Reverse engineering della patch: analisi delle strutture
    • Corruzione del puntatore "pContainer"
    • Riesame della patch
    • Corruzione di SignatureOffset
    • Corruzione di altri valori
    • Controllo delle funzioni che consentono di leggere il token SYSTEM
    • Scrittura del nostro processo per ottenere l'elevazione dei privilegi locale
    • Codice sorgente del PoC

Lo scenario utilizzato qui era Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918

Creazione del file di log BLF iniziale

Il primo passo è creare un file chiamato MyLog.blf nella cartella pubblica (%public%), usando la funzione CreateLogFile():

Creazione di più file di log BLF casuali

Quindi crea diversi file di log con nomi casuali usando un ciclo.

E all'interno del ciclo, chiama la nostra funzione getBigPoolInfo():

Chiama NtQuerySystemInformation() con 0x42 (66 decimale) come primo argomento; restituirà in v5 le informazioni sulle allocazioni fatte nel bigpool, la cui struttura è di tipo SYSTEM_BIGPOOL_INFORMATION.

Dobbiamo chiamare questa funzione due volte. La prima restituirà un errore, ma ci darà la dimensione corretta del buffer per chiamarla una seconda volta e ottenere le informazioni desiderate.

Interfaccia utente grafica, applicazione Descrizione generata automaticamente

v5 riceverà le informazioni della struttura SYSTEM_BIG_POOL_INFORMATION.

Il numero di allocazioni nel bigpool è memorizzato nel primo campo chiamato Count; nel secondo campo c'è un array di strutture SYSTEM_BIGPOOL_ENTRY.

Poi cercheremo in tutte le strutture il tag "Clfs" e la dimensione 0x7a00.

Memorizza in un array chiamato kernelAddrArray il VirtualAddress, che è il primo campo di ogni struttura che ha tag CLFS e dimensione 0x7a00. D'ora in poi, i pool che soddisfano entrambe le condizioni saranno chiamati: "pool giusti".

Oltre a memorizzare ogni pool giusto nell'array, memorizza l'ultimo pool giusto trovato nel contenuto della variabile a2, usata come argomento della funzione.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

In questo modo a2 punta sempre all'ultimo pool giusto creato con tag CLFS e dimensione 0x7a00.

La variabile v26 memorizza sempre il pool giusto precedente trovato, poiché è uguale a v24 (v26=v24) prima di chiamare getBigPoolinfo(), ma v24 viene aggiornata all'uscita dalla chiamata con l'ultimo pool giusto trovato, e v26 rimane con il pool giusto precedente trovato.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Poi sottrae i due indirizzi e, se il risultato è negativo, inverte gli operandi in modo che sia sempre positivo.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

In questo modo v32 memorizzerà la differenza tra i VirtualAddress degli ultimi due pool giusti trovati.

Poi fa qualcosa di simile; in questo caso v23 è inizialmente zero, quindi la prima volta assegna v23=v32.

La volta successiva nel ciclo, v23 ha ancora lo stesso valore e non è zero, quindi esce e arriva qui.

V32 ha l'ultima differenza e v23 quella precedente; se sono uguali, esce e incrementa di uno, ma azzera il contatore.

L'idea è trovare 6 confronti consecutivi di tag CLFS e dimensione 0x7a00 le cui differenze siano uguali, e quella differenza sarà 0x11000. Vedremo durante l'esecuzione che, quando trova 6 (poiché ricomincia da zero) consecutivi con distanze uguali, darà quel valore di differenza tra loro.

Testo Descrizione generata automaticamente

Lì vediamo che ha trovato 6 consecutivi ed è uscito dal ciclo di creazione dei file di log.

Nella cartella "public" possiamo vedere i file creati

Costruzione del file di log iniziale:

La nostra funzione craftFile() apre il file originale (MyLog.blf) e lo modifica per attivare il bug.

Dopo aver modificato il file, è necessario cambiare il CRC32, altrimenti otterremo un errore di file corrotto

Questo valore si trova all'offset 0x80C del file.

Esecuzione di un Heap Spray controllato

Successivamente, esegue un HeapSpray usando la funzione VirtualAlloc() per allocare memoria agli indirizzi arbitrari 0x10000 e 0x5000000 rispettivamente, e salvando nella seconda allocazione (0x10000), il valore 0x5000000, ogni 0x10 byte.

Preparazione dei metodi CreatePipe() / NtFsControlFile()

Usa CreatePipe() per creare una pipe anonima e chiama NtFsControlFile() usando 0x11003c come argomento per aggiungere un attributo; successivamente è possibile chiamare la stessa funzione con l'argomento 0x110038 per leggerlo.

Maggiori dettagli su questo metodo sono disponibili QUI

Lì vediamo il buffer di input, cioè l'attributo che stiamo aggiungendo; se chiamiamo di nuovo NtFsControlFile() con l'argomento 0x11038, in output dovrebbe restituire questo stesso attributo.

Cerca nel pool il tag dell'attributo creato (NpAt)

E quando lo trova, salva in v30.Pointer il VirtualAddress di questo pool.

V30.pointer+24 punta a AttributeValueSize nel pool del kernel e lo salva in uno degli HeapSpray che abbiamo effettuato prima.

L'idea è scrivere a quell'indirizzo del kernel+8, per sovrascrivere AttributeValue.

Testo Descrizione generata automaticamente

La struttura PipeAttribute ha come primo campo un LIST_ENTRY di 16 byte, poi un puntatore al nome dell'attributo di 8 byte e successivamente, all'offset 0x18 (24 decimale), il campo AttributeValueSize che è quello che stiamo memorizzando nell'HeapSpray.

Dopodiché, carichiamo CLFS.sys e ntoskrnl in usermode e, usando GetProcAddress(), troviamo gli indirizzi delle funzioni ClfsEarlierLsn() e SeSetAccessStateGenericMapping().

Quindi chiamiamo la funzione FindKernelModulesBase() che troverà la base del kernel di entrambi i moduli usando NtquerySystemInformation(), questa volta con l'argomento SystemModuleInformation per restituire le informazioni su tutti i moduli.

In questo modo possiamo calcolare l'offset di ogni funzione e poi ottenerli nel kernel.

Una volta preparata la memoria, verrà attivata la vulnerabilità:

La funzione pipeArbitraryWrite() viene chiamata due volte; c'è un flag che inizialmente è zero per la prima chiamata e quando nella seconda chiamata vale 1, cambierà i valori dell'HeapSpray.

Testo Descrizione generata automaticamente

Nella prima chiamata, all'indirizzo di memoria 0x5000000, si trovano i seguenti valori

Ricorda che questo valore, oltre ad essere allocato a quell'indirizzo, viene memorizzato nel nostro HeapSpray.

Ecco com'è la memoria dopo la prima chiamata, come abbiamo detto, all'indirizzo intorno a 0x5000000

E nell'HeapSpray a partire dalla memoria 0x10000 memorizzerà il puntatore a AttributeValueSize ogni 0x10 byte, oltre al puntatore a 0x5000000.

Lettura del System Token:

Questa sequenza attiverà il bug:

CreateLogFile() viene chiamata di nuovo sul file modificato e su un altro con un nome casuale.

AddLogContainer() viene quindi chiamata usando gli handle di quei file.

Viene chiamata NtSetinformationFile() e gli handle vengono chiusi, il che corrompe il puntatore (verrà spiegato più avanti)

Interfaccia utente grafica, applicazione Descrizione generata automaticamente con confidenza media

L'HeapSpray impedisce che a questo punto si verifichi un BSOD:

Impostando un breakpoint lì, possiamo vedere che il puntatore è corrotto e punta al nostro HeapSpray, con il quale possiamo controllare le prossime due chiamate di funzione della vtable.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Testo Descrizione generata automaticamente

RAX assume il valore 0x5000000 e salta prima alla funzione situata a 0x5000000+18 e poi a 0x5000000+8.

Testo Descrizione generata automaticamente con confidenza media

Immagine che contiene Interfaccia utente grafica Descrizione generata automaticamente

Quindi salta prima a fnClfsEarlierLsn() e poi a fnSeSetAccessStateGenericMapping().

Tracciamo dal breakpoint e vediamo che raggiunge CLFS!ClfsEarlierLsn().

Testo Descrizione generata automaticamente

Questa funzione viene chiamata esclusivamente perché quando restituisce, imposta EDX a 0xFFFFFFFF

Interfaccia utente grafica Descrizione generata automaticamente con confidenza media

All'indirizzo 0xFFFFFFFF avevamo memorizzato il risultato di SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Come accennato, quando si ritorna da CLFS!ClfsEarlierLsn(), il valore di RDX è 0x00000000FFFFFFFF

Testo Descrizione generata automaticamente

Arriviamo alla seconda funzione nt!SeSetAccessStateGenericMapping()

Interfaccia utente grafica, applicazione Descrizione generata automaticamente

Questa funzione è utile, poiché RCX punta al nostro HeapSpray e il valore di RDX è 0xFFFFFFFF, il cui contenuto controlliamo

Interfaccia utente grafica, applicazione, Teams Descrizione generata automaticamente

Testo Descrizione generata automaticamente

Il contenuto di RCX+0x48 contiene il puntatore a AttributeValueSize che era stato memorizzato in v30.Pointer+24

Interfaccia utente grafica, testo Descrizione generata automaticamente

Interfaccia utente grafica, testo Descrizione generata automaticamente con confidenza media

Interfaccia utente grafica, testo Descrizione generata automaticamente

Il valore di quel puntatore a AttributeValueSize viene spostato in RAX; poi legge il contenuto dell'indirizzo 0xFFFFFFFF dove avevamo memorizzato l'indirizzo di SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Quindi sovrascrive in RAX+8 il campo successivo, che è AttributeValue()

Testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente

Naturalmente, AttributeValue normalmente punterebbe nel kernel all'attributo che abbiamo aggiunto.

Calendario Descrizione generata automaticamente

E ora lo sovrascriveremo con un puntatore al risultato di system _EPROCESS & 0xFFFFFFFFFFFFFFF00.

Ciò significherà che quando chiameremo di nuovo la funzione NtFsControlFile(), questa volta con l'argomento 0x110038 per leggere l'attributo, invece di restituire le "A" a cui puntava il puntatore AttributeValue, leggerà da _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 il numero di byte richiesti e li restituirà nel buffer di output, con cui possiamo ottenere alla prima chiamata il valore del SYSTEM TOKEN.

Testo Descrizione generata automaticamente

v9b è l'indirizzo iniziale del Output Buffer dove è stato copiato il contenuto del risultato di System EPROCESS & 0xFFFFFFFFFFFFFFF000.

A questo aggiunge v14, che sono gli ultimi 3 byte di System EPROCESS, e poi aggiunge 0x4b8, che è l'offset di Token per questa versione di Windows 11; quindi trova il contenuto di quell'indirizzo, che avrà salvato il valore del System Token.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Testo Descrizione generata automaticamente

Validazione del token

Testo Descrizione generata automaticamente con confidenza bassa

Ricorda che gli ultimi 4 bit sono stati modificati, non è significativo, quindi il valore corrisponde ancora.

Sovrascrittura del token del nostro processo con quello di sistema

Nella seconda chiamata il valore di Flag è 1, poiché è stato incrementato alla fine della prima chiamata.

Interfaccia utente grafica, testo, applicazione, email Descrizione generata automaticamente

Lì vediamo l'ordine in cui vengono memorizzati i valori

Interfaccia utente grafica, testo Descrizione generata automaticamente

All'indirizzo 0xFFFFFFFF con il valore del System Process Token che abbiamo appena trovato.

Interfaccia utente grafica, testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente

E nell'HeapSpray c'è il valore dell'indirizzo del Token del mio processo a cui sottraggo 8. Questo valore più otto verrà usato come destinazione; ricorda che hai scritto all'indirizzo puntato da RAX+8.

Interfaccia utente grafica, testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente

All'indirizzo di memoria che inizia a 0x5000000

Interfaccia utente grafica Descrizione generata automaticamente

Vediamo anche che usa il nome di un altro container, poiché quello precedente è in uso dal processo di sistema e non può essere aperto di nuovo o eliminato.

Poi il bug viene attivato per la seconda volta nello stesso modo del primo tentativo.

Si arriva di nuovo a CLFS!ClfsEarlierLsn().

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

impostando RDX a 0xFFFFFFFF

Uno screenshot di un cellulare Descrizione generata automaticamente

Poi si arriva a nt!SeSetAccessStateGenericMapping()

Testo Descrizione generata automaticamente

Legge l'indirizzo del Token del mio processo meno 8, dove andrà a scrivere

Testo Descrizione generata automaticamente

Poi legge il SYSTEM TOKEN

Testo Descrizione generata automaticamente

E scrive all'indirizzo del Token del mio processo (aggiunge 8) il System Token

E in questo modo il mio processo ha il System Token

Testo Descrizione generata automaticamente

Una volta scritto il token, avviamo un processo per verificare i privilegi; in questo caso, lanciamo Notepad.exe

Esecuzione di un processo come sistema:

Testo Descrizione generata automaticamente

Interfaccia utente grafica, applicazione, Word Descrizione generata automaticamente

Testo Descrizione generata automaticamente con confidenza bassa

Ricorda che questo POC funziona solo su Windows 11; su Windows 10 produrrà un BSOD, quindi dovresti apportare alcune modifiche per farlo funzionare correttamente; non viene spiegato in questo post del blog.

Reverse engineering della patch:

Analisi delle strutture

Le strutture e la maggior parte della documentazione sul formato dei file CLFS sono state prese dall'eccellente lavoro di IONESCU su CLFS Internals.

Possiamo vedere che è stato aggiunto un controllo nella funzione ClfsBaseFilePersisted::LoadContainerQ

Testo Descrizione generata automaticamente con confidenza media

I valori che eseguono un'addizione appartengono alla struttura _CLFS_BASE_RECORD_HEADER.

Sequenza temporale Descrizione generata automaticamente con confidenza media

Nota che il Base Block inizia all'offset 0x800 del file e termina all'offset 0x71FF; i primi 0x70 byte corrispondono al Log Block Header.

Come buona pratica, possiamo aggiungere la struttura _CLF_LOG_BLOCK_HEADER su IDA

struct _CLFS_LOG_BLOCK_HEADER

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

Poi abbiamo l'Header Base Record (_CLFS_BASE_RECORD_HEADER) che inizia all'offset 0x870 dall'inizio del file ed è lungo 0x1338 byte.

Interfaccia utente grafica, Testo, Applicazione, Email Descrizione generata automaticamente

Se vuoi importarlo in IDA, prima devi aggiungere i seguenti tipi e le strutture mancanti

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

Ora è pronto per essere aggiunto:

typedef struct _CLFS_BASE_RECORD_HEADER

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

Interfaccia utente grafica, Testo Descrizione generata automaticamente

Dopo aver incluso le strutture, notiamo che esegue un'addizione tra cbSymbolZone e l'indirizzo in cui termina _CLFS_BASE_RECORD_HEADER. (start + 1338h)Testo, Applicazione Descrizione generata automaticamente

Ricorda che cbSymbolZone è stato modificato nel file di log creato ad hoc da 0x000000F8 a 0x0001114B.

(offset 0x1b98 del file)

0x800(offset dell'inizio del Base Block) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = 0x1b98

File MyLog.blf con cbsymbolZone modificato:

Tabella Descrizione generata automaticamente

Immagine contenente Testo Descrizione generata automaticamente

Poiché la patch si trova nella funzione CClfsBaseFilePersisted::LoadContainerQ, dobbiamo dare un'occhiata all'oggetto CClfsBaseFilePersisted.

Impostando un breakpoint su CLFS!CClfsBaseFilePersisted::LoadContainerQ, quando CreateLogFile viene chiamato con l'handle del file creato ad hoc, si interromperà.

Interfaccia utente grafica, Testo, Applicazione Descrizione generata automaticamente

Chiamiamo la funzione CClfsBaseFile::GetBaseLogRecord per ottenere l'indirizzo del Base Log Record (_CLFS_BASE_RECORD_HEADER)

Interfaccia utente grafica, applicazione, sequenza temporale Descrizione generata automaticamente

RAX punterà all'indirizzo di _CLFS_BASE_RECORD_HEADER

Interfaccia utente grafica, Testo, Applicazione, Email Descrizione generata automaticamente

Nota la struttura _CLFS_BASE_RECORD_HEADER in memoria e il campo cbsymbolZone a 0x1328 byte più avanti

Immagine contenente Testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente

r14 contiene la struttura corrispondente a "this", che è CClfsBaseFilePersisted poiché è il this della funzione CClfsBaseFilePersisted::LoadContainerQ.

Interfaccia utente grafica, Applicazione Descrizione generata automaticamente

La struttura CClfsBaseFilePersisted in memoria:

Testo Descrizione generata automaticamente

Quindi, creiamo una struttura di lunghezza 0x21c0 per completare i suoi campi mentre ne facciamo reverse (è una struttura non documentata) e la chiameremo struct_CClfsBaseFilePersisted

Tabella Descrizione generata automaticamente con confidenza media

All'interno della funzione CClfsBaseFile::GetBaseLogRecord() si ottiene il puntatore a _CLFS_BASE_RECORD_HEADER e sappiamo che il "this" in quella funzione è la struttura: struct_CClfsBaseFilePersisted.

Sequenza temporale Descrizione generata automaticamente con confidenza media

Legge due campi (offset 0x28 e 0x30)

Interfaccia utente grafica, Applicazione, Tabella Descrizione generata automaticamente

Il campo 0x28 è una word e ha valore 6, quindi cambiamo il tipo in word nella struttura.

Testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente con confidenza media

Interfaccia utente grafica, Testo, Applicazione Descrizione generata automaticamente

Per ora, lo rinominiamo in costante 6 (const_6)

Testo Descrizione generata automaticamente

Interfaccia utente grafica, Testo, Applicazione Descrizione generata automaticamente

Secondo la documentazione, 6 sarebbe il numero di blocchi CLFS_METADATA_BLOCK_COUNT. Il campo potrebbe riferirsi a questo valore.

E quel puntatore si trova all'offset 0x30.

Tabella Descrizione generata automaticamente

Nota che la dimensione mostrata lì include l'header con lunghezza 0x10

Testo Descrizione generata automaticamente

Forma Descrizione generata automaticamente con confidenza media

Quando viene chiamata la funzione ExAllocatePoolWithTag, vengono richiesti alcuni byte, ma l'header non è incluso, quindi nella chiamata verranno richiesti 0x90 byte (0xa0 – 0x10).

Cercando il testo +30h], le istruzioni che scrivono all'offset 0x30, abbiamo trovato una lunga lista, ma filtrando la lista per il tipo di oggetto CClfsBaseFilePersisted rimangono pochi risultati e troviamo immediatamente dove viene allocata quella dimensione, e con lo stesso tag. (Suggerimento: i nomi di funzioni Create e Initialize sono sempre i primi da guardare)

Testo Descrizione generata automaticamente con confidenza bassa

Interfaccia utente grafica, Testo, Applicazione Descrizione generata automaticamente

Poiché non conosciamo ancora il nome, lo chiameremo pool_0x90, che è un'altra struttura non documentata, e creeremo una struttura di quella dimensione.

Testo Descrizione generata automaticamente

Interfaccia utente grafica, Tabella Descrizione generata automaticamente

Il pool_0x90 in memoria ha un altro puntatore al proprio offset 0x30.

Applicazione, Tabella Descrizione generata automaticamente con confidenza media

Quest'altro puntatore punta al blocco base nel file (il Base block inizia all'offset 0x800)

Forma Descrizione generata automaticamente

Immagine contenente calendario Descrizione generata automaticamente

Immagine tratta dal post del blog di Zscaler:

Interfaccia utente grafica, applicazione, email Descrizione generata automaticamente

L'allocazione è enorme, perché contiene l'intero blocco base.

Testo Descrizione generata automaticamente

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Quindi, creeremo una nuova struttura di dimensione 0x7a00 e la chiameremo BASE_BLOCK

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

I primi 70 byte, come già sappiamo, corrispondono a _CLFS_LOG_BLOCK_HEADER e i successivi 0x1338 a _CLFS_BASE_RECORD_HEADER.

Testo Descrizione generata automaticamente

Quindi, aggiungendo l'inizio del Base Block con l'offset al record successivo (che è 0x70), otteniamo _CLFS_BASE_RECORD_HEADER

Applicazione Descrizione generata automaticamente con confidenza bassa

Il _CLFS_BASE_RECORD_HEADER in memoria.

Calendario Descrizione generata automaticamente

Osservando altri metodi dello stesso oggetto CClfsBaseFilePersisted, in CClfsBaseFilePersisted::AddContainer si ottiene con CClfsBaseFile::GetBaseLogRecord anche l'indirizzo di _CLFS_BASE_RECORD_HEADER.

Testo Descrizione generata automaticamente

Successivamente, chiama CClfsBaseFile::OffsetToAddr usando cbOffset, ottiene l'indirizzo di _CLFS_CONTAINER_CONTEXT e memorizza cboffset nell'array rgbcontainers che si trova all'offset 0x328 di _CLFS_BASE_RECORD_HEADER.

Interfaccia utente grafica, applicazione Descrizione generata automaticamente

La funzione CClfsBaseFile::OffsetToAddr viene utilizzata per trovare gli indirizzi delle strutture a partire dall'offset

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

A questo punto, l'offset del container che verrà memorizzato a 0x328 è ancora 0, perché non abbiamo ancora aggiunto un container.

Motivo di sfondo Descrizione generata automaticamente con confidenza bassa

il PoC chiama CreateLogFile due volte, la prima con il file malformato MyLog.blf e la seconda con il file normale MyLogxxx.blf, quindi dobbiamo interrompere il debug due volte in tutti i punti precedenti e annotare su un blocco note gli indirizzi delle strutture sopra per entrambi i file.

Testo Descrizione generata automaticamente

Andiamo un po' avanti fino a CLFS!CClfsLogFcbPhysical::AllocContainer impostando un breakpoint su di esso ed eseguendo fino a lì.

Quando AddLogContainer() viene raggiunto nel POC, ci fermiamo al breakpoint.

Immagine contenente applicazione Descrizione generata automaticamente

Impostiamo anche un breakpoint su CClfsBaseFilePersisted::AddContainer+176, dove abbiamo visto prima che troverà l'offset e il puntatore alla struttura _CLFS_CONTAINER_CONTEXT.

Screenshot di un computer Descrizione generata automaticamente con confidenza media

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Quando il debugger si interrompe, possiamo vedere che l'offset è 0x1468.

Testo Descrizione generata automaticamente

In RAX verrà restituito l'indirizzo della struttura _CLFS_CONTAINER_CONTEXT.

Interfaccia utente grafica, testo, applicazione, tabella Descrizione generata automaticamente

la struttura è ancora vuota perché il container non è stato ancora aggiunto.

Testo Descrizione generata automaticamente

Nota che il valore SignatureOffset=0x50 che abbiamo scritto all'offset 0x868 nel file malformato, sottraendo 0x800 dall'inizio del blocco base, si troverà nella struttura _CLFS_LOG_BLOCK_HEADER all'offset 0x68.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Testo, lettera Descrizione generata automaticamente

Quando il PoC chiama la funzione AddLogContainer() usando il file malformato, all'offset 0x68 di _CLFS_LOG_BLOCK_HEADER, invece del valore 0x50 che abbiamo scritto lì, in memoria c'è attualmente un 0xFFFF0050.

Immagine contenente testo Descrizione generata automaticamente

A un certo punto, quel valore è stato alterato dal programma; per vedere quando è successo, nella prossima esecuzione imposteremo un breakpoint di memoria in scrittura.

L'offset è memorizzato a r15 + 0x328 (r15 punta alla struttura _CLFS_BASE_RECORD_HEADER)

Testo Descrizione generata automaticamente con confidenza media

Interfaccia utente grafica Descrizione generata automaticamente con confidenza bassa

RBX memorizza l'offset 0x1468.

Immagine contenente calendario Descrizione generata automaticamente

Quindi, all'indirizzo del Base Block + 0x70 + l'offset 0x1468 che abbiamo trovato, ci sarà l'indirizzo del container CLFS_CONTAINER_CONTEXT.

Testo Descrizione generata automaticamente

Nella struttura CLFS_CONTAINER_CONTEXT all'offset 0x18 ci sarà il puntatore pContainer che verrà memorizzato lì; possiamo impostare un breakpoint di memoria in scrittura e vedere quando viene scritto.

Testo Descrizione generata automaticamente

Immagine contenente testo Descrizione generata automaticamente

Questo è il puntatore che dobbiamo corrompere, poiché nella funzione in cui si trova la vulnerabilità, prima legge CLFS_CONTAINER_CONTEXT, poi lo sposta in r15 e successivamente legge il valore di r15+18, che è questo puntatore su cui abbiamo appena impostato il breakpoint in scrittura.

Interfaccia utente grafica, applicazione, tabella Descrizione generata automaticamente

Interfaccia utente grafica, applicazione, Word Descrizione generata automaticamente

memorizza il pContainer all'offset 0x1c0 della struttura struct_CClfsBaseFilePersisted.

Testo, applicazione, lavagna Descrizione generata automaticamente

Dopo diverse interruzioni, raggiungiamo il momento in cui viene corrotto. La parte alta dell'indirizzo del puntatore è stata cambiata da FF a zero.

Calendario Descrizione generata automaticamente

Questo accade quando viene chiamata la seconda AddLogContainer() del file malformato: il puntatore del precedente MyLogxxx viene corrotto.

Il problema si verifica perché SignaturesOffset, che dovrebbe essere 0x50, ora è 0xFFFF0050, quindi consente di scrivere fuori dai limiti nella memset che segue.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Interfaccia utente grafica, testo Descrizione generata automaticamente

Corrompere il puntatore "pContainer":

La funzione memset() corromperà la struttura _CLFS_CONTAINER_CONTEXT che si trova subito sotto; questa struttura corrisponde al file MyLogxxx, poiché quando sono stati creati, li ha posizionati a 0x11000 byte di distanza l'uno dall'altro.

In questo modo calcola esattamente dove scrivere nella struttura successiva e azzera la parte alta del puntatore, così punta all'heap utente dove è stato creato lo HeapSpray.

la struttura del blocco base del file malformato è solo 0x11000 prima di quella del file MyLogxxx.

Malformato:

Forma Descrizione generata automaticamente

MyLogxxx

Immagine contenente testo Descrizione generata automaticamente

RCX è minore di RDX perché è stato aggiunto 0xFFFF0050 invece di 0x50 come dovrebbe essere.

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

e arriviamo alla funzione memset(), per impostare la quantità di 0xb0 byte a zero, con RCX che punta alla struttura CLFS_CONTAINER_CONTEXT del file MyLogxxx, specificamente ai cinque byte alti di pContainer.

Testo Descrizione generata automaticamente

Questo puntatore verrà corrotto sovrascrivendo i primi byte:

Testo Descrizione generata automaticamente

rimanendo puntato a un indirizzo di memoria precedentemente controllato da noi tramite HeapSpray

Testo Descrizione generata automaticamente

Immagine contenente testo Descrizione generata automaticamente

Quindi, l'handle del file MyLogxxx verrà chiuso e si arriva a CClfsBaseFilePersisted::RemoveContainer: la vulnerabilità viene finalmente innescata.

Testo, applicazione Descrizione generata automaticamente con confidenza media

Rivisitando la patch

Ora che abbiamo più informazioni, notiamo che qui legge Base_Block.LOG_BLOCK_HEADER.SignaturesOffset e il Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount

Nella prima parte della patch, SignaturesOffset non dovrebbe essere maggiore di 0x7a00; nel nostro caso era originariamente 0x50; se arrivasse con un valore maggiore di 0x7a00 ci farebbe uscire.

Immagine contenente diagramma Descrizione generata automaticamente

Eseguendo il PoC sulla macchina patchata, confronta 0x50 con 0x7a00 e, poiché è minore, continua.

Testo Descrizione generata automaticamente

Nel blocco successivo, il cbSymbolZone malformato viene aggiunto al valore dell'indirizzo finale di _CLFS_BASE_RECORD_HEADER e questa somma viene memorizzata in result_1.

Testo Descrizione generata automaticamente

Quindi, l'indirizzo del Base_Block viene sommato al valore SignatureOffset, che in un file normale è 0x7980.

Testo Descrizione generata automaticamente

L'indirizzo massimo del base_block è 0x7a00; ora la SymbolZone è consentita fino a 0x80 prima del limite.

Lo memorizzerà in result_2, cioè quello sarebbe il limite massimo per la SymbolZone all'interno del base block, poi confronta entrambi i risultati: se il primo è maggiore del secondo, significa che è uscito dai limiti.

Testo Descrizione generata automaticamente

Testo Descrizione generata automaticamente

Ovviamente il primo membro sarà più grande del secondo e non continuerà, poiché la prima somma di cbSymbolZone + indirizzo finale di _CLFS_BASE_RECORD_HEADER supera il limite (che è result_2) e porta a un "fuori dai limiti".

Interfaccia utente grafica, testo, applicazione Descrizione generata automaticamente

Corrompere il SignatureOffset

L'ultima cosa che dovremmo capire è dove il valore SignatureOffset di 0x50 diventa 0xFFFF0050.Quindi, ricominciamo, riavviamo e fermiamoci in CLFS!CClfsBaseFilePersisted::LoadContainerQ dove il valore non è ancora stato modificato in memoria ed è ancora 0x50.

Imposta un breakpoint di accesso all'offset 0x68 in SignatureOffset.

Descrizione calendario generata automaticamente

E dopo diverse fermate, rileviamo il momento giusto in cui modifica il valore, in ClfsEncodeBlockPrivate.

Interfaccia utente grafica, testo generato automaticamente

Questa funzione non è patchata, quindi potrebbe essere un comportamento causato dal valore basso di 0x50 e dal resto dei valori manipolati.

Tra i valori manipolati, possiamo vedere il valore ccoffsetArray il cui nome nella struttura _CLFS_BASE_RECORD_HEADER è rgClients e rappresenta l'array di offset che puntano all'oggetto Client Context.

Il campo rgClients si trova all'offset 0x138 (0x9a8-0x800-0x70) della struttura _CLFS_BASE_RECORD_HEADER.

Interfaccia utente grafica, tabella generata automaticamente

Testo generato automaticamente con media confidenza

Nella PoC, questo valore è malformato per puntare a un falso oggetto client context, chiamato FakeClientContextTesto, lavagna generati automaticamente

Screenshot di un computer generato automaticamente con media confidenza

Questa è la struttura Client Context _CLFS_CLIENT_CONTEXT

struct _CLFS_CLIENT_CONTEXT

{

CLFS_NODE_ID cidNode;

CLFS_CLIENT_ID cidClient;

USHORT fAttributes;

ULONG cbFlushThreshold;

ULONG cShadowSectors;

ULONGLONG cbUndoCommitment;

LARGE_INTEGER llCreateTime;

LARGE_INTEGER llAccessTime;

LARGE_INTEGER llWriteTime;

CLFS_LSN lsnOwnerPage;

CLFS_LSN lsnArchiveTail;

CLFS_LSN lsnBase;

CLFS_LSN lsnLast;

CLFS_LSN lsnRestart;

CLFS_LSN lsnPhysicalBase;

CLFS_LSN lsnUnused1;

CLFS_LSN lsnUnused2;

CLFS_LOG_STATE eState;

union

{

HANDLE hSecurityContext;

ULONGLONG ullAlignment;

};

};

Il valore eState si trova all'offset 0x78 dall'inizio della struttura, nel file manipolato 0x23a0+0x78.

Testo generato automaticamente con media confidenza

Grafico, grafico a dispersione generato automaticamente

Questo valore mostra lo stato del log.

typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

questo valore è impostato a CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20

L'altro valore malformato è fAttributes che corrisponde all'insieme dei flag FILE_ATTRIBUTE associati al file di log di base (come System e Hidden).

Interfaccia utente grafica generata automaticamente con bassa confidenza

Testo, lettera generati automaticamente

Poiché il campo inizia un byte prima, a 0xa, e si estende per due byte, il valore di fAttributes è 0x100.

Un'immagine contenente una tabella generata automaticamente

Interfaccia utente grafica, testo, applicazione generati automaticamente

Infine, c'è il valore blocknameoffset che punta all'offset 0x1bb8, cioè, aggiungendo 0x78 e 0x800, punta all'offset 0x2428 del file.

Testo generato automaticamente

Testo, lettera generati automaticamente

Nota che l'offset del Client Context è 0x1b30

Tabella generata automaticamente

Quindi, il Client Context si trova all'offset 0x23a0.

Testo generato automaticamente con media confidenza

Tabella generata automaticamente

E appena 0x10 prima, c'è il valore corrispondente a blocknameoffset.

Testo, lettera generati automaticamente

Tabella generata automaticamente con bassa confidenza

Che punterebbe alla stringa con il nome

l'ultimo è il blockattributeoffset che si trova 0xC prima del Client Context, a 0x2394.

Tabella generata automaticamente

Questi ultimi due valori appartengono a una struttura precedente al Client Context, lunga 0x30 byte, chiamata**_CLFSHASHSYM**

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

Un'immagine contenente un diagramma generata automaticamente

Testo generato automaticamente

si trovano a 0x20 e 0x24 byte dall'inizio della struttura _CLFSHASHSYM, quindi nella struttura _CLFSHASHSYM il valore chiamato blockNameOffset nella POC è il campo cbSymName e il blockAttributteoffset è il campo cbOffset.

Un'immagine contenente testo generata automaticamente

Testo generato automaticamente con media confidenza

Questi sono i valori malformati, ora dobbiamo vedere come influiscono sul cambiamento del nostro SignaturesOffset dal valore 0x50 a 0xFFFF0050.

Diamo un'occhiata alla funzione CClfsBaseFile::AcquireClientContext(), che dovrebbe restituire il client context.

Interfaccia utente grafica, testo, applicazione, email generati automaticamente

chiama la CClfsBaseFile::GetSymbol con il quarto argomento che sarà _CLFS_CLIENT_CONTEXT ** dove memorizzerà il puntatore al Client Context.

Interfaccia utente grafica, testo, applicazione generati automaticamente

Dentro la funzione CClfsBaseFile::GetSymbol passiamo l'offset malformato ccoffsetArray a CClfsBaseFile::OffsetToAddr e otteniamo l'indirizzo del client context; impostiamo un breakpoint lì così si fermerà quando chiama il file creato con CreatelogFile.

Interfaccia utente grafica, applicazione generata automaticamente con media confidenza

Si ferma lì con l'argomento manipolato ccoffsetArray.

Interfaccia utente grafica, applicazione generata automaticamente

Tabella generata automaticamente

La funzione CClfsBaseFile::OffsetToAddr restituisce il falso Client Context

Un'immagine contenente un'interfaccia utente grafica generata automaticamente

E controlla che il valore di cbOffset non sia zero, dato che 0xC si trova prima della struttura _CLFS_CLIENT_CONTEXT che è in RAX.

Screenshot di un computer generato automaticamente

Un'immagine contenente testo generata automaticamente

Poi confronta cbOffset con **ccoffsetArray (**che è in RSI): devono essere uguali, altrimenti otterremo un errore.

Screenshot di un computer generato automaticamente

Controlla anche che cbSymName sia uguale a cbOffset+0x88, altrimenti otterremo un errore anche in quel caso.

Interfaccia utente grafica, testo, applicazione generata automaticamente con media confidenza

E infine confronta il byte cidClient con zero

Un'immagine contenente un diagramma generata automaticamente

Se tutti questi controlli hanno successo, il client context verrà salvato.

Screenshot di un computer generato automaticamente

L'output della funzione r14 punta al Client Context

Interfaccia utente grafica, testo, applicazione generati automaticamente

Quando si esce da CClfsLogFcbPhysical::Initialize avremo l'indirizzo di CLFS_CLIENT_CONTEXT.

Testo generato automaticamente

Ora legge il valore di fAttributes (0x100)

Interfaccia utente grafica, testo, applicazione generati automaticamente

questa funzione appartiene alla classe CClfsLogFcbPhysical

Testo generato automaticamente

Interfaccia utente grafica, testo, applicazione generati automaticamente

Che è stata allocata qui, la sua dimensione è 0x15d0 e il suo tag è “ClfC”

Testo generato automaticamente con media confidenza

Creiamo una struttura per memorizzare ciò che stiamo reversando, la chiameremo: struct_CClfsLogFcbPhysical.

Un'immagine contenente una tabella generata automaticamente

Nota che a 0x2b0 salva l'indirizzo della struttura CClfsBaseFilePersisted.

Un'immagine contenente un'applicazione generata automaticamente

Dopo aver salvato molti valori nella struttura, arriva a una parte importante: testa eState con 0x20.

Interfaccia utente grafica, testo, applicazione generati automaticamente

Interfaccia utente grafica, testo, applicazione, tabella generati automaticamente

Poiché il valore manipolato era 0x20, il test restituirà 1.

Tabella generata automaticamente

Interfaccia utente grafica, testo, applicazione generati automaticamente

Vediamo che nel costruttore, nella vtable, c'è

Testo generato automaticamente

Verificherà se il file è multiplexed.

Interfaccia utente grafica, applicazione generata automaticamente

Quindi, segue il percorso desiderato, arrivando a CClfsLogFcbPhysical::ResetLog.

Interfaccia utente grafica, applicazione generata automaticamente

Testo, applicazione, tabella generati automaticamente con media confidenza

Diversi campi vengono inizializzati a zero, tranne uno che viene inizializzato a 0xFFFFFFFF00000000.

Interfaccia utente grafica generata automaticamente con bassa confidenza

Qui recupera il Client Context

Interfaccia utente grafica, applicazione generata automaticamente con media confidenza

memorizza il valore 0xFFFFFFFF00000000.

Interfaccia utente grafica, applicazione generata automaticamente

Interfaccia utente grafica, applicazione generata automaticamente

Un'immagine contenente un calendario generata automaticamente

Scrive 0xFFFFFFFF all'offset 0x5c, che è la parte alta di CLFS_LSN lsnRestart.ullOffset

Testo generato automaticamente con media confidenza

Interfaccia utente grafica, testo, lettera generati automaticamente

Ora eseguiamo la funzione ClfsEncodeBlockPrivate(), che è quella responsabile di sovrascrivere lo 0x50 con 0xFFFF0050 come abbiamo visto prima.

Lì legge il valore di SignatureOffset = 0x50, che è ancora come lo abbiamo messo nel file malformato, e lo aggiunge all'inizio di CLFS_LOG_BLOCK_HEADER.

Testo generato automaticamente

questo è un ciclo che scrive 2 byte; dato che SignatureOffset, invece di puntare a un valore corretto che in un file normale è un valore alto, per esempio 0x3f8, che fa scrivere più avanti, qui scriverà nello stesso CLFS_LOG_BLOCK_HEADER

L'idea è di cambiare la destinazione di scrittura per cercare di corrompere il valore di SignatureOffset.

File Normale

Tabella generata automaticamente

A questo punto, inizierà a iterare e a scrivere due byte.

Interfaccia utente grafica, applicazione generata automaticamente

Il contatore deve raggiungere il valore 0x3d per uscire dal ciclo.

Interfaccia utente grafica, applicazione generata automaticamente

RCX sta aumentando da 0x200, siamo già al terzo ciclo e il suo valore è 0x600

Interfaccia utente grafica, testo, applicazione generati automaticamente

nell'iterazione 0xe, RCX è 0x1a00

Interfaccia utente grafica, testo generati automaticamente

Un'immagine contenente un calendario generata automaticamente

Quello era il punto in cui aveva scritto il 0xFFFFFFFF000000.

Interfaccia utente grafica, testo, applicazione generati automaticamente

Tabella generata automaticamente con media confidenza

Sta leggendo gli ultimi due byte FFFF

Testo generato automaticamente

E poi li copierà in R8

Un'immagine contenente un calendario generata automaticamente

Un'immagine contenente un calendario generata automaticamente

Come abbiamo visto, questo valore è critico perché permette di bypassare il controllo e scrivere fuori dai limiti per corrompere il puntatore pContainer del file che segue la memset(), scrivere zeri nella parte alta e lasciarlo puntare alla nostra memoria controllata (HeapSpray).

Nella CClfsBaseFilePersisted::AllocSymbol, la stessa somma che andrà a ottenere la destinazione della memset, che è cbSymbolZone + indirizzo finale di CLFS_BASE_RECORD_HEADER, la confronta prima con Base_block + 0xFFFF0050, quindi ha valori corrotti su entrambi i lati dell'equazione.

CbSymbolZone= 0x1114B

È il valore malformato che, aggiunto all'indirizzo finale di CLFS_BASE_RECORD_HEADER, farà scrivere fuori dai limiti; l'altro membro del confronto, che dovrebbe essere l'indirizzo del Base Block + SignatureOffset, rimane SignatureOffset =0xFFFF0050, il che permette a questo controllo di essere superato e di scrivere fuori dai limiti nella memset(), azzerando la parte alta del puntatore che rimarrà puntato al nostro HeapSpray.

Interfaccia utente grafica, applicazione, tabella generati automaticamente

Poiché RCX è minore di RDX.

Interfaccia utente grafica, applicazione generata automaticamente

Come abbiamo visto prima. (I valori possono differire perché appartengono a un'esecuzione precedente)

Corromperà il puntatore, impostando i byte più alti a 0

Tabella generata automaticamente

Lasciandolo puntare a un'area di memoria che controlliamo tramite HeapSpray

Screenshot di un computer generato automaticamente con bassa confidenza

Tabella generata automaticamente

Quindi, quando la vulnerabilità viene attivata, arriviamo a CClfsBaseFilePersisted::RemoveContainer

Un'immagine contenente un'interfaccia utente grafica generata automaticamente

Lì ci sarà il puntatore già corrotto e potrà essere sfruttato come abbiamo visto in precedenza.

Interfaccia utente grafica, applicazione generata automaticamente

A questo punto abbiamo sfruttato il bug; questo porta a controllare le funzioni che permettono di leggere il token di SYSTEM e scrivere nel nostro processo per ottenere l'escalation dei privilegi locale.

Speriamo che lo troviate utile; se avete dubbi potete contattarci a [email protected] e [email protected]

Buon divertimento!

Scarica lo strumento