
Tutorial su CVE-2022-37969 incentrato sulla metodologia di sfruttamento del Kernel, non sulle cause interne della CVE.
Questo documento è stato creato per chiarire aspetti generali riguardanti lo sfruttamento di Windows. Spiega concetti di base applicati alla CVE-2022-37969. Il risultato finale è un PoC funzionante. Non chiarisce ogni aspetto della CVE, ma fornisce pezzi di codice riutilizzabili e spiega meccanismi che si possono trovare in molti exploit generali.
L'utente target è un reverse engineer principiante, uno sviluppatore di exploit che cerca un codice sorgente di proof of concept funzionante per testare e comprendere le nozioni fondamentali sui Windows Internals. Fornisce un punto di riferimento per approfondire.
Requisiti: conoscenze base di kernel debugging, Reverse Engineering di base, nozioni fondamentali sui Windows internals, capacità di programmazione in c/c++
Un programma è un pezzo di codice che gira su una macchina. Generalmente un programma riceve dati (input), esegue calcoli usando gli input e genera dati (output). La maggior parte dei programmi è scritta da esseri umani e quindi contiene bug. Un bug è generato da un codice sorgente che non è stato scritto correttamente (il programmatore voleva fare qualcosa con l'input, ma il codice risultante era diverso dall'intento originale). La maggior parte dei bug viene corretta prima del rilascio del prodotto, ma alcuni rimangono. Questo accade perché esistono diversi tipi di bug, alcuni più difficili da individuare di altri.
Windows è un programma per computer, è stato scritto da esseri umani e quindi contiene bug. Perché è importante? Perché i sistemi Windows possono eseguire programmi che trattano dati sensibili, come conti bancari, database sanitari e altri. Alcuni bug possono essere usati per ottenere illegalmente l'accesso a dati riservati (questo è un buon caso d'uso per un exploit).
Esistono diversi tipi di bug, alcuni utili e altri no. Generalmente i bug sono generati dagli input del programma che, in combinazione con le righe di codice scritto male, generano un output o un comportamento malformato del programma. Trovare quell'input è il lavoro dello specialista di sicurezza (o hacker). Il passo successivo è valutare il comportamento/output malformato risultante e rispondere alla domanda: "Può essere usato in modo utile?". È qui che i bug vengono classificati in diverse categorie. Per esempio, un bug può generare un comportamento che corrompe alcune strutture dati e causa il riavvio del computer target. La sua utilità è limitata. Un bug può far sì che l'input venga scritto in una zona di memoria che controlla i permessi di accesso a file riservati. Questo tipo di bug è più utile.
Quindi, dall'insieme di tutti i bug possibili, l'hacker cerca il sottoinsieme più utile al proprio scopo. In generale, il problema è: "Posso dare al programma target un input appositamente creato in modo da non rompere il sistema ma da poter elevare il mio livello di accesso e trarne vantaggio?"
Dopo questa introduzione non tecnica, l'obiettivo del tutorial può essere formulato così: Possiamo trovare un programma Windows che accetta input malformati e che, a causa di un errore del codice degli sviluppatori, possa elevare illegalmente i nostri permessi da utente normale ad amministratore?
Programma target: Windows CLFS (Common Log File System Driver)
Nome exploit: CVE-2022-37969
Tipo: Elevazione locale dei privilegi
DOWNLOAD ISO VULNERABILE: Scarica qui
Lo spazio degli indirizzi di Windows è approssimativamente diviso tra user-space (in cui girano i programmi generici) e kernel-space (in cui girano il sistema operativo stesso e i software dei componenti hardware --> driver). Un utente normale non deve poter accedere al kernel-space, ma esistono meccanismi con cui i programmi utente possono accedere a parti del codice del kernel (system call, procedure dei driver). Perché ci serve l'accesso? Per interagire con il sistema operativo in modo sicuro e controllato, come previsto dai progettisti del sistema operativo.
Alcuni driver usano input forniti dall'utente per operare su strutture dati del kernel. Se l'input genera un bug, il kernel potrebbe essere corrotto. Un caso è il Common Log File System Driver. Usando un input speciale possiamo forzare il driver ad alterare le strutture dati del kernel che contengono il livello di accesso privilegiato dell'utente e sovrascrivere utente normale con amministratore.
Cosa bisogna modificare per elevare il privilegio ad amministratore?
Iniziamo tenendo presente l'obiettivo finale. Windows memorizza all'interno di una struttura dati del kernel chiamata _EPROCESS le informazioni per ogni processo in esecuzione sul sistema. Esempio di _Eprocess
Un campo importante è struct _EX_FAST_REF Token. Questa è un'altra struttura dati che punta a dati che fanno riferimento al livello di privilegio del rispettivo processo. Nell'immagine seguente, il processo System ha un token di sistema e il processo Explorer ha un token utente normale.

Quindi, per elevare il privilegio di Explorer.exe dovremmo copiare il valore da _EPROCESS-->Token di System a _EPROCESS-->Token di Explorer. Realizzeremo qualcosa di simile copiando il Token di System nel Token del nostro programma e lanciando un Prompt dei comandi dal processo elevato (i processi figli ereditano il token del processo padre).
Per completare queste azioni ci servono meccanismi per:
Introduzione: la natura di Windows nel corso degli anni: Come con la scoperta di nuove vulnerabilità, Windows ha avuto bisogno di patch per mitigarle. Inoltre, con l'emergere di nuove tecnologie, Windows ha avuto bisogno di aggiornamenti per rimanere competitivo. Un requisito cruciale era la compatibilità all'indietro con le versioni precedenti. E a volte la sicurezza è stata ottenuta tramite l'oscurità. Strutture dati e definizioni di funzioni sono state rimosse dai manuali, ma la funzionalità è rimasta. Grazie al reverse engineering, i ricercatori sono riusciti a usare queste funzionalità per vari scopi.
Per trovare l'indirizzo nel kernel di _EPROCESS useremo una funzione non documentata: NtQuerySystemInformation (vedi il link per i parametri). Usando il parametro SystemInformationClass possiamo specificare che tipo di informazioni vogliamo recuperare. Recupereremo informazioni generali sui processi specificando il valore SystemExtendedHandleInformation (#define SystemExtendedHandleInformation 0x40).
Un'avvertenza sull'uso di NtQuerySystemInformation è che non sappiamo in anticipo la lunghezza dei dati restituiti, ma NtQuerySystemInformation ha un meccanismo che aiuta. Se viene chiamata con un array di dimensione sbagliata per i dati richiesti, restituisce ERROR e la dimensione corretta che sarebbe dovuta essere richiesta. Questo può essere usato per leggere correttamente le informazioni sui processi nel modo seguente:
NtQuerySystemInformation con un parametro SystemInformationLength fittizioReturnLength restituitoNtQuerySystemInformation con il valore corretto di SystemInformationLength restituito in precedenza