KDP, nuova tecnologia Microsoft per prevenire attacchi al Kernel di Windows 10

lug 10, 2020

Presto in Windows 10 per proteggere kernel e driver.

KDP, nuova tecnologia Microsoft per prevenire attacchi al Kernel di Windows 10
Microsoft ha presentato la sua nuova tecnologia proprietaria per prevenire attacchi malware a kernel e driver di Sistemi operativi desktop. KDP (Kernel Data Protection) si affiancherà alla protezione del firmware in dispositivi Windows 10 di Microsoft Defender ATP.

Gli aggressori, confrontati con tecnologie di sicurezza che impediscono il danneggiamento della memoria, come Code Integrity (CI) e Control Flow Guard (CFG), stanno probabilmente spostando le loro tecniche verso il danneggiamento dei dati. Gli aggressori utilizzano tecniche di corruzione dei dati per indirizzare la politica di sicurezza del sistema, intensificare i privilegi, manomettere l'attestato di sicurezza, modificare strutture di dati "inizializza una volta", tra gli altri.

Kernel Data Protection (KDP) è una nuova tecnologia che impedisce gli attacchi di corruzione dei dati proteggendo parti del kernel e dei driver di Windows attraverso la sicurezza basata sulla virtualizzazione (VBS). KDP è un insieme di API che offrono la possibilità di contrassegnare la memoria del kernel come di sola lettura, impedendo agli aggressori di modificare la memoria protetta. Ad esempio, abbiamo visto gli aggressori utilizzare driver firmati ma vulnerabili per attaccare le strutture di dati delle politiche e installare un driver dannoso e non firmato. KDP mitiga tali attacchi garantendo che le strutture dei dati delle politiche non possano essere manomesse.

Il concetto di protezione della memoria del kernel come sola lettura ha preziose applicazioni per il kernel di Windows, i componenti di posta in arrivo, i prodotti di sicurezza e persino i driver di terze parti come il software anti-cheat e di gestione dei diritti digitali (DRM). Oltre alle importanti applicazioni di sicurezza e protezione antimanomissione di questa tecnologia, altri vantaggi includono:

  • Miglioramenti delle prestazioni: KDP riduce l'onere per i componenti di attestazione, che non dovrebbero più verificare periodicamente le variabili di dati che sono state protette da scrittura
  • Miglioramenti dell'affidabilità: KDP semplifica la diagnosi dei bug di corruzione della memoria che non rappresentano necessariamente vulnerabilità della sicurezza
  • Fornire un incentivo a sviluppatori e fornitori di driver per migliorare la compatibilità con la sicurezza basata sulla virtualizzazione, migliorando l'adozione di queste tecnologie nell'ecosistema
KDP utilizza tecnologie supportate per impostazione predefinita su PC con core protetto, che implementano un insieme specifico di requisiti dei dispositivi che applicano le migliori pratiche di sicurezza di isolamento e affidabilità minima alle tecnologie su cui si basa il sistema operativo Windows. KDP migliora la sicurezza fornita dalle funzionalità che compongono i PC core protetti con l'aggiunta di un altro livello di protezione per i dati di configurazione del sistema sensibili.

In questo post condivideremo i dettagli tecnici su come funziona la protezione dei dati del kernel e su come viene implementata su Windows 10, con l'obiettivo di ispirare e abilitare sviluppatori e fornitori di driver a sfruttare appieno questa tecnologia progettata per affrontare gli attacchi di corruzione dei dati.

Kernel Data Protection: una panoramica

Negli ambienti VBS, il normale kernel NT viene eseguito in un ambiente virtualizzato chiamato VTL0, mentre il kernel sicuro viene eseguito in un ambiente più sicuro e isolato chiamato VTL1. Maggiori dettagli su VBS e il kernel sicuro sono disponibili su Channel 9 qui e qui . KDP ha lo scopo di proteggere i driver e il software in esecuzione nel kernel di Windows (ovvero il codice del sistema operativo stesso) dagli attacchi basati sui dati . È implementato in due parti:

  • KDP statico consente al software in esecuzione in modalità kernel di proteggere staticamente una sezione della propria immagine da eventuali manomissioni da qualsiasi altra entità in VTL0.
  • Dynamic KDP aiuta il software in modalità kernel a allocare e rilasciare memoria di sola lettura da un "pool sicuro". La memoria restituita dal pool può essere inizializzata una sola volta.
La memoria gestita da KDP viene sempre verificata dal kernel sicuro (VTL1) e protetta mediante l'hypervisor tramite le tabelle di traduzione degli indirizzi di secondo livello (SLAT). Di conseguenza, nessun software in esecuzione nel kernel NT (VTL0) sarà mai in grado di modificare il contenuto della memoria protetta.

KDP dinamico e statico, che sono già disponibili nell'ultima versione di Windows 10 Insider Build e funzionano con qualsiasi tipo di memoria, ad eccezione delle pagine eseguibili. La protezione per le pagine eseguibili è già fornita dall'integrità del codice protetto da hypervisor (HVCI), che impedisce che qualsiasi memoria non firmata sia mai eseguibile, garantendo la condizione W ^ X (una pagina che è scrivibile o eseguibile, ma mai entrambe). HVCI e le condizioni W ^ X non sono spiegate in questo articolo (per ulteriori dettagli, consultare il nuovo libro di Windows Internals in arrivo).

KDP statico

Un driver che desidera proteggere una sezione della sua immagine tramite KDP statico deve chiamare l'API MmProtectDriverSection, che presenta il seguente prototipo:

NTSTATUS MmProtectDriverSection (PVOID AddressWithinSection, SIZE_T Size, ULONG Flags)

Un driver specifica un indirizzo situato all'interno di una sezione dati e, facoltativamente, la dimensione dell'area protetta e alcuni flag. Al momento della stesura di questo documento, il parametro "size" è riservato per un utilizzo futuro: l'intera sezione di dati in cui risiede l'indirizzo sarà sempre protetta dall'API.

Nel caso in cui la funzione abbia esito positivo, la memoria che supporta la sezione statica diventa di sola lettura per VTL0 e protetta tramite SLAT. Non è consentito scaricare un driver con una sezione protetta; tentare di farlo comporterà, in base alla progettazione, un errore di schermata blu. Tuttavia, sappiamo che a volte un driver dovrebbe essere in grado di essere scaricato. Pertanto, abbiamo introdotto il flag MM_PROTECT_DRIVER_SECTION_ALLOW _UNLOAD (1). Se il chiamante lo specifica, il sistema sarà in grado di scaricare il driver di destinazione, il che significa che in questo caso, la sezione protetta sarà prima non protetta e quindi rilasciata da NtUnloadDriver.

KDP dinamico

Dynamic KDP consente a un driver di allocare e inizializzare la memoria di sola lettura utilizzando i servizi forniti da un pool sicuro, che è gestito dal kernel sicuro. Il consumatore crea innanzitutto un contesto di pool sicuro associato a un tag. Tutte le future allocazioni di memoria del consumatore saranno associate al contesto del pool sicuro creato. Dopo aver creato il contesto, le allocazioni di sola lettura possono essere eseguite tramite un nuovo parametro esteso all'API ExAllocatePool3:

PVOID ExAllocatePool3 (flag POOL_FLAGS, SIZE_T NumberOfBytes, tag ULONG, PCPOOL_EXTENDED_PARAMETER ExtendedParameters, ULONG Count);

Il chiamante può quindi specificare la dimensione dell'allocazione e il buffer iniziale da dove copiare la memoria in una struttura di dati POOL_EXTENDED_PARAMS_SECURE_POOL. L'area di memoria restituita non può essere modificata da nessuna entità in esecuzione in VTL0. Inoltre, al momento dell'allocazione, il chiamante fornisce un tag e un valore di cookie, che sono codificati e incorporati nell'allocazione. Il consumatore può, in qualsiasi momento, verificare che un indirizzo rientri nell'intervallo di memoria riservato alle allocazioni KDP dinamiche e che il cookie e il tag attesi siano effettivamente codificati in una determinata allocazione. Ciò consente al chiamante di verificare che il puntatore a un'allocazione sicura del pool non sia stato cambiato con un'allocazione diversa.

Simile al KDP statico, per impostazione predefinita l'area di memoria non può essere liberata o modificata. Il chiamante può specificare al momento dell'allocazione che l'allocazione è modificabile o modificabile utilizzando i flag SECURE_POOL_FLAGS_FREEABLE (1) e SECURE_POOL_FLAG_MODIFIABLE (2). L'uso di questi flag riduce la sicurezza dell'allocazione ma consente di utilizzare la memoria KDP dinamica in scenari in cui la perdita di tutte le allocazioni sarebbe impossibile, come le allocazioni che vengono eseguite per processo sulla macchina.

Implementazione di KDP su Windows 10
Come accennato, sia KDP statico che KDP dinamico si basano sulla memoria fisica protetta da SLAT nell'hypervisor. Quando un processore supporta SLAT, utilizza un altro livello per la traduzione dell'indirizzo di memoria. (Nota: AMD implementa lo SLAT tramite "tabelle di pagine nidificate", mentre Intel utilizza il termine "tabelle di pagine estese".)

La traduzione dell'indirizzo di secondo livello (SLAT)

Quando l'hypervisor abilita il supporto SLAT e una VM è in esecuzione in modalità non root VMX, il processore traduce un indirizzo virtuale iniziale chiamato Indirizzo virtuale ospite (GVA, o indirizzo virtuale di fase 1 in ARM64) in un indirizzo fisico intermedio chiamato Indirizzo fisico ospite (GPA o IPA in ARM64). Questa traduzione è ancora gestita dalle tabelle delle pagine, indirizzate dal registro di controllo CR3 gestito dal SO guest. Il risultato finale della traduzione restituisce al processore un GPA, con la protezione dell'accesso specificata nelle tabelle delle pagine degli ospiti. Nota che solo i software che funzionano in modalità kernel possono interagire con le tabelle delle pagine. Un rootkit di solito funziona in modalità kernel e può effettivamente modificare la protezione delle pagine fisiche intermedie.

L'hypervisor aiuta il processore a tradurre GPA utilizzando le tabelle di pagine estese (o nidificate). Sui sistemi non SLAT, quando un indirizzo virtuale non è presente nel TLB, il processore deve consultare tutte le tabelle delle pagine nella gerarchia per ricostruire l'indirizzo fisico finale. Come mostrato nella Figura 1, l'indirizzo virtuale è diviso in quattro parti (sui sistemi LA48). Ogni parte rappresenta l'indice in una tabella di pagine della gerarchia. L'indirizzo fisico della tabella PML4 iniziale è specificato dal registro CR3. Questo spiega perché il processore è sempre in grado di tradurre l'indirizzo e ottenere il successivo indirizzo fisico della tabella successiva nella gerarchia. È importante notare che in ciascuna voce della gerarchia nella tabella delle pagine, il kernel NT specifica una protezione della pagina attraverso una serie di attributi.

Diagramma che mostra la traduzione dell'indirizzo di fase 1 X64 dall'indirizzo virtuale all'indirizzo fisico dell'ospite

Figura 1. La traduzione dell'indirizzo X64 Stage 1 (dall'indirizzo virtuale all'indirizzo fisico dell'ospite)

Quando SLAT è attivo, l'indirizzo fisico intermedio specificato nel registro CR3 del guest deve essere tradotto in un indirizzo fisico di sistema reale (SPA). Il meccanismo è simile: l'hypervisor configura il campo nCR3 del blocco di controllo della macchina virtuale attiva (VMCB) che rappresenta la VM attualmente in esecuzione all'indirizzo fisico delle tabelle di pagine nidificate (o estese) (notare che il campo è chiamato "puntatore EPT" nell'architettura Intel). Le tabelle delle pagine nidificate sono costruite in modo simile alle tabelle delle pagine standard, quindi il processore deve eseguire la scansione dell'intera gerarchia per trovare l'indirizzo fisico corretto, come illustrato nella figura 2. Nella figura, "n" indica le tabelle delle pagine nidificate nella gerarchia, che è gestita dall'hypervisor, mentre "g" indica le tabelle delle pagine guest, che sono gestite dal kernel NT.

Diagramma che mostra la traduzione dell'indirizzo fisico X64 fase 2 da GPA a SPA

Figura 2. La traduzione dell'indirizzo fisico X64 Stage 2 (da GPA a SPA)

Come mostrato, la traduzione finale di un indirizzo virtuale guest in un indirizzo fisico del sistema passa attraverso due tipi di traduzione: GVA in GPA, configurato dal kernel della VM guest, e GPA in SPA, configurato dall'hypervisor. Si noti che nel caso peggiore, la traduzione coinvolge tutti e quattro i livelli della gerarchia di pagine, il che si traduce in 20 ricerche di tabelle. Il meccanismo potrebbe essere lento ed è mitigato dal supporto del processore per un TLB avanzato. Nelle voci TLB è incluso un altro ID che identifica la VM attualmente in esecuzione (chiamato identificatore del processore virtuale o VPID nei sistemi Intel, ID spazio indirizzo o ASID nei sistemi AMD), in modo che il processore possa memorizzare nella cache il risultato della traduzione di un indirizzo virtuale appartenente a due macchine virtuali diverse senza alcuna collisione.

Diagramma che mostra la voce nidificata di una tabella di pagine NPT nella gerarchia

Figura 3. Voce nidificata di una tabella di pagine NPT nella gerarchia

Come evidenziato nella Figura 3, una voce NPT specifica più attributi di protezione dell'accesso. Ciò consente all'hypervisor di proteggere ulteriormente l'indirizzo fisico del sistema (non è possibile accedere all'NPT da nessun'altra entità ad eccezione dell'hypervisor stesso). Quando il processore tenta di leggere, scrivere o eseguire un indirizzo a cui gli NPT non consentono l'accesso, viene sollevata una violazione NPT (violazione EPT nell'architettura Intel) e viene generata un'uscita VM. Un'uscita VM generata dalla violazione di NTP non si verifica frequentemente. In generale, viene prodotto in configurazioni nidificate o quando il software MBEC è in uso per HVCI. Se la violazione NPT si verifica per altri motivi, l'hypervisor di Microsoft inietta un'eccezione di violazione dell'accesso al processore virtuale (VP) corrente.

Implementazione statica del KDP

La protezione SLAT è il principio principale che consente l'esistenza di KDP. In Windows, le implementazioni KDP dinamiche e statiche sono simili e sono gestite dal kernel sicuro. Il kernel sicuro è l'unica entità in grado di emettere l' hypercall ModifyVtlProtectionMask all'hypervisor con l'obiettivo di modificare la protezione dell'accesso SLAT per una pagina fisica mappata nel VTL0 inferiore.

Per KDP statico, il kernel NT verifica che il driver non sia un driver di sessione o mappato con pagine di grandi dimensioni. Se esiste una di queste condizioni o se la sezione è una sezione scartabile, non è possibile applicare KDP statico. Se l'entità che ha chiamato MmProtectDriverSectionL'API non ha richiesto che l'immagine di destinazione fosse scaricabile, il kernel NT esegue la prima chiamata nel kernel sicuro, che blocca l'intervallo di indirizzi normale (NAR) associato al driver. L'operazione di "pinning" impedisce di riutilizzare lo spazio degli indirizzi del driver, rendendo il driver non scaricabile. Il kernel NT porta quindi tutte le pagine che appartengono alla sezione in memoria e le rende private (cioè non indirizzate dal prototipo PTE). Le pagine vengono quindi contrassegnate come di sola lettura nelle strutture foglia PTE (evidenziate come "gPTE" nella figura 2). In questa fase, il kernel NT può finalmente chiamare il kernel sicuro per proteggere le pagine fisiche sottostanti tramite SLAT. Il kernel sicuro applica la protezione in due fasi:

  1. Registrare tutte le pagine fisiche che appartengono alla sezione e contrassegnarle come "di proprietà di VTL0" aggiungendo gli NTE corretti (normali indirizzi di tabella) nel database e aggiornando i PFN sicuri sottostanti, che appartengono a VTL1. Ciò consente al kernel sicuro di tenere traccia delle pagine fisiche, che appartengono ancora al kernel NT.
  2. Applicare la protezione di sola lettura alla tabella SLAT VTL0. L'hypervisor utilizza una tabella SLAT e VMCB per VTL.
La sezione dell'immagine di destinazione è ora protetta. Nessuna entità in VTL0 sarà in grado di scrivere su nessuna delle pagine appartenenti alla sezione. Come evidenziato, il kernel sicuro in questo scenario ha protetto alcune pagine di memoria inizialmente allocate dal kernel NT in VTL0.

Implementazione dinamica di KDP

Dynamic KDP utilizza i servizi forniti dal nuovo heap di segmento per allocare memoria da un pool sicuro, che è quasi interamente gestito dal kernel sicuro.
Nelle prime fasi del suo processo di avvio, il gestore della memoria NT calcola l'indirizzo di base virtuale randomizzato di una regione da 512 GB utilizzata per il pool sicuro, che si estende esattamente su una delle voci PML4 del 256 kernel. Più tardi nella fase 1, il gestore della memoria NT emette una chiamata sicura, internamente denominata INITIALIZE_SECURE_POOL, che include l'area di memoria calcolata e consente al kernel sicuro di inizializzare il pool sicuro.

Il kernel sicuro crea un NAR che rappresenta l'intera regione virtuale da 512 GB appartenente al kernel NT non sicuro e inizializza tutti i relativi NTE appartenenti al NAR. Lo spazio di indirizzi virtuali del pool sicuro nel kernel sicuro è largo 256 GB, il che significa che la mappatura PML4 è condivisa con alcuni altri contenuti e non è allo stesso indirizzo di base rispetto a quello NT. Quindi, durante l'inizializzazione del descrittore del pool sicuro, il kernel sicuro calcola anche un valore delta, che è la differenza tra l'indirizzo di base del pool sicuro nel kernel sicuro e quello riservato nel kernel NT (come mostrato nella figura 4). Questo è importante, perché consente al kernel sicuro di specificare al kernel NT dove mappare una pagina fisica appartenente al pool sicuro.

Diagramma che mostra il pool sicuro da VTL1 a VT0 delta

Figura 4. Il valore Secure Pool da VTL1 a VTL 0 DELTA.

Quando il software in esecuzione nel kernel VTL0 richiede l'allocazione della memoria dal pool sicuro, viene effettuata una chiamata sicura al kernel sicuro, che richiama la funzione heap RtlpHpAllocateHeap interna , che è esposta in entrambi i VTL. Se l'heap del segmento calcola che non ci sono più segmenti di memoria liberi rimasti nel pool sicuro, chiama la routine SkmmAllocatePoolMemory , che alloca nuove pagine di memoria per il pool. L'heap cerca sempre di evitare il commit di nuove pagine di memoria se non è necessario.

Come l'API NtAllocateVirtualMemory, esposta dal kernel NT, l' API SkmmAllocatePoolMemory supporta due tipi di operazioni: riserva e commit. Un'operazione di riserva consente al gestore della memoria del kernel sicuro di riservare alcuni PTE necessari per l'allocazione del pool. Un'operazione di commit alloca effettivamente le pagine fisiche libere.

Le pagine fisiche sono allocate da un gruppo di pagine libere che appartengono al kernel protetto, i cui PFN sicuri sono nello stato sicuro e mappati nella tabella delle pagine di VTL 1, il che significa che sono allocate tutte le gerarchie della tabella di paging VTL 1. Come KDP statico, il kernel sicuro invia l'hypercall "ModifyVtlProtectionMask" all'hypervisor, con l'obiettivo di mappare le pagine fisiche come di sola lettura nella tabella SLAT VTL0. Dopo che le pagine diventano accessibili a VTL0, il kernel sicuro copia i dati specificati dal chiamante e richiama NT.

Il kernel NT utilizza i servizi forniti dal gestore della memoria per mappare le pagine fisiche del guest in VTL0. Ricordare che l'intero spazio degli indirizzi fisici della partizione radice sia di VTL0 che di VTL1 è mappato con la mappatura dell'identità, il che significa che un numero di pagina fisico guest valido in VTL0 è valido anche in VTL1. Il kernel sicuro chiede al gestore della memoria NT di mappare una pagina appartenente al pool sicuro conoscendo esattamente a quale indirizzo virtuale deve essere mappata la pagina. Questo grazie al valore delta calcolato precedentemente nella fase 1 (figura 4).

L'allocazione viene restituita al chiamante in VTL0. Le pagine sottostanti, come con KDP statico, non sono più scrivibili da nessuna entità in VTL0.

I lettori astuti noteranno che la descrizione di KDP sopra riportata riguarda solo la creazione di protezioni SLAT per gli indirizzi fisici guest che supportano una determinata area di memoria protetta. KDP non applica il modo in cui viene tradotto l'intervallo di indirizzi virtuali mappato in un'area protetta. Oggi, il kernel sicuro verifica solo su base periodica che le aree di memoria protette si traducano nell'appropriato GPA protetto da SLAT. La progettazione di KDP consente la possibilità per future estensioni di affermare un controllo più diretto sulla gerarchia di traduzione degli indirizzi delle aree di memoria protette.

Applicazioni di KDP su componenti di posta in arrivo

Per dimostrare come KDP può fornire valore a due componenti di posta in arrivo, stiamo evidenziando come è implementato in CI.dll , il motore di integrità del codice in Windows e il motore di attestazione del runtime di Windows Defender System Guard.

Innanzitutto, CI.dll. L'obiettivo dell'utilizzo di KDP è proteggere lo stato delle politiche interne dopo che è stato inizializzato (ovvero, letto dal registro o generato al momento dell'avvio). Queste strutture di dati sono fondamentali per la protezione come se fossero manomesse: un driver correttamente firmato ma vulnerabile potrebbe attaccare le strutture di dati dei criteri e quindi installare un driver senza segno sul sistema. Con KDP, questo attacco viene mitigato garantendo che le strutture dei dati delle politiche non possano essere manomesse.

In secondo luogo, Windows Defender System Guard. Per fornire l'attestazione di runtime, il broker di attestazione può connettersi al driver di attestazione solo una volta. Questo perché lo stato è archiviato nella memoria VTL1. Il driver memorizza lo stato della connessione nella sua memoria e questo deve essere protetto per evitare che un attacco tenti di ripristinare la connessione con un agente broker potenzialmente manomesso. KDP può bloccare queste variabili e garantire che sia possibile stabilire una sola connessione tra il broker e il driver.

L'integrità del codice e Windows Defender System Guard sono due delle caratteristiche fondamentali dei PC protetti . KDP migliora la protezione di questi sistemi di sicurezza fondamentali e aumenta il livello che gli aggressori devono superare per compromettere i PC con core protetto.

Questi sono solo alcuni esempi di quanto sia utile proteggere la memoria del kernel e dei driver in sola lettura per la sicurezza e l'integrità del sistema. Con l'adozione più ampia di KDP, prevediamo di poter ampliare l'ambito di protezione mentre cerchiamo di proteggere dagli attacchi di corruzione dei dati in modo più ampio.

Iniziare con KDP

Sia il KDP dinamico che statico non hanno ulteriori requisiti oltre a quelli necessari per l'esecuzione della sicurezza basata sulla virtualizzazione. In condizioni ideali, VBS può essere avviato su qualsiasi computer che supporti:

  • Estensioni di virtualizzazione Intel, AMD o ARM
  • Traduzione dell'indirizzo di secondo livello: NPT per AMD, EPT per Intel, traduzione dell'indirizzo Stage 2 per ARM
  • Opzionalmente, MBEC hardware, che riduce il costo delle prestazioni associato a HVCI
Ulteriori informazioni sui requisiti per VBS sono disponibili qui. Su PC con core protetto , la sicurezza basata sulla virtualizzazione è supportata e le funzionalità di sicurezza supportate dall'hardware sono abilitate per impostazione predefinita. I clienti possono trovare PC con core protetto da una varietà di fornitori partner che dispongono delle funzionalità complete di sicurezza con core protetto ora migliorate da KDP.
Articolo di HTNovo
Creative Commons License

Modulo di contatto

Archivio