Anche questo argomento mi stava confondendo. Ci sono solo una o due frasi in quel problema di Phrack che menzionano anche in modo intelligente la durata del sovraccarico del buffer (probabilmente perché questa è una tecnica tipica). Paj28 sembra avere la risposta giusta, ma è ancora un po 'vago su come si potrebbe acquisire un canale laterale abbastanza accurato per fare in modo che questo lavoro in pratica. Se è necessario un numero elevato di tentativi per eseguire analisi statistiche, si riduce chiaramente l'efficacia dell'attacco (specialmente nel caso remoto in cui si verifica una nuova connessione di rete e un fork () per ogni tentativo).
I metodi SSP comuni utilizzano un singolo canarino che viene copiato nello stack da una variabile a livello di programma memorizzata in una sezione protetta. Questa inizializzazione avviene durante il caricamento eseguibile. Tre varianti conosciute di questa inizializzazione sono (a) dal kernel durante exec * () syscall, (b) da un pezzo di codice di avvio per gli eseguibili collegati in modo statico, o (c) dal frammento di codice del caricatore dinamico che viene inserito in un'immagine runtime dei file eseguibili collegati in modo dinamico.
Soprattutto, questo trucco byte per byte può essere sconfitto. Considera quanto segue:
{01} Invece di allocare spazio per una parola a 64 bit, possiamo lasciare due spazi word-length nel frame dello stack. Uno di questi slot variabile è posizionato prima dei buffer in cui è improbabile che un overflow possa modificarlo.
{02} Dopo aver eseguito il prologo della funzione, invece di copiare un canarino fisso in questi due slot, in realtà deriviamo un canarino dinamico da due valori (che a loro volta possono essere resi dinamici in una certa misura). Molto probabilmente i nostri valori di origine verrebbero presi da un blocco di memoria protetta casuale pre-inizializzata, simile a come sono stati stabiliti i vecchi canarini.
{03} Il metodo di derivazione potrebbe essere reso arbitrariamente strong, anche crittografico, ma non è necessario che questa tecnica funzioni.
{04} Una semplice strategia può rendere la vita dell'attaccante molto difficile. Supponiamo che il metodo di derivazione sia semplicemente qualcosa come A + B = C, dove A e B sono valori a 64 bit pseudo-casuali. C, il valore di controllo, sarebbe allo stesso modo 64 bit.
{05} Ora, quando l'attaccante va a sovrascrivere il canarino, può solo raggiungere B. Un sopravvive in pila intatto a causa dell'esistente in un indirizzo virtuale inferiore.
{06} A + B! = C, quindi ovviamente il canary check fallisce come ci aspettiamo.
{07} Tuttavia, che cosa ha imparato l'attacco dalla sovrascrittura del primo byte di B? Solo che A + B non è uguale a C, naturalmente. Dato che l'attacco non ha informazioni su cosa siano effettivamente A e C, questo crea un numero elevato di valori possibili per B.
{08} In questo schema non è possibile suddividere in modo parziale i valori della parola in byte e attaccarli tutti separatamente. A causa della natura dell'aritmetica modulare (avvolgimento), A [0] + B [0] = C [0] NON garantisce che A + B = C. I bit da byte inferiori possono essere portati in byte più alti, e bit da quelli più alti i byte possono overflow in byte più bassi. È ancora probabile che l'attaccante sia in grado di capire quale dovrebbe essere il primo byte di B, ma diventa drasticamente più difficile con ogni byte successivo.
{09} Supponendo che si trovi un mezzo per sconfiggere facilmente questo tre esempio di parole a 64 bit in tempi ragionevoli, il concetto si estende arbitrariamente a un numero maggiore di variabili e metodi di derivazione delle funzioni più sofisticati dell'aritmetica modulare. Sembra praticamente ovvio che ci sia uno schema che sia abbastanza veloce per l'uso nel mondo reale e sufficientemente sicuro da scoraggiare quasi tutti gli attacchi plausibili.
{10} La tecnica può essere ulteriormente migliorata se riserviamo e garantiamo un'area di memoria per la memorizzazione degli input canarino. Solo uno dei valori di calcolo delle canarie deve effettivamente apparire sullo stack per rilevare il sovraccarico del buffer. Tutti gli altri possono esistere altrove, impedendo loro di essere letti facilmente anche se l'autore dell'attacco è riuscito a sovraccaricare lo stack durante un contesto di esecuzione di una funzione diversa.
È molto improbabile che io sia la prima persona a capire che ci sono miglioramenti significativi di quelli che possono essere fatti allo schema di protezione dello stack per difendersi da attacchi simili e simili. Mi limito a presentare questo come una banale prova di concetto, "just in case".