sfregamento parte 1

Fuzzing Parte 1: La Teoria

fuzzing

Prima di tutto, cos'è esattamente il fuzzing? Quando eseguiamo il fuzz test di un programma o di una funzione che riceve input (qualsiasi tipo di input), proviamo diverse combinazioni di input finché non otteniamo un crush o un altro risultato desiderato (spesso perdite di memoria). Quando un programma non disinfetta correttamente il suo input, l'input malformato o improprio potrebbe provenire dall'utente, il che può causare arresti anomali e comportamenti strani o indefiniti. Un buon esempio di fuzzing di cui probabilmente hai già sentito parlare è dirompente, quando proviamo URL diversi, iniziando da quelli comuni finché non troviamo directory legittime in un sito web. Strumenti come fuffa fare un ottimo lavoro.

Il fuzzing è particolarmente utile nei programmi scritti in linguaggi di basso livello o meno recenti, come Basic, Assembly, C/C++, ecc., che non impediscono al programmatore di scrivere errori come il typecasting improprio, l'uso dopo libero [di un puntatore], o overflow della memoria (buffer, stack, integer), che normalmente non portano necessariamente a crash in fase di esecuzione o errori in fase di compilazione e potrebbero essere sfruttati da un astuto attaccante. Di solito, quando viene menzionato il termine 'fuzzing', si fa riferimento a questo tipo di fuzzing.

Tipi di Fuzzing

Ci sono tre approcci al fuzzing che dovremmo elencare.

Fuzzing della scatola bianca

Con "whitebox fuzzing" ci riferiamo a un tipo di fuzzing in cui il fuzzer tenta di analizzare la struttura interna del programma per tracciare e massimizzare la copertura del codice. La copertura del codice si riferisce alla proporzione di filiali che abbiamo raggiunto nel nostro codice; ogni volta che un'istruzione condizionale come if or while viene eseguito, il codice si divide in un ramo in cui l'affermazione è vera e in un altro in cui è falsa. La logica è che se c'è un errore nascosto in qualche ramo, dobbiamo prima assicurarci di raggiungere quel ramo in primo luogo. Per eseguire questo tipo di analisi, il programma deve essere strumentato durante la compilazione.

La strumentazione funziona chiamando una funzione speciale ogni volta che viene eseguito un ramo, che lo registra come eseguito, a volte anche fino alla risoluzione di quale linea, in modo da poter tracciare la copertura. La strumentazione potrebbe anche aiutare a trovare bug. Ad esempio, Address Sanitation (ASan) aiuta a trovare perdite di memoria che normalmente non causano arresti anomali. Poiché l'analisi strutturale completa richiede molte risorse, il fuzzing della whitebox è lento, ma molto più efficace nel trovare bug e vulnerabilità, specialmente quelli nascosti in profondità nel programma. Ovviamente per eseguire una strumentazione estesa è necessario avere accesso al codice sorgente, che non è sempre disponibile, soprattutto come attaccante.

Fuzzing della scatola nera

In questo approccio, invece, non ci interessa la struttura del programma, e lo trattiamo come una scatola nera. Tuttavia, possiamo utilizzare l'output del programma per provare a capire cosa succede all'interno e quindi creare input più intelligenti ed efficienti. Poiché evitiamo il sovraccarico associato al fuzzing della whitebox, il fuzzing della blackbox è molto più veloce ma meno efficiente nel trovare errori e vulnerabilità.

Fuzzing della scatola grigia

Questo approccio cerca di trovare un equilibrio tra i rispettivi pro e contro dei suddetti approcci; utilizza una strumentazione leggera durante la compilazione invece di un'analisi completa per calcolare la copertura del codice e tenere traccia dello stato del programma. I fuzzer più popolari oggi, come AFL, HongFuzz e LibFuzzer sono fuzzer graybox. Per eseguire tale fuzzing, di solito è necessario il codice sorgente, ma ci sono soluzioni alternative se è disponibile solo un eseguibile, ma danneggiano seriamente le prestazioni. In questo articolo non toccherò queste tecniche.

Tecniche di Fuzzing

Come nel dirbusting, il fuzzing non deve essere fatto usando la forza bruta, cioè provando input casuali. In effetti, la forza bruta non è una buona tecnica per il fuzzing e questo viene fatto raramente. Fortunatamente, ci sono una serie di tecniche più intelligenti che portano a risultati migliori in quantità ragionevoli di tempo e risorse. Qui ne parlerò due:

Fuzzing basato sulle mutazioni

Questa tecnica si basa sul fuzzing graybox. Iniziamo con un input legittimo e ben formato e applichiamo un paio di tipi di mutazioni su di esso. Usando la strumentazione, cerchiamo di prendere le mutazioni di maggior successo, che rivelano nuovi rami, e usarle come seme per la generazione successiva. Questo processo ricorda in qualche modo la selezione naturale darwiniana che vediamo in natura. Abbiamo anche l'utile effetto collaterale che le mutazioni filtrate dal programma vanno fuori uso e ci rimane solo input funzionante.

Lo strumento più noto (e probabilmente il fuzzer più noto in generale) è AFL, o americano Fuzzy Lop.

AFL utilizza tre tipi di mutazioni di base:

  • Cancellazione di un bit casuale.
  • Inserimento di un bit casuale.
  • Capovolgimento di un bit casuale (da 0 a 1 o viceversa)

AFL non funziona con una sola mutazione alla volta, ma distribuisce le sue risorse tra un paio di esse e preferisce quelle con il maggior numero di mutazioni energia. L'energia di ogni mutazione si basa sui rami del codice che esegue: ai rami più rari, che passano la maggior parte degli input, viene assegnata più energia. Ogni ramo ha un hash univoco e questo consente di tenere traccia del numero di esecuzioni. È importante ricordare che l'input iniziale in questa tecnica può provenire da un dizionario contenente più di una variazione.

Fuzzing grammaticale

Probabilmente sei consapevole del fatto che molti programmi non possono ricevere input non strutturati, ma hanno regole specifiche su come può essere formato l'input. Queste regole sono chiamate sintassi dell'input, poiché sono simili alle regole sintattiche nelle lingue parlate. Per eseguire il fuzz test di tali programmi in un modo che copra la maggior parte delle opzioni sintattiche, il fuzzer deve essere a conoscenza della sintassi, altrimenti l'input prodotto utilizzando semplici mutazioni verrebbe filtrato e scartato. Tali fuzzer esistono già e possono fuzz JavaScript, WASM, URL e altri. ma al momento della stesura di questo articolo, la maggior parte di essi sono tutti sperimentali e lenti e, per quanto ne so, sono tutti scritti in Python, il che è ottimo per la prototipazione e le demo, ma non tanto per i fuzzer di livello di produzione ottimizzati. Pertanto, sono usati raramente.

Successivo – Parte 2

Nel prossimo capitolo “Fuzzing con AFL“, Dimostro come eseguire il fuzz di un semplice programma utilizzando AFL per trovare arresti anomali che potrebbero essere pericolosi nel mondo reale.

Salta al contenuto