Abuso degli enclave VBS per creare malware elusivo
Sommario
Introduzione
La sicurezza basata sulla virtualizzazione (VBS) è una delle innovazioni più interessanti di questo settore. La capacità di isolare componenti critici del sistema operativo ha consentito a Microsoft di ottenere notevoli miglioramenti in termini di sicurezza con funzioni come Credential Guard e HVCI (Hypervisor-Protected Code Integrity).
Una funzione spesso sottovalutata all'interno della VBS è rappresentata dagli enclave VBS , basati su una tecnologia che consente di isolare una sezione di un processo, rendendola inaccessibile da altri processi, dal processo stesso e, persino, dal kernel.
Gli enclave VBS possono disporre di un'ampia gamma di applicazioni per la sicurezza e Microsoft li utilizza per implementare alcuni servizi importanti, tra cui la controversa funzione Recall. Inoltre, Microsoft supporta lo sviluppo degli enclave VBS effettuato da terze parti e promuove attivamente la loro adozione.
Anche se gli enclave possono aiutare a proteggere i sistemi, risultano potenzialmente molto allettanti per i criminali: si tratta, pur sempre, di malware che gestisce l'esecuzione all'interno di un enclave può rimanere invisibile ai sistemi di rilevamento e indagine basati sulla memoria.
Abbiamo deciso di esaminare gli enclave VBS per capire come possono essere usati per scopi dannosi e in questo blog andremo a descrivere i principali risultati della nostra ricerca. Approfondiremo la conoscenza degli enclave VBS esaminando alcuni comportamenti non documentati in precedenza, descrivendo le diverse situazioni che possono consentire ai criminali di eseguire codice dannoso al loro interno ed esaminando le varie tecniche che il malware dell'enclave può usare.
Inoltre, andremo a presentare una tecnica di elusione della memoria, denominata Mirage, che è basata su un nuovo approccio da noi definito con la siglaBYOVE (Bring Your Own Vulnerable Enclave). Andremo così ad esaminare il modo con cui i criminali possono usare versioni vecchie e vulnerabili di enclave legittimi per implementare questa sofisticata tecnica di elusione.
Livelli di attendibilità virtuale
Tradizionalmente, Windows si basa su processori con livelli ad anello per impedire la manomissione del sistema operativo da parte delle applicazioni degli utenti. Questa funzione dell'hardware consente di separare il sistema operativo dalle applicazioni degli utenti: il kernel viene eseguito nell'anello ring0, che è isolato dalle applicazioni della modalità utente ring3. Il problema correlato a questo approccio consiste nel fatto che fornisce ai criminali un percorso relativamente semplice per compromettere il sistema operativo, ossia gli exploit del kernel.
Il kernel di Windows offre una superficie di attacco molto ampia. Un lunghissimo elenco di driver di terze parti, insieme ad un'ampia gamma di servizi resi vulnerabili dallo stesso kernel, porta allo sviluppo di un flusso apparentemente costante di exploit del kernel. Utilizzando questo exploit, un criminale può controllare ogni aspetto del sistema operativo. Il confine della modalità utente/kernel si è dimostrato inadeguato.
Per colmare questa lacuna, Microsoft ha introdotto un ulteriore confine di sicurezza nel sistema operativo sotto forma di livelli di attendibilità virtuale (VTL). I privilegi dei VTL sono basati sull'accesso alla memoria: ogni livello di attendibilità fornisce entità eseguite al suo interno con diverse autorizzazioni di accesso alla memoria fisica. Tra l'altro, queste autorizzazioni impediscono ai VTL inferiori di accedere alla memoria dei livelli superiori.
Analogamente alla tradizionale architettura ad anello dei processori, i VTL separano il sistema operativo in diverse "modalità" di esecuzione, a partire dal livello VTL0 e finendo (potenzialmente) al livello VTL16. A differenza della struttura ad anello dei processori, in cui il livello ring0 è quello con il massimo numero di privilegi, i VTL superiori dispongono di un maggior numero di privilegi rispetto ai livelli inferiori.
Attualmente, Windows utilizza due principali livelli di attendibilità: i livelli VTL0 e VTL1 (è utilizzato anche il livello VTL2, ma non rientra nell'ambito di questo blog). Il livello VTL0 viene utilizzato per eseguire i componenti tradizionali del sistema operativo, tra cui il kernel e le applicazioni della modalità utente. Il livello VTL1, che dispone di un maggior numero di privilegi rispetto al livello VTL0, crea due nuove modalità di esecuzione: la modalità protetta del kernel e la modalità utente isolata.
Modalità protetta del kernel
La modalità protetta del kernel si riferisce all'esecuzione del livello ring0 VTL1 e viene usata per eseguire il kernel in modo sicuro: un kernel eseguito nel livello VTL1 dispone, pertanto, di un maggior numero di privilegi rispetto ad un kernel normale. Utilizzando questi privilegi, il kernel protetto può applicare le policy al kernel normale e limitarne l'accesso alle aree sensibili della memoria.
Come abbiamo già detto, il kernel offre un'ampia superficie di attacco e può risultare vulnerabile. Rimuovendo alcuni privilegi dal kernel e concedendo, invece, questi privilegi al kernel protetto, possiamo ridurre l'impatto di una potenziale violazione del kernel,
che, in teoria, può comunque consentire ad un criminale di violare un intero sistema. Ciò nonostante, questa situazione non dovrebbe verificarsi quasi mai perché il kernel protetto è molto limitato e non supporta driver di terze parti, il che riduce notevolmente la sua superficie di attacco.
Modalità utente isolata
Il livello VTL1 crea anche un'altra interessante modalità di esecuzione, denominata modalità utente isolata (IUM), che si riferisce alla modalità di esecuzione sul livello ring3 VTL1. La modalità IUM viene utilizzata per eseguire processi sicuri, un tipo speciale di processo in modalità utente che utilizza le funzionalità di isolamento della memoria offerte dal livello VTL1. La memoria della modalità IUM non è accessibile da alcun codice VTL0, incluso quello del kernel normale. Questa modalità di esecuzione è la struttura portante di funzioni basate sull'isolamento come Credential Guard.
Riepilogando, l'introduzione di VTL0/1 determina quattro modalità di esecuzione (Figura 1).
- Ring0 VTL0: modalità del kernel normale
- Ring0 VTL1: modalità protetta del kernel
- Ring3 VTL0: modalità utente normale
- Ring3 VTL1: modalità utente isolata
Che cosa sono gli enclave VBS?
Un'altra funzione consentita tramite la modalità IUM è rappresentata dagli enclave VBS. Un enclave VBS è una sezione della modalità utente all'interno della IUM, in cui possiamo caricare le DLL denominate "moduli dell'enclave".
Quando un modulo viene caricato in un enclave, quest'ultimo diventa un "ambiente di esecuzione attendibile", in cui cioè i dati e il codice in esso contenuti sono inaccessibili a tutto ciò che viene eseguito in VTL0 e, pertanto, non possono essere manomessi o rubati (Figura 2). Questa funzione risulta utile per isolare le operazioni riservate dai criminali che possono violare un sistema.
Una modalità utente può richiamare le API di Windows dedicate per creare un enclave, caricare un modulo al suo interno e avviare la sua inizializzazione. Un modulo dell'enclave appare sotto forma di una DLL che è stata compilata specificamente per questo scopo. Una volta inizializzato l'enclave, la modalità utente di hosting non può accedere alla sua memoria, ma solo interagire con essa richiamando le funzioni esportate dai suoi moduli tramite l'API CallEnclave .
Nella Figura 3, viene illustrato un esempio di codice che implementa questo processo (un esempio più dettagliato viene fornito da Microsoft).
Poiché Microsoft tenta di restringere quanto più possibile l'accesso al livello VTL1, per caricare una DLL all'interno di un enclave è necessario firmarla correttamente tramite uno speciale certificato emesso da Microsoft. Qualsiasi tentativo di caricare un modulo senza una firma di questo tipo determinerà un esito negativo del processo. L'opzione di firmare i moduli dell'enclave può essere delegata solo a terze parti affidabili. L'aspetto interessante è che non viene controllato chi può caricare questi moduli: purché siano firmati, qualsiasi processo può caricare moduli arbitrari in un enclave.
I moduli dell'enclave sono concepiti per fungere da "unità computazionali" con una capacità molto limitata di interagire o influire sul sistema, quindi, a causa di questa caratteristica, sono limitati all'utilizzo di un numero minimo di API, che impedisce loro di accedere alla maggior parte dei componenti del sistema operativo.
Le API disponibili all'interno di un enclave vengono importate da librerie dedicate, che sono caricate nel livello VTL1. Ad esempio, mentre i processi normali si basano sulla libreria ntdll.dll per richiedere i servizi al sistema operativo, i moduli dell'enclave utilizzano la libreria vertdll.dll , un sostituto della ntdll, che viene usato per comunicare con il kernel protetto tramite le chiamate di sistema.
Malware dell'enclave
Il concetto del malware dell'enclave può, certamente, risultare allettante per i criminali perché fornisce due vantaggi significativi:
Esecuzione in un'area isolata della memoria: lo spazio degli indirizzi di un enclave è inaccessibile a qualsiasi elemento eseguito nel livello VTL0, inclusi sistemi EDR e strumenti di analisi, il che rende il rilevamento molto più complesso.
Chiamate API non tracciabili: le chiamate API effettuate dall'interno di un enclave possono rimanere invisibili ai sistemi EDR, che riescono a monitorare le API in modalità utente inserendo degli hook nelle librerie del sistema e utilizzando i driver per monitorare le attività interne al kernel. Le chiamate API attivate da un enclave non possono essere rilevate da queste tecniche perché le chiamate dell'enclave vengono eseguite dal livello VTL1 e non passano attraverso questi componenti VTL0 in cui sono stati inseriti gli hook.
Nella Figura 4, viene illustrato questo vantaggio: è possibile monitorare un normale processo che richiama le API di Windows inserendo un hook all'interno della NTDLL o nello stesso kernel. Tuttavia, un modulo dell'enclave passa attraverso la libreria vertdll che risiede nel livello VTL1 e richiama le chiamate al kernel protetto (entrambe inaccessibili dai sistemi EDR).
Riconoscendo questo potenziale, abbiamo deciso di esaminare il concetto di malware dell'enclave. Per sfruttare un enclave a questo scopo, bisogna chiedersi due domande:
- In che modo i criminali possono eseguire codice dannoso all'interno di un enclave?
- Quali tecniche possono utilizzare i criminali una volta effettuata l'esecuzione all'interno di un enclave?
In che modo i criminali possono eseguire codice dannoso all'interno di un enclave?
Come abbiamo già detto, i moduli dell'enclave, per poter essere caricati, devono essere firmati con un certificato emesso da Microsoft, tramite il quale vengono approvate solo le entità che possono eseguire il loro codice all'interno di un enclave. Ciò nonostante, i criminali hanno comunque qualche opzione a disposizione.
Innanzitutto, un criminale può sfruttare una vulnerabilità del sistema operativo, come la CVE-2024-49706, che, scoperta da Alex Ionescu (un dipendente di Winsider Seminars & Solutions), potrebbe consentire ad un criminale di caricare un modulo non firmato all'interno di un enclave. Questa vulnerabilità è stata corretta con una patch da Microsoft, tuttavia i criminali più motivati potrebbero identificare bug simili in futuro.
Un secondo approccio diretto sarebbe quello di ottenere una firma legittima: un'operazione che dovrebbe essere consentita perché Microsoft rende visibile la firma dell'enclave a terze parti tramite la piattaforma Trusted Signing . Anche se certamente non banale, un criminale avanzato potrebbe ottenere l'accesso ad un'entità Trusted Signing e firmare il suo enclave.
Oltre a queste due opzioni, abbiamo esaminato altre due tecniche che potrebbero consentire ai criminali di eseguire codice all'interno di un enclave VBS, abusando degli enclave che possono essere sottoposti a debug e sfruttando gli enclave vulnerabili.
Abuso dei moduli degli enclave che possono essere sottoposti a debug
Al momento della creazione del modulo di un enclave VBS, uno sviluppatore può configurarlo in modo da poterlo sottoporre a debug. Compilare un modulo con questa impostazione consente di sottoporlo a debug. Poiché i moduli degli enclave vengono eseguiti nel livello VTL1, di norma, non è possibile sottoporli a debug perché il debugger non può accedere alla memoria dell'enclave per recuperare i dati o inserire dei breakpoint. La Figura 5 mostra un esempio di debugger che non riesce a leggere da un indirizzo della memoria all'interno di un enclave.
Un aspetto interessante consiste nel fatto che il modulo di un enclave che può essere sottoposto a debug, se eseguito, viene comunque caricato nel livello VTL1. Per attivare l'operazione di debug, il kernel protetto implementa alcune eccezioni da applicare ai moduli degli enclave che possono essere sottoposti a debug. Ad esempio, se si tenta di leggere dalla memoria di un modulo di questo tipo, il kernel normale genera una chiamata al kernel protetto SkmmDebugReadWriteMemory , che verifica se il modulo di destinazione può essere effettivamente sottoposto a debug prima di eseguire l'operazione richiesta.
Un esempio di questo tipo viene illustrato nella Figura 6: dopo aver caricato il modulo dell'enclave che può essere sottoposto a debug, il debugger riesce a leggere dalla memoria dell'enclave.
Analogamente, è possibile modificare anche le autorizzazioni della memoria all'interno del modulo di un enclave che può essere sottoposto a debug mediante il processo VTL0 (un'eccezione implementata nella chiamate del kernel protetto SkmmDebugProtectVirtualMemory ).
Microsoft consiglia vivamente agli sviluppatori di non immettere sul mercato moduli di enclave che possono essere sottoposti a debug perché, in tal modo, si viene a minare lo scopo principale dei moduli, ossia isolare una sezione della memoria da VTL0 (Figura 7). L'utilizzo di un modulo che può essere sottoposto a debug implica che i dati gestiti dal modulo stesso possono diventare facilmente vulnerabili.
I rischi di un'applicazione di produzione eseguita sul modulo di un enclave che può essere sottoposto a debug sono chiari; tuttavia, i criminali possono effettivamente sfruttarlo per un altro scopo, ossia per eseguire codice non firmato nel livello VTL1. Se un criminale ottiene un qualsiasi modulo di un enclave firmato che può essere sottoposto a debug, può eseguire codice nel livello VTL1 effettuando le seguenti quattro operazioni:
- Ottenere l'indirizzo di una routine all'interno del modulo dell'enclave tramite GetProcAddress
- Cambiare la protezione della memoria della routine in RWX
- Sovrascrivere il codice della routine con uno shellcode arbitrario
- Attivare la routine tramite CallEnclave
La Figura 8 illustra il codice che implementa questo processo.
Il problema ovvio dal punto di vista di un criminale è che si tratta di un'arma a doppio taglio: come il criminale riesce ad accedere alla memoria dell'enclave, un sistema EDR può farlo altrettanto bene. Ciò nonostante, c'è un lato positivo: quello di eludere il monitoraggio delle API perché le chiamate API eseguite dal modulo dell'enclave passano attraverso le DLL del livello VTL1 e il kernel protetto, limitando la visibilità dei sistemi EDR.
Nel complesso, questo metodo potrebbe risultare utile per creare un impianto "semi-VTL1" nascosto, che sfrutta alcuni dei vantaggi ottenuti dall'esecuzione interna all'enclave.
Abbiamo tentato di usare VirusTotal e altre fonti per identificare il modulo di un enclave che può essere sottoposto a debug, ma, al momento della stesura di questo documento, l'operazione ha avuto esito negativo. Tuttavia, riteniamo sia giusto presupporre che, passando del tempo e adottando maggiormente la tecnologia dell'enclave, alcuni moduli potrebbero venire esfiltrati.
BYOVE (Bring Your Own Vulnerable Enclave)
Come abbiamo già detto, Windows utilizza le firme per impedire il caricamento di enclave non affidabili nel livello VTL1. Questo approccio non è esclusivo degli enclave: il concetto è emerso con la tecnologia DSE (Driver Signing Enforcement) , che impedisce l'esecuzione di driver non affidabili nel kernel di Windows.
Per superare questo vincolo, i criminali hanno iniziato ad usare la tecnica BYOVE (Bring Your Own Vulnerable Enclave) , con cui, anche se non possono caricare il proprio driver, riescono a caricare un driver firmato legittimo che contiene una vulnerabilità nota da poter poi sfruttare per eseguire codice non firmato nel kernel.
Il nostro intento era esaminare questo approccio nel contesto degli enclave per rispondere alla domanda: Possiamo abusare del modulo di un enclave firmato vulnerabile per eseguire codice in modalità IUM?
Il primo passo è stato trovare un enclave di questo tipo, che ci ha rapidamente portato a scoprire la CVE-2023-36880 , una vulnerabilità presente nel modulo di un enclave VBS che viene usato da Microsoft Edge. Questa vulnerabilità consente ai criminali di leggere e scrivere dati arbitrari all'interno dell'enclave. Mentre la vulnerabilità è stata catalogata da Microsoft come vulnerabilità di divulgazione delle informazioni, le note specificano anche che potrebbe portare all'esecuzione limitata di codice (Figura 9).
La vulnerabilità è stata scoperta da Alex Gough del team addetto alla sicurezza di Chrome, che ha anche condiviso una PoC (Proof-of-Concept) per sfruttarla. Dopo aver individuato una versione vulnerabile di questo enclave in VirusTotal, abbiamo iniziato a tentare di sfruttarla per eseguire il codice.
La nostra idea era quella di abusare della primitiva di lettura/scrittura per sovrascrivere lo stack dell'enclave con una catena ROP, che ci avrebbe consentito di eseguire uno shellcode all'interno dell'enclave. Durante la valutazione di questa operazione, abbiamo fatto una scoperta interessante: gli enclave sono protetti dall'esecuzione di codice non firmato tramite ACG (Arbitrary Code Guard),
una funzione di mitigazione della sicurezza, che è stata progettata per bloccare l'esecuzione di codice generato in modo dinamico, ossia creato al momento del runtime anziché come parte dell'eseguibile del processo originale o delle sue DLL. La funzione ACG viene implementata applicando due regole:
- Le nuove pagine eseguibili non possono essere generate dopo il caricamento iniziale del processo
- Una pagina già eseguibile non può essere scritta
La funzione ACG viene applicata per impostazione predefinita sul kernel normale, tuttavia, in modalità utente viene applicata solo ai processi configurati per usarla. La nostra ricerca ha mostrato che, per i processi IUM, che includono gli enclave, la funzione ACG sembra essere applicata automaticamente, come sul kernel normale.
Possiamo osservare questo fenomeno tentando di allocare una nuova pagina RWX all'interno di un enclave tramite VirtualAlloc, ma l'operazione non riesce e viene assegnato il codice di errore 0x677, STATUS_DYNAMIC_CODE_BLOCKED (Figura 10). Se si tenta di usare VirtualProtect per modificare le autorizzazioni di una pagina eseguibile o di trasformare una pagina in eseguibile, si otterrà lo stesso risultato.
Per capire questo comportamento, possiamo esaminare la funzione del kernel sicuro denominata SecureKernel!NtAllocateVirtualMemoryEx, che gestisce l'allocazione della memoria dinamica in IUM. La funzione valuta la maschera di protezione richiesta e, se è richiesta una pagina eseguibile, viene restituito l'errore ACG STATUS_DYNAMIC_CODE_BLOCKED. Simili controlli sono implementati in SkmmProtectVirtualMemory per impedire di apportare modifiche alle pagine IUM esistenti(Figura 11).
Finora, non abbiamo trovato un metodo per bypassare la funzione ACG all'interno di un enclave e caricare codice non firmato nell'enclave. In teoria, è possibile sfruttare completamente una catena ROP: i criminali possono richiamare in modo arbitrario le API nel livello VTL1, ad esempio, ma noi non abbiamo seguito questa direzione. Ciò nonostante, siamo comunque riusciti ad identificare un'altra interessante applicazione per gli enclave vulnerabili, di cui tratteremo più avanti in questo post.
Quali tecniche possono utilizzare i criminali una volta effettuata l'esecuzione all'interno di un enclave?
Dopo aver compreso che gli enclave possono avere un enorme potenziale perché potenzialmente consentono ai criminali più motivati di eseguire attività dannose, abbiamo cercato di capire quali tecniche sono disponibili per questo tipo di malware.
L'utilizzo più diretto degli enclave è quello per cui sono stati progettati: come un enclave può proteggere i dati sensibili dai criminali, può anche essere usato per nascondere i loro "segreti" alle entità del livello VTL0.
Questa caratteristica può risultare utile in molti casi: per memorizzare i payload al di fuori dei sistemi EDR, per sigillare le chiavi di crittografia in posizioni lontane dagli analisti o per mantenere la configurazione dei malware sensibili esterna ai dump della memoria.
Come nel caso di opzioni più avanzate, molte tecniche di malware tradizionali non possono essere implementate all'interno di un enclave. Poiché gli enclave sono vincolati ad un sottoinsieme limitato di API di sistema, non possono interagire con i componenti principali del sistema operativo, come, ad es., file, registro di sistema, componenti di rete e altri processi.
Ciò nonostante, sono comunque disponibili varie tecniche da poter utilizzare all'interno di un enclave per sfruttare i vantaggi derivanti dall'esecuzione in modalità IUM.
Accesso alla memoria in modalità utente VTL0
Nonostante il loro accesso limitato al sistema, gli enclave possono comunque accedere ad una risorsa di importanza critica: la memoria di elaborazione. Gli enclave possono eseguire operazioni di lettura/scrittura nell'intero spazio degli indirizzi di elaborazione, anche nel livello VTL0.
A questo accesso, si applicano, tuttavia, alcune restrizioni, ad esempio per eseguire il codice negli enclave sono richieste le autorizzazioni della memoria, che non è possibile cambiare. Un enclave, quindi, non può scrivere su una memoria non scrivibile né trasformare una memoria non eseguibile in una eseguibile. La Figura 12 illustra l'esecuzione di codice in un enclave e dimostra le diverse possibilità e i vincoli stabiliti.
Accedendo alla memoria in modalità utente VTL0, possiamo implementare varie tecniche utili. Caricando un enclave dannoso in un processo, possiamo monitorare e rubare informazioni sensibili in maniera furtiva o correggere con patch alcuni valori all'interno del processo per cambiare il suo comportamento.
Implementare queste tecniche con un enclave apporta un vantaggio notevole: come abbiamo detto prima, l'attivazione delle API da un enclave ci consente di eludere il monitoraggio dei sistemi EDR. Poiché l'accesso alla memoria in queste tecniche viene eseguito mediante un enclave, si riesce a mantenere un atteggiamento furtivo.
Esecuzione di codice in modalità utente VTL0
Anche se un enclave può leggere/scrivere sulla memoria in modalità utente VTL0 con le autorizzazioni appropriate, il codice memorizzato nel livello VTL0 non può essere mai eseguito all'interno di un enclave, anche se dispone delle autorizzazioni di esecuzione.
Nonostante non sia in grado di eseguire codice VTL0 al suo interno, un enclave può attivarlo "da remoto". Utilizzando l'API CallEnclave con un indirizzo VTL0, un enclave può attivare il codice VTL0 in un normale thread in modalità utente (Figura 13).
Forzando il processo in modalità utente ad agire "per loro conto", gli enclave possono accedere indirettamente al sistema in modi ad essi solitamente non consentiti. Ad esempio, un enclave può attivare una routine VTL0 che legge da un file, crea un socket, ecc.
È importante notare che non vi sono vantaggi in termini di elusione per l'esecuzione di codice in modalità utente: poiché il codice viene eseguito come qualsiasi altro codice in modalità utente, può risultare visibile ai sistemi EDR.
Funzione di anti-debugging
Un'altra interessante applicazione per i malware degli enclave è l'anti-debugging. Il fatto che il codice eseguito all'interno dell'enclave rimanga inaccessibile alle applicazioni VTL0, inclusi i debugger, fornisce ai malware significativi vantaggi nei loro confronti.
La riduzione delle API rese visibili agli enclave implica che non tutte le tradizionali tecniche di anti-debugging sono disponibili da un enclave. Ad esempio, le API NtQueryInformationProcess o IsDebuggerPresent e tutte le API di data/ora non sono disponibili per gli enclave. Ciò nonostante, abbiamo comunque altre opzioni a disposizione.,
Innanzitutto, possiamo basarci sull'accesso dell'enclave allo spazio degli indirizzi VTL0 del processo per leggere la PEB di processo manualmente e per controllare il valore del flag "BeingDebugged". Se viene rilevato un debugger, l'enclave può terminare il processo.
Un altro approccio consiste nell'implementare una tecnica di anti-debugging basata sul tempo (Figura 14). Anche se le API di data/ora non sono disponibili per gli enclave, possono comunque usare l'istruzione di assemblaggio rdtsc , che restituisce il numero di cicli del clock del processore eseguiti dall'avvio. In tal modo, possiamo misurare il tempo impiegato tra le varie chiamate dell'enclave e terminare il processo se viene rilevato un ritardo significativo.
Spostando alcune parti critiche del nostro codice in un enclave insieme ad un controllo di anti-debugging, possiamo creare un malware completamente invisibile alle analisi dinamiche, che risiede nell'enclave per poter essere eseguito correttamente, mentre il processo in modalità utente non può applicare patch ai controlli eseguiti al suo interno. Se implementato correttamente, questo approccio potrebbe essere sconfitto solo eseguendo il debug di Hyper-V o del kernel protetto.
BYOVE - 2° round
In una sezione precedente, abbiamo tentato di sfruttare un modulo di un enclave vulnerabile per eseguire il codice in modalità IUM. Quando abbiamo capito che non ce l'avremmo fatta, abbiamo pensato di verificare se il concetto di BYOVE potesse avere altre applicazioni, quindi abbiamo deciso di esaminare ulteriormente il modulo dell'enclave vulnerabile.
In questo caso, la vulnerabilità (CVE-2023-36880) deriva dalle funzioni SealSettings e UnsealSettings , che sono state esportate dal modulo dell'enclave. La funzione SealSettings riceve un puntatore su un buffer di dati, lo crittografa e scrive i risultati ad un indirizzo di destinazione fornito dal chiamante. La funzione UnsealSettings opera in modo simile, decrittografando un buffer fornito e scrivendo ad un indirizzo specificato.
Il problema con queste due funzioni consiste nel fatto che non verificano l'indirizzo di destinazione né l'indirizzo di origine del buffer, puntando, quindi, a qualsiasi indirizzo presente all'interno del processo, inclusi gli indirizzi all'interno dello stesso enclave. Nella Figura 15, viene illustrato il codice vulnerabile, che mostra la funzione UnsealSettings mentre esegue un'operazione di memcpy ad un indirizzo arbitrario fornito dall'utente.
Questa vulnerabilità fornisce ad un criminale due capacità di sfruttamento (Figura 16).
Scrittura arbitraria all'interno dell'enclave: un criminale può richiamare la funzione SealSettings per crittografare i dati arbitrari, quindi richiamare la funzione UnsealSettings per puntare ad un indirizzo di destinazione all'interno dell'enclave. In tal modo, i dati originali vengono scritti nella memoria dell'enclave.
Lettura arbitraria all'interno dell'enclave: un criminale può richiamare la funzione SealSettings, fornendo un indirizzo all'interno dell'enclave come puntatore del buffer di origine. In tal modo, i dati vengono crittografati dalla memoria dell'enclave e scritti in una posizione controllata dal criminale, che poi decrittografa tali dati richiamando la funzione UnsealSettingsper consentire di leggere i dati arbitrari dall'enclave.
Mirage: elusione della memoria basata sul livello VTL1
Anche se non siamo riusciti ad eseguire codice nel livello VTL1, la primitiva della scrittura arbitraria consente di eseguire due operazioni esclusive:
Memorizzare i dati arbitrari nel livello VTL1, in cui diventano inaccessibili alle entità del livello VTL0
Scrivere i dati arbitrari nello spazio degli indirizzi del processo normale (VTL0) dal livello VTL1, impedendo alle entità del livello VTL0 di monitorare questa operazione
Inoltre, poiché queste funzionalità sono facilitate dal caricamento di un modulo dell'enclave firmato, possono essere usate dai criminalisenza richiedere alcuna firma da parte loro.
Per dimostrare il potenziale di queste funzionalità, ci siamo imbattuti in una tecnica di elusione della scansione della memoria, chiamata "Mirage", che si ispira ad un'altra tecnica di elusione, denominata Gargoyle, che crea un payload in grado di passare continuamente da uno stato "innocuo" ad uno stato di attacco (Figura 17).
Mentre Gargoyle implementa questa tecnica passando dalla memoria eseguibile a quella non eseguibile e viceversa, Mirage mira a raggiungere un risultato simile passando dalla memoria del livello VTL1 a quella del livello VTL0 e viceversa. Questo ciclo memorizza uno shellcode nella memoria dell'enclave VTL1, lo trasferisce periodicamente nell'enclave VTL0 sfruttando la vulnerabilità, lo esegue, quindi lo cancella subito dopo dalla memoria dell'enclave VTL0 (Figura 18).
Questo approccio presenta due vantaggi principali: innanzitutto, poiché il payload impiega la maggior parte del tempo nascosto nel livello VTL1, è resiliente alle operazioni di scansione e di eliminazione della memoria. Questo approccio è vantaggioso rispetto alla tecnica Gargoyle perché, durante la sua fase di inattività, il nostro payload non solo è invisibile, ma è anche inaccessibile.
Il secondo vantaggio consiste nel fatto che la scrittura dello shellcode nel livello VTL0 viene eseguita dall'enclave. Come abbiamo già detto, i sistemi EDR non riescono a monitorare il codice eseguito nel livello VTL1, quindi, i consueti hook di questi sistemi non sono in grado di intercettare lo shellcode scritto nella memoria.
Abbiamo sviluppato una PoC per Mirage (Figura 19) non per scopi di attacco, ma solo per illustrare il concetto alla base di questa tecnica.
Rilevamento
Attualmente, gli enclave vengono utilizzati da un numero molto limitato di applicazioni. Anche se la loro adozione si è diffusa maggiormente, vengono comunque ancora caricati solo da specifici processi anziché in modo casuale. Ad esempio, il comando calc.exe , probabilmente, non caricherà mai un enclave VBS
A causa di questa caratteristica, l'utilizzo di un enclave anomalo può rappresentare un'ottima opportunità per il rilevamento. Gli addetti alla sicurezza devono sfruttare questa possibilità creando degli standard di riferimento per i casi di utilizzo degli enclave VBS noti e legittimi e segnalando eventuali deviazioni da questi standard. L'utilizzo degli enclave può essere identificato in due modi: monitorando le API degli enclave e rilevando le DLL caricate degli enclave.
Le API degli enclave
Le API riportate di seguito vengono utilizzate per gestire gli enclave VBS mediante un processo di hosting e dovrebbero indicare un'operazione di caricamento:
- CreateEnclave
- LoadEnclaveImageW
- InitializeEnclave
- CallEnclave
- DeleteEnclave
- TerminateEnclave
Le DLL caricate degli enclave
Un'altra opzione per individuare un utilizzo anomalo dell'enclave è rilevare il caricamento delle DLL tipicamente usate al suo interno, ossia le librerie Vertdll.dll e ucrtbase_enclave.dll. Poiché queste DLL possono essere usate esclusivamente dall'enclave, la loro presenza indica che il processo ne sta, probabilmente, utilizzando una di esse.
Conclusione
Gli enclave VBS forniscono agli sviluppatori uno strumento straordinario per proteggere le sezioni sensibili delle loro applicazioni, ma come abbiamo appena dimostrato, possono essere anche usati dai criminali per "tenere al sicuro" i loro malware. Anche se questo concetto è perlopiù teorico ad un certo punto, è sicuramente possibile che i criminali più esperti, in futuro, inizieranno ad usare gli enclave VBS per scopi dannosi.
Ringraziamenti
Vorrei ringraziare per il loro lavoro Matteo Malvica di Offsec e Cedric Van Bockhaven di Outflank, che hanno, di recente, condotto un progetto di ricerca molto simile a quello qui descritto. Vi consigliamo di leggere il loro primo blog di una serie costituita da due parti, di cui la seconda parte verrà pubblicata dopo l'evento Insomni’Hack 2025.