Dicembre 2017

Volume 32 Numero 12

Il presente articolo è stato tradotto automaticamente.

C++ - Supporto per Visual C++ per la protezione del buffer basato su stack

Da Hadi Brais | 2017 dicembre

Software esegue un'operazione che ha non deve per eseguire in base alla relativa specifica funzionale, è detto difetti o bug. In tale specifica le regole che determinano quando gli accessi e le modifiche ai dati e altre risorse devono essere consentite collettivamente costituiscono un criterio di sicurezza. I criteri di sicurezza definiscono in sostanza le implicazioni per il software essere protetti e quando un particolare difetto deve essere considerato come un problema di sicurezza anziché semplicemente un altro bug.

Dato diverse minacce da tutto il mondo, la sicurezza è più importante di mai oggi e, di conseguenza, deve essere parte integrante il software development lifecycle (SDL). Ciò include le opzioni, ad esempio la posizione in cui archiviare i dati, il runtime di C/C++ API da utilizzare, e strumenti che consentono di rendere più sicuro il software. Seguendo le linee guida componenti di base di C++ (bit.ly/1LoeSRB) sostanzialmente consente la scrittura di codice corretto e gestibile. Inoltre, il compilatore Visual C++ offre molte funzionalità di sicurezza che sono facilmente accessibili tramite le opzioni del compilatore. Questi possono essere classificati come l'analisi di sicurezza statico o dinamico. Esempi di controlli di sicurezza statico utilizzando la /Wall e /ANALYZE commutatori e i controlli della linee guida di base di C++. Questi controlli vengono eseguiti in modo statico e non influiscono sul codice generato, anche se aumentano il tempo di compilazione. Al contrario, i controlli dinamici vengono inseriti nei file binari del file eseguibile generati dal compilatore o al linker. In questo articolo parlerà in particolare un'opzione di analisi di sicurezza dinamica, vale a dire /GS, offrendo la protezione contro gli overflow del buffer basato su stack. Viene illustrato il modo in cui il codice viene trasformato quando tale opzione è attivata su e quando può o non è possibile proteggere il codice. Utilizzerò Visual Studio Community 2017.

Ci si potrebbe chiedere, perché non è sufficiente attivare tutte queste opzioni del compilatore ed eseguire con esso. In generale, è consigliabile utilizzare tutti i commutatori consigliati indipendentemente dal fatto che si comprenderne il funzionamento.  Tuttavia, conoscere i dettagli del funzionamento di una tecnica particolare consente di determinare l'impatto che potrebbe disporre di codice e su come rendere meglio di usarla. Si consideri, ad esempio, gli overflow del buffer. Il compilatore offre un'opzione per gestire tali difetti, ma usa un meccanismo di rilevamento che forza il programma in modo anomalo quando viene rilevato un sovraccarico del buffer. Che migliora la protezione? Dipende. Innanzitutto, anche se tutti gli overflow del buffer sono danneggiati, non tutte sono vulnerabilità della sicurezza e in modo che non significa necessariamente che lo sfruttamento ha avuto luogo. E anche in caso affermativo, il danno potrà avere già eseguito nel momento in cui che è stato attivato il meccanismo di rilevamento. Inoltre, a seconda di come l'applicazione è progettata, in modo anomalo di un arresto anomalo del programma potrebbe non essere adatto perché impossibile da se stessa da una vulnerabilità di tipo denial of service (DoS) o causare potenzialmente peggiore situazione che coinvolgono dati persi o danneggiati.  Come verrà illustrato in questo articolo, l'operazione da eseguire solo ragionevole è a rendere l'applicazione resiliente a arresti anomali del sistema, anziché la disabilitazione o modifica il meccanismo di protezione.

Ho scritto un numero di articoli sulle ottimizzazioni del compilatore per MSDN Magazine (sono disponibili il primo a msdn.com/magazine/dn904673). L'obiettivo è principalmente per migliorare il tempo di esecuzione. Sicurezza può anche essere considerata come un obiettivo di trasformazioni del compilatore. Vale a dire, anziché l'ottimizzazione della fase di esecuzione, sicurezza sarà ottimizzata, riducendo il numero di potenziali problemi di sicurezza. Questa prospettiva è utile poiché vengono forniti suggerimenti quando si specificano più opzioni del compilatore per migliorare il tempo di esecuzione sia la sicurezza, il compilatore potrebbe avere più obiettivi potenzialmente in conflitto. In questo caso, è in qualche modo bilanciare o assegnare una priorità questi obiettivi. Verrà illustrato l'impatto su alcuni aspetti del codice, /GS particolarmente velocità, il consumo di memoria e dimensioni del file eseguibile. Che è un altro motivo per comprendere le funzioni queste opzioni al codice.

Nella sezione successiva, fornita un'introduzione agli attacchi di flusso con particolare attenzione a overflow del buffer di stack di controllo. Verrà illustrato come si verificano e come un utente malintenzionato può sfruttare tali. Si esamineranno quindi in dettaglio /GS impatto del codice e il livello a cui è possibile ridurre tali attacchi. Infine, viene illustrato come utilizzare lo strumento di analisi statica di binari BinSkim per eseguire una serie di controlli di verifica critici in un determinato file binario eseguibile senza il codice sorgente.

Attacchi di flusso di controllo

Un buffer è un blocco di memoria utilizzata per archiviare temporaneamente i dati da elaborare. Buffer possono essere allocate dall'heap di runtime, lo stack di thread, utilizzare direttamente l'API Windows VirtualAlloc, o come una variabile globale. Buffer possono essere allocati dall'heap di runtime utilizzando le funzioni di allocazione di memoria di C (ad esempio malloc) o l'operatore new di C++. Buffer da allocare dallo stack utilizzando una variabile di matrice automatico o la funzione alloca. Le dimensioni minime di un buffer possono essere zero byte e la dimensione massima dipende dalle dimensioni del blocco più grande disponibile.

Due funzionalità particolare del linguaggi di programmazione che realmente distinguono da altri linguaggi, ad esempio c#, C++ e C sono:

  • È possibile eseguire arbitrario operazioni aritmetiche sui puntatori.
  • È correttamente possibile dereferenziare un puntatore in qualsiasi momento purché punta alla memoria allocata (dal punto di vista del sistema operativo), anche se il comportamento dell'applicazione potrebbe essere indefinito se che non fa riferimento la memoria che è proprietario.

Queste funzionalità che rendono le lingue molto potente, ma essi costituire una minaccia grande nello stesso momento. In particolare, un puntatore in cui è destinato a essere utilizzato per accedere o eseguire l'iterazione su contenuto di un buffer potrebbe essere erroneamente o intenzionalmente modificato in modo che punti all'esterno dei limiti del buffer per leggere o scrivere posizioni di memoria adiacenti o gli altri. La scrittura oltre l'indirizzo di un buffer più grande viene chiamata un overflow del buffer. Scrittura prima che l'indirizzo più piccolo di un buffer (ovvero l'indirizzo del buffer) viene chiamato un underflow di buffer.

Una vulnerabilità di overflow del buffer basato su stack ha stata individuata di recente in un componente popolarità software (che non denominati I). Questo ha restituito dalla funzione sprintf sicura, come illustrato nel codice seguente:

sprintf(buffer, "A long format string %d, %d", var1, var2);

Il buffer è allocato dallo stack dei thread ed è una dimensione fissa. Tuttavia, le dimensioni della stringa da scrivere nel buffer dipendono dal numero di caratteri necessari per rappresentare i due numeri interi specificate. Le dimensioni del buffer non sono sufficiente per contenere la stringa più grande, determinando un sovraccarico del buffer quando vengono specificati i valori integer di grandi dimensioni. Quando si verifica un overflow, ottengano danneggiati posizioni di memoria adiacente più in alto nello stack.

Per dimostrare perché è pericoloso, prendere in considerazione in un buffer allocato dallo stack in genere sono posizionati nello stack frame della funzione dichiarante in base a standard x86 convenzioni di chiamata e tenuto le ottimizzazioni del compilatore considerazione, come Nel figura 1.

Un tipico x86 Stack Frame
Figura 1 x86 tipico Stack Frame

In primo luogo, il chiamante inserisce gli argomenti che non sono passati attraverso i registri da nello stack in un determinato ordine. Quindi, x86 istruzione di chiamata inserisce l'indirizzo restituito nello stack e passa alla prima istruzione nel destinatario della chiamata. Se non viene eseguita ottimizzazione omissione (FPO) del puntatore ai frame, chiamato inserisce i puntatori ai frame corrente nello stack. Se il chiamato utilizza dei costrutti di gestione delle eccezioni che non sono stati ottimizzati, verrà posizionato accanto un frame di gestione delle eccezioni nello stack. Quel frame contiene puntatori a e altre informazioni sui gestori di eccezioni definite nel destinatario della chiamata. Non statico le variabili locali che non sono state ottimizzate e che non possono essere mantenuti nei registri o che lo spill da registri vengono allocate dallo stack in un determinato ordine. Successivamente, è necessario salvare qualsiasi registri salvati dal chiamato utilizzati dall'oggetto chiamato nello stack. Infine, i buffer di dimensione in modo dinamico allocati con alloca vengono inseriti nella parte inferiore dello stack frame.

Uno qualsiasi degli elementi di dati nello stack potrebbe essere determinati requisiti di allineamento, pertanto padding blocchi possono essere allocate in base alle necessità. Il frammento di codice nel destinatario della chiamata che consente di impostare lo stack frame (ad eccezione di argomenti) viene chiamato il prologo. Quando la funzione sta per restituire al chiamante, un frammento di codice chiamato epilogo è responsabile della deallocazione fino a stack frame e inclusi l'indirizzo del mittente.

La differenza principale tra il x86/x64 e ARM convenzioni di chiamata è che il puntatore restituito indirizzo e frame vengono mantenuti nei registri dedicati in ARM anziché sullo stack. Ciononostante, stack buffer oltre i limiti accessi costituiscono un problema grave in ARM perché altri valori nello stack possono essere puntatori.

Un overflow del buffer di stack (scrittura supera il limite superiore di un buffer) potrebbe sovrascrivere alcun i puntatori di codice o i dati archiviati di sopra del buffer. Underflow dello stack del buffer (scrittura di sotto del limite inferiore di un buffer) potrebbe sovrascrivere i valori dei registri salvati dal chiamato, che possono anche essere puntatori di codice o i dati. Un oggetto arbitrario oltre i limiti scrittura sarà causa l'arresto anomalo dell'applicazione o si comportano in modo indefinito. Tuttavia, un attacco appositamente consente all'utente malintenzionato di prendere il controllo dell'esecuzione dell'applicazione o dell'intero sistema. Ciò può essere ottenuto la sovrascrittura di un puntatore di codice (ad esempio l'indirizzo del mittente) in modo che punti a un frammento di codice che esegue la finalità dell'autore dell'attacco.

GuardStack (GS)

Per ridurre i basata su stack accede, è possibile aggiungere manualmente necessari limiti controlli (aggiunta se le istruzioni per verificare che un puntatore specificato sia entro i limiti) o usare un'API che consente di eseguire questi controlli (ad esempio, snprintf). Tuttavia, la vulnerabilità ancora persistenti per diversi motivi, ad esempio calcoli di interi non corretto o utilizzate per determinare i limiti del buffer o per eseguire il controllo dei limiti di conversioni di tipi. Pertanto, è richiesto un meccanismo di attenuazione dinamica per prevenire o ridurre la possibilità di sfruttamento delle vulnerabilità.

Attenuazione generali includono casualmente lo spazio degli indirizzi e l'uso di stack non eseguibile. Attenuazione dedicato tecniche possono essere classificate in base che l'obiettivo è impedire oltre i limiti accede accada acquisendolo prima che si verificano, o per rilevare oltre i limiti che accede a un certo punto dopo che si verificano. Entrambi sono possibili ma prevenzione aggiunge un overhead significativo delle prestazioni.

Il compilatore Visual C++ offre due meccanismi di rilevamento che sono simili, ma hanno scopi diversi e i costi di prestazioni diverse. Il primo meccanismo fa parte dei controlli degli errori di runtime, che può essere abilitato tramite l'opzione /RTCs. Il secondo è GuardStack (denominata controllo di sicurezza Buffer nella documentazione di) e il controllo di sicurezza in Visual Studio, che può essere abilitato utilizzando l'opzione /GS.

Con /RTCs, il compilatore alloca i blocchi di memoria di piccole dimensioni aggiuntive dallo stack in modo interfoliazione in modo che ogni variabile locale nello stack è inserita tra due blocchi di questo tipo. Ognuno di questi blocchi aggiuntivi viene riempita con un valore speciale (attualmente 0xCC). Questa operazione viene gestita dal prologo del metodo chiamato. Nell'epilogo, una funzione di runtime viene chiamata per verificare se uno di questi blocchi sono stato danneggiato e segnalare un overflow del buffer potenziale o un underflow. Questo meccanismo di rilevamento aggiunge overhead in termini di prestazioni e lo spazio dello stack, ma è progettato per essere utilizzato per il debug e garantire la correttezza di programma, non solo come prevenzione.

GuardStack, d'altra parte, è stato progettato per avere più basso overhead e come prevenzione che può funzionare effettivamente in un ambiente di produzione, potenzialmente dannose. Pertanto, /RTCs deve essere utilizzato per le compilazioni di debug e GuardStack deve essere utilizzato per entrambe le compilazioni. Inoltre, il compilatore non consente di utilizzare /RTCs con le ottimizzazioni del compilatore, mentre GuardStack compatibile e non interferire con le ottimizzazioni del compilatore. Per impostazione predefinita, entrambe sono abilitate nella configurazione di Debug, mentre solo GuardStack è abilitata nella configurazione di rilascio di un progetto di Visual C++. In questo articolo, GuardStack solo verranno descritte in dettaglio.

Quando GuardStack è abilitata, una tipica x86 stack di chiamate si presenta come quanto mostrato nella figura 2.

Un tipico x86 Stack Frame protetti tramite GuardStack (/ GS)
Figura 2 x86 tipico Stack Frame protetti tramite GuardStack (/ GS)

Esistono tre differenze rispetto al layout dello stack nel figura 1. Innanzitutto, un valore speciale, denominato un cookie o un Canarie, viene allocato sopra le variabili locali. Le variabili in secondo luogo, locale sono più probabile che presentano gli overflow vengono allocate sopra tutte le altre variabili locali. In terzo luogo, alcuni degli argomenti che sono particolarmente sensibili all'overflow del buffer vengono copiati in un'area sotto le variabili locali. Naturalmente, per apportare queste modifiche si verifica un diverso prologo ed epilogo vengono utilizzati, come verrà ora illustrato.

Prologo di una funzione protetta approssimativamente necessario includere le seguenti istruzioni aggiuntive su x64:

sub         rsp,8h
mov         rax,qword ptr [__security_cookie] 
xor         rax,rbp 
mov         qword ptr [rbp],rax

8 byte aggiuntivi viene allocato dallo stack e viene inizializzata su una copia del valore del __security_cookie XOR variabile globale con il valore di RBP registrato. Quando viene specificata l'opzione /GS, il compilatore Collega automaticamente il file oggetto compilato dal file di origine gs_cookie.c. Questo file definisce __security_cookie come una variabile globale a 64 bit o 32 bit di uintptr_t il tipo su x64 e x86, rispettivamente. Pertanto, ogni immagine eseguibile portabile (PE) compilati con /GS include una singola definizione di tale variabile utilizzata per i prologhi ed epiloghi delle funzioni di tale immagine. X86, il codice è che lo stesso, ad eccezione di registri di 32 bit e i cookie vengono utilizzato.

L'idea di base per l'utilizzo di un cookie di sicurezza è da rilevare, appena prima della restituzione della funzione, se il valore del cookie è diventato diverso da quello del cookie di riferimento (variabile globale). Ciò indica un potenziale sovraccarico del buffer causato da un tentativo di sfruttamento o semplicemente un bug inoffensivo. È fondamentale che il cookie possiede entropia elevata a renderlo estremamente difficile per un utente malintenzionato indovinare. Se un utente malintenzionato è in grado di determinare il cookie utilizzato in un determinato stack frame, GuardStack ha esito negativo. Verrà illustrato più sui GuardStack non può eseguire più avanti in questa sezione.

Il cookie di riferimento viene assegnato un valore costante arbitrario quando l'immagine viene generato dal compilatore. Pertanto, deve essere attentamente inizializzata, fondamentalmente prima che venga eseguito qualsiasi codice. Le versioni recenti di Windows siano a conoscenza di GuardStack e inizializzerà il cookie a un valore di entropia elevata in fase di caricamento. Quando è abilitata l'opzione /GS, la prima cosa il punto di ingresso di un file EXE o DLL è inizializzare il cookie mediante una chiamata di security_init_cookie definito in gs_support.c e dichiarato in Process. h. Questa funzione inizializza il cookie dell'immagine se non è stata inizializzata in modo appropriato il caricatore di Windows.

Si noti che senza XOR'ing con RBP, divulgazione semplicemente il cookie di riferimento in qualsiasi momento durante l'esecuzione (tramite una lettura oltre i limiti, ad esempio) è sufficiente possa compromettere GuardStack. XOR'ing con RBP consente di generare in modo efficiente i cookie diversi e l'utente malintenzionato è necessario conoscere il cookie di riferimento e RBP per scoprire il cookie per uno stack frame. RBP da sola non è necessariamente a entropia elevata perché il relativo valore dipende da come il compilatore ottimizzato il codice e lo spazio dello stack utilizzate finora fosse eseguita da ASLR address space layout randomization (), se abilitata.

L'epilogo di una funzione protetta approssimativamente necessario includere le seguenti istruzioni aggiuntive su x64:

mov         rcx,qword ptr [rbp]
xor         rcx,rbp 
call        __security_check_cookie
add         esp,8h

In primo luogo, il cookie nello stack è che XOR deve produrre un valore che deve per essere lo stesso come il cookie di riferimento. Il compilatore genera le istruzioni per assicurarsi che il valore di RBP utilizzato nel prologo ed epilogo è lo stesso (a meno che non è in qualche modo ottenuto danneggiato).

La funzione __security_check_cookie, dichiarata in vcruntime.h, collegata dal compilatore e il suo scopo consiste nel convalidare il cookie nello stack. Questa operazione viene eseguita principalmente confrontando il cookie con il cookie di riferimento. In caso contrario, il codice passa alla funzione __report_gsfailure, che è definita in gs_report.c. In Windows 8 e versioni successive, per __fastfail chiamata la funzione termina il processo. In altri sistemi, la funzione termina il processo chiamando UnhandledExceptionFilter dopo la rimozione di qualsiasi gestore potenziali. In entrambi i casi, l'errore viene registrato da segnalazione errori Windows (WER) e contiene informazioni quali dello stack frame il cookie di sicurezza ricevuto danneggiato.

Quando /GS è stata introdotta in Visual C++ 2002, è Impossibile sostituire il comportamento di un controllo di cookie stack specificando una funzione di callback. Tuttavia, poiché lo stack è in uno stato non definito e poiché il codice già ottenuto eseguito prima è stato rilevato l'overflow, non è quasi nulla che può essere eseguita in modo affidabile a quel punto. Pertanto, le versioni successive a partire da Visual C++ 2005 eliminati questa funzionalità.

L'Overhead di GuardStack

Per ridurre al minimo il sovraccarico, sono protetti solo le funzioni che il compilatore considera vulnerabile. Versioni diverse del compilatore possono utilizzare diversi algoritmi non trattati nella documentazione per determinare se una funzione è vulnerabile, ma in generale, se una funzione definisce una matrice o una struttura di dati di grandi dimensioni e ottiene i puntatori a tali oggetti, è probabile che questo sia considerati vulnerabili. È possibile specificare che una particolare funzione non protetti applicando __declspec(safebuffers) alla relativa dichiarazione. Tuttavia, questa parola chiave viene ignorata se applicato a una funzione che inline in una funzione protetta o quando una funzione protetta inline in essa contenuti. È inoltre possibile forzare il compilatore per proteggere una o più funzioni utilizzando il pragma strict_gs_check. I controlli security development lifecycle (SDL), abilitato l'uso di /sdl, specifica GuardStack strict per tutti i file di origine e di altri controlli di sicurezza dinamica.

GuardStack copia parametri vulnerabili in un percorso sicuro di sotto di variabili locali in modo che se si verifica quando un overflow, sarebbe più difficile danneggiato tali parametri. Un parametro che è un puntatore o un riferimento di C++ può essere qualificata come un parametro vulnerabile. Consultare la documentazione su /GS per altre informazioni.

Ho ho condotto degli esperimenti con applicazioni di produzione di C/C++ per determinare l'overhead correlata alle prestazioni e immagine di dimensioni diverse. Risultati sono indipendenti dal quale il compilatore considera vulnerabile funzioni (refrained dall'uso di /sdl perché consente di altri controlli di sicurezza, che hanno i propri sovraccarico) ho applicato strict_gs_check su tutti i file di origine. L'overhead delle prestazioni più grande, viene visualizzato è di 1,4% e l'overhead di dimensioni immagine più grande è 0,4%. Lo scenario peggiore avverrebbe in un programma che passa la maggior parte del tempo la chiamata di funzioni protette che eseguono un impegno minimo. Programmi reali ben progettati non presentano questo comportamento. Tenere presente inoltre che GuardStack comporta un overhead dello spazio di stack potenzialmente non trascurabile.

Sull'efficacia delle GuardStack

GuardStack è progettato per ridurre solo un tipo specifico di vulnerabilità, vale a dire stack overflow del buffer. Più importante, utilizzando GuardStack autonomamente contro questa vulnerabilità potrebbe non fornire un elevato livello di protezione poiché esistono modi per un utente malintenzionato di accedere intorno a esso:

  • Il rilevamento di un cookie danneggiato si verifica solo quando la funzione restituisce. La quantità di codice potrà venire eseguita tra il momento in cui che il cookie è danneggiato e l'ora in cui vengono rilevati dati danneggiati. Tale codice potrebbe usare altri valori dallo stack, sopra o sotto il cookie, che sono stati sovrascritti. Crea un'opportunità per un utente malintenzionato di assumere il controllo dell'esecuzione dell'applicazione (parziale). In tal caso, il rilevamento non anche avvenga affatto.
  • Un overflow del buffer può ancora verificarsi senza sovrascrivere il cookie. Nel caso più pericoloso potrebbe essere un buffer allocato con alloca all'overflow. Argomenti anche protetti e i registri salvati dal chiamato possono essere sovrascritto in questo caso.
  • Potrebbe essere possibile determinare una perdita di alcuni cookie oltre i limiti di utilizzo memoria letture. Poiché diverse immagini usano i cookie di riferimento diverso, e poiché i cookie sono XOR sarebbe con il RBP, può essere più difficile per un utente malintenzionato di rendere utilizzare dei cookie persi. Tuttavia, il sottosistema di Windows per Linux (WSL) sia stato introdotto un altro modo per i cookie di perdita. WSL fornisce un'emulazione del fork di chiamata di sistema Linux da cui viene creato un nuovo processo che duplica il processo padre. Se l'applicazione da un attacco fork richiede un nuovo processo per gestire i client in ingresso, un client dannoso può emettere un numero relativamente ridotto di richieste per determinare i valori dei cookie di sicurezza.
  • Diverse tecniche proposte di indovinare cookie di riferimento di un'immagine in determinate situazioni. Non sono a conoscenza di eventuali attacchi reali in cui è stato individuato il cookie di riferimento, ma non è sufficientemente piccola per chiudere la probabilità di successo. XOR'ing con RBP aggiunge un altro livello molto importante di difesa contro gli attacchi.
  • GuardStack riduce le vulnerabilità grazie all'introduzione di potenziali vulnerabilità diversi, in particolare, di DoS e di perdita di dati. Quando viene rilevato il danneggiamento di un cookie, l'applicazione viene interrotta improvvisamente. Per un'applicazione server, l'utente malintenzionato può causare il server in modo anomalo, potenzialmente la perdita o danneggiamento dei dati importanti.

Pertanto, è importante che è cercare prima di scrivere codice corretto e sicuro con l'aiuto di strumenti di analisi statica. Quindi, dopo la strategia di difesa in profondità, utilizzano GuardStack e altre soluzioni correttive dinamiche offerte da Visual C++ (molti dei quali sono abilitati per impostazione predefinita nelle build di rilascio) nel codice che si effettua la spedizione.

/GS con /ENTRY

La funzione di punto di ingresso predefinito (* CRTStartup) specificato dal compilatore quando si esegue la compilazione per produrre un file EXE o un file DLL esegue quattro operazioni nell'ordine: Inizializza il cookie di sicurezza di riferimento; Inizializza il runtime di C/C++; chiama la funzione principale dell'applicazione. e consente di terminare l'applicazione. Per specificare un punto di ingresso personalizzato, è possibile utilizzare l'opzione del linker /ENTRY. Tuttavia, possono causare la combinazione di un punto di ingresso personalizzato con gli effetti di /GS agli scenari interessanti.

Il punto di ingresso personalizzato e le funzioni da che essa chiamate sono candidati per la protezione. Se il caricatore di Windows viene inizializzato in modo appropriato il cookie, le funzioni protette utilizzerà una copia di un cookie di riferimento che è la stessa nei prologhi ed epiloghi. Pertanto, si verificherà alcun problema.

Se Windows non inizializza in modo appropriato il cookie e la prima cosa il punto di ingresso personalizzato consiste nel chiamare security_init_cookie, tutte le funzioni protette userà il cookie di riferimento corretto, ad eccezione di punto di ingresso. Tenere presente che viene creata una copia del cookie di riferimento nell'epilogo. Se il punto di ingresso restituisce in genere, il cookie verrà archiviato l'epilogo e il controllo ha esito negativo, generando un falso positivo. Per evitare questo problema, è necessario chiamare una funzione per terminare il programma (ad esempio uscita) e non restituiscono in genere.

Se Windows non inizializza in modo appropriato il cookie e il punto di ingresso non è stata chiamata security_init_cookie, tutte le funzioni protette utilizzerà il cookie di riferimento predefinito. Fortunatamente, poiché questo cookie è XOR con RBP, entropie dei cookie utilizzati sarà zero. Pertanto, si otterranno comunque un livello di protezione, soprattutto con ASLR. Tuttavia, è consigliabile inizializzare correttamente il cookie di riferimento da security_init_cookie chiamante.

Utilizzo di BinSkim per verificare GuardStack

BinSkim è uno strumento di analisi del binario chiaro, statico che consente di verificare la correttezza dell'utilizzo di alcune delle funzionalità di sicurezza utilizzata in un determinato file binario PE. Una determinata funzionalità che supporta BinSkim è GuardStack. BinSkim è open source (github.com/Microsoft/binskim) in licenza MIT e scritti completamente in c#. Supporta x86, x64 e i file binari ARM Windows che vengono compilati con le versioni recenti di Visual C++ (2013 +). È possibile utilizzare sia come strumento autonomo o, più è interessante notare che includono (incluso) nel codice. Ad esempio, se si dispone di un'applicazione che supporta PE plug-in, è possibile utilizzare BinSkim per verificare che una viene utilizzato plug-nelle funzionalità di protezione consigliate e rifiutare caricarlo in caso contrario. Viene spiegato in questa sezione utilizzare BinSkim come strumento autonomo.

Per quanto riguarda GuardStack, lo strumento di verifica che il file binario specificato rispetti le regole seguenti quattro:

  • EnableStackProtection: Controlla il flag corrispondente che viene archiviato nel file PDB associato. Se il flag non viene trovato, la regola avrà esito negativo. In caso contrario, viene passato.
  • InitializeStackProtection: Scorre l'elenco delle funzioni globali definiti nel file PDB associato per trovare le funzioni di security_init_cookie e __security_check_cookie. Se entrambi non vengono trovate, lo strumento prende in considerazione che /GS non è stato abilitato. In questo caso, EnableStackProtection deve avere esito negativo. Se non è definito security_init_cookie, la regola avrà esito negativo. In caso contrario, viene passato.
  • DoNotModifyStackProtectionCookie: Cerca il percorso del cookie di riferimento utilizzando i dati di configurazione di caricamento dell'immagine. Se non viene trovato il percorso, la regola avrà esito negativo. Se i dati di configurazione carico indicano che viene definito un cookie, ma l'offset non è valido, la regola avrà esito negativo. In caso contrario, passa la regola.
  • DoNotDisableStackProtectionForFunctions: Utilizza il file PDB associato per determinare se sono presenti eventuali funzioni con l'attributo __declspec(safebuffers) applicato su di essi. La regola non riesce se vengono trovati. In caso contrario, viene passato. Utilizzando __declspec(safebuffers) non è consentito per il processo SDL Microsoft.

Per utilizzare BinSkim, scaricare il codice sorgente dal repository GitHub e compilarlo. Per eseguire BinSkim, eseguire il comando seguente nella shell preferita:

binskim.exe analyze target.exe --output results.sarif

Per analizzare più di un'immagine, è possibile utilizzare il comando seguente:

binskim.exe analyze myapp\*.dll --recurse --output results.sarif --verbose

Si noti che è possibile utilizzare caratteri jolly nei percorsi di file. --Recurse consente di specificare che BinSkim deve analizzare le immagini presenti nelle sottodirectory, troppo. --Opzione verbose indica BinSkim da includere nel file dei risultati, le regole che passato, non solo quelli che non è riuscita.

Il file di risultati è nello statico Analysis risultati interscambio formato (SARIF). Se viene aperta in un editor di testo, sono disponibili voci simili a quanto mostrato nella figura 3.

File di risultati dell'analisi BinSkim figura 3

{
  "ruleId": "BA2014",
  "level": "pass",
  "formattedRuleMessage": {
    "formatId": "Pass ",
    "arguments": [
      "myapp.exe",
    ]
  },
  "locations": [
    {
      "analysisTarget": {
        "uri": "D:/src/build/myapp.exe"
      }
    }
  ]
}

Ogni regola è un ID regola. La regola ID BA2014 è l'ID della regola DoNotDisableStackProtectionForFunctions. Microsoft SARIF SDK (github.com/Microsoft/sarif-sdk) include il codice sorgente di un'estensione di Visual Studio che visualizza i file SARIF in Visual Studio.

Conclusioni

La tecnica di mitigazione dinamica GuardStack è una mitigazione basati su rilevamento estremamente importante alle vulnerabilità di overflow del buffer di stack. È attivato per impostazione predefinita in entrambe la modalità di Debug e di build di rilascio in Visual Studio. È stato progettato per produrre un overhead irrilevante per la maggior parte delle applicazioni in modo che può essere ampiamente utilizzato. Tuttavia, non fornisce una soluzione definitiva per tali vulnerabilità. Overflow del buffer sono comuni per i buffer allocati dallo stack, ma può verificarsi anche in qualsiasi area di memoria allocata. Prevalenza, overflow del buffer basato su heap sono semplicemente come perilous. Per questi motivi, è estremamente importante utilizzare altre tecniche di mitigazione offerte da Visual C++ e di Windows, ad esempio controllo (Guard flusso), Address Space Layout Randomization (ASLR), (Protezione esecuzione programmi), sicuro strutturati eccezione gestisce (SAFESEH ), e la gestione delle eccezioni strutturata sovrascrivere protezione (SEHOP). Tutte queste tecniche di lavoro nuove per la protezione avanzata dell'applicazione. Per ulteriori informazioni su queste tecniche e di altri utenti, fare riferimento a bit.ly/2iLG9rq.


Hadi Braisè un studioso dottorato nell'indiano Institute della tecnologia Delhi, la ricerca di ottimizzazioni del compilatore, architettura del computer e strumenti e tecnologie correlati. Egli blog su hadibrais.wordpress.com e possano essere contattati in hadi.b@live.com.

Grazie ai seguenti esperti per la revisione dell'articolo:  Shayne Hiet blocco (Microsoft), Mateusz Jurczyk (Google), Preeti Ranjan Panda (IITD), Andrew Pardoe (Microsoft)


Viene illustrato in questo articolo nel forum di MSDN Magazine