Sei sulla pagina 1di 21

SOFTWARE

FUZZING

Una breve introduzione


Principali tecniche di bug finding

• Fuzzing
• Symbolic execution
• Static analysis
• Dynamic analysis
• Program verification

Di queste, il fuzzing è la tecnica che ha la maggiore accuratezza, allo stesso tempo è la più
scalabile e quella più facile da impiegare.
Breve storia del fuzzing
Il fuzzing, detto anche software fuzzing o fuzz testing, è una tecnica di bug finding inventata nel
1989 da Barton Paul Miller, professore dell’università del Wisconsin-Madison, che venne da lui
impiegata per testare l’affidabilità di diverse utilities del sistema operativo Unix.

Da allora, il fuzzing si è evoluto in varie forme, e molti fuzz testers sono stati inventati, che
combinano il fuzzing con le altre tecniche prima menzionate, e più recentemente anche con
tecniche di machine learning.
Cos’è il fuzzing

Il fuzzing è una tecnica di bug finding che consiste nell’inviare una massiccia quantità di dati di
input generati randomicamente, detti testcases, ad un programma target. I testcases possono
prendere la forma di file o anche messaggi di protocollo, a seconda del tipo di applicazione
testata.

Il fuzzing è una delle tecniche più promettenti per scoprire vulnerabilità, e nonostante i suoi
svantaggi e limiti, resta molto utilizzato, soprattutto nel mondo dell’industria. La qualità del fuzz
testing, tuttavia, dipende molto dalla qualità del pool dei testcases.

Vediamo adesso il processo di funzionamento del fuzzing.


Processo di funzionamento del fuzzing
1. Generazione dei testcases: la qualità della generazione influenza pesantemente il risultato
finale del fuzzing. Gli input dovrebbero rispettare le specifiche del formato di file o del
protocollo.
2. Esecuzione del programma: i testcases vengono somministrati al programma, i fuzzers
inizializzano e terminano automaticamente il processo dell’applicazione target.
3. Monitoraggio: l’esecuzione viene monitorata, per rilevare possibili crash o blocchi e per
raccogliere i testcases che li hanno provocati, in modo tale da poter fare future indagini.
4. Analisi: si cerca di capire le cause e la locazione del crash o blocco. Questo viene fatto
attraverso l’uso di debugger o di strumenti di binary instrumentation.
Tipi di fuzzers
Generation based vs Mutation based

Nel primo caso, è necessario conoscere la struttura del formato di file o dei messaggi di
protocollo. Tale conoscenza può essere acquisita mediante documenti di specifica o attraverso
gli RFC di riferimento, ma questo non è sempre possibile, specie nel caso di formati di file e
protocolli proprietari. I testcases vengono generati sulla base di un file di configurazione, che
funge da template.

Nel secondo caso, è necessario un pool di file iniziali. I testcases sono generati dalla mutazione
dei file iniziali. Due problemi chiave di questi fuzzer sono dove fare la mutazione e quali valori
utilizzare.
White box vs Grey box vs Black box

I white box fuzzers necessitano della disponibilità del codice sorgente. L’analisi del codice
sorgente permette di ottenere una comprensione più profonda dell’applicazione e di conseguenza
generare dei testcases migliori, che permettano una migliore copertura del codice.

I black box fuzzers, invece, fanno fuzz testing senza alcuna conoscenza del funzionamento
interno del programma target.

I grey box fuzzers stanno nel mezzo.


Directed vs Coverage based

Il primo tipo di fuzzers genera dei testcases che ci permettono di percorrere certi path del
programma, in questo senso è appunto diretto verso uno specifico path.

Il secondo tipo di fuzzers genera testcases in modo tale da coprire quanto più codice sorgente
possibile.

Il primo tipo permette un fuzz testing più rapido, mentre dal secondo tipo ci si aspetta una maggiore
copertura del codice sorgente. Un problema chiave di entrambi i tipi di fuzzers è come estrarre le
informazioni sui path eseguiti.
Smart vs dumb

Gli smart fuzzers correggono la generazione dei testcases in base alle informazioni raccolte su
come i testcases influenzano il comportamento del programma. Per i fuzzers di tipo mutation
based, il feedback ricevuto potrebbe servire a capire quale parte del file mutare e come mutarla.

I dumb fuzzers permettono una migliore velocità di testing, mentre gli smart fuzzers generano
testacases migliori e permettono una maggiore efficacia del processo.
Vantaggi e svantaggi del fuzzing

1. Vantaggi: facile da impiegare, alta accuratezza e buona scalabilità

2. Svantaggi: potrebbe essere necessaria la conoscenza del formato di file o del protocollo, che
potrebbero essere proprietari, inoltre gli input che generano un comportamento incorretto
potrebbero costituire una frazione molto piccola dell’insieme dei possibili input, per cui la
probabilità di accedere a certe parti di codice potrebbe essere molto bassa
Fuzzing mutation based e generation based a confronto

Fuzzing mutation based


• Facile da settare e automatizzare
• È richiesta poca o nessuna conoscenza sul formato dell’input
• Limitato dal corpus iniziale
• Potrebbe fallire per protocolli con meccanismi di checksum
Fuzzing generation based
• Può gestire meccanismi come il checksum
• Sono necessarie le specifiche del formato di file o del protocollo, non sempre facilmente
reperibili
• Scrivere dei generatori può essere difficile, le prestazioni del fuzzing dipendono dalla qualità
delle specifiche
Sfide chiave del fuzzing
Dove e come mutare

Nel caso dei fuzzers mutation based, capire dove e come mutare è fondamentale. Infatti, solo le
mutazioni in poche posizioni chiave del file potrebbero influenzare il flusso di controllo del
programma. Inoltre, un altro problema è determinare il valore che potrebbe dirigere il test verso
parti interessanti del programma.

In breve, una mutazione «cieca» potrebbe portare ad uno spreco di risorse computazionali e di
tempo, per cui una migliore strategia di mutazione accrescerebbe l’efficienza del processo.
Bassa copertura del codice

Una più alta copertura del codice sorgente si risolve in una copertura più alta degli stati di
esecuzione del programma, il che garantisce una maggiore probabilità di scoprire bug.

Molti testcases coprono gli stessi pochi path, mentre molto del codice non viene raggiunto.

I fuzzers coverage based tentano di risolvere il problema mediante tecniche di analisi del
programma, come l’instrumentazione del binario.
Passare la validazione dell’input

Spesso i programmi validano l’input, per controllare che sia conforme alle specifiche di
protocollo o di formato di file. Questo serve a risparmiare computazione e a proteggersi da input
malevoli e malformati.

I testcases non conformi alle specifiche potrebbero non passare il controllo.

I testcases generati dai black box fuzzers potrebbero non superare la validazione, il che
abbasserebbe l’efficienza del software fuzzing.
File format fuzzing
Molte applicazioni fanno della gestione dei file la loro attività principale, il fuzzing è usato
pesantemente per scoprire vulnerabilità in questo genere di applicazioni.

Il file può riguardare formati di file standard e non.

Un importante sottocampo di ricerca del fuzzing è il fuzzing dei browser, dato che i browser
permettono di gestire, oltre a tipi di file tradizionali come HTML, CSS e JavaScript, anche file
PDF, XML, SVG, solo per citarne alcuni.
Kernel fuzzing
Qui si verifica il problema di come raccogliere le informazioni sui crash, dato che un crash a livello
di kernel butta giù l’intero sistema.

Altro problema è come far interagire il fuzzer con il kernel, problema risolvibile attraverso l’uso
delle syscalls, qua però nasce la questione di come ordinare le chiamate di sistema, inoltre i valori
dei parametri delle chiamate dovrebbe essere random ma al tempo stesso ben formati secondo le
specifiche.

Inoltre è difficile instrumentare kernel closed source come quelli di Windows e Mac OS.
Fuzzing di protocolli
Molte applicazioni locali vengono trasformate in servizi di rete, e le applicazioni client comunicano
col server attraverso un protocollo.

Problemi di sicurezza nei protocolli possono portare a information leakage e denial of service, tra le
altre cose.

È però possibile che non siano disponibili gli RFC di riferimento per quanto riguarda un certo
protocollo, o che siano difficile da seguire.

Un esempio notevole di protocol fuzzer è SPIKE.


Quanto fuzzing è necessario?
• I fuzzer di tipo mutation based potrebbero generare un numero potenzialmente infinito di
casi di test. Quando possiamo dire che il fuzzing è stato eseguito per abbastanza tempo?
• I fuzzer del tipo generation based possono generare un numero finito di testcases. Cosa
succede se proviamo tutti i testcases e non riusciamo a trovare bug? Questo risultato deve
essere interpretato come un’assenza di bug oppure no?
Code coverage
Nasce dunque l’esigenza di definire la metrica del code coverage, usata per determinare quanto
codice è stato eseguito.

Dati su questa metrica possono essere ottenuti attraverso una varietà di strumenti di profilazione
come gcov o lcov.

Ci sono vari tipi di code coverage, tra cui il line coverage (quante linee di codice sono state
eseguite) e il branch coverage (quanti salti condizionali sono stati eseguiti).
Benefici del code coverage
Consente di rispondere a queste domande:

• Quanto è buono il testcase iniziale?


• Quanto è migliore il fuzzer X rispetto al fuzzer Y?
• Sto traendo dei benefici dall’esecuzione di più fuzzers differenti?
• Quali parti del codice sto eseguendo e con quale frequenza?

Potrebbero piacerti anche