Control Flow Guard per la sicurezza della piattaforma
Che cos'è Control Flow Guard?
Control Flow Guard (CFG) è una funzionalità di sicurezza della piattaforma altamente ottimizzata creata per combattere le vulnerabilità di danneggiamento della memoria. Inserendo restrizioni rigorose sulla posizione da cui un'applicazione può eseguire il codice, rende molto più difficile per gli exploit eseguire codice arbitrario tramite vulnerabilità quali gli overflow del buffer. CFG estende le tecnologie di mitigazione degli exploit precedenti, ad esempio /GS (controllo della sicurezza del buffer), Prevenzione esecuzione dati (DEP) e AsLR (Address Space Layout Randomization).
L'uso di CFG può essere utile per:
- Prevenire il danneggiamento della memoria e gli attacchi ransomware.
- Limitare le funzionalità del server solo a ciò che è necessario in un determinato momento per ridurre la superficie di attacco.
- Rendere più difficile sfruttare il codice arbitrario tramite vulnerabilità come gli overflow del buffer.
Questa funzionalità è disponibile in Microsoft Visual Studio ed è in esecuzione in versioni compatibile con CFG di Windows; Windows 10 e Windows 11 nel client e Windows Server 2019 e versioni successive sul lato server.
Gli sviluppatori sono fortemente invitati a abilitare cfg per le applicazioni. Non è necessario abilitare CFG per ogni parte del codice, perché verrà eseguita correttamente una combinazione di codice abilitato per CFG e non. Tuttavia, se non si abilita CFG per tutto il codice, è possibile aprire lacune nella protezione. Inoltre, il codice abilitato per CFG funziona correttamente nelle versioni CFG-Unware di Windows ed è quindi completamente compatibile con loro.
Come è possibile abilitare CFG?
Nella maggior parte dei casi non è necessario modificare il codice sorgente. È necessario solo aggiungere un'opzione al progetto di Visual Studio e il compilatore e il linker abiliteranno cfg.
Il metodo più semplice consiste nel passare a Project | Proprietà | Proprietà di configurazione | C/C++ | Generazione di codice e scegliere Sì (/guard:cf) per Control Flow Guard.
In alternativa, aggiungere /guard:cf a Project | Proprietà | Proprietà di configurazione | C/C++ | Riga di comando | Opzioni aggiuntive (per il compilatore) e /guard:cf in Project | Proprietà | Proprietà di configurazione | Linker | Riga di comando | Opzioni aggiuntive (per il linker).
Per altre informazioni, vedere /guard (Abilita Protezione del flusso di controllo).
Se si compila il progetto dalla riga di comando, è possibile aggiungere le stesse opzioni. Ad esempio, se si compila un progetto denominato test.cpp, usare cl /guard:cf test.cpp /link /guard:cf.
È anche possibile controllare dinamicamente il set di indirizzi di destinazione di icall considerati validi da CFG usando SetProcessValidCallTargets dall'API di gestione della memoria. La stessa API può essere usata per specificare se le pagine non sono valide o non sono valide per cfg. Le funzioni VirtualProtect e VirtualAlloc considerano per impostazione predefinita un'area specificata di pagine eseguibili e di cui è stato eseguito il commit come destinazioni di chiamata indirette valide. È possibile eseguire l'override di questo comportamento, ad esempio quando si implementa un compilatore JUST-in-Time, specificando PAGE_TARGETS_INVALID quando si chiama VirtualAlloc o PAGE_TARGETS_NO_UPDATE quando si chiama VirtualProtect come descritto in Costanti di protezione della memoria.
Come si fa a indicare che un file binario è sotto Controllo flusso di controllo?
Eseguire lo strumento dumpbin (incluso nell'installazione di Visual Studio) dal prompt dei comandi di Visual Studio con le opzioni /headers e /loadconfig : dumpbin /headers /loadconfig test.exe. L'output per un file binario in CFG dovrebbe indicare che i valori di intestazione includono "Guard" e che i valori di configurazione del caricamento includono "CF Instrumented" e "FID table present".
Come funziona realmente CFG?
Le vulnerabilità software vengono spesso sfruttate fornendo dati improbabili, insoliti o estremi a un programma in esecuzione. Ad esempio, un utente malintenzionato può sfruttare una vulnerabilità di overflow del buffer fornendo più input a un programma del previsto, eseguendo così l'area riservata dal programma per contenere una risposta. Questo potrebbe danneggiare la memoria adiacente che può contenere un puntatore a funzione. Quando il programma chiama tramite questa funzione, può quindi passare a una posizione imprevista specificata dall'utente malintenzionato.
Tuttavia, una combinazione potente di compilazione e supporto in fase di esecuzione da CFG implementa l'integrità del flusso di controllo che limita strettamente la posizione in cui le istruzioni di chiamata indirette possono essere eseguite.
Il compilatore esegue le operazioni seguenti:
- Aggiunge controlli di sicurezza leggeri al codice compilato.
- Identifica il set di funzioni nell'applicazione che sono destinazioni valide per le chiamate indirette.
Supporto del runtime, fornito dal kernel di Windows:
- Mantiene in modo efficiente lo stato che identifica le destinazioni di chiamata indirette valide.
- Implementa la logica che verifica che una destinazione di chiamata indiretta sia valida.
Per illustrare:
Quando un controllo cfg ha esito negativo in fase di esecuzione, Windows termina immediatamente il programma, interrompendo così qualsiasi exploit che tenta di chiamare indirettamente un indirizzo non valido.