Sei sulla pagina 1di 239

Sommario

Lezione 1 09/03 (1-Introduzione)....................................................................................................................5


Introduzione al corso e alle vulnerabilità software.......................................................................................5
Concetti chiave sulle vulnerabilità software.................................................................................................6
Vulnerabilità Software..................................................................................................................................7
Attack vectors ed exploit..............................................................................................................................9
Enumerazione delle vulnerabilità...............................................................................................................10
CVSS (common vulnerability scoring system).............................................................................................11
CWE (Common Weakness Enumeration)...................................................................................................13
CPE (Common Enumeration)......................................................................................................................13
Vulnerability disclosure..............................................................................................................................14
Vulnerability management.........................................................................................................................15
Lezione 2 11/03 (2-BufferOverflow)..............................................................................................................15
Buffer Overflow..........................................................................................................................................15
Mappatura della memoria dei processi......................................................................................................17
Stackoverflow.............................................................................................................................................18
Prevenzione buffer overflow......................................................................................................................21
Contromisure Buffer overflow....................................................................................................................22
Demo: scrivere un exploit con pwndbg/pwntools......................................................................................23
Lezione 3 16/03 (2-Lab-BufferOverflow).......................................................................................................33
Lezione 4 18/03 (3-Sanitizers).......................................................................................................................34
Problemi di gestione della memoria...........................................................................................................34
Heartbleed..................................................................................................................................................35
Esempio heap buffer overflow....................................................................................................................36
Integer overflow.........................................................................................................................................38
Format string attacks..................................................................................................................................40
Doouble-free & Use-After-Free..................................................................................................................41
Information leakage...................................................................................................................................42
Dirty pipe....................................................................................................................................................43
Sanitize.......................................................................................................................................................44
Valgrind......................................................................................................................................................46
Memcheck..................................................................................................................................................49
SGCheck......................................................................................................................................................54
I vari Sanitizer.............................................................................................................................................56
Lezione 5 23/03 (4-WebSecurity-ClientSideAttacks).....................................................................................58
Web security...............................................................................................................................................58
URL (Universal Resource Locator)...............................................................................................................59
http Protocol...............................................................................................................................................61
Standard HTTP/1.1 request methods.........................................................................................................62
Http RESPONSE...........................................................................................................................................65
Javascript....................................................................................................................................................66
Session management in web applications & Cookies.................................................................................67
Sessione demo di furto di cookie................................................................................................................69
Cross-Site Request Forgery (CSRF/XSRF)....................................................................................................69
Contromisure Cross Site.............................................................................................................................71
Lezione 6 25/03..............................................................................................................................................73
Cross site scripting attacks (XSS).................................................................................................................73
Non persistente (Reflected)........................................................................................................................74
Persistente (Stored)....................................................................................................................................74
Contromisure XSS: approccio di filtraggio..................................................................................................76
Contromisure XSS: approccio di encoding..................................................................................................76
Contromisure XSS: usare Content Security Policy.......................................................................................77
5-WebSecurity-ServerSideAttacks.............................................................................................................78
Contromisure SQL Injection........................................................................................................................81
OS Command & Code Injection..................................................................................................................82
Server-Side Request Forgery (SSRF)............................................................................................................83
Lezione 7 30/03..............................................................................................................................................83
Esercitazione Web security (5-Lab-WebSecurity).....................................................................................83
Lezione 8 01/04 (6-InputValidation)..............................................................................................................86
Input Validation..........................................................................................................................................86
Divisione del sistema..................................................................................................................................90
Validazione di tutti gli input........................................................................................................................90
Encoding e decoding...................................................................................................................................91
Unicode......................................................................................................................................................93
Numeri........................................................................................................................................................95
Paths,URLs..................................................................................................................................................95
Stringhe......................................................................................................................................................97
Regular expression.....................................................................................................................................98
Validazione con specifiche formali...........................................................................................................102
Lezione 9 06/04 (7-IntroFuzzing).................................................................................................................102
Fuzzing......................................................................................................................................................102
Fuzzing agli inizi........................................................................................................................................103
Positive vs Negative testing......................................................................................................................104
Profondità del fuzz testing........................................................................................................................106
Generation fuzzers...................................................................................................................................107
Mutation fuzzers......................................................................................................................................108
Failure monitoring....................................................................................................................................109
Tool...........................................................................................................................................................109
Lezione 10 08/04 (8-AdvancedFuzzing).......................................................................................................111
Advanced Fuzzing.....................................................................................................................................111
Advanced Fuzzing - Algoritmi Genetici.....................................................................................................111
Fuzzing basato su coverage di codice.......................................................................................................114
American Fuzzy Lop (AFL).........................................................................................................................115
AFL analisi della coverage.........................................................................................................................117
Feature aggiuntive - Dizionari...................................................................................................................118
Feature aggiuntive – Crash de-duplication...............................................................................................119
Feature aggiuntive – Triaging e troncamento di input..............................................................................119
Feature aggiuntive – Sanitizers.................................................................................................................120
Lezione 11 13/04 (8-Lab-Fuzzing)................................................................................................................121
Lezione 12 22/04 (9-StaticAnalysisIntro).....................................................................................................121
Static Analysis introduction......................................................................................................................121
Automated static analysis, limiti dei tool..................................................................................................122
Type checker............................................................................................................................................123
Style checkers / Defect finders / Quality scanners...................................................................................124
Security defect finders..............................................................................................................................126
Limiti teorici..............................................................................................................................................132
Lezione 13 27/04 (10-StaticAnalysisTools)..................................................................................................133
Findbugs...................................................................................................................................................133
GitHub CodeQL.........................................................................................................................................136
Anatomia di una query CodeQL................................................................................................................138
Dataflow analysis e Taint Analysis queries...............................................................................................142
Lezione 14 29/04 (10-Lab-StaticAnalysis)....................................................................................................146
Lezione 15 04/05 (11-SecureLifecycle)........................................................................................................147
Perché abbiamo bisogno di un ciclo di sviluppo software sicuro?............................................................148
Microsoft SDL...........................................................................................................................................148
Agile and DevOps......................................................................................................................................154
Lezione 15 06/05 (12-Attackers)..................................................................................................................156
Cyber Threat Intelligence.........................................................................................................................156
Tipi di CTI..................................................................................................................................................158
Threat actors............................................................................................................................................158
Piramide del Dolore..................................................................................................................................161
Cyber Kill Chain (CKC) model....................................................................................................................162
Diamond model........................................................................................................................................162
MITRE att&ck............................................................................................................................................164
Lezione 17 11/05 (12-Lab-Attackers)...........................................................................................................167
Astaroth malware.....................................................................................................................................167
Lezione 18 13/05 (13-BasicMalware)..........................................................................................................169
Malware analysis......................................................................................................................................169
Basic Static Analysis: Antivirus..................................................................................................................170
Packed/obfuscated malware....................................................................................................................172
PE Executable files....................................................................................................................................173
PE Sections...............................................................................................................................................176
Sandboxing...............................................................................................................................................181
Lezione 19 18/05 (13-Lab-BasicMalware)...................................................................................................182
Basic Static Analysis Lab...........................................................................................................................182
Lezione 20 20/05 (14-ReverseEngineeringX86)...........................................................................................182
Assembler.................................................................................................................................................183
Disassembly..............................................................................................................................................185
X86 ISA......................................................................................................................................................186
Registri......................................................................................................................................................187
Reverse Engineering.................................................................................................................................192
Variabili globali e variabili locali, costrutti................................................................................................192
Convenzioni per le chiamate di funzioni...................................................................................................196
Array e strutture.......................................................................................................................................199
X86-64......................................................................................................................................................201
Lezione 21 25/05..........................................................................................................................................202
Analyzing Windows Malware (15-WindowsMalware)..............................................................................202
Lezione 22 27/05 (15-Lab-WindowsMalware)............................................................................................224
Lezione 22 01/06 (16-MalwareDetection)...................................................................................................227
Malware Detection – Signature................................................................................................................227
Strategie di OPSEC più sicure....................................................................................................................229
Content-based Network indicators...........................................................................................................229
Combinare tecniche di analisi statica e dinamica.....................................................................................231
Tecniche di offuscamento.........................................................................................................................233
Base64......................................................................................................................................................235
La prospettiva dell’attaccante...................................................................................................................238
Lezione 24 08/06 (16-Lab-MalwareDetection)............................................................................................239

Lezione 1 09/03 (1-Introduzione)


Introduzione al corso e alle vulnerabilità software
Il corso parlerà di diversi argomenti quali:

 Software vulnerabilities
 Secure programming
 Static Analysis
 Fuzzing

Ed in più, rispetto all’anno scorso, visto che il corso è aumentato a 6 CFU vediamo anche:

 Cyber kill chain


 Malware analysis
 Malware detection

Ci sono un po’ di prerequisiti per il corso e il prof cercherà di fare qualche ripetizione ma se riusciamo ad
avere alcune nozioni su Linux, Intel x86, programmazione web, etc sono tutte super utili.

Di seguito c’è un ipotetico calendario di lezioni con un approccio Bottom-Up, partiremo dalle vulnerabilità
più importanti come il XSS e il buffer overflow fino a salire. Le lezioni segnate come LAB saranno dove
consolideremo attraverso il pc le informazioni acquisite.

I libri consigliati sono diversi e sono mostrati sulle slide, il primo è molto pratico e prenderemo alcuni
esercizi di laboratorio da qui. Il secondo è vecchiotto ma copre bene le basi di Malware analysis.

I vari esempi sono disponibili su una repository GitHub all’indirizzo “https://github.com/rnatella/swsec” e ci


fornirà anche una macchina virtuale con già i software installati.
Come si svolge l’esame finale? Non c’è una parte scritta ma un progetto da svolgere in 1-3 persone +
discussione orale (domande sul corso + domande sul progetto). Il progetto deve essere accordato prima
dell’esame.

I potenziali temi sono riportate sulla slide 10-11.

Il ricevimento è su appuntamento il giovedì mattina alle 11, online su Microsoft teams e c’è il link per il
calendario.

Concetti chiave sulle vulnerabilità software


Innanzitutto, rivediamo i classici concetti di assets e threat. Per assets intendiamo una qualsiasi cosa che
per l’azienda ha un valore ed include una vastissima moltitudine di elementi.

Nella software security quelli di nostro interesse saranno gli assets intangibili; quindi, dati sensibili o le
proprietà intellettuali ad esempio. Gli assets sono acceduti tramite software e rendere sicuro quest’ultimo
non fa altro che influenzare il primo, addirittura in alcuni casi addirittura il nostro software potrebbe essere
un assets.

Ripetiamo ovviamente la trinità CIA (Confidenciality, Integrity e Availability), ja wajù non fatemeli sbobinare
di nuovo so almeno quattro esami che li vediamo.

Ad un threat associamo un pericolo, una potenziale azione negativa, o evento causato da una vulnerabilità
che comporta un impatto non voluto su un elaboratore o un’applicazione.

Ma andiamo a vedere nello specifico i threat associati alla software security, visto che quelli sopra sono
quelli più generali. Quelli mostrati nell’immagine successiva provengono dall’ENISA che è una ente dal
quale possiamo approfondire alcuni dettagli.
L’ENISA oltre ad elencare threat elenca anche i così detti Threat actor ovvero coloro i quali attuano
effettivamente queste minacce e sono:

 State-sponsored actors, sono i gruppi di attaccanti malevoli sponsorizzati dallo stato. Non si
intende qualcuno pagato in maniera pubblica gli stessi, ma sovvenzionati in maniera nascosta dagli
stati per evitare problemi di relazioni internazionale. Sono ovviamente super pagati e strutturati in
maniera sofisticata. I loro tipi di attacchi sono i più disparati.
Spesso fanno uso di APT (Advanced Persistent Threats) ovvero metodi di attacco furtivi e
sofisticati applicati contro bersagli di grosse dimensioni. Gli attaccanti, una volta entrati in un sito,
non buttano giù un sito o rubano le informazioni ma rimangono silenti e cercano di massimizzare i
danni prima di poter mostrarsi.
 Cybercriminals, quest’ultimi sono motivati solamente dal profitto e quindi il loro interesse
principale è il furto di dati legati alle carte di credito, personali, etc. Le tecniche più usate sono
ovviamente l’attacco attraverso ransomware e criptominer.
 Hacktivist, persone mobilitate da ideologie politiche, ambientali, religiosi etc. Tendono ad attaccare
attraverso DDoS o doxing per attirare l’attenzione.
 Hacker-for-hire, di solito persone singole che lavorano su commissione creando exploit o malware.
Uno famoso è un certo Gookee, si crede russo, che creava exploit per i bancomat.

Il punto in comune tra tutti i threat actors visti sono lo sfruttare le vulnerabilità software.

Vulnerabilità Software
Esistono diverse definizioni, in base ai vari standard e documenti, ma per noi va bene usare quello di ENISA
che li definisce come: “L’esistenza di una debolezza, problema di design o implementazioni errate che
possono portare ad un evento indesiderato ed inatteso che compromette la sicurezza del computer, rete,
applicazione o protocollo”.

Le debolezze devono essere accessibili ed attuabili dagli attaccanti, altrimenti non sono debolezze.
Chiaramente il corso consisterà nel cercare di prevenire o mitigare, il più possibile, queste vulnerabilità
attraverso programmazione e sviluppo sicuro. Le categorie di vulnerabilità sono quattro, ma qualcuno
considera solo le prime due come vulnerabilità software (Design Flaws & bugs)

 I difetti di design possono essere la mancanza di access control quali la mancanza di log in o
protezione dei canali di comunicazione.
 I bug, che vedremo inizialmente, sono il buffer overflow o l’uso sbagliato di API etc.
 La misconfiguration è dovuta ad un uso di configurazioni standard facilmente attaccabili oppure
l’esposizione, non necessaria, di dati o superfici di attacco.
 Gli utenti che non posseggono buone pratiche di cybersecurity, quali password management o
l’usare i propri dispositivi in ambienti di lavoro.

Un’altra lista pubblicata da OWASP mostra i TOP 10 web application security risk, noi ci concentreremo
però sulle prime tre:

 Broken access control, capita quando la gestione delle sessioni sono implementate in maniera
errata. Ad esempio, usare API senza autenticazione o non proteggere i token d’accesso
 Cryptographic failures, i dati sensibili o segreti non sono sufficientemente protetti. Questo capita
perché le password non sono conservate in maniera hashata ma in chiaro, oppure perché le
comunicazione non sono protette, etc.
 Injection, quando capita di inserire dei dati in un sito attraverso forms o altro e se quest’ultimi non
vengono sanitificati è possibile che il nostro server esegui il codice inviato attraverso delle form.

Attack vectors ed exploit


Un vettore d’attacco è un qualcosa tramite il quale veniamo attaccati, ad esempio: E-mail, allegati, pagine
web, etc. Di per sé non sono cose negative.

Formalmente viene definito come il percorso o il mezzo attraverso il quale un attaccante può ottenere
accesso ad un computer e consegnare un payload (un qualcosa che contiene il software malevolo) o
ottenere un risultato malevolo.

Per exploit intendiamo un insieme di comandi, che sfrutta l’attack vector, attraverso il quale si ottengono
dei vantaggi attraverso le vulnerabilità.

Vediamo un esempio:

1) Un attaccante esegue un exploit


2) L’exploit fa
a. viaggiare su un attack vector un payload
i. Usando una e-mail
ii. Visitando un sito infettato
b. viaggiare il payload direttamente al server vulnerabile
3) Se il payload attiva la vulnerabilità ho l’effetto di far arrivare dei malware oppure ottenere l’accesso
remoto al sistema.

Ma una vulnerabilità che aspettativa di vita possiede? Vediamo insieme al grafico l’evoluzione temporale
della stessa. Il software viene rilasciato con all’interno un qualche tipo di difetto, ad un certo punto tale
difetto viene scoperto e viene inventato un exploit per attaccarlo (“in the wild” indica che siamo in una
situazione senza regolamentazione), la vulnerabilità viene scoperta dal produttore e ad un certo punto
verrà scoperta anche dal pubblico.

Il periodo di tempo in cui gli utenti non sono consapevoli della vulnerabilità ma qualcuno la sfrutta, si
chiama zero day.
Quando la vulnerabilità è nota qualcuno ci applica una pezza attraverso gli IDS, finalmente il creatore
rilascia una patch di correzione per eliminare la vulnerabilità ed infine la patch dovrebbe essere installata
dagli utenti.

L’arco chiamato follow on attack consiste in quel periodo nel quale, nonostante la vulnerabilità sia nota
essa può comunque essere sfruttata. La windows of exposure indica tutto l’arco temporale, dalla
introduzione fino alla sua rimozione.

Enumerazione delle vulnerabilità

I team di analisi hanno il compito di scoprire le vulnerabilità software ed una volta scoperte devono essere
pubblicate attraverso dei bugiardini di sicurezza, ed archiviate in database online. Per evitare uno sforzo
inutile e di pubblicare duplicati, il MITRE ha introdotto lo schema di common vulnerabilities and exposures
(CVE). Non è l’unico ma è quello più usato, esso fornisce enumerazione e catalogazione.

La sua forma è la seguente:


Attenzione però a non confonderci, quando si usa una CVE si sta parlando di una specifica vulnerabilità di
uno specifico prodotto, in questo caso il “2014-0160” ci informa di un bufferoverflow in OpenSSL v1.0.1.

Per chi vuole approfondire c’è il sito CVEdetails.com, nel dettaglio abbiamo che il CVE è strutturato come:

 Una descrizione della vulnerabilità


 Un punteggio di gravità CVSS (common vulnerability scoring system).
 Un CWE (Common Weakness Enuemration)
 Una lista di CPE (prodotti impattati dal CVE)
 I link agli exploit ai fini di testing.

CVSS (common vulnerability scoring system)


I coefficienti del CVSS sono diversi e si mischiano tra di loro, noi ne faremo una panoramica non ci
interessano più di tanto.

 Le metriche di base sono legate alla vulnerabilità al di fuori di qualsiasi contesto e vi è un


questionario associato ad ogni elemento
Vediamo un esempio, abbiamo una vulnerabilità segnata come CVE-2009-0658 Adobe Acrobat Buffer
Overflow nella quale una vittima, se apre un pdf malevolo, consente l’attaccante di eseguire codice
malevolo.

Lo score è di 7.8.

L’attack vector è locale o network? Uno potrebbe pensare: “va beh io lo mando online e quindi è network”,
si è vero ma per abilitarlo ci deve essere l’utente che apre il file e quindi è locale. La complessità
dell’attacco? Bassa. Privilegi richiesti? Nessuno, chiunque può aprire. User interaction? Richiesta, è l’utente
che deve aprire.

Sintetizzato il CVSS viene mostrato in questo modo:

 Le metriche temporali tengono conto del tempo per cui la vulnerabilità è conosciuta, più tempo
rimane in giro e più è conosciuta
 Le metriche ambientali che dipendono in quale ambiente si trova la vulnerabilità, se nella mia
azienda ci sono degli assets di un certo tipo sono più o meno vulnerabile a tale attacco

CWE (Common Weakness Enumeration)


Classifica non i singoli prodotti vulnerabili ma le categorie di vulnerabilità, ad esempio il buffer overflow ha
il codice CWE-120. Le liste ufficiali sono visualizzabili al link https://cwe.mitre.org/.

Il CWE è strutturato come un’enciclopedia con gli oggetti ordinati per diversi elementi, la vulnerabilità
generica è conosciuta come classe. In alcuni casi abbiamo gli oggetti base che sono più specifici e spiegano
come rilevare/prevenire tale vulnerabilità. Variant sono invece super specifici, magari per un determinato
linguaggio. Infine, abbiamo le categorie che mostrano, ad esempio, un gruppo di oggetti con un attributo in
comune.

CPE (Common Enumeration)


È un modo per elencare i prodotti vulnerabili, non è noto ma spesso i prodotti che vengono
commercializzati hanno nomi diversi nella stessa azienda o in base a chi viene acquistato. Nell’esempio
vediamo windows 2000.
Vulnerability disclosure
Come si pubblica una vulnerabilità? Non esiste un modo perfetto di farlo, spesso vi sono molte entità in
gioco con interessi contrastanti.

Esistono due scuole di pensiero principali:

1) Responsible disclosure, qui l’analista che trova la vulnerabilità prima comunica con chi vende e gli
fornisce un periodo di tempo per sistemare il problema. Scaduto il tempo fornito si informa il
pubblico di tale vulnerabilità, durante questo periodo gli utenti sono molto penalizzati.
2) Full disclosure, l’analista subito la pubblica e delle volte anche con un bel exploit disponibile in
modo da forzare una riparazione il più velocemente possibile.

I CSIRT (computer security incident response teams) sono i team di cybersecurity e si occupano di tutta una
serie di operazioni quali:

 Information and incident handling


 Security monitoring
 Vulnerability management
 Situational awareness
 Cybersecurity knowledge management
Di seguito l’immagine mostra cosa fanno di solito i partecipanti ai CSIRT, da non confondere con i SOC
(security operation center) che si occupano di altro.

Vulnerability management
Cosa devono fare le aziende per gestire tali vulnerabilità? Aggiornare senza pietà, non ci protegge dagli zero
day ma almeno ci protegge da quelli noti. Sulle slide ci sono piccole analisi per le cose specifiche.

Lezione 2 11/03 (2-BufferOverflow)


Buffer Overflow
Oggi rivedremo l’attacco di buffer overflow, ma ovviamente con una visione più focalizzata per la sicurezza
software. Per avere un buffer overflow abbiamo bisogno di:

 Un buffer (un area di memoria) a dimensione fissa, come un array.


 Dei dati da scrivere sull’array e almeno una parte di questi dati viene scritta al di fuori dal buffer, di
solito alla fine del buffer ma non è detto.
Un altro modo per chiamare buffer overflow è buffer overruns, in più in base a dove si trova il buffer
abbiamo stack overflow o heap overflow.

Il buffer overflow è uno dei primi tipi di attacchi della storia dell’informatica, addirittura con un tipo di
Worm (il quale si replica da una macchina all’altra) che sfrutta vulnerabilità di tipo buffer overflow. Ancora
oggi ci sono un sacco di buffer overflow, addirittura SUDO di Linux può essere soggetta ad attacco
permettendo di vedere la password durante la dicitura.

I linguaggi di programmazione di sistema sono intrinsecamente soggetti al buffer overflow mentre quelli di
più alto livello prevengono o fanno auto resize del buffer.

Per vedere in che modo funziona il buffer overflow ripetiamo prima alcuni concetti di base C/C++, nello
specifico come funzionano le stringhe:

 \0 carattere di terminazione che non compare a video

Vediamo un buffer di 6 caratteri dei quali 5 sono il carattere “Hello” e poi c’è il terminatore che possiede un
byte pari a zero. Il NUL character è diverso da NULL ma per sicurezza lo chiameremo NIL per evitare
confusione con il valore NULL.

 Allocare un array

Per allocare un buffer in C scriviamo la classica notazione per array e nell’esempio abbiamo 10 caratteri di
cui 9 al massimo sono disponibili e uno per terminatore. Il linguaggio C prevede che dobbiamo creare
sempre un array di dimensione almeno pari a tale dimensione, ma lo standard non dice nulla se ne
allochiamo uno di dimensione minore.
Vediamo adesso un esempio di funzione che legge da tastiera una stringa gets( ), e di come a livello di
codice esista una vulnerabilità di tipo buffer overflow.

Quindi se scriviamo qualcosa con meno di 9 caratteri tutto va per il meglio, se ne usiamo di più il
programma ci restituisce segmentation fault e quindi il processo viene ucciso dal SO.

Questo capita perché lo standard C non definisce cosa accade in caso di errore, fissare un comportamento
richiede una VM come Java.

Purtroppo, per capire l’attacco dobbiamo rivedere nuovamente come funziona il processore, quello che
vedremo è basato su “Smashing the stack for fun”.

Mappatura della memoria dei processi


Quando abbiamo un programma abbiamo a disposizione una memoria virtuale (quindi byte) dove può
mettere tutti i suoi dati e tutto il suo codice che è fatta in questa maniera:

L’esempio è 232 byte per un totale di 4 GB (0 xFFFFFFFF ¿

 Area testo (in verde), corrisponde ad una copia del programma che lanciamo
 Area dati globali, tutte le variabili dichiarate fuori dal main o dalle funzioni
o Global data inizializzati
o Global data non inizializzati
 Area heap, usata quando usiamo funzioni come malloc()
 Area stack

Parleremo poi da qui in avanti del processore Intel x86. Ricordiamo poi che ci sono tantissime variazioni,
come la programmazione multi thread che comporta la creazione di stack diversi.
Stackoverflow
Concentriamoci per ora sullo stack overflow, lo stack viene richiamato ogni volta che chiamiamo una
funzione e lo stack salva:

 L’indirizzo di ritorno, “da dove vengo”


 I parametri di chiamata,
 Le variabili locali (se vengono create dalla funzione),
 Il valore di ritorno pure sullo stack

Vediamo un esempio dove sopra abbiamo un main in C e sotto il codice assembly.

“f” è l’indirizzo del codice ed è un numero. Abbiamo in questa immagine quattro push, perché si conta
anche call che fa una push sullo stack.

Tipicamente lo stack cresce dal basso verso l’alto, SP (stack pointer) indica la cima dello stack e l’indirizzo
di ritorno viene messo sopra i tre parametri.
La funzione f() possiede due variabili locali, la dimensione totale del buffer richiesto sarà da 15 byte ed
infatti il codice assebly userà una subl con un valore pari alla somma delle due variabili locali.

Il valore $20 è in esadecimale ed è pari a 32

Lo SP viene fatto salire sopra a tutto e vengono collocati i due buffer, ma viene aggiunto un altro valore ed è
il frame pointer (FP) ovvero un registro dove segniamo l’indirizzo delle variabili locali e viene salvato in
%ebp frame stackpointer (fotografia dello stack prima di mettere gli array sopra).
Ma cosa sta succedendo? Ora quando si ha un buffer overflow abbiamo che esso sfonda il buffer e quindi
partendo dall’alto scende fino alla base dello stack. Questo vuol dire che tutti i pezzi sotto vengono
sovrascritti ed il punto critico che stiamo modificando è il return value, che sarà intenzione dell’attaccante
il cambiarlo. Il nuovo valore di ritorno sarà del codice specifico scelto dall’attaccante.

Un possibile risultato di un attacco sarà:

La cosa più importante da capire oggi è: come calcolare l’indirizzo da mettere nel return pointer.

Domanda, se io ho caricato i dati nel buffer perché dovrei avere tutti questi problemi a farli eseguire?
Semplicemente perché i dati nei buffer vengono solo letti e non eseguiti e quindi si trovano nella parte
verde del primo grafico della struttura in memoria.

L’arte di creare un messaggio di buffer overflow è sartoriale per cui un pezzo di codice finisce nel mezzo
dello stack, alla fine invece abbiamo un indirizzo e lo NOP Sled (che è un riempitivo)

Vedremo nelle prossime settimane gli altri tipi di buffer overflow ma incominciamo a vedere un’immagine
dei vari tipi:
Prevenzione buffer overflow
Ora la domanda è più che lecita, come si provengono i buffer overflow?

La cosa più naturale e semplice è quella di scartare tutte le funzioni che non sono sicure by-design, quindi:

 gets(), legge l’input senza controllarlo


 strcpy (3 ) , strcat (3), copia e concatenazione da sorgente a destinazione e non hanno un limite
 Non solo funzioni, anche dei semplici cicli possono causare overflow.

Quelle che apparentemente sembrano sicure sono:

 char∗strncpy (char∗DST , const char∗SRC , ¿ ¿ t LENGTH ), vi sono tre parametri


o Destinazione
o Sorgente
o Dimensione
 char∗strncat (char∗DST , const char∗SRC , ¿ ¿t LENGTH )
 ∫ sprintf ( char∗STR , const char∗FORMAT ,...);
Ma perché sono apparentemente sicure? Prima di tutto perché non sono di facile utilizzo, in secondo
presentano alcuni problemi. Ad esempio, la funzione di copia o concatenamento non usano il terminatore
Questo porta ad un attacco nel quale manipoliamo l’URL. Un altro esempio riguarda snprintf ( )

In questo caso quando c’è un overflow non ci dice che è minore di zero ma ci restituisce il secondo ramo e
quindi il numero di caratteri che avrebbe scritto. Allora qual è l’unico modo corretto? Questo seguente
dove usiamo snprintf () con ulteriori parametri ovvero la lunghezza della sorgente % .∗s.

La lunghezza serve ad evitare, come succede nella snprintf () normale, che un attaccante inserisca un
messaggio senza terminatore.

Esistono poi librerie esterne che sistemano alcune problematiche, ma non sono interoperabili o non sono
già installate, etc.

Contromisure Buffer overflow


Esistono due macro-famiglie di difese:

 Approcci basati su Compilatori


o Canarino
o LibSafe
o Alternative inserite dal compilatore (Fortify_source)
 Approcci basati a tempo di compilazione/SO
o Stack non eseguibile
o Allocazione dei dati/codice randomizzato all’interno della memoria (ASLR)
Vediamo un attimo l’approccio basato su canarino. Lo stack, prima di eseguire la funzione immette un
valore tra l’indirizzo di ritorno e il resto dello stack e tale valore è il canarino e questo stesso valore viene
salvato in un registro segreto casuale e non è scritto nel programma. Supponiamo che vi è stato un buffer
overflow, prima di effettuare il return la funzione legge il canary e confronta il valore con il registro. Se il
valore non coincide allora c’è stato un problema e blocca l’esecuzione.

Vi è poi un altro elenco di difese e attacchi, ma spesso sembra giocare al gatto e al topo

Demo: scrivere un exploit con pwndbg/pwntools


Vediamo un laboratorio/esempio valido su distribuzioni linux con processore INTEL NO ARM, nel caso
vanno cambiati dei parametri 1:01:00.

L’esempio riguarda un programma vulnerabile che si mette in ascolto su un porto di rete, il programma è di
tipo “echo” e per fare ciò usa una funzione chiamata copier che ha un buffer di 1024 byte e fa operazioni
non sicure.

La stringa può essere scritta su 4000 byte ma può essere copiata su una di mille; quindi, ci sono tutti gli
ingredienti per un disastro.
Gli screen successivi saranno quelli della macchina del prof. Nello specifico lancia un eseguibile $ ./vuln server
che si mette ad ascoltare una socket, apriamo un’altra shell ed utilizziamo l’applicativo netcat
nc localhost 4001 e vediamo che si collega.

Le PIPE in Linux concatenano le informazioni creando un fork di processi. Prima di fare l’attacco dobbiamo
disattivare tutta una serie di difese:

 Disattivare la randomizzazione degli indirizzi


 Disattivare lo stack non eseguibile
 Disattivare lo stack-guard (Sarebbe il canarino in Linux)

il parametro -g è comodo per vedere il debugging in maniera più pulita, verrà così generato un file chiamato
“vuln_server”.

Domanda: Questo file generato usando i seguenti flag è sempre vulnerabile, oppure dobbiamo rilanciarlo
ogni volta? No, è ormai vulnerabile.
Scarichiamo ed usiamo poi altri due pacchetti mostrati di seguito

Sulla slide vi sono alcuni comandi utili.

Se noi invece di mandare una stringa limitata a poi byte ne mandiamo una da 1200 (che è maggiore di 1024
lunghezza del buffer), che succede? Il server viene ucciso dal SO
E già così abbiamo realizzato un DoS, ma noi vogliamo fare di più e vogliamo iniettare del malicious code.

Usiamo adesso Pwndbg, e lo lanciamo usando $ gdb ./vuln server il programma però per eseguire ha bisogno
anche del comando $ run.

Reinseriamo il comando di python 3 della slide precedente ed otteniamo diverse cose, ovviamente il
processo è nuovamente ucciso ma gdb mi dà ulteriori informazioni. Vediamole tutte nelle successive
immagini:

Il programma si è interrotto nella funzione copier() alla riga 14, senza aprire il sorgente scendendo vediamo
il pezzo di codice dove si trovava il processore al momento del crash. Di seguito abbiamo anche l’immagine
dei registri RAX , RBX , RCX , RDX che sono i registri dati (non li vedremo tutti) mentre i più importanti
sono il RSP (stack pointer register) e RIP (instruction pointer register) che indica l’indirizzo del codice che
stiamo eseguendo.

Se facciamo un passo indietro abbiamo che dove punta il RIP, 0 x 5555 etc è l’area verde di testo mentre lo
stack si trova all’indirizzo molto distante 0 xFFFF .
Di seguito invece abbiamo lo stack inondato di A

Nel registro RBP troviamo invece dei valori 0 x 4141414141414141 che è la rappresentazione numerica
di “A”. Per avere più informazioni scriviamo disass più la funzione della quale vogliamo avere più
informazioni, nello specifico vediamo il codice assembly. La stringa sottolineata (con la freccia sulla sinistra)
dice dove ci trovavamo prima della morte.

Il punto chiave è ret il quale prende l’indirizzo di ritorno dallo stack e fa sì che il processore lo esegua,
normalmente su questo registro dovremmo scrivere qualcosa che è del tipo 0 x 5555 ovvero quello del
main; mentre adesso si trova il 0 x 4141414141414141 ed è questo il problema.

Le slide mostrano le stesse cose, forse c’è solo qualche valore diverso perché c’è qualche altra roba nei
registri.

Vediamo alcune immagini per capire meglio com’è realizzato a livello tecnico l’attacco

Quello che è successo è questo, l’immagine è un po’ una rappresentazione di tutta la mia vita, un urlo
continuo.
Se volessimo fare un attacco serio non inseriremmo delle semplice “A” ma inseriremo all’interno
dell’Instruction pointer l’indirizzo dell’inizio del buffer. La freccia nell’immagine successiva sarà più in basso
e nello specifico alla zona NOP, ma come calcoliamo e cosa ci mettiamo in questi registri?

Il valore X che dobbiamo decidere lo dobbiamo calcolare facendo così:

1. Leggeremo il valore dell’RSP al momento del crash, prendendolo da gdb


2. Sottrarre un offset
Calcolare l’offset non è semplice perché non conosciamo precisamente qual è la distanza tra l’inizio del
buffer e il puntatore. Per calcolarlo si utilizza un trucco, ovvero il ciclo di De Bruijn; quindi, invece che
inserire la sequenza di “A” inseriamo delle sequenze particolari

Le sequenze possono sembrare casuali ma in realtà ogni 8 caratteri abbiamo una stringa definita in
maniera unica rispetto ad un qualsiasi altro ottetto. È matematicamente dimostrato, non è magia.

Quindi noi inseriamo questo ciclo e prendiamo la stringa che vediamo nel registro RSP. Giustamente non
cercheremo a mano dove si trova la stringa ma useremo il comando $ cyclic−n 8−l eaaaaaaf e
l’algoritmo ci restituirà un valore decimale, in questo caso pari a 1032, da sottrarre come offset.
Facciamo un po’ di conti, abbiamo i 1032 byte + 8 byte (contenenti l’indirizzo di ritorno). Dei 1032 byte
dobbiamo mettere:

 Lo shellcode che non generemo noi, ne useremo uno già fatto e la sua dimensione è di importanza
fondamentale, perché se occupasse troppo potrebbe sovrascrivere le sue stesse aree dati.
Potremmo scriverlo ma non è necessario (in questo caso 57 byte).
 Le NOP sled che vanno calcolate
 NOPs di 64 byte (un valore arbitrario)

Come generiamo lo shellcode? Utilizziamo l’altro programma che abbiamo scaricato, sull’immagine c’è
tutto il processo di creazione
Sulla slide successiva c’è segnato di cambiare l’indirizzo di ritorno dello script, e lo troviamo nelle prossima
slide:
Il +128 è importante e lo spiegherà dopo.

Se tutto è andato bene, vedremo come dall’immagine che il server non è morto ma è uscito normalmente,
senza accorgersi di nulla, mostrando un “hello world!!”

Sulla documentazione troviamo numerosi shellcode library, nello specifico noi useremo quelle segnate nel
quadrato azzurro e faremo collegare il bersaglio (aprendo una shell) verso di noi.

Nell’esecuzione live apriamo un terzo terminale per avere tutto attivo, il 3° è il server, il 2° è chi ha lanciato
il messaggio, infine, il 1° mostra la connessione attiva attraverso una shell.

Se in 1° scriviamo $ ls otteniamo l’esecuzione di tale comando sul server e vediamo i file disponibili
Sulle slide finali ci sono altri esempi ed informazioni

Spieghiamo finalmente perché abbiamo aggiunto quel +128, se ripetessimo l’attacco senza gdb non
avremmo successo perché l’indirizzo visto vale solo se eseguito in gdb. L’aggiunta del valore +128 è fatta
appositamente per consentire l’attacco fuori da gdb e lo spazio aggiuntivo funziona proprio come un
cuscinetto.

Lezione 3 16/03 (2-Lab-BufferOverflow)


Nella giornata di oggi si è fatto laboratorio, sia per ricreare l’attacco visto ieri, sia per farne uno nuovo.
Introduciamolo brevemente in modo da poter essere più facilmente capito attraverso la lettura delle slide.

Il programma che andremo ad attaccare è “wisdom-alt.c” ed è trovabile sul link di gitHub visto il primo
giorno. Questo programma funziona in maniera molto banale, con 1) inseriamo una perla di saggezza e con
2) ci mostra le frasi inserite fino a quel momento.

Vi sono due vulnerabilità, una si riconosce facilmente:

 Quando inseriamo la frase, se è troppo grande il programma crusha


 Legata all’array globale “ptrs”

Lo scopo dell’esercizio è fare in modo che il programma esegua due funzioni: write_secret(),
pat_on_back(). Che sono nel programma ma non vengono mai chiamate, e il nostro buffer overflow
dovrebbe fare in modo che ritornino su queste.

Bonus dell’esercizio è iniettare dello shellcode nel programma. Nelle slide ci sono dei suggerimenti,
innanzitutto il nostro payload sarà diverso perché dobbiamo prima la stringa “2” e poi invio.

Attenzione l’eseguibile è a 32bit e quindi gli indirizzi sono di 4 byte invece che 8 come visti l’ultima volta.

Ci sono poi i suggerimenti per chiamare la funzione “write_secret” e il template per l’exploit. Bisogna
inserire l’indirizzo di write_secret() o dello shellcode infine bisogna mettere l’offset (l’altra volta era 1032
tipo).

In questo caso l’istruzione RET non legge il registro SP, ma quello IP perché il modo in cui cresce lo stack è
differente con questi processori.

Suggerimenti del prof a fine lezione:

Occorre concatenare il payload e in più -n 4 perché dobbiamo fare a 4 byte e non a 8.

Dopo ottenuto il valore classico su RIP calcoliamo l’offset (nel caso qui era 151) e lo inseriamo nello script
“gen_shellcode.py”

Dobbiamo inserire anche l’indirizzo della write_secret(), attenzione per ricavare il valore seguiamo le slide
perché abbiamo bisogno che il programma sia in run altrimenti il valore ritornato non è corretto.
Lezione 4 18/03 (3-Sanitizers)
L’argomento di oggi sarà una categoria di tool che prende il nome di sanitizer, vedremo anche un’ulteriore
panoramica degli attacchi a basso livello.

Problemi di gestione della memoria


Tutt’oggi moltissimi software scritti in C/C++ presentano ancora moltissime vulnerabilità legate alle gestioni
della memoria, questo si vede soprattutto nei web browser che mostrano costantemente il fianco a queste
cose. Il progetto chrome mostra una roadmap di come dovrebbe evolvere la sicurezza e le best practise per
risolvere queste cose

Usare un GC (garbage collector) è la soluzione intermedia ma è una soluzione pesante e rallenta


l’esecuzione, le migliori configurazioni sono quelli che usano nuovi linguaggi di dominio come Rust. In
questo linguaggio è impossibile compilare un programma che ha problemi di memoria e si parla di tecniche
statiche, mentre Java effettua tali controlli a run time.

Il problema dei buffer overflow è subdolo, perché immaginiamo di avere un programma che sovrascrive un
buffer potremmo avere che lo stesso potrebbe continuare a funzionare tranquillamente nonostante ne
abbia subito un buffer overflow. Il fatto che un programma non venga ucciso da un SO può capitare per
diversi motivi:

 Il return pointer non è sovrascritto


 La sovrascrittura è ancora all’interno dell’heap/stack area
 Il buffer è letto oltre il suo limite.
 Ce ne sono altre di situazioni ancora più subdole.

Se vedessimo i vari CWE noteremmo che esistono numerosissimi tipi di buffer overflow. Osserviamo adesso
probabilmente il più recente e famoso:

Heartbleed
È un buffer overflow di tipo overread, e non essendo una sovrascrittura non avremo bisogno di uno
shellcode, ovvero leggiamo dati che non dovremmo leggere. Questo tipo di attacco riguarda la libreria
OpennSSL che viene struttata da server SSH.

La vulnerabilità consta del fatto che è possibile leggere altre informazioni oltre a quelle che richiede, e visto
che SSH contiene al proprio interno molte informazioni sensibili è chiaro che tale vulnerabilità è gravissima.
Ma capiamo come funziona; il server accetta le richieste di un client di heartbeat ovvero risponde alle
richiede di “sei vivo?”, il server risponde alla richiesta (stringa più la sua lunghezza) ma se l’attaccante
manda un messaggio di 3 lettere e ne richiede 500 avrà tranquillamente più lettere di quante ne dovrebbe
ricevere.

Esempio heap buffer overflow


Per capire meglio il ruolo del SO vediamo questo heap overflow; quindi, questa volta i dati sono allocati
con una malloc ().

Nell’immagine allochiamo un puntatore a X con 100 di dimensione del buffer, ma se l’attaccante passa un
valore più grande avremo un buffer overflow solo che stando nell’area heap è chiamato heap buffer
overflow. Ma capiamo meglio con l’esempio in esecuzione
Perché in un caso crasha ed in uno no? Vediamolo nel debugger gdb.

Se stampiamo l’indice del ciclo notiamo che si è fermato a 134496 iterazioni e possiamo raccogliere altri
dati, come l’indirizzo del buffer e l’indirizzo di fine.

Notiamo però una cosa anomala, i tre “000” alla riga di gdb $2, e vediamola per bene con un disegno.

Ricordiamo che l’heap è un’area del processo preallocata dal SO e quando facciamo malloc () abbiamo
che viene tagliata una fettina di quest’area. Facendo il ciclo for sovrascriviamo i 100 elementi e tutto
quello che ci sta vicino, e si ferma all’indirizzo 0 x 55555557 a 000 . Come mai? Perché i SO usano per le
pagine quando creano l’area di memoria per un processo, non allocano i singoli bit, e le stesse sono
tipicamente 4 Kb (linee rette nella figura). Sono da vedere come se fossero delle mattonelle e noi
possiamo accedere solo alle zone verdi e non bianche.
Questa cosa però è controproducente perché i buffer overflow piccoli fanno sì che si rimanga all’interno
dell’area verde non si nota il problema.

Esistono poi altri attacchi relativi al buffer overflow, vediamone alcuni.

Integer overflow
Non parliamo più di buffer ma in questo caso stiamo parlando di numeri, e sappiamo che i numeri sono su
un numero fisso di byte. Cosa succederebbe se facessimo arrivare una variabile al massimo rappresentabile
e poi usassimo le notazioni come “++”?

È un bel casino, la tabella alla destra, infatti, riassume un po’ uno studio in cui si mostrano le casistiche dei
comportamenti di C per determinati compilatori e processori.

Questa cosa è super pericolosa, perché se sbagliamo il calcolo di un intero e questo viene usato in un array
possiamo avere un buffer overflow. Vediamo un esempio ipotetico: Il programma prende un numero ed
una stringa e taglia quest’ultima in base al numero che le abbiamo dato e li mette in un buffer di
dimensione 10.
Questo programma è poi scritto in maniera tale che se passiamo il valore 65536, non a caso, abbiamo il
buffer overflow.

Perché quel valore non è casuale? Il programma passa quel valore e poi si ha la conversione, il programma
usa un ∫ ¿ con segno e quindi 4 byte; il programma ha poi un bug voluto dove n viene copiata in len che è
ti tipo unsigned short di 2 byte (su alcune architetture) tagliano il numero.

Il programma controlla infine che 0 è più piccolo di 10 e allora lui continua.


Format string attacks
Questo tipo di attacchi è un po’ meno comune, ma quando usiamo le funzioni come printf () o scanf ( )
dobbiamo fornire una stringa formato che usano i “%” in aggiunta a delle variabili. Ma se usiamo la seconda
opzione come nell’immagine usiamo in maniera pericolosissima le funzioni sopra mostrate, questo perché
se l’attaccante mette dei “%” nella stringa potrebbe prendere possesso della printf (), che possiede un
proprio linguaggio.
Doouble-free & Use-After-Free
Questo tipo di attacchi riguarda l’uso di new , free , malloc( ), delete , etc … infatti C/C++ non includono un
garbage collector automatico, capita quindi che, una volta finito con l’uso di memoria allocata è il
programmatore che deve manualmente liberarla.

Vediamo l’esempio dove:

1. Creiamo un oggetto attraverso new


2. Delete del puntatore
3. Il puntatore va sul myMethod()

L’oggetto è stato deleted, ma il puntatore è stato cambiato? No, “p” contiene ancora l’indirizzo dell’oggetto
precedente. Questa mancanza di pulizia crea la vulnerabilità, i dati non vengono puliti per una questione di
velocità.

Questo metodo myMethod() esegue? Si, ma funziona con i dati sporchi di prima. L’attaccante potrebbe
quindi iniettare la stringa rossa, magari tramite buffer overflow, con “q” stessa dimensione di “p”.
Purtroppo, l’allocatore del SO riusa l’area creata precedentemente, se della stessa dimensione, e c’è quindi
una probabilità che “MaliciousClass()” sovrascriva “p”, è una specie di polimorfismo malefico per cui il
puntatore p viene risolto a tempo di compilazione.

Tale problema si chiama use-after-free

Vediamo un esempio, spesso sono i browser ad essere soggetti a questi attacchi perché sono programmi
legati agli eventi.

In questo caso il browser Safari è vulnerabile ad un attacco di use-after-free che porta ad una possessione
del browser.
L’attaccante può scegliere l’ordine di esecuzione dei tag con il quale eseguire l’attacco, quando il browser
legge queste cose segnate in rosso abbiamo:

1. Creazione di un oggetto animator e vi è il tag animate a sinistra


2. C’è una chiamata a JavaScript che esegue le due funzioni centrali
3. C’è il delete
4. La funziona continua ad eseguire ed usa l’oggetto animator, che in teoria è stato cancellato e si è
creato il use-after-free.

Ultima cosa è ovviamente usare l’oggetto malevolo, quindi l’attaccante potrebbe fare un “for” per allocare
oggetti malevoli

Tale operazione di allocare tutti questi oggetti nell’heap si chiama heap spraying.

Information leakage
In C/C++ le variabili non sono inizializzate in maniera automatica, se un programma non le assegna sono
undefined e prendono i valori che si trovano sullo stack, nell’esempio successivo avremo che
stamperemmo spazzatura.
Vediamo un altro caso, una chiamata di sistema che prende in ingresso “skb” che è un buffer per i pacchetti
di rete ed un parametro d’uscita. Ci troviamo nel kernel, e lo stack del kernel è separato da quello dei
programmi normali, e sono state già fatte delle chiamate di sistema; quando scende il puntatore abbiamo
che i dati non vengono cancellati e quindi la memoria risulta sporca. La funzione crea un oggetto map di 32
byte e dentro di essi vi sono dei valori, la loro somma è 28 byte e non 32; il compilatore arrotonderà il
valore di 28 a 32 e questo per motivi di architettura ed efficienza.

La map avrà quindi 28 byte inizializzati e 4 byte che sono LEAKED cioè rubabili. Ma quindi che ci può fare un
attaccante? Potrebbe rubare gli indirizzi dello stack e usarlo poi per un altro attacco come il buffer
overflow.

Ma allora come si risolve questa situazione? Semplice avremmo dovuto prima scrivere tutti “0” in tutti e
32 byte e poi dopo assegnare i valori.

Dirty pipe
Fa i buchi in terra (cit.), vi è un problema con il kernel di Linux < 5.8 e con una chiamata a sistema abbiamo
la privilage escalation.
Tutte queste cose che abbiamo visto sono chiamate undefined behaviour perché dipendono dal
processore, compilatore etc.

Per poter rilevare tutti questi problemi si fa uso quindi dei tool chiamati Sanitizers

Sanitize
Sono un gruppo di dynamic bug finding tool che rilevano bug in C/C++ nascosti. Questi programmi avanzati
lavorano iniettando delle righe aggiuntive (instrumentation, nell’esempio sono quelle segnate in rosso) nel
programma in zone specifiche quali:

 Ogni allocazione heap


 Ogni accesso ad array
 Chiamate ad API
 Etc
Ogni volta che trovano un problema mostrano un alert e fermano il programma, ma vediamo un esempio.
Abbiamo un programma e ne vogliamo misurare la coverage (copertura l’abbiamo vista ad ingegneria del
software)

Oltre alla coverage possiamo controllare tutti i buffer mettendo un controllo su ogni indice di array e
controllano sei i confini sono rispettati, oppure controllare i valori non definiti, etc.

Useremo due tool uno chiamato Valgrind e un gruppo eterogeneo di Sanitizers


Valgrind
Più che un tool è un framework molto ricco ed è composto da diversi tool, non effettua solo analisi di
vulnerabilità ma anche ottimizzazione di programmi come i colli di bottiglia. Possiede un framework core e
in più vi sono tutti i vari tool come: cache profiler, memory checker, etc.

Il suo uso è molto semplice, immaginiamo un programma Linux come “date” che stampa l’ora del sistema.
Per analizzare il comando lanciamo Valgrind usiamo il nome, e le opzioni per l’analisi; ci fornisce la stampa
del programma + info derivanti dall’analisi di Valgrind. Fa quello che abbiamo chiamato prima
instrumentation.

Nell’esempio il tool “chachegrind” misura i cache miss fatti dal processore


Il programma non viene cambiato, viene solo instrumentato e quindi il suo comportamento è invariato;
purtroppo diventa molto più lento del normale con delle tempistiche delle volte anche 100 volte
superiori.

Il tool funziona anche “black box” non richiede di ricompilare il programma, funziona anche con il binario
quindi è super potente.

Quando riportato nella frase di prima è riassumibile dicendo che Valgrind usa Dynamic Binary
Instrumentation

Con tante virgolette è come se fosse una macchina virtuale di C/C++, abbiamo infatti che:

 L’eseguibile viene caricato da Valgrind a piccoli pezzi, chiamati basics blocks


 Ogni pezzo viene tradotto in un assembly interno di Valgrind chiamato Intermediate
Representation
 L’IR viene iniettato con altre istruzioni per tenere traccia
 Il codice viene poi ri tradotto ed infine eseguito dal processore.
Vediamo un esempio di come funziona per trovare errori come il buffer overrun, oppure memory leak.

Abbiamo nuovamente un malloc (100) e proviamo a scrivere all’indirizzo 200. L’istrumentation crea una
struttura dati allocations e ogni volta che c’è un malloc () ci segniamo l’indirizzo dell’area e la dimensione.
Poi ogni volta che devo leggere il puntatore iniettiamo una if
Visto che si occupa di lettura binaria lui lavora sul codice binario, quindi MOVE etc. Il prof mostra il codice
C per comodità.

Memcheck
È un tool che si occupa di rilevare errori di memoria, può rilevare i tipici problemi di programmazione in
C/C++. I lavori che fa sono diversi e sono raccolti nella slide

Vediamo un esempio di utilizzo, basandoci sempre sul puntatore di dimensione di 100 come prima.
Ma come funziona questa magia? Memcheck implementa una “CPU virtuale” nella quale abbiamo:

 Ogni bit in memoria/registri ha associato un “valore valido” (V) bit. V = 1 se il bit contiene un valore
inizializzato
 Ogni bit in memoria ha associato “un indirizzo valido” (A) bit. A = 1 se il programma può in maniera
legittima leggere o scrivere in quella locazione di memoria

Il processore virtuale ha i suoi R registri e il fatto di avere tutte queste repliche di byte comporta il
rallentamento e l’uso intensivo di risorse visto prima.

Ma come si sfruttano questi bit? Come sappiamo non possiamo usare la memoria se non è stata già
allocata, sfruttiamo malloc() che alloca spazio e imposta il bit A ad 1.
Con l’animazione possiamo vedere che quando lanciamo un programma l’area codice mette tutti i bit di
quell’area alti. Poi abbiamo l’area heap che di default all’inizio è tutto a zero.

Supponiamo poi di fare una malloc() e otteniamo una fettina di heap che diventa con un indirizzo valido ma
con un valore non valido.

Se facciamo memset ( p , 0 , N) scrivendo tutti zero nel buffer, anche i bit V diventano verdi. Stesso identico
discorso per lo stack che appena chiamato è con tutti i bit rossi e quindi non disponibili, se però chiamiamo
una funzione gli indirizzi diventano validi e saliamo con lo stack.

Quando poi inizializziamo il valore “a” abbiamo che i valori validi crescono
Un'altra cosa molto potente di Valgrind è che quando copiamo una variabile in un’altra variabile, anche i bit
vengono propagati alle copie. Vediamo l’esempio in cui poniamo b = p[0]

Il programma copierà il valore di “p” in un registro, ad esempio R2 e poi lui ricopierà il valore sullo stack.

Vediamo le regole che funzionano in Memcheck:

 A=1, V =1 la lettura e la scrittura sono ok, situazione ottimale.


 A=0 ,V =0 errore perché non posso leggere o scrivere su un qualcosa non allocato
 A=0 ,V =1 errore ma non è applicabile perché non può starci una variabile ma non è allocata
 A=1, V =0 scrivere si, leggere dipende perché potrebbero esserci tutte le problematiche viste in
questa giornata.

Vi è scritto depends perché non è detto che questo errore si veda subito ma solo se le variabili generano un
effetto visibile.
Vediamo un esempio concreto, il nostro programmino in C++ che facciamo a fondamenti e che adesso ci
vergogniamo di vedere.

Tutti questi esempi e chiacchiere per spiegare bene come Valgrind mostra i messaggi di errore, nel caso di
valore non inizializzato otteniamo un report del genere.

Cosa ci dice? Che quando facciamo la stampa di “x”, abbiamo che dentro printf() c’è l’istruzione di selezione
inserita in automatico che porta a quest’errore. Ovviamente l’errore è che tale valore non è inizializzato.
La stampa è simile al “call stack” di gdb e vediamo che la chiamata parte dal main, che chiama printf.c che
chiama _itoa.h.

Un altro esempio è qui dove c’è il write con una variabile non inizializzata.

SGCheck
Il tool di prima funziona solo per lo stack overflow, per l’heap dovremo usare lui. Quindi i due sono
complementari.
Infatti, vediamo un problema di stack array con sempre la traduzione in linguaggio macchina con la singola
istruzione “Sub” ma, purtroppo, non riconosce le due arre separatamente quindi cosa dobbiamo fare?

SGCheck usa un approccio euristico, se un’istruzione legge/scrive dentro un array globale o sullo stack
anche solo una volta, allora dovrebbe accedere sempre allo stesso.
Alle 01:31:30. Fa vedere l’esempio di utilizzo di Valgrind tramite l’esempio del Titanic. Se volete provare a
fare gli stessi esempi la sua VM possiede già i tool.

I vari Sanitizer
Scriviamo “vari sanitizer” perché sono appunto un gruppo di tool con il nome Sanitizer alla fine, e si
occupano di lavorare su codice sorgente quindi quando compiliamo e sono diversi da Valgrind. Sono super
facili sa usare, basta usare l’opzione “-fsanitize = address”; esistono altri tipi di opzioni attivabili ma soltanto
uno alla volta.

Ricompila il programma facendo sempre instrumentation però è più veloce proprio perché è statico
Vediamo un esempio di funzionamento:

Il risultato è molto simile a Valgrid. Ma come fa ad essere così veloce? Il tool è meno preciso perché lavora
per word (quindi gruppo di 8 byte) rispetto al singolo come capitava prima. Crea quello che in gergo è
chiamato shadow byte, ovviamente come detto sarà meno preciso nel rilevare un problema.

Vediamo cosa succede in memoria, supponiamo un heap overflow abbiamo che l’address sanitizer mette
vicino ad ogni buffer gli shadow byte diversi da 0. Così se il programma scrive fuori dalla zona rossa del
buffer abbiamo un avviso.
Quando effettuiamo free(), ASanitizer mette il buffer in quarantena, per rilevare use-after-free.

Nelle restanti slide ci sono le parti di codice che vengono iniettate e altri differenti tipi di tool sempre del
Sanitizer.

Lezione 5 23/03 (4-WebSecurity-ClientSideAttacks)


Web security
Oggi vedremo un po’ i concetti base del web che ci servono per capire meglio come vengono portati avanti
determinati attacchi, soprattutto quelli Client Side ovvero quelli che riguardano un utente che visita un sito,
e come configurare le difese. Il sito segnato come “Web security academy” produce strumenti di sicurezza e
utilizza questo sito per far fare test e testarne le capacità.

Gli attacchi che vedremo prescindono da http/HTTPS, sappiamo che l’ultimo protocollo è http che usa la
cifratura su TLS/SSL, poiché si concentrano sulle web application e quindi “soprassediamo” al tipo di
protocollo usato.

Sappiamo anche che internet si basa sul protocollo client-server schematizzato come segue:
Il web server quando genera una risposta può recuperarla da un disco oppure eseguendo una web
application, ma il risultato finale è lo stesso.

URL (Universal Resource Locator)


Ovviamente l’URL è un protocollo hostname usato per identificare le risorse sul web, e l’ultima parte della
stringa è indicata come path resources. Nei primi siti web il path resources era proprio il percorso per la
risorsa del server, ovvero quello segnato come contenuto statico.

In altri casi più sofisticati, come il dynamic content, il link non è solo un file da leggere ma è un percorso ad
un programma da eseguire.

In generale l’URL è un oggetto molto complesso, di cui noi usiamo molto poco ed il minimo che possiamo
avere è “http: + percorso”. Infatti, se lanciato così ci cerca nel local host.

Tutto quello dopo i “:” è opzionale e abbiamo, come mostrato dall’immagine, un sacco di diverse opzioni.
Ma concentriamoci sulla parte dopo il dominio, quindi da “path” in poi poiché prima ha più a che fare con i
protocolli di rete. Dopo il “?” vi è la possibilità di trovare una query che può essere usata per inviare
informazioni al server, ed è un dizionario con una rappresentazione chiave-valore nella forma:

varname1=value 1∧varname 2=value2


Abbiamo quindi una o più variabili, separate da “&”, indicate nella query. L’URL comprende anche una
parte dopo i parametri segnata con # che però noi server ignoriamo e viene usata solo dal client.

Problema: Cosa succede se noi in questi parametri ci aggiungiamo valori come “?, #, &”? Visto che sono
caratteri speciali? La regola semplice richiede di usare i valori esadecimali (dell’ASCII) preceduti da un %.
Ricapitolando, se pure scrivessimo come la parte sottolineata quello che manderebbe il browser è quello
scritto con i valori esadecimali

http Protocol
I messaggi http di tipo request and response sono composti da tre parti:

1. Request/response line
2. Header fields
3. Body (optional)

Ogni linea dei messaggi viene terminata con i caratteri ¿ ¿ o da 0 x 0 d 0 a in binario. Una linea vuota separa
l’ultimo campo header dal body.

Come abbiamo detto la prima riga è la richiesta e vi è il percorso con il numero di protocollo, infine ci sono i
caratteri terminatori e a capo di tutto l’azione da effettuare. In questo caso GET.

Gli header sono stringhe di testo con vari campi e l’ultima parte è il body che tipicamente è vuoto, se
facciamo un upload.
Concentriamoci sulla prima riga, il così detto metodo

Standard HTTP/1.1 request methods


 GET, tipicamente è la più semplice ed è un metodo “safe”, ha lo scopo di effettuare tutte quelle così
dette operazioni “read only” e che non comportano cambiamenti.

Si usa la GET nei casi si voglia recuperare una risorsa per la lettura solo per convenzione, ma non è scritto
nelle regole di funzionamento del protocollo. Vediamo un esempio:

Supponiamo di aver aperto una pagina contenente un tag “img”, il browser quando legge il tag fa una
richiesta GET per ottenere l’immagine, oppure c’è un link, se ci clicco sopra il browser fa una GET.

 POST, abbiamo una particolarità ovvero mandiamo i parametri separati dal body ed è quindi da
usare quando ci sono dei dati sensibili. Quando proviamo ad aggiornare una pagina che usa POST ci
sarà sempre un avviso di tentativo di riconsegna modulo.

Vista la caratteristica di POST di gestire contenuti sensibili sarà il metodo usato per quando si dovranno
gestire degli ipotetici effetti collaterali.
Vediamo come al solito un esempio, il tag <form> prevede a sua volta un link che il browser visiterà dopo
aver premuto “submit” e lo visiterà con il metodo POST.

Quindi ricapitoliamo il passaggio dei parametri, nel caso della GET, fanno parte del path e sono conservati
nella cronologia del browser e spesso scritti all’interno dei log file.

Ed è per questo motivo che non si usano le GET per mandare dati sensibili, visto che verrebbero salvati in
chiaro sia nelle richieste sia nel log.
Lo stesso caso, ma visto con il metodo POST, è come detto un passaggio tramite body e non header; in più
l’azione non dovrebbe essere ripetibile, infatti, verremo avvisati con un warning.

Quando mandiamo qualcosa in un body dobbiamo specificare cosa stiamo mandando, altrimenti chi legge
non sa interpretare cosa sia. Per fare ciò dobbiamo usare delle parole chiave come “content-type/content-
lenght”.

Gli headers sono come delle variabili non nel senso che ci inventiamo gli header ma sono variabili del
protocollo http e seguono delle regole:
Ce ne sono innumerevoli, uno che conosciamo è l’user agent che informa il server del tipo del browser che
stiamo usando.

Il tag refered non viene incluso sempre, ma quando clicchiamo su un sito e ce ne porta su un altro
abbiamo che il secondo viene informato del sito dal quale proveniamo. Questo tag pone dei problemi di
privacy.

Gli status code sono suddivisi in famiglie.

Http RESPONSE
È simile alla request, l’unica differenza sta nella prima linea la “status line” che informa del risultato della
richiesta
Javascript
Javascript è un linguaggio di scripting lato client, questo vuol dire che quando riceviamo una pagina essa
non conterrà solo tag di immagini e testo ma anche dei piccoli programmi.

L’output sarà mostrato all’interno del browser, ma solo il risultato non il codice. Javascript può in realtà fare
moltissime cose:

 Alterare contenuti delle pagine (DOM objects)


 Tracciare eventi (click del mouse, movimento, etc)
 Richieste web problematiche e risposte di lettura
 Connessioni persistenti mantenute (AJAX)
 Leggere e scrivere cookie

Lato server invece è più probabile usare Java o Python ma noi, nei nostri esempi, useremo PHP il qual
codice verrà processato da un interprete PHP. Il risultato del codice PHP è tipicamente un testo HTML.
Session management in web applications & Cookies
Sappiamo che http è un protocollo stateless e ogni richiesta che facciamo è indipendente l’una all’altra, le
Web applications creano una sessione per ogni utente identificandolo con un token per poter soprassedere
a tale problema.

La web application conserva il token e lo manda al client attraverso un header http (cookie), il browser
conserva il token e automaticamente lo rinvia al server ad ogni richiesta http.
La sintassi del cookie è quella mostrata di seguito, ed è un header quindi si trova dopo la riga di request e
ha sempre la coppia variabile-qualcosa.

Il fatto che i cookie servano per l’autenticazione li rende automaticamente super importanti; infatti, se
qualcuno li rubasse potrebbe tranquillamente impersonarci.

Questo perché abbiamo detto che il server controlla il cookie per mantenere lo stato delle richieste, se
rileva che non è cambiato pensa sia sempre lo stesso utente.

Un identificatore di sessione deve essere difficile da indovinare. formato da caratteri alfanumerici


randomici e molto grandi, questo perché un attaccante potrebbe tentare un attacco a forza bruta e crearne
uno corretto. Sempre per evitare il brute force hanno una certa durata di scadenza, non troppo lunga. Un
ulteriore accortezza, anche se banale, è disabilitare i cookies con il log out.

Per configurare quanto detto, di solito in linguaggi come Java, c’è il file web.xml che permette si configurare
tutti questi parametri.
Sessione demo di furto di cookie
Spiegazione a 48:07 come al solito si trova tutto sul github.

Cross-Site Request Forgery (CSRF/XSRF)


Per spiegare questo tipo di attacco dobbiamo introdurre il concetto di Cross-Site Request. Definiamo same
site request come una pagina che da un sito web manda una richiesta http dallo stesso sito; mentre
definiamo cross site request come una pagina che proviene da una richiesta http proveniente da un altro
sito.

Ad esempio, una web page (non Facebook) che include un link Facebook. Quando l’utente clicca sul link una
richiesta HTTP viene mandata a Facebook.
Questa cosa può creare non pochi problemi proprio perché legato al passaggio dei cookie. Vediamo un
esempio con due siti A e B e siamo anche autenticati su entrambi quindi abbiamo due cookie, nel caso (1) e
(2) ognuno ha i suoi cookie ma nel caso (3) i cookie che verranno mandati sono quelli di B.

Ma perché si crea il problema? Beh, supponiamo che un sito web sia stato creato da un utente malevolo e
supponiamo anche che l’attaccante abbia fatto del social engineering invogliando l’utente ad accedere sul
suo sito.

La richiesta parte in automatico perché legge il tag e lo visita. È possibile fare una demo di attacco CSRF
c’è sempre il link su GitHub visione a 1:16:00.
Contromisure Cross Site
Il problema fondamentale è che il server non conosce la distinzione tra same-site request e cross-site
request, ma quando il browser fa una richiesta cross site lo sa che lo sta facendo? Si, la nota perché sa cosa
abbiamo cliccato. Ci sono differenti soluzioni, dunque, per risolvere tale problema:

Come contromisura di referer header abbiamo ormai molti browser che non lo inseriscono più per motivi
di privacy e quindi non viene riportato. La soluzione usata e migliore è quella che usa degli speciali tipi di
cookie con l’attributo “SameSite”, dove all’atto della creazione di un cookie inseriremo nella variabile di
configurazione un impedimento di condivisione cookie, impedendo così l’attacco.

Oggi la contromisure più comune è il secret token, questo valore è segreto (per l’attaccante) e permette al
server di capire se la richiesta è cross site o meno.
Vediamo lo scenario, come quello di prima, ma con l’aggiunta di un token. La prima volta che un utente
visita il sito B ottiene la pagina e da qualche parte vi è il codice che contiene il token. Quando l’utente clicca
sul link, partendo da una pagina di B, il link include il token. Se invece Alice visita un’altra tab malevola e
clicca sul link quest’ultimo token non ci sarà.

Così facendo il server sarà in grado di riconoscere se vi è una richiesta cross site o no. Domanda: Visto che il
token viene mostrato nella pagina HTML, è dinamico? Sì, perché è in chiaro e quindi a meno che
l’attaccante non abbia un virus non se ne fa nulla.
Lezione 6 25/03
Cross site scripting attacks (XSS)
Completiamo prima la parte finale della presentazione scorsa, quella del client side attack. Poi passiamo al
prossimo blocco di slide.

In qualche modo il browser deve limitare la capacità di Javascript di eseguire arbitrariamente quello che
vuole; infatti, uno script di un sito come “attacker.com” non dovrebbe essere capace di alterare il layout di
una pagina di una banca, etc. Questo è il caso di malware di tipo advertising (pubblicità) che può spingere a
frodi informatiche.

Contro tale problematica abbiamo il Same Origin Policy, ovvero un meccanismo che ci garantisce che il
Javascript proveniente da un sito non possa modificare o leggere risorse di altri siti.

Ad esempio, il Javascript della pagina di reppublica.it non potrà leggere i cookie messi a disposizione dal
corriere. Vediamo l’esempio dei cookie schematizzandolo

Se repubblica ci manda un file good.js contenente codice Javascript esso potrà leggere, tramite classi, i
cookie ed il browser lo consente perché proveniente dalla stessa origine. Ora se aprissi un'altra tab
chiamata evil.com e ricevessi un suo .js esso non potrà leggere i cookie di website.com perché l’origine è
differente.

La policy è del browser.

Il XSS ha lo scopo di sovvertire la SOP, e l’attaccante non invia il suo Javascript attraverso del social
engineering ma fa dei giri particolari per evitare le regole della SOP. Facendo così il browser della vittima si
fidi del codice malevolo inviatogli dal server e lo attui.

Le possibili conseguenza dell’attacco sono:

 Web defacing, codice Javascript può usare API DOM per effettuare cambiamenti arbitrari alla
pagina, ad esempio cambiare le immagini o parti di testo
 Spoofing requests
 Stealing information
Esistono vari tipi di XSS e i due principali sono:

 Non persistente (Reflected)


 Persistente (Stored)

Non persistente (Reflected)


Per avere successo in questo attacco ci deve essere un particolare comportamento da parte del sito, nello
specifico il sito non deve essere in grado di sanificare la http request malevola e quindi quando poi il sito
inoltra la risposta alla vittima; egli eseguirà il codice malevolo.

È per questo motivo che SOP non funziona, proprio perché il codice arriva dal sito originale.

La chiave del reflected XSS è quella di trovare una web application che effettua l’eco degli input dell’utente
in una risposta html, ovvero avere un “reflected behaviour”.

Per capire l’effetto immaginiamo di cercare su un e-commerce la parola “calzini” e nella pagina abbiamo
“hai cercato: calzini”. Noi al posto di “calzini” mettiamo del codice javascript e portiamo avanti l’attacco.

Invece che la risposta come “calzini” possiamo mettere del codice Javascript che farà lo stesso percorso di
prima e la risposta avrà il codice dell’attaccante.

Persistente (Stored)
Nel persistente l’attaccante invia direttamente del codice malevolo al sito, come ad esempio la bacheca
vista nell’esercitazione di ieri e qualcuno nella propria biografia immette codice Javascript che andrò poi
salvato su un DB. Quando qualcuno visiterà quella bacheca preleverà del codice malevolo.
Vediamo adesso una demo di XSS, sempre sul social network Elgg. Minuto 22:00

Nel codice malevolo invece che mettere un alert inseriamo qualcosa di extra alla pagina, come l’url del
server dell’attaccante. Questo tag, quando l’utente lo riceve sul suo browser, farà una GET su l’url e nei
parametri concateniamo i cookie, l’escape serve per fare URL encoding.

Per fare l’attacco abbiamo bisogno poi di un server in ascolto, in questo caso usiamo netcat per aspettare il
risultato.

Il valore del cookie recuperato è quello segnato, ricordiamo che sono delle coppie chiave = valore e il “=” è
codificato come %3D
Il secondo esempio mostrato è che quando alice visita la bacheca di Sammy ne diventa automaticamente
amico

Contromisure XSS: approccio di filtraggio


La cosa più semplice da fare è prevedere un sistema di filtraggio degli input da parte di un utente; quindi,
così facendo rimoviamo il codice JS dai possibili input. La cosa più difficile è ovviamente prevedere tutti i
possibili modi di rendere il codice embeded attraverso i vari possibili tag. Normalmente si utilizzano delle
librerie open source conosciute che effettuano filtraggio di codice java.

Di seguito infatti vediamo come MySpace, che ha subito l’attacco da Samy, implementasse male il sistema
di filtering e di come spezzare il tag Javascript funzionasse.

Contromisure XSS: approccio di encoding


In questo caso sostituiamo tutti i caratteri di controllo con delle rappresentazioni alternative, così facendo i
dati sono encoded prima di essere inviati al browser. Quindi c’è una sostituzione dei marcatori di HTML con
delle rappresentazioni alternative.

Come prima preferiamo l’uso di moduli/funzioni già esistenti che sicuramente sono più completi di quelli
che scriveremo noi.
Contromisure XSS: usare Content Security Policy
Il problema, come descritto prima, è che il browser riceve il codice HTML mischiato con Javascript e nella
risposta il browser non sa distinguere se quel Javascript gli viene da una fonte fidata o no. In questo
approccio evitiamo di far eseguire al browser script scritti come mostrati nell’immagine e forziamo i dati e il
codice ad essere separati.

Questa tipo di scrittura di Javascript, negata, si chiama “inlined”.

Vediamolo con lo schema, abbiamo due server e due porzioni di Javascript. Noi in questo caso abbiamo già
bloccato il Javascript inlined, e supponiamo anche che l’attaccante non sia in grado di uploadare il file sul
server e quindi ne deve usare uno suo.

Nel http header segnato con (2) il server dice al client di chi si deve fidare, e l’attaccante supponiamo che
non possa modificarlo perché il server è stato configurato in maniera corretta.

Ma come si setta una regola CSP? Se siamo in un server Apache si aggiunge questa direttiva con i vari tipi di
domini. Oppure possiamo farlo anche in PHP.
5-WebSecurity-ServerSideAttacks
In generale nel OWASP Top 10 Application security risk parliamo di injection in generale, questo copre un
cappello enorme di differenti attacchi, noi ovviamente non li vedremo tutti. Anche il XSS è considerabile un
injection visto che iniettiamo all’interno del browser mentre adesso lo faremo nel server.

Vediamo nuovamente a volo SQL, è un linguaggio che lavora sulle tabelle e sono strutturate come segue e
con diversi modi di visita.

Le operazioni sulle tabelle sono:

 Select
 Update
 Insert
 Drop
 -- o # come commenti

La SQL injection sono un insieme di attacchi che puntano ad accedere, e a danneggiare, un DB al quale non
abbiamo accesso. Normalmente un DB non è accessibile da un indirizzo pubblico e l’unica cosa che può fare
è mandare una richiesta all’applicazione web in maniera malevola.
Immaginiamo di avere un’applicazione con il classico username e password, una volta premuto il tasto
“submit” una richiesta http viene inviata con i dati inseriti

Il codice PHP che legge lo statement SQL è la seguente, e ci restituisce un solo valore per via della AND
Notiamo in che modo la parte fissa della query è concatenata con la parte variabile, gli input dell’utente:
$ eid , $ pwd sono inseriti (interpolati) nella stringa query ( $ sql ). È possibile per un attaccante cambiare la
struttura di una richiesta SQL manipolando la stringa.

Se un utente inserisse EID 5002' ¿ quello che succederebbe, all’interno del codice sarebbe comunque un
attacco perché modifica il comportamento del DB. Questo perché normalmente si dovrebbe inserire anche
la password ma il “#” provoca la generazione di un commento.

La query che verrà eseguita è quella mostrata di seguito, il problema è che ritornerà dei valori anche se
l’utente non conosce la password dell’impiegato. Abbiamo una falla nella sicurezza

Possiamo ovviamente fare anche cose più complesse, ad esempio mettiamo caso di non conoscere
nemmeno l’username a questo punto per ottenere tutti i record del database modifichiamo le richieste del
WHERE per far sì che tutto sia vero. Il nostro payload sarà “a' OR 1=1.

Ovviamente non esistono soltanto attacchi alle SELECT, ma anche alle UPDATE o INSERT INFO e tutte le
opzioni viste prima.
È anche possibile concatenare più query e per ottenere ciò si utilizza il “;” questo viene fatto perché spesso
con una sola query non si può fare tutto. Fortunatamente questo tipo di attacco, ovvero il concatenamento
non funziona su mySQL questo perché l'estensione di PHP non consente multiple query.

Contromisure SQL Injection


Come per gli XSS abbiamo il filtering e il data encoding; quindi, i caratteri speciali vengono codificati in
modo da non essere eseguibili. Vediamo un esempio:

Per fare il filtraggio PHP mySQL extension ha incluso un metodo: mysqli ::real ¿ (). Ma la soluzione migliore
rimane il prepared statement.

Per prepared statement intendiamo una feature che viene implementata per migliorare le performance di
richieste SQL ripetute:

 Si manda un template di richiesta SQL al database con certi parametri lasciati generici
 Dopo preleviamo i dati per modificare l’operazione, in questo modo le due strutture sono separate.
 I dati utenti non hanno la possibilità di cambiare la struttura della query.
Anche questa volta abbiamo che il problema è sempre lo stesso, vengono mischiate istruzioni e dati
insieme e lo possiamo vedere nello schema.

OS Command & Code Injection


Anche se mostrati insieme hanno sfumature diverse, code/command injection sono visibili quando un input
unsafe è eseguito come codice da un applicazione ma se:

 Il programma che lo esegue è esterno, OS command injection


 Valutazione dinamica del codice, code injection

L’impatto di tale attacco è critico visto che comporta l’esecuzione di codice arbitrario, come scritto sopra il
primo caso può applicare anche quando una web application inoltra dati unsafe ad una shell di sistema.

Quindi nel caso reale si avrà un utente che scriverà example . come la pagina PHP eseguirà il comando di
sistema, ma senza la sanificazione dell’input un attaccante potrebbe scrivere:
example . com;codiceRandom e Bash o altri interpreti di shell vedranno il carattere “;” come un
separatore ed eseguiranno la seconda parte.

Per il secondo caso, quindi la code injection, abbiamo che i linguaggi di scripting forniscono feature
avanzate per lanciare una stringa variabile come programma, queste funzioni sono spesso indicate come
“eval(),evaluate(), assert(), etc”
In questi casi il codice iniettato è eseguito dall’interprete invece che dalla system shell.

Server-Side Request Forgery (SSRF)


Nelle SSRF l’attaccante sfrutta una web app per inviare richieste ad un altro server, nello scenario reale
abbiamo che spesso i computer, della nostra rete interna, sono dietro un firewall che blocca le
comunicazioni verso l’interno. Se l’attaccante riesce a portare un payload malefico attraverso una richiesta
http avremo che l’SSRF riesce a propagare il danno.

Potrei usare la web app pure per effettuare DoS, senza che si possa rintracciare l’attaccante.

Questo tipo di attacco può essere devastante perché spesso le reti interne non effettuano input validation e
quindi compromesso uno vengono compromessi facilmente tutti. In più abbiamo anche un'altra possibilità,
se la rete è cloud abbiamo che alcuni indirizzi speciali contengono delle informazioni (come quella riportata
sulla slide di AWS che è fissa e locale), è possibile leggere password ed informazioni critiche.

Ogni programma che consente una connessione è potenzialmente vulnerabile all’SSRF. Un esempio e
alcune contromisure sono sulle slide, ma come detto dal prof questo voleva essere solo un accenno.

Lezione 7 30/03
Esercitazione Web security (5-Lab-WebSecurity)
La scaletta di esercitazione sarà:

 Cross-Site Request forgery (CSRF)


o SameSite cookies
 Cross-Site Scripting (XSS)
o Stored XSS attack on POST request
o Content Security Policy (CSP)
 SQL Injection
o Modify table
o Prepared statements
Nel caso abbiate bisogno installate docker e docker compose:
https://docs.docker.com/engine/install/ubuntu/

https://docs.docker.com/compose/install/

È necessario configurare gli hostname (es. www.csrflabelgg.com), modificando il file /etc/hosts in Linux.
Potete trovare tutte le indicazioni nel README.md che si trova nella cartella di ogni esercizio (ad esempio:
https://github.com/rnatella/swsec/tree/main/web-security/csrf-elgg). Sono riportate anche le istruzioni
per il sistema operativo Mac OS X.

Il primo esercizio richiederà di andare su www.csrflab-defense.com, ATTENZIONE PER FAR FUNZIONARE IL


TUTTO DOVETE FAR PARTIRE DOCKER, ALTRIMENTI IL SITO NON VERRà RICONOSCIUTO SIA CHE SIATE IN
NAT SIA CHE SIATE SU HOST-ONLY.

Il sito mette in automatico tre cookie, ci sono due link che fanno arrivare a vedere quali cookie sono same
site quali no. L’obiettivo dell’esercizio è rispondere alle domande:

 Perché alcuni cookie non sono inviati in certi scenari?


 Come possono i Same site cookies aiutare un server a rilevare quando è in corso una richiesta
cross-site?
 Come usereste il meccanismo dei cookie SameSite in Elgg?

È possibile vedere anche il codice sorgente della pagina, la troviamo sempre nelle cartelle.

Se abbiamo già avviato docker in lezioni o situazioni precedenti il “docker compose” potrebbe dare
problemi, per risolvere tale situazione bisogna ripulire e i comandi sono i seguenti:

$ docker ps per elencare eventuali container ancora avviati


$ docker kill <id>¿ per uccidere un container che è stato elencato dal comando precedente
$ docker container prune per cancellare dal disco lo stato dei container eliminati
$ docker network prune
Prima però di tutti questi comandi eseguite $ docker −compose down nella cartella degli esempi che
abbiamo già avviato.

A 29:00 fa vedere cosa vediamo con il primo esercizio.

Alle 36:50 vi è il secondo esempio con l’opzione di edit-profile request. Nel power point vi è lo sniffing delle
richieste e poi vi un template di partenza di ajax request di tipo POST. Ovviamente anche qui, prima
spegniamo il docker e poi riavviamo però nell’altra cartella

Se abbiamo problemi con il passaggio dei parametri proviamo a fare in questo modo:

l’url lo scopriamo facilmente, mentre in content mettiamo tutti i parametri, non solo la description.
Il 47 in guid si deve ricavare, poi chiede se fosse possibile togliere “l’if” e si tramite l’uso di CURL che
permette di fare richieste GET e POST da shell e non viene vista dai browser. In più permette di evitare di
autoattaccarci.

All’ora 1:22:05 parte l’esempio di CSP, si trova nello stesso container di elgg, le pagine elencate hanno
codice HTML e Javascript e il nostro obiettivo è effettuare modifiche all’applicazione per far vedere che
tutto venga eseguito, restituendo “OK” invece che “failed”.

Se non è chiaro l’esercizio lo vediamo meglio adesso:

1. Capire perché andando su alcuni siti ci da “ok” invece che “failed”.


2. A livello di codice dobbiamo trovare nella cartella “apache_csp.con*”
a. Il primo indirizzo non ha nessun parametro, senza CSP
b. Il secondo ha una riga chiamata “header set” e fa in modo che il web server mandi il
content security policy, se riusciamo a modificare dobbiamo poi far ripartire il container
per vedere se tutto è andato ok.
c. Per il terzo dobbiamo aprire il file “php_index.php” e qui troviamo la content security
policy.

All’ora 1:44:50 parte con la SQL Injection, lanciamo nuovamente il docker sulla terza cartella e ci sono le
varie richieste da fare.
Lezione 8 01/04 (6-InputValidation)
Input Validation
Abbiamo appena concluso la prima parte del corso in cui abbiamo visto un po’ di vulnerabilità importanti,
ce ne sarebbero altre e le vediamo nell’immagine. Ma il punto in comune è il problema di mischiare dati
con codice, e la soluzione comune a queste vulnerabilità è quella di sanitizzare gli input.

Spesso si usano due termini, anche intercambiabili tra loro, ed in alcuni contesti vi è una lieve differenza:

 Validation, respingere o non respingere i dati che sono anomali.


 Sanitization, trasformare i dati in ingresso non necessariamente rigettandoli ed eliminando
caratteri anomali.

La roadmap di quello che dovremmo fare è la seguente:

 Dobbiamo capire cosa difendere e poi minimizzare la superfice d’attacco


 Validare tutti gli input
o Uso di whitelist e non blacklist
o Decodifica prima della validazione
o Numeri: Convertire un numero, controllare minimo/massimo, usare il giusto tipo
o Path e Url: convertire sempre in forma canonica
o Upload file multimediali: use un parser standard di formtati
o Text: essere il più stringenti possibile e validare solo i valori prestabiliti
o Riusare le spunte, se possibile.
 In tutti gli altri casi creare espressioni regolari limitatorie
o Attenzione al regex DoS
 Validazione con specifiche forme.

Ma cos’è una superfice d’attacco? Informalmente è l’insieme degli attack vector che un attaccante può
usare per inviare flussi di dati malevoli.

Ovviamente il concetto principale è che: Più è larga la superfice d’attacco => Più è semplice fare danni o
subire exploit.

Anche le applicazioni da linea di comando hanno una attack surface, la cosa potrebbe lasciarci interdetti
ma se proviamo a lanciare $ sudo cat non avremo una grande superficie, a prima vista, però vi sono in
realtà moltissimi canali aperti.

Ad esempio, l’utente con il quale ci siamo loggati, l’attuale cartella di lavoro, le variabili d’ambiente, etc e
tutte queste cose l’applicazione le legge come se fosse la roba proveniente dalle GET e POST.
Molte librerie e programmi sono controllate attraverso le, appena citate, variabili d’ambiente le quali sono
spesso oscurate o nascoste o addirittura non documentate. In alcune circostante gli attaccanti possono
prendere controllo delle variabili d’ambiente per effettuare comandi con l’uso di privilegi.

Ma come funziona?

Ogni processo in Linux ha una struttura dati nel kernel chiamata Process control block (PCB) e sia windows
che Linux hanno la possibilità di settare, per ogni processo, le variabili d’ambente. Quando noi diamo il
comando $ export * stiamo avendo a che fare con il processo della shell (quello che a noi è il rettangolo
grigio), lanciando poi una fork avremo che i dati (come myprog) copiano tutti gli elementi della shell.

Arriva il genio che dice: Va beh facciamo il controllo. Eh, bravo ma non è facile, se vediamo il continuo
dell’esempio scopriamo che purtroppo Linux è pieno di variabili d’ambiente di default e che spesso sono
sconosciute.

Nell’esempio c’è $IFS input field separator.


Vi è poi anche l’esempio di un attack surface che passa per le variabili d’ambiente conosciuto come
Shellshock.

Quindi i consigli che ci dà sono principalmente due:

1. Non programmare i sistemi in C, che è difficile, ma usiamo Go Lang


2. Fare attenzione alla superfice d’attacco

Tipicamente però noi svilupperemo applicazioni web e dall’OWASP possiamo vedere quali sono i tipici
attacchi che potremmo subire.

Un altro consiglio che non fa mai male è effettuare uno screen completo dell’Attack Surface, magari
attraverso tool come l’attack surface Detector (ASD) che consiste in un plugin per OWASP ZAP e Burp Suite
e ci informa sull’AS del nostro sito. Ciò che è in grado di fare consiste in:

 Enumeration degli endpoint (routes, parameters)


 Analisi del codice sorgente
 Analisi black box
 Testing a forza bruta
 Spider, per capire le pagine raggiungibili
Divisione del sistema
Di solito la input validation viene fatta nella linea che separa il nostro ambiente dal resto del mondo, questo
perché tipicamente ciò che è all’interno dei trust boundaries non viene criptato o protetto ed i motivi sono
molteplici di questa scelta.

Quindi effettuare una separazione dei privilegi del sistema ci aiuta contro l’input validation e tale tecnica si
chiama defence in dept (difesa in compartimenti)

Validazione di tutti gli input


Vediamo un contro esempio sbagliato di validazione, un programma che effettua blacklist e che usava
Apache Webserver. Questo programma prendeva in ingresso un nome utente “Qalias” e si comportava
come rubrica telefonica; l’attacco consisteva nell’inviare una stringa “x%10 (ritorno a capo) più comando”.
Ripetiamo che questo attacco è un OS command injection.

Tra la creazione della stringa e la shell c’era un controllo sui caratteri, rimuovendo quelli ostili però non il
controllo a capo. Questo è un esempio di blacklist, ovvero diciamo cosa non va entrando.
Ma quindi le blacklist sono sempre sbagliate da usare o inutili? No, sono molto comode per effettuare
testing, infatti, sono utili per identificare i dati che non dovremmo accettare.

Encoding e decoding
Il decoding degli input va fatto sempre prima dell’input validation questo perché in caso contrario sorgono
problemi.

Infatti, se vediamo la nostra stringa di URL in input sappiamo che è costituita da byte codificati (senza spazi)
e dopo il coding troviamo la stringa ASCII.

Vi è un esempio di vulnerabilità su zoom, ma il pdf non ha la registrazione del video quindi potete vederlo al
minuto 28:00 della lezione.

Spieghiamo cos’è successo, la vittima riceve un link zoom che apre una pagina HTML contenente un altro
link, più lungo, non di tipo http ma zoom. La stringa verrà usata in una SQL Injection, ma come SQL
Injection? Si, perché Zoom usa un database interno per conservare informazioni quali configurazioni
fotocamera, etc.
Il comando “strings” usato nella shell mostra le stringhe presenti nel programma e le stampa, nello
specifico qui ha rilevato le query presenti (quelle con select). Domanda, questo usa i prepared statement?
Sì e no, alcune parti ce le hanno come la 3° Select.

L’attaccante poi ha provato le solite configurazioni per vedere se qualcuna di esse funzionava; quindi, ha
provato l’escaping con un singolo apice ed il commento alla fine. Vedremo che viene filtrata usando la
sanitification
L’attacco però ha successo se mandiamo una stringa come success¿ 2' ∨1=1 (Da inviare con un
programma perché ovviamente non ci permetterà di inviare una codifica del genere).

L’errore è che qui si fa prima sanitificazione e poi decoding, questo perché il decoding prende C2 e uno
degli apici e lo trasforma in un carattere.

Come risolviamo questa cosa? Si utilizza il così detto unicode sandwich, nella slide ci sono anche due link
utili per approfondire. Per capire la figura abbiamo che si è schematizzato nella seguente maniera:

 Il mondo esterno al programma legge e scrive in byte


 Il programma quando lavora su questi byte ci dà un significato decodificando e codificando.
 All’ingresso e all’uscita dobbiamo avere byte.

Unicode
Partiamo con il dire che non è un modo di codificare, ma è una tabella/repertorio di caratteri e associa ad
un numero un simbolo MA NON fornisce una rappresentazione in byte. UTF-8 è lo schema di enconding
più conosciuto per Unicode e utilizza una codifica variabile da 1 a 4 byte.

Il fatto della codifica a lunghezza variabile implica anche che se prendiamo un gruppo qualsiasi di byte, non
è detto che il codice sia per forza associato ad un carattere valido (quello che vediamo con il ?).
Per capirlo meglio vediamo un esempio di codifica con python e perché c’è stato il problema di passaggio
da python2 a python3

 Python2 quando si usano i doppi apici crea un oggetto chiamato “str”, se prima degli apici usavamo
“u” avevamo una stringa di tipo unicode.
Per essere pure più userfriendly quando si concatenavano due stringe di natura diversa realizza una
conversione automatica, con lil problema dell’eccezione mostrata nel secondo blocco.
 Python3 quando si usano i doppi apici ora abbiamo una stringa unicode e quindi c’è un casino
assurdo, se vogliamo usare una stringa di byte usiamo il carattere “b”. In più non fa conversioni
implicite e forza la correzione.

La codifica va poi concordata con gli utenti, quindi, bisogna informare in anticipo il tipo di schema di
codifica.
Supponiamo ora di aver decodificato e di aver usato una whitelist, adesso dobbiamo fare altri controlli

Numeri
Dobbiamo sempre convertire i numeri nel tipo desiderato, come abbiamo visto non fare una cosa del
genere potrebbe portare a buffer overflow:

 Nel caso di interi, con una macchina a 64 bit, abbiamo tipicamente overflow quando si immette
18446744073709551615 (2^64-1) => -1
 Per interi non negativi bisogna usare i tipi unsigned integer
 Se le frazioni non sono ammesse usare i l tipo integer
 Vogliamo calcolare un minimo ed un massimo? Consideriamo l’uso di una whitelist
 Floating point? Quello imparato a Calcolo numerico.

Paths,URLs
I dati in forma canonica sono quelli più semplici ed in formato standard. Il percorso dei file o gli URL sono
particolarmente vulnerabili ai canonicalization bugs (path traversal, URL manipulation, …). Nell’immagine
che vediamo abbiamo tre esempi di percorsi non canonici del percorso canonico.

Se nell’esempio successivo uso i “..” posso leggere un file che non sta nella web app, ma uno che sta nella
sotto-sottocartella. L’attaccante invece che leggere un file normale prende e legge un file di configurazione
segreto.
Ovviamente il modo per contrastare tale tipo di attacchi è non accettare percorsi arbitrari ma forzare l’uso
della forma canonica. Quando si caricano file, il file path dovrebbe essere deciso sempre dal server non dal
cliente.

Ma quando avviene l’input validation? Quando dobbiamo fare l’upload dei file. Anche in questo caso non
accettiamo tipi di file non voluti ma sempre solo quelli che conosciamo noi, attraverso whitelist.

Vediamo un esempio in spring.


Le estensioni più particolari delle quali dobbiamo fare attenzione sono quelle mostrate nella slide:

Tutte queste bisogna in ogni modo impedire che vengano implementati.

Stringhe
Dove possibile riordiamoci di usare sempre whitelisting per le stringe, in più cerchiamo anche sempre di
convertire le stringhe nel tipo enumeration (enum). Se non ci ricordiamo come funziona l’enum c’è un
esempio di seguito ma in generale serve ad associare determinate stringhe a numeri. In più se possibile
usare classi già esistenti.

Ma se l’enumerazione e il whitelist non fossero possibili, cosa facciamo?

 Limitiamo la lunghezza massima, facciamo così per evitare buffer size & counter DoS.
 Se le stringhe sono di tipo comune (e-mail, url, etc) utilizziamo il riuso di librerie standard
 Tool comuni per l’utilizzo di regular expression (RE)
 Se la situazione è complessa usiamo una compilation di tool.

Anche l’OWASP ci mette in guarda da e-mail ed URL che potrebbero sembrare semplici, ma non lo sono.
Regular expression
Sono strumenti molto potenti e ci vengono in aiuto quando non abbiamo un filtro che possiamo usare e
siamo costretti ad utilizzarne uno ex novo.

Per essere più formali le regular expression sono un oggetto che definiscono un pattern, immaginiamo di
voler filtrare le stringhe e accettare solo quelle che iniziano per “hello”. Regex è un mini-linguaggio usato
per la definizione di pattern testuali, in più fornisce la risposta Vero o Falso.

Una parola come “amaca” non passerebbe perché non c’è nulla dopo “ca”. Se vogliamo provare le regular
expression possiamo usare attraverso la shell linux con “grep”.

Ovviamente noi non vogliamo ricercare sottostringhe ma vederlo sull’uso dell’input validation, facendo in
modo che:

 La stringa d’ingresso combaci con interamente con il pattern.


 Il pattern deve essere il più limitante possibile
 Il pattern descrive solo input corretti
 Se non rispetta il pattern, si rigetta l’input.
Per fare quanto richiesto dobbiamo usare dei caratteri speciali come mostrati di seguito:

Ci sono poi sulle slide altri esempi di espressioni, quella sulla quale facciamo più attenzione è quella dove
potevamo mettere l’anno mille come ingresso:

Quindi qui siamo già sicuri che ci possono essere fino a 4 caratteri, poi abbiamo la possibilità di salvare
dentro delle variabili dei pezzi che abbiamo associato (la sintassi dipende dal linguaggio) e le utilizziamo per
fare i controlli multi-stage.
Se vogliamo fare pratica possiamo usare il sito https://regexper.com/ che ci permette di visualizzare le
regular expression, si basa sulle macchine a stati finiti (cosa che le RE sono). Vi è anche la possibilità di usare
un altro sito per il debugging https://regex101.com/

Purtroppo, le RE sono vulnerabili ad attacchi di tipo DoS e questo prende il nome di ReDoS. Se noi
scriviamo male le RE e un attaccante ci invia una stringa malevola il processore viene mangiato dalla RE;
nonostante il grafico mostri effettivamente una crescita esponenziale abbiamo che la crescita dipende da
come sia tata scritta la RE.

Com’è possibile intuire dal grafico la qualità delle RE dipende dal backtraking, ma cos’è? È il ripercorrere il
grafo della macchina a stati finiti al fine di ritrovare la soluzione alla RE. Infatti, alcune RE possono avere più
di una soluzione, nel caso questo succeda si ha che il sistema ritorna fino all’ultima soluzione trovata e
riprova a trovare una soluzione fino a quando tutte le opzioni sono esaurite.

Vediamo un esempio: Vogliamo il match della regola “.*”! ovvero vogliamo una parola con doppi apici e
punto esclamativo.

Ad occhio è facile notare che non c’è un match, ma l’algoritmo ci metterà molto tempo e risorse.
Ma quindi come dovremmo scriverla questa benedetta RE? Se vediamo la macchina a stati finiti avremo che
essa è rappresentata come segue, e le parti segnate in rosso solo le parti più critiche.

Questo capita perché la scritta è non deterministica (NFA) causando quindi sovrapposizione, allora per
risolvere tale problema scriviamo una RE in questo modo: Tutto quello che non è doppio apice, ripetuto
zero o più volte, più !
Infatti, se ripetiamo l’esercizio di prima il numero di passaggi è di gran lunga inferiore. Sulle slide ci sono
altri esempi di cattive RE.

Validazione con specifiche formali


Non le vedremo benissimo perché riguarda l’informatica teorica, ma se noi volessimo fare del parsing più
sofisticato avremmo che le RE semplici sono alquanto limitate e quindi esistono formal specification che
permettono di filtrare al massimo delle capacità.

Immaginiamo di dover effettuare il filtraggio per delle reti di pacchetto o per il linguaggio C, è super difficile,
usiamo una grammatica in BNF che ne indica la sintassi. Per il C abbiamo che ogni parentesi tonda aperta
deve avere una parentesi tonda chiusa, o cose così. Quindi abbiamo che i parser engine prendono un
pacchetto in ingresso e lo analizzano per verificarlo con la grammatica, se viene rifiutato lo scartiamo; ma
se il pacchetto viene accettato viene trasformato direttamente in una struttura dati già organizzata.

Lezione 9 06/04 (7-IntroFuzzing)


Fuzzing
L’argomento del giorno è il security testing, in particolare parliamo di una specifica tecnica chiamata
Fuzzing (Fazzing). Oggi vedremo una prima parte poi la prossima lezione completeremo l’argomento, ma
cominceremo a vedere anche tool di testing automatico.

Cosa intendiamo per fuzzing? È un approccio di tecniche di testing per la robustezza/security e come
caratteristica ha quello di usare moltissimi input, è un po’ diverso dai test funzionali visti ad ingegneria del
software perché lì il test è più centellinato.

Vista l’enorme quantità di sforzo richiesto è altamente improbabile che venga svolta in maniera manuale, è
molto più probabile che la si effettui in maniera automatica. Gli elementi che diamo al sistema per fare
questo tipo di testing sono chiamati fuzz inputs, il termine fuzzy indica (nel campo ingegneristico) un
segnale rumoroso.

Ma qual è la caratteristica principale per la quale è così importante questo tipo di testing? L’essere molto
efficace per grossi volumi di dati e quindi perfetto per trovare vulnerabilità di sicurezza. In più è un
approccio popolare per la scoperta di vulnerabilità sconosciute (0-day).

Il fuzzing si applica in tutte le attack surface, queste mostrate nell’immagine sono le aree tipiche:
 Digital media, si intende windows media player o qualsiasi applicazione che si occupa di contenuti
multimediali e quindi l’input è molto complesso.

Vediamo un'altra prospettiva dell’attack surface, tipicamente il fuzzing si applica ad attack surface di basso
livello come il traffico di rete su IP/Wireless ma in altri casi anche dall’alto come i testi del file system.

Di Fuzzers ne sono già disponibili per centinaia di differenti attack vector ed un elenco di tecnologie
proprietarie ed emergenti (immature) sono i migliori target:

 Next Generation Networks (Triple-Play) come VoIP e IPTV


 Data/video streaming protocols come MPEG2-TS (DVB-C/S/T)
 IPv6 e relativi protocolli
 Wireless technologies come il WiFi, 6LoPAN, Zigbee, Bluetooth, NFC, e RFID
 Industrial networks (SCADA)
 Vehicle area networks, come CAN e MOST

Fuzzing agli inizi


Il fuzzing all’inizio consisteva nell’inviare dati casuali attraverso input standard e un (pseudo)terminale, ad
esempio un test fu su “cc” che è il nonno di “gcc” che usiamo noi:

il comando “fuzz” genera un flusso lunghissimo di caratteri casuali.


I risultati dei primi test sul fuzzing mostrarono che un buon 24-33% dei programmi su sette versioni di UNIX
crashavano con ingressi casuali. Questo vuol dire che c’erano moltissimi buffer overruns e codici di ritorno
non controllati, e come visto nelle prime lezioni questo può essere il preludio ad attacchi più gravi.

Oggi abbiamo che l’uso del fuzzing si è evoluto e abbiamo aziende come Google e Microsoft che lo
impiegano per trovare più del 50% dei bug nei loro prodotti.

Positive vs Negative testing


Ritornando a quanto detto ad inizio lezione il tipo di testing visto negli altri corsi è conosciuto come Positive
testing mentre il fuzzing è conosciuto, in maniera complementare, come negative.

Facciamo una ripetizione di IS, quando riceviamo dei requisiti essi sono spesso positivi ovvero il “software
deve fare x” mentre i negati non vengono richiesti esplicitamente. Dopo i requisiti passiamo
all’implementazione che cerca di coprirli tutti evitando quelli negativi, quello che otteniamo però non è
detto che sia quello desiderato; infatti, nel cerchio di destra abbiamo due cerchi concentrici:

 Funzionalità che volevamo


 Programma effettivo che otteniamo, quelle chiamate conformance fault sono cose che il nostro
software fa ma che non dovrebbe fare. E spesso lo troviamo con il testing funzionale.

Ovviamente noi vorremmo che i due cerchi si sovrappongano il più possibile, ma non si riesce mai.
Il positive test si colloca quindi nel cerchio bianco, mentre il negative si colloca fuori dal cerchio grigio.

Ricapitoliamo il tutto:

 Funcional testing (positive): si va per requisiti positivi e si fanno dei test scritti a mano
 Performance e stress testing: visto al corso d’impianti, si va sulla quantità
 Robustness e fuzz testing: si va sulla quantità e sul tipo diverso di test. Rispetto al test funzionale
qui è tutto automatico.

Ma quindi come si scrivono fuzz test? Il modo più basilare è quello di inserire stringhe molto lunghe a caso
nelle richieste del programma, oppure stringhe sempre lunghe ma casuali.
Profondità del fuzz testing
Quando testiamo un programma come word non gli mandiamo un .mp4 ma uno pseudo word, questo
concetto è chiamato profondità del fuzz testing. Vediamo un esempio, consideriamo il protocollo FTP dove
ci logghiamo con username e password e poi chiediamo di vedere i file con LIST.

Se facciamo fuzzing la prima cosa che possiamo provare a fuzzare è la linea di comando, con il comando
USERR mionome è probabile che l’FTP server rigetti l’input (se non robusto crasherà). In questo caso il
fuzz è stato rigettato per INVALID SYNTAX, ma questo tipo di problema viene scremato spesso e volentieri
subito; quindi, non è proprio di grande interesse.

Un test più subdolo potrebbe essere quello segnato con (3) nell’immagine, in cui non diamo il comando
PASS oppure lo omettiamo/scambiamo. Questo è più efficace come test ed è di invalid state.

Il (4) è quello che va più in profondità


Vediamo la macchina a stati finiti dell’FTP, se facessimo il “test della scimmia” (quello segnato in rosso)
sarebbe poco profondo e quindi non utilissimo; mentre quello “smart” è il processo di arrivare prima nelle
parti importanti e critiche del sistema e poi andiamo con valori casuali.

Quindi i tool che useremo avranno a disposizione:

• Purely-random: Ovvero quelli iniziali del prof. Miller con caratteri completamente a caso. Il target
scarterà facilmente la richiesta

• Generation fuzzers: Sono quelli che costano anche più soldi e sono tool realizzati ad-hoc per cosa voglio
testare, partendo da un modello per generare gli attacchi

• Mutation fuzzers: Non parte da un modello ma parte da input “validi” (seed) come file validi o tracce
Wireshark, da questi input aggiunge poi del fuzz iniettando dei valori anormali.

Generation fuzzers
Il più costoso e nell’immagine di sotto vediamo peachtech che all’interno ha i protocolli di rete più usati e
per ognuno di essi ha una FSM (macchina a stati finiti). Quindi è un approccio molto potente ma che
richiede enormi conoscenze, in più il testing così effettuato segue il protocollo ma spesso i prodotti reali
deviano dallo standard.
Mutation fuzzers
Molto più economico e si comporta un po’ come man in the middle prendendo una traccia che noi gli
diamo e iniettando poi rumore a caso, questo fino a quando per pura casualità non troviamo un messaggio
che fa crashare il sistema.

Ma quindi cosa fa un tool di fuzzing? Da un punto di vista teorico è come se fosse un ciclo while infinito.
Failure monitoring
Ovvero come facciamo a capire se il sistema è vivo o morto? Vediamo i casi:

 Denial of Service: Crash e reboot logs provenienti dal SO, Network timeouts
 Problemi relativi al Filesystem: File access monitoring (es. "../../../../../etc/passwd")
 Command injection: Come abbiamo visto le fork copiano il contenuto dello stack, Injected fuzz
inputs all’interno delle DB queries, OS commands, etc.
 Vulnerabilità associate alla Memory: Valgrind, AddressSanitizer

Tool
Vedremo una panoramica di tre tool diversi per coprire le varie caratteristiche. Iniziamo con:

 Metasploit fuzzers, generation-based fuzzing tools

Oltre a già tutto quello che sappiamo su metasploit esso contiene anche una collezione di fuzzers.
L’esempio di come si apre e funziona è a 1:08:40

Le limitazioni di questo tool sono dovute al fatto che ha pochi protocolli; quindi, se non vi è il protocollo
basta è finita non possiamo testare nulla mentre l’altra è dovuta alla mancanza di automatismi come il
monitoring, in più il sistema non gira all’infinito ma si ferma al primo crash.

 Sulley fuzzers, generation-based fuzzing tools

Rispetto a metasploit ha un vantaggio, possiamo programmare noi il protocollo attraverso python.


L’esempio delle slide riguarda l’uso di TLS handshake.

Noi faremo fuzzing sul primo messaggio di ClientHello, procediamo con l’apertura di Wireshark per leggere
il messaggio e notiamo che ci sono alcune parti del messaggio che sono costanti; ad esempio, i primi tre
byte che rappresentano il tipo di messaggio (nel nostro caso 22 handshake) e gli altri segnati nella slide.
Tali valori costanti non sono i migliori candidati per fare fuzzing, ricordiamoci la questione della
profondità di fuzzing.

Questo di seguito è un programma python che utilizza delle funzioni “s_”, la prima è l’inizialize.
La lezione continua leggendo le altre slide ma fa anche delle esercitazioni. Consiglio la visione per vederli
passo passo.

 Mutiny (Talos) mutation fuzzing

Non è molto famoso, non supporta python3 addirittura potrebbe essere un progetto abbandonato. Come
detto per i mutation noi forniamo un pick up e lo utilizzerà come base. Alle 1:44:50 inizia con l’esempio.

Lezione 10 08/04 (8-AdvancedFuzzing)


Advanced Fuzzing
Oggi continuiamo sulla questione del fuzzing, nello specifico qualche tool più moderno rispetto a quelli
basilari dell’ultima volta. In più vedremo anche degli algoritmi più specifici e mercoledì ci sarà esercitazione
sul tool AFL per il fuzzing.

Possiamo riassumere le due tecniche migliori e più recenti in:

 Algoritmi genetici per la selezione degli input


 Coverage driven fuzzing, in riferimento alla copertura del test

Advanced Fuzzing - Algoritmi Genetici


Abbiamo visto che il concetto base per un fuzzing utile è l’utilizzo di combinazioni inaspettate che
performano un payload malformato, uniti a pezzi prestabiliti che portano a determinate profondità. L’idea
degli algoritmi genetici è quella di prendere gli input validi e provare quante più possibili permutazioni
attraverso una grande varietà di dati e mutazioni.

Vediamo un esempio sulle sessioni di rete, FTP richiede di inviare ogni volta delle stringhe ed in questo caso
abbiamo una sessione rappresentata in questa maniera:
Più sessioni possiamo raggrupparle in pool:

 Le sessioni all’interno di un pool sono mischiate/mutate ad ogni “generazione” al fine di ottenere


nuove sessioni
 Sessioni provenienti da differenti pool sono anch’esse mischiate ogni n° generazione

Lo scopo è massimizzare la fitness, ma vediamo il processo passo per passo. Lo stato iniziale di questi
diversi pool lo chiamiamo generation 0 e si eseguono tutte queste sessioni sul sistema, per ogni esecuzione
misuriamo la metrica di fitness.

Mischiate le sessioni ottengo una generation 1 dove le sessioni migliori sono mantenute così come sono, le
altre invece vengono mischiate attraverso il crossover. Non tutte vengono pescate con la stessa probabilità
ed in più non si ha solo un mixaggio casuale ma si aggiungono anche delle mutazioni randomiche.
Vediamo meglio cosa comporta effettuare crossover:

1. Seleziona due sessioni casuali


a. Più alta è la fitness più alta è la probabilità
b. La migliore non viene toccata
2. Seleziona un punto di crossover casuale
3. Genera A’ e B’ effettuando il crossing tra A e B
4. Ripetere fino a quando il numero di nuove sessioni non è uguale al numero iniziale di sessioni.

Nel caso di FTP potremmo ottenere una cosa del genere.

Mentre per un esempio di mutation è la seguente, sopra partiamo senza mutazioni poi abbiamo A’ dove
inseriamo spazzatura.
Poi abbiamo il crossover anche per i pool

Vi è poi anche sempre la mutazione dei pool che può consistere in un aggiunta/eliminazione randomica di
una o più sessioni, spesso quelle con una fitness più bassa.

Fuzzing basato su coverage di codice


Come facciamo a capire che un input è di buona qualità? Usiamo il principio della coverage, dove se
facessimo del fuzzing perfetto avremmo una coverage massima nel quale copriremmo tutte le porzioni di
codice, le metriche di coverage possono essere diverse:

 Coverage di blocchi base


 Di funzioni
 Di transizioni (branches) tra i blocchi base
 Etc…

In soldoni se rivediamo l’esempio del sanitizer segniamo ogni parte visitata.


Un altro modo di classificare i programmi di fuzzing, oltre ai due visti, è dividerli come nel testing in:

 Black box fuzzers: visti la settimana scorsa, non monitorano minimamente il programma interno e
quindi non usano feedback ma guardano solo gli input di fuzzing precedenti e i fallimenti del
programma.
 White box fuzzer: li usiamo adesso e usano la coverage, quindi, guardano in parte all’interno del
programma.
 Grey box fuzzer (non verranno usati):

American Fuzzy Lop (AFL)


AFL è un fuzzer basato su mutazioni, grey box e non basato su modelli in più è a linea di comando. Gli
input file possono essere di vario tipo e ha un’ottima configurazione base, in più può essere concatenato ad
altri tool.

Vediamo un esempio nel quale il target è un parser di immagini JPEG e l’input era un file di testo
contenente la parola “hello”. Da questo è partito con un algoritmo genetico ed è riuscito a trovare valori
validi. Nell’immagine a lato ci sono però delle immagini, senza senso, ma valide.
Si è scoperto che il primo byte deve essere 0 xFF (magic bytes) e sono utili per determinare sempre che
tali file con questi byte sono di tipo JPEG.

Come funziona AFL? Prima di tutto non dobbiamo usare un compilatore standard ma uno fornitoci:

Per il tipo di file in ingresso la cosa migliore sarebbe avere un file con la coverage più alta possibile ma con
la dimensione più piccola. Il professore lancia un esempio a 50:28.

Vediamo però un overview del processo realizzato da AFL. Si parte da una cartella di test contenente degli
XML, li prova uno alla volta mettendoli in coda e poi ne effettua la mutazioni ottenendo dei file diversi. Il
programma viene eseguito con i file mutati e a questo punto o il programma crasha oppure no, nel primo
caso salviamo cosa è successo e salviamo anche quando rimane hanged (appeso).

Analizziamo tutte e tre le coverage e poi immettiamo solo la migliore.


Ad 55:40 c’è un animazione.

Come detto troveremo gli output di AFL nelle seguenti directory:

 Queue/: Test cases che coprono nuovi path


o Include anche il file iniziale fornito
 Crashed/: test cases “unici” che hanno fatto crashare il programma
 Hangs/: test cases “unici” che hanno provocato un time-out del programma.

AFL analisi della coverage


Tra tutti i criteri visti qui usiamo la branch coverage, ricordando da IS abbiamo che un programma può
essere rappresentato come un grafo (control flow graph). Come scritto si è scelto di valutare la copertura
dei branches perché una loro strana combinazione potrebbe portare a transizioni di stato inaspettate.
Il branch coverage si misura con l’instrumentazione dove in ogni branch incrementa il contatore. Altra
animazione a 1:03:00. Di seguito una schermata riassuntiva di AFL dopo l’esecuzione.

Nelle slide successive c’è la spiegazione di ogni statistica cosa rappresenta.

Feature aggiuntive - Dizionari


Normalmente AFL fa solo bit-flip e poco altro, se però dobbiamo fare fuzzing di un’applicazione web è
abbastanza difficile generare delle stringhe valide, per questo motivo creiamo un file di testo con tutte delle
parole chiavi utili da passare con il flag “-x”.

AFL può usare come input più dizionari per iniettare mutazioni
Feature aggiuntive – Crash de-duplication
Tornando ai problemi di prima abbiamo visto che un programma potrebbe crashare molte volte ma
differenti tipi di crash possono triggherare le stesse vulnerabilità e quindi avere casi come in figura dove
97 mila crash con in realtà uno solo.

Per capire come fare ci possono essere diversi modi, quello più semplice è vedere il punto di crash del
sistema, come in Valgrind, e lo usiamo come punto di partenza per il backtrace.

Un altro approccio, più intelligente, è vedere gli hit counter dove praticamente contiamo quali sono i punti
uguali che si sono toccati per l’errore. Se sono gli stessi allora non si conterà il nuovo errore come qualcosa
di diverso.

Feature aggiuntive – Triaging e troncamento di input


Ritornando alla questione dello “small” input, una volta trovato un input file che ha generato un crash
avere un file di dimensione minima ci aiuta ad:

 Accelerare l’esecuzione del target binario


 Concentrarsi solo su mutazioni importanti
 Triaging (valutazione solo delle piccole porzioni)

Infatti, nell’esempio di seguito vediamo di come venga tagliata tutta la parte inutile del file che non
contribuisce al crashing dell’applicazione.
L’operazione è tutta automatica ma per farlo bisogna seguire questi comandi:

Feature aggiuntive – Sanitizers


Per effettuare un fuzzing efficace necessariamente abbiamo bisogno dei sanitizers, per utilizzarlo dobbiamo
abilitare l’addressSanitizer attraverso una variabile d’ambiente
Ad 1:30:20 fa vedere un programma vulnerabile chiamato appunto vulnerable.

Lezione 11 13/04 (8-Lab-Fuzzing)


Lezione di esercitazione, nello specifico sulla vulnerabilità di hearbleed. L’obiettivo è usare AFL, i sanitizer
più altri piccoli esercizi per completare le richieste del prof.

Per il corretto svolgimento dell’esercizio dobbiamo, da fare anche se abbiamo la sua macchina virtuale.

- Installare AFL
- Andare sul repository del corso e fare submodule init e update che servono per scaricare OpenSSL.
- Compilare OpenSSL e rivedendo la lezione dell’altra volta settiamo le variabili d’ambiente.

Qualcuno invece di usare lo STDIN preferisce inviare un file, è un alternativa valida ma non cambia nulla.

Se qualcuno ha problemi a compilare il programma “handshaking.gcc” e nello specifico sono problemi di


linking, significa che bisogna usare il compilatore g++. Nonostante il file sia in C openSSL nota codice C++. Lo
si può lanciare come:

afl-g++

afl-clang-fast++

Lezione 12 22/04 (9-StaticAnalysisIntro)


Static Analysis introduction
Lasciandoci dietro la prima parte del corso con vulnerabilità e le diverse catalogazione più il fuzzing, che è
una tecnica di analisi dinamica, passiamo adesso all’analisi statica.

 Analisi Dinamica, nome un po’ accademico per riferirsi a quello che normalmente chiamiamo
testing. Prendiamo un software e lo testiamo usando specifici input e verificando gli output.
 Analisi Statica, prendiamo il software così com’è senza eseguirlo e cerchiamo di estrapolare delle
informazioni. Non è associato a specifici input

Potrebbe sembrare che l’analisi dinamica sia migliore di quella statica ma non è così, come in ogni cosa ci
sono i pro e i contro. Un contro dell’analisi dinamica, che non si ha in quella statica, è che possiamo vedere
solo quali sono i percorsi del software e delle sue uscite. In riferimento a quanto detto l’altra lezione
parliamo sempre di Coverage e se non copriamo tutto, cosa spesso probabile, non sappiamo se siamo
vulnerabili su quella determinata porzione di codice o meno.

In una prossima lezione vedremo il ciclo di sviluppo di software in ottica security, di seguito ne vediamo uno
per capire dove si effettua l’analisi statica. La zona interessata in questa lezione è la zona di code dove
effettuiamo la così detta code review, qui vi è un qualcuno/più persone che guardano il codice ed
effettuano code inspection.
Un’altra fase legata all’analisi statica è il design architetturale dove si decide la code review da dove deve
iniziare e cosa deve vedere, evitando di analizzare parti a vuoto. Intendiamo che se abbiamo un sistema
software che si occupa di servizi online, ha senso sapere quali componenti potrebbero essere soggetti a XSS
e quindi mi conviene analizzare questi invece che altri pezzi.

Fare analisi del codice, per un essere umano, è un lavoraccio e per questo motivo si farà una combinazione
di software più quella umana. Ovviamente tool di analisi statica permettono di effettuare code review in
maniera più veloce e con più risultati ma l’uso di un essere umano è fondamentale per notare potenziali
problemi o errori.

Automated static analysis, limiti dei tool


Come benefici abbiamo detto che sicuramente ha una coverage maggiore ma i contro possono essere:

 Limitata capacità di analisi, ovviamente noteranno solo vulnerabilità standard perché purtroppo
hanno “i paraocchi” e non posseggono la logica del prodotto.
 L'analisi statica può comunque produrre degli errori:
o Linea di falso allarme, magari c’è una input validation che impedisce a questa linea di
essere eseguita ma l’analisi statica non la nota
o Linea di falsi contrari, cioè una vulnerabità vera che non viene notata come ad esempio
heartbleed
 Time consuming.

Di tool ne abbiamo ovviamente tantissimi e le aziende li osservano con grande attenzione perché i loro costi
non sono indifferenti e valutano pure in bisogno di ore uomo.

Oggi faremo un po’ una panoramica di questi tool, soprattutto per capire come funzionano e lavorano.

In generale un tool parte da un codice sorgente, però delle volte potrebbe essere anche l’eseguibile o il
bytecode (come in java). Oltre a queste cose vi è il sistema di build del programma, intendiamo Maven o il
makefile, se un programma non lo riusciamo a compilare probabilmente non riusciamo nemmeno ad
analizzarlo.
Tutto il file viene letto/estratto da un componente chiamato Parser/Extractor, oltre al programma che
dobbiamo analizzare, per fare un’analisi accurata, prende altri due elementi:

 Le modeling rules, cioè deve sapere quali sono i canali di input per il software o meglio definito
come l’environment, dove valuta l’ambiente etc.

 Libreria o framework con il quale è sviluppato il codice.

Quello che produce il parser è un database dove ci sono le informazioni del programma chiamato
Intermediate Representation (IR), veramente immaginabile come una tabella SQL. Quindi le analisi che
andremo a fare sono delle vere e proprie query, che non saremo obbligati a scrivere ma è sempre possibile
estendere le regole built-in. Il risultato finale è una review dal quale faremo l’analisi.

Type checker
La prima categoria dei controlli statici viene fatta già dai compilatori, in maniera più o meno simile da quella
fatta dai tool commerciali. Permette di prevenire alcune classi di vulnerabilità ma vediamo un esempio.

Nel primo caso, a sinistra, abbiamo un controllo sulle dimensioni dei tipi ed è tutto corretto perché la
dimensione di short è inferiore di quella di int. Vediamo a destra il caso opposto, ovviamente è pericoloso
perché questo potrebbe causare buffer overflow ma alcuni linguaggi (come Java ci mostra) ci da errori,
mentre C/C++ fa casting implicito e permette una cosa del genere.

Il compilatore, se usato senza una configurazione particolare, purtroppo non ci aiuta moltissimo in casi con
linguaggi come il C. Se vogliamo essere avvisati di situazioni come quella precedente dobbiamo aggiungere
dei flag come “-Wall -pendantic “ dove si abilitano i warning.
Vediamo adesso un esempio su un bug conosciuto come “goto fail” riguardante SSL del browser Safari,
nello scaricare il certificato di validità. Purtroppo, la pessima gestione delle parentesi causava una zona di
codice morto che non veniva mai eseguita

Se abilitiamo le opzioni di warning mostrare sopra potremmo notare questa vulnerabilità.

Style checkers / Defect finders / Quality scanners


Alcuni controlli dei compilatori rientrano nelle tre categorie mostrate sopra, essi sono controlli su alcuni
pattern di programmazione (schemi di programma), non sono veri e propri problemi ma sono modi di
buona programmazione.

Se vediamo un esempio in JAVA sul confronto delle stringhe abbiamo che non si usa “==” per valutare
l’uguaglianza ma si deve usare il metodo.

Spesso la quality e la sicurezza vanno a braccetto, infatti se andiamo a vedere un po’ su dei diagrammi per
osservare come sono in relazione possiamo constatare che in parte sono sovrapposti.
Static analysis: Source vs. Executable code
Abbiamo detto che alcuni programmi prendono il codice sorgente altri prendono il bytecode, ma qual è la
differenza? Tipicamente è meglio avere il codice sorgente poiché abbiamo più informazioni, si può delle
volte ricostruire dal binario ma è difficile. I contro sono che non è possibile analizzare codice di terze parti.

Passiamo poi ad un altro tipo di tool conosciuti come text scanner che sono sconosciuti ma in realtà ne
esistono di diversi, questi tool funzionano come il comando “grep” e quindi prendono il testo del codice e
controllano la presenza di testo specifico, vediamo un esempio:
Security defect finders
È il modo più avanzato poiché non guarda il programma come un file di testo ma creano un modello e lo
analizzano per valutarne le proprietà di sicurezza. Tale approccio è molto pesante e potrebbe comunque
creare falsi positivi.

Esistono sia molti progetti accademici che professionali che lo realizzano, ma vediamo effettivamente qual è
il tipo di modelli che vengono creati. Ci ragioneremo anche la settimana prossima quando apriremo gli
analizzatori e creeremo delle query.

Prima di vederli dobbiamo ripetere un attimo come funziona un compilatore, nella parte alta abbiamo un
linguaggio sorgente con una serie di informazioni. Abbiamo poi due passi fondamentali per la creazione di
un modello e sono:

 Lexical analysis, passaggio preliminare dove vengono tolti i commenti e gli spazi
 Parsing, partendo dai token si crea il modello

Da questo generiamo poi un modello ad albero, quando nell'istruzione vi è un if o un’istruzione all’interno


di un’altra abbiamo della rappresentazione ad albero dove andremo a creare un nodo padre ed un nodo
figlio. Anche il confronto avrà un nodo padre e figlio.

Quello al quale arriviamo sarà il Abstract syntax tree (AST).

Ogni parte del programma (dichiarazioni, assegnazioni, etc) sono rappresentati come nodi all’interno
dell’albero. L’analisi statica consiste a questo punto in una visita dell’albero in cerca di vulnerabilità.

Vediamo un esempio, ogni funzione ha un albero separato.


Il nodo method declaration rappresenta tutto il blocco, poi abbiamo quattro figli con il figlio più grosso che
è il metodo body; a sua volta il body avrà tre figli e così via. Si procede appunto ricorsivamente.

L’albero associato sarà il seguente, se usiamo questo programma con Eclipse.

Vediamo altri modelli con i quali abbiamo a che fare, come i grafi. Da un AST possono essere generati più
tipi di graph data structres come, ad esempio, il control flow graph.
Altri modelli sono il data flow graph, sembra simile al precedente ma è leggermente diverso poiché qui
vediamo tutte le istruzioni che analizzano dati.

Abbiamo poi anche i grafi di chiamata.

Types of security analysis


Immaginiamo allora di avere adesso tutta questa serie di modelli, che ce ne facciamo? La prima tipologia di
analisi si chiama:

1. Taint propagation analysis (analisi della propagazione del veleno), serve per trovare problemi
come la SQL injection e XSS. Partiamo dai dati che arrivano dall’attack surface, che sono considerati
avvelenati (input non fidati), e vediamo se è possibile raggiungere degli “scoli” ovvero dei punti
critici (come le query SQL).
Partiamo dall’esempio nel quale leggiamo con “fgets” dallo standard input e all’interno vi è un comando
“system” che esegue un comando nella shell del sistema operativo. Vogliamo capire se i dati dell'attaccante
possono raggiungere system.

2. Range analysis, determina il range di potenziali valori delle variabili o dei buffer e qui capiamo
anche qualche collegamento in più rispetto alla taint propagation. La vulnerabilità associata è
quella dei buffer overflow.
3. Type state analysis, vediamo direttamente un esempio.

Immaginiamo di avere un programma che usa una lista linkata con variabili di tipo node. Si chiama type
state poiché ad ogni nodo si abbina una macchina a stati finiti, quando facciamo due free su due nodi
abbiamo un errore.

Static analysis is not perfect


Quindi ripetiamo ancora una volta, l’analisi statica non è perfetta e questi errori nascono sia per problemi
teorici che per problemi di scalabilità. Nonostante questi problemi rimane comunque fondamentale.
Chiariamo meglio i due tipi di errori che si possono avere:

 Falsi negativi. Il nostro programma presenta delle vulnerabilità (cerchio più largo) ma il tool non le
rileva tutte.
 Falsi positivi, l’esatto contrario di quanto detto sopra.

Per capirlo per bene vediamo un esempio. Immaginiamo di essere un analizzatore e vedere questo testo. In
questo programma c’è un problema di buffer overflow “x” è di cinque caratteri. Il compilatore per capirlo
come fa? A prescindere dalla copia si effettua un’asserzione e poi prova a navigare attraverso un DB che si
crea.

Tale tecnica viene fatta anche per valori non fissi e con variabili.
Vediamo un altro esempio che possiede però due possibili percorsi.

Questo esempio è per capire che questa piccola porzione di codice è probabilmente affogata in altre 10M
righe di codice; quindi, il numero di percorsi cresce esponenzialmente con i vari percorsi e siamo costretti
quindi ad accettare delle approssimazioni. Le approssimazioni che verranno fatte saranno fatte in diversi
modi:

 Context sensitive algorithm, se ad esempio abbiamo due porzioni di codice (rappresentati con i
triangolini arancione più piccoli) dovremmo effettuare due analisi diverse perché dipendono dai
parametri d’ingresso.
 Context Insensitive algorithm, effettuiamo un’approssimazione e viene analizzato il triangolo
arancione una sola volta e quest’unica analisi mi porta a dire che z è maggiore o uguale a zero per
entrambi i casi.

 Path sensitive algorithm


 Path Insensitive algorithm

Se guardiamo l’immagine successiva abbiamo “fgets” che sappiamo essere una funzione da evitare e poi
una “prinft” che sappiamo essere vulnerabile. Il programma è vulnerabile? No, perché per come sono fatti
gli if, avendo entrambi nella condizione “x” eseguiamo sempre la “y=hello” e poi la print mai avrà le due
assegnazioni di y in contemporanea.
 Flow insensitive

Nel semplificare l’analisi introduciamo anche il concetto di errore poiché altrimenti il consumo di memoria
sarebbe troppo pesante. I tool, quindi, trovano un trade off il più possibile e spesso il costo del tool deriva
proprio da questo.

Limiti teorici
Anche se avessimo un super computer avremmo sempre dei falsi positivi/negativi, ma com’è possibile?
Questo è dovuto a dei limiti proprio matematici dimostrati da Godel attraverso dei teoremi undecidable
ovvero non è dimostrabile se un teorema è vero o falso.
Lezione 13 27/04 (10-StaticAnalysisTools)
Prima di iniziare alcune informazioni di servizio, tra oggi e venerdì finiremo l’analisi statica e venerdì 29 sarà
esercitazione e vedremo la parte pratica con questi tool.

I tool abbiamo detto che non sono tutto nella vita ma è importante il processo di sviluppo degli stessi, cosa
che vedremo la settimana prossima. Oggi vedremo due tool, FindBugs e GitHubCodeQL.

Findbugs
Il nome originale del progetto è sì FindBugs ma adesso se vogliamo usarlo lo troviamo con il nome di
SpotBugs, può essere usato in tre modi:

 Programma Java separato, sconsigliato


 Nell’Ide come eclipse, ed è il più consigliato
 Integrato nella build automation, quindi quando facciamo una build o lanciamo un progetto c’è
incorporata l’analisi statica da parte del compilatore.

Find Security Bugs è un’estensione orientata alla sicurezza per Findbugs. Sulle slide ci sono i passaggi e i link
per il download e per l’installazione.

Vediamo il progetto WebGoat che è un’applicazione web volutamente vulnerabile usata per approfondire e
capire i problemi di tali applicazioni. Nel caso lo cercassimo noi attenzione, la versione più nuova è
differente da quella vecchia che noi usiamo; questo perché ripetiamo per fare l’analisi statica bisogna fare
la build del progetto.

Al minuto 14:00 apre WebGoat e lo naviga spiegando com’è strutturato e funziona. Possiamo vedere che è
strutturato in sotto lezioni in base al tipo di attacco; quindi, lo stesso file appare più volte nelle stesse
lezioni. I numeri vicino alle lezioni sono i warning che ha trovato.

Per analizzarlo seleziona webgoat/tasto destro/SpotBugs/FindBugs in basso vedremo la barra di


caricamento e poi potremmo cambiare la visualizzazione tra Java e quella dello “scarrafone” dove vedremo
il report delle vulnerabilità
Il programma ci organizza gli allarmi in quattro livelli (dal peggiore al meno grave):

1. Scariest
2. Scary
3. Troubling
4. Of concern

Dentro ogni voce poi c’è una sottodivisione in:

 High confidence
 Normal confidence

Questo perché non è sempre sicuro al 100% dell’allarme dato, sempre per i discorsi fatti la scorsa lezione, e
per questo separa le due categorie di risultati.

Lo scarrafone che troviamo a riga 82 riguarda la funzione che gestisce la query sul JDBC e usa una classe
statement e prende in ingresso una stringa dall’oggetto sqlStatement convertito in stringa.

Questo è un caso senza prepared statement. Salendo troviamo che il problema è a riga 68, consiglio
l’ascolto per capire per bene.
C’è poi un’altra cosa super utile, se andiamo in Window/Show View/Bug info abbiamo che quando
selezioniamo una voce troviamo una cosa utilissima, ovvero un’intera finestra che ci aiuta a come
programmare.
Continuiamo poi al minuto 28:12 con l’altro tool.

GitHub CodeQL
Già solo che c’è GitHub è un marchio di qualità, e sfrutta un piccolo linguaggio chiamato LGTM (looks good
to me). È un tool che nasce come piattaforma di analisi statica continua per CI/CD (ovvero il ciclo di
sviluppo è integrato in strumenti che ogni volta che effettuano i commit testano e fanno altro per
aumentare la sicurezza del codice.)

Il fatto che GitHub analizzi tutti i codici sulla sua piattaforma, per vedere se ci sono delle CVE comuni, si
chiama variant analysis. Questo è utile per sradicare intere categorie di bug e quindi ogni volta che si
scova una nuova categoria di attacco, come le injection, si crea una query con il linguaggio di CodeQL e le si
eliminano.
Quindi questo linguaggio è il punto cardine del tool, ma come scriviamo una query? È piuttosto simile
all’SQL dei database relazionali, ha poi una spruzzata di Object Oriented e c’è poi la logica.

La figura di seguito è identica a quella dell’altra volta, partiamo sempre da un sorgente che viene elaborato
da un estrattore, il quale lo mette in un database e poi abbiamo le query che vengono prese da un
compilatore e svolte automaticamente. Useremo Visual Code per vedere i risultati.
Abbiamo detto che per partire dobbiamo compilare il programma, questa cosa era facile in eclipse perché
lo fa tutto in automatico, per farlo in questo tool dobbiamo usare il comando $ mvn cleaninstall .

Quindi prima ancora di fare l’analisi statica dobbiamo verificare che compili sulla macchina statica. Dopo
fatto il build lo dobbiamo ribuildare però facendo l’estrazione: $codeql database create “webgoat-db” –
language=java”.

Quello che succede con il secondo comando è che CodeQL chiama Maven e quello che verrà compilato in
Maven verrà indicizzato in un Database.

Riapre la condivisione e fa vedere il funzionamento a 43:30

Anatomia di una query CodeQL


Una volta imparata la sintassi è abbastanza semplice scrivere queste query, poi ovviamente visual studio ci
aiuterà con l’auto compilazione. Partiamo dall’esempio, l’obiettivo è trovare tutte le parti di programma
che chiamano “createStatement”, non per forza per cercare delle vulnerabilità.

Visto che la query è specifica per JAVA faremo l’import, premessa: nelle query troveremo gli uguali scritti =
ma vale la stessa cosa per il Sql, QUESTA NON è UN ASSEGNAZIONE è un’equazione da risolvere
Partiamo dal “from”, che stavolta parte prima di “select” , e definisce il punto di partenza della query ed in
questo caso partiamo da tutte le chiamate di tutti i metodi.

In “where” iniziamo a ritagliare lo spazio come in SQL e prenderemo tutti gli statement indipendentemente
dall’oggetto.

Con “select” possiamo applicare delle funzioni ai risultati, ad esempio in questo caso mostriamo tutte le
entry.

Vedremo mano a mano quelle più complesse ma partiamo per adesso dalle structural queries, si chiamano
strutturali perché non stiamo vedendo ancora il dataflow e vogliamo solo vedere metodi che si chiamano
in un certo modo.

Partiamo sempre da quello visto l’altra volta, quindi immaginiamo che abbiamo un programma che ha
generato un AST come riportato in figura. Nel DB abbiamo delle relazioni, ognuna per le tipologie
dell’albero.
Usiamo il codice di seguito come partenza, il problema subito lo notiamo è che il metodo “write” scrive
dentro un array e alla posizione “loc” scrive un valore. Il bug di questo programma è che il corpo dell’if è
commentato, per cui l’obiettivo di questa query è trovare tutti gli if senza istruzioni all’interno

Nel “from” metteremo gli “if” ed i blocchi, nel “where” metteremo che il blocco deve essere parte dell’if e
che il blocco presenti zero istruzioni. Se ne troviamo uno scriviamo “this if-statement is redundant”.

ATTENZIONE FORSE LA CLASSE BLOCK è SBAGLIATA E SI DEVE USARE Blockstmt. Lo accenna nell’esempio
al minuto 1:17:00
Ci sono modi anche per strutturare le query che invece di scrivere tutto nel “where” possiamo scrivere
delle funzioni, come in questo caso “isEmpty” che prede in ingresso l’if con il getThen(). Abbiamo modo di
vedere pure un altro concetto che è quello di predicato, ovvero una condizione logica.

In riferimento all’AST vediamo un esempio a 1:20:00 per farlo clicchiamo tasto destro su un file/CodeQL:
View AST.

È possibile creare anche delle nuove classi, come la “EmptyBlock” rendendo più affinata la classe “Block”
Continua con la correlata di esempi di codici CodeQL che vedremo meglio durante l’esercitazione.

Dataflow analysis e Taint Analysis queries


Queste due sono cugine e la seconda è una tipologia specifica della prima. La dataflow cerca di collegare fra
loro una variabile, che chiamiamo sorgente, con una variabile destinazione questo perché la destinazione
potrebbe essere un DB o un SO e lo vogliamo proteggere

La tainted analysis ricostruisce il percorso ed è in grado di collegarci il parametro d’ingresso “tainted” con la
“y” passata in “callFoo”.

CodeQL fornisce delle API per effettuare queste analisi:

 Intra procedurale – local data flow, il modello controlla una sola funzione come l’esempio di prima.
 Inter procedurale – global data flow, il modello controlla il flusso attraverso le varie chiamate di
funzione

Per effettuare la dataflow analisi (locale) dobbiamo seguire i seguenti passi:

La classe Node rappresenta gli oggetti del nostro programma, quindi può essere una variabile/un method
access/etc. è come se fosse una classe padre. Poi vi è il predicato localFlow il quale, a partire da due
elementi, ci dirà se sono collegati dal dataflow.

Vediamo un esempio, sempre rispetto all’immagine di prima:

localflow (tainted , y ) sarebbe un predicato vero o falso? Sarà vero perché CodeQL ha già creato un DB e
ha visto che tainted e “y” sono collegati.

Vi è poi anche “localflowStep” che controlla se due variabili sono immediatamente collegate tra loro.

Dataflow graph node NON SONO NODI AST. Abbiamo bisogno di mappare i nodi attraverso l’uso di
predicati.

Vediamo ora l’ultimo esempio riguardante proprio la taint analysis. Abbiamo a disposizione la classe
TaintTracking con un altro predicato “localTaint” che ci permette di capire se due variabili sono taintate tra
loro. Ma prima di tutto, perché non usiamo il Dataflow e abbiamo bisogno di una classe ad-hoc? Perché
la taint propagation ha a che fare con gli input esterni e quindi è un flow particolare, vogliamo i flussi
dall’attack surface.
Nelle slide poi vi sono tutte le librerie usate ed implementate in CodeQL.

Iniziamo adesso con il nostro esempio, anticipiamo che dovremo creare la nostra sottoclasse di
TaintTracking e dovremmo poi creare dei predicati (questo per creare dei source e dei sink).

Supponiamo che “mySource()” e “mySink()” siano la mia sorgente e la mia destinazione dei dati.
Domanda: c’è della taint propagation nel programma? Sì, giustamente prendo un dato dall’esterno però c’è
comunque della sanitification e quindi vi è una protezione.

Come scriverà la query?

Stiamo cercando, dove c’è “exists”, un MethodAccess “ma” tale che il nome sia “mySource” e la classe deve
vare un nome “myClassA”.

Possiamo poi aggiungere un altro predicato chiamato “isSanitizer” in modo da filtrare i flussi del dataflow
che rispettano determinate regole. L’obiettivo è che se non tenessimo conto di questi controlli ci
troveremmo tantissimi falsi positivi.
A 1:46:20 fa vedere l’esempio su visual studio.

Lezione 14 29/04 (10-Lab-StaticAnalysis)


La macchina virtuale che vi ho condiviso include già il software necessario (CodeQL, Visual Studio Code +
estensione). In alternativa, potete installare i tool in questo modo:

1. Scaricare la versione più recente di CodeQL da


https://github.com/github/codeql-cli-binaries/releases (include solo i tool per linea di comando),
scomprimere in una cartella a piacere, e aggiungere il percorso della cartella tra i percorsi di
sistema ($PATH in Linux/Mac, %PATH% in Windows)

2. Installare Visual Studio Code

3. Installare l'estensione di Visual Studio Code per CodeQL (tramite la sezione "Estensioni", oppure
tramite il link https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-codeql)

4. Clonare sul proprio computer la cartella di lavoro per CodeQL, tramite il comando: git clone --
recursive https://github.com/github/vscode-codeql-starter

5. Aprire il file "vscode-codeql-starter.code-workspace" (si trova nella cartella creata dal passo
precedente) tramite Visual Studio Code

Vediamo oggi l’esercizio di analisi statica sul software U-Boot, il quale è scritto in C e non cambia molto da
quanto visto durante il corso con Java. U-boot è un bootloader che prende, quando accendiamo una
scheda, l’immagine del SO dalla rete e in questa vulnerabilità di oggi se l’attaccante ha il controllo del
server; può sovvertire il processo di boot ostacolandolo.

In particolare, la vulnerabilità riguarda l’uso di memcpy , l’analisi che faremo sarà di tipo taint propagation.
Nel codice troveremo le classiche configurazioni delle socket in C, infatti quando dobbiamo analizzare un
pacchetto di rete i byte in esso sono ordinati in modo diverso rispetto al processore (Little endian & big
endian) e la funzione ntohl sta per conversione net to host.

Vogliamo quindi scrivere query che collegano questi due punti del programma.
Per prima cosa ci iscriviamo su git hub e ci logghiamo per poter partecipare alla U-Boot Challenge e andare
sul sito mostrato sul PDF.

La challenge crea un repository sull’account, indifferente se pubblico o privato, nella voce issues c’è un
messaggio automatico che spiega i primi passaggi.

Per procedere con l’esercizio possiamo:

Usare le pull request (PR): ovvero un modo per contribuire in maniera più veloce ad un progetto, il flusso di
lavoro tipico prevede il branch nel quale facciamo tutte le modifiche e poi una volta finito facciamo la pull
request. La richiesta andrà ad una persona che la valuta e permette poi la modifica.

Noi impersoneremo sia il controllore che quello che modifica, per abilitarlo sconsiglia l’uso della linea di
comando. Usiamo VSCode per creare un branch (lo vediamo in basso a sinistra, c’è scritto “main”) e ci
facciamo poi anche i commit etc.

Quando facciamo una Pull request scatta una git hub action che prende la query e la esegue sul progetto.
Se quest’ultimo funziona apparirà un bottone verde con scritto “merge pull request”.

Per importare il database scaricato bisogna andare su VSCode, casella QL -> From an archive. Quand’è tutto
pronto facciamo “close issue” e ogni volta che facciamo close esce il prossimo step.

Alcune volte potrebbe presentarsi il problema di non riuscire ad effettuare le pull request, questo è un
problema proprio di git hub ma tranquilli che lo stesso si può andare avanti con l’esercizio.

Se continua a presentare problemi sembra essere dovuto alla versione del punto 4 ad inizio lezione: “git
clone --recursive https://github.com/github/vscode-codeql-starter”, per sicurezza sulla VM possiamo
cancellare la cartella “codeql-starter” e riscaricarlo.

Lezione 15 04/05 (11-SecureLifecycle)


Oggi sarà una lezione un po’ più di alto livello, saremo più generici e parleremo di processi di sviluppo
software. Metteremo insieme tutto quanto visto finora:

 Testing
 Analisi statica
 Vulnerabilità
In maniera professionale, enterprise.

Perché abbiamo bisogno di un ciclo di sviluppo software sicuro?


Da dove nasce un secure software lifecycle, sappiamo anche già cosa vuol dire software lifecycle (l’abbiamo
visto agli esami di ingegneria di software e SAD/PSSS) ma non sappiamo come si mischia con la sicurezza.

Siamo agli albori degli anni 90 e fare sicurezza voleva dire fare due cose:

1) Sicurezza di rete
2) Sicurezza di sistemi

L’approccio unico alla software security era il penetrate and patch ed era un approccio reattivo. Questo
significa che il software veniva sviluppato nel modo classico tramite Waterfall o i processi incrementali, poi
prima del rilascio si faceva penetration testing e quindi si cercavano vulnerabilità software.

L’altro approccio, sempre legato al modello reattivo, è far uscire il prodotto e scoprire i problemi dopo
l’uscita.

Questo approccio si chiama reattivo perché non ci stiamo anticipando con le attività di security.
Ovviamente quest’approccio è pessimo, se vediamo i costi ci rendiamo conto di qual è il danno per
un’azienda. Non solo vi è il problema dei costi ma vi è anche il problema delle patches che vengono
rilasciate in fretta e furia e possono tranquillamente introdurre nuove vulnerabilità o problemi. Infine,
l’ultima problematica è che la patch non è sempre installata dai clienti, facendo così moltissimi impiegano
ancora software danneggiati e non funzionanti.

Ma perché non si aggiornano i dispositivi? Eh, il conto è aggiornare la nostra macchina e un conto l’azienda
che ne ha un centinaio. Si parla infatti di Patching resource-intensive, e tale attività risulta in una riduzione
dell’availability del sistema e del servizio. Il NIST per gestire gli aggiornamenti ha proprio un documento
compilato proprio usando le tecniche di risk management; quindi, componente per componente si vaglia
se:

• Accettare (la vulnerabilità ha severità bassa e l’asset è poco importante)

• Mitigare (patch, disable feature, upgrade, deploy più controlli)

• Trasferimento (cyber-insurance, SaaS)

• Non aggiornare evitando (Si fa un analisi costi-benefici e si elimina la superfice d’attacco)

Microsoft SDL
Ad un certo punto della storia Microsoft faceva prodotti che a livello di sicurezza erano i lucchetti della
Kikko; quindi, Bill Gates sprona tutti i livelli dell’azienda a darsi come priorità la security.
Da questa situazione si è avuto il ciclo di sviluppo di Microsoft chiamato Microsoft Security Development
Lifecycle (SDL).
Quindi si è passato da un approccio reattivo, all’approccio proattivo.

Questo approccio prevede 12 attività:

1. PROVIDE TRAINING: gli ingegneri, gli sviluppatori, devono conoscere le vulnerabilità e gli aspetti
tecnici. L’istruzione però riguarda anche tutta l’azienda, tutti i livelli gestionali devono essere al
corrente degli obiettivi e delle prospettive di un attaccante, tutti devono conoscere i possibili rischi
di sicurezza. Ogni volta che escono nuovi attack vector bisogna conoscerli, quindi bisogna essere
sempre aggiornati.
2. DEFINE SECURITY REQUIREMENTS: per fare un software bisogna avere dei requisiti di sicurezza.
Come al solito si parte sempre dai requisiti funzionali e li estendiamo con requisiti di sicurezza. Altre
fonti per i requisiti sono gli standard (se ad esempio siamo nell’ambito finanziario, si utilizza lo
standard PCI DSS dedicato alle carte di credito). Altri requisiti possono provenire da incidenti
passati, threats noti.

Vediamo un attimo un approccio, chiamato Abuse Cases e serve come estensione dell’UML. Si parte quindi
da uno Use Case tradizionale, immaginiamo un E-commerce store con due utenti con i loro casi d’uso.

In un Abuse case si aggiungono i security threats (li disegniamo fuori dal rettangolo e con un altro colore, e
non sono use case). Si ragiona in termini di attaccanti. L’omino “unmotivated admin” non è malevolo ma
può comunque influire sulla security.

Ai requisiti normali, ci aggiungiamo requisiti si sicurezza: si aggiungono nuovi use cases legati alla security,
che vengono chiamati countermeasures. Oppure come visto a SSD si parla di security control del NIST (che
nascono dagli use cases gialli).

Prima di cominciare a sviluppare si prova a definire degli obiettivi aggiuntivi.


Il “change password” non è un caso d’uso necessario: noi imponiamo all’utente di cambiare la password
così che cambiandola periodicamente riduciamo il pericolo del furto di password. Questo impone allo
sviluppatore di fare delle cose in più, oppure con delle policy imponiamo di non utilizzare delle password
banali, in modo da prevenire.

Vediamo altri esempi: aggiungiamo il monitoring per tenere traccia di tutti gli ordini e quindi posso
ricostruire il tentativo di un ordine illecito, oppure posso in tempo reale rilevare un tentativo di truffa.
Questa è ad esempio una forma di detection.

3) DEFINE METRICS AND COMPLIANCE REPORTING: “Se non puoi misurarlo, non puoi migliorarlo”, se
non abbiamo delle metriche di security non riusciamo a capire se stiamo bene così o no. Si
utilizzano le key performance indicators (KPI), ovvero delle metriche che possono essere tenute
sotto controllo per vedere se lo sviluppo sta procedendo bene. Ad esempio, quando viene trovato
un bug, è importante segnarsi che si è trovato un bug, per capire quali bugs sono stati trovati e
quali sono stati ancora risolti o meno. Non sappiamo niente dei bugs non noti, ma almeno
contiamo quelli noti. Un’altra cosa è tenere traccia dei security work items, ovvero quale dei
controlli sono stati fatti e quali noi. L’obiettivo è l’accountability: lo potremmo tradurre come
“responsabilizzare” le persone.
Vediamo qualche esempio: qualunque software open source deve avere la lista di bugs aperti.
Questo di seguito è OpenStack.

4) PERFORM THREAT MODELING: l’obiettivo di quest’attività è analizzare il progetto di un sistema


(siamo nella fase di design architetturale) e vogliamo capire se va bene il design flaws e poi,
quando avrò implementato il sistema, quando avrò il codice, ci aiuta a fare testing.

Supponiamo di avere un diagramma UML del sistema, e notiamo che c’è un componente che potrebbe
avere SQL injection, che cosa faremo? Faremo la threat analysis e quando facciamo il testing, facciamo sql
injection su quel componente (ci viene detto quindi quale componente dobbiamo testare e quello che
dobbiamo testare). STRIDE è un approccio per trovare i threat, le minacce. Microsoft propone di effettuare
il diagramma dei componenti, fare un’analisi componente per componente e per ognuno farsi sei
domande: può l’oggetto essere soggetto a

Facciamo un esempio: abbiamo la classica applicazione web


Si fa un diagramma come questo chiamato Data Flow Diagram (DFD) dove mettiamo una palla per ogni
componente e una freccia dove c’è un flusso di informazione. Per ogni palla possiamo applicare un
approccio chiamato STRIDE PER ELEMENT: vediamo quindi se quei 6 threat sono validi per quell’elemento.

Un altro approccio è STRIDE PER INTERACTION: per ogni flusso di dati (quindi per ogni freccia) si possono
avere quei problemi oppure no, ovviamente essendoci molte più frecce che palle l’operazione sarà più
laboriosa.

L’esito di questa modellazione è fare Risk Analysis o Threat modelling analysis e per ognuno di questi rischi
dobbiamo dare un indice di rischio dato da impatto e likelihood.

Viene dato un valore tra low e high, un numero solitamente tra 1 e 5, per capire quali sono i rischi
importanti e quali meno importanti.

Il prodotto tra impatto e likelihood rappresenta l’indice di rischio: il risultato mi fa capire cosa devo fare, in
quanto posso accettare il rischio (quindi non faccio niente), posso mitigare il rischio, oppure posso trasferire
il rischio (pago qualcuno per prendersi cura di quel rischio), oppure faccio testing per prevenire.

Esistono poi altri approcci e metodi per la threat modelling, sono presenti sulle slide per chi volesse.
5) ESTABLISH DESIGN REQUIREMENTS: c’è un abuso di terminologia, traduciamolo come “bisogna
progettare bene”. Dobbiamo fare un design sicuro:

Questo è un elenco di requisiti, dei principi di progettazione sicura e questi non si applicano tutti bene al
giorno d’oggi.

Ad esempio, “economy of mechanism”, si intende parsimonia nello sviluppare soluzioni: i nostri secure
control non devono essere troppo complessi altrimenti diventano anch’essi vulnerabili. Nelle web
application abbiamo parlato delle espressioni regolari, per fare la validazione dell’input, che possono essere
soggetti a denial of service. IPSEC è molto famoso in negativo, un protocollo per fare traffico IP in modo
sicuro.

Oppure “open design”: il design deve essere aperto, cioè mettere delle password cablate è una violazione
dell’open design. Si parte dall’idea che un attaccante conosca il codice ed il design e potrebbe scovare la
password nascoste nel sorgente. Stesso discorso valido per la crittografia: gli algoritmi di cifratura sono
aperti, l’unica cosa che non è nota è la chiave.

Oppure “least privilege”: un’applicazione non deve fare più di quello che dovrebbe fare; quindi, diamo il
minor privilegio possibile ai componenti software.

Oppure “fail-safe defaults”: se qualcosa va storto, un utente si sta loggando ma capita un’eccezione, nel
dubbio, fail-safe, non lo facciamo entrare.

Noi come ingegneri abbiamo degli utensili importanti da dover utilizzare per mitigare il rischio, li abbiamo
visti anche già a SSD:

6) DEFINE AND USE CRYPTOGRAPHY STANDARDS: Non è banale perché non bisogna fare crittografia
a caso, bisogna sceglierla con cura e in modo appropriato. Non creare algoritmi casuali fai da te, le
librerie di cifratura sono complesse da scrivere, è importante fare riuso. Come fare a capire se
librerie e algoritmi sono aggiornati? Per prima cosa tenere occhio a ciò che dicono le guide di
Microsoft; secondo consiglio, è utilizzare analisi statica che guarda il sorgente e ci può avvertire che
stiamo utilizzando una libreria o un package non aggiornato.

7) MANAGE THE SECURITY RISK OF USING THIRD-PARTY COMPONENTS: non scriveremo mai un
software da zero, ma utilizzeremo componenti di terze parti. Ovviamente anche quest’ultimi
possono essere vulnerabili: possono esserlo già o possono esserlo in futuro. Come si fa? Microsoft
suggerisce innanzitutto dell’avere un inventario di cosa usare; ci vuole un plan to respond, quindi
sempre i responsabili devono avere un piano di contromisure, devono sapere cosa fare se un
componente diventa vulnerabile (disattivare una funzione, fare backup e tornare ad una versione
precedente); bisogna usare tool di software composition analysis (SCA), ovvero software che ci
dicono quali sono le dipendenze nel software, se ci sono vulnerabilità note (ci dicono se utilizziamo
versioni obsolete di una libreria), inoltre fanno license compliance (ad esempio GPL è la licenza di
Linux, se noi lo modifichiamo, siamo obbligati a pubblicare il codice che abbiamo modificato come
open source).

8) USE APPROVED TOOLS: bisogna fare attenzione ai compilatore e ai tools di sviluppo che usiamo.
Oggi una fonte problematica sono proprio i tools di sviluppo: gli attacchi che a tal proposito hanno
luogo si chiamano Software Supply Chain Attacks. Alcuni malware non attaccano il prodotto
mentre sta eseguendo su un cliente, ma lo sviluppatore del prodotto: tentano di infettare la build
infrastructure, ad esempio fanno il commit di una modifica malevola in un progetto open source,
mettono una back door in un processore, furto di certificati o account di uno sviluppatore. Il più
famoso di questi attacchi è SolarWinds: SolarWinds è un’azienda americana che fa monitoraggio
delle reti, qualcuno ha infettato il loro prodotto (si pensa la Russia).

9) PERFORM STATIC ANALYSIS SECURITY TESTING (SAST): effettuiamo analisi statica, in quanto è utile
per trovare algoritmi di cifratura deprecati, numeri casuali deboli.

10) PERFORM DYNAMIC ANALYSIS SECURITY TESTING (DAST): fare fuzzing, ovvero forme di analisi
dinamiche per trovare vulnerabilità e quindi eseguire il programma. A differenza del SAST possiamo
trovare problemi come l’integrazione dei componenti, che a livello statico è quasi impossibile.

11) PERFORM PENETRATION TESTING: effettuiamo il penetration testing, un tipo di analisi manuale (in
contrapposizione al SAST e il DAST che sono automatici) in quanto c’è una persona che prova a
forzare il nostro prodotto. Permette di trovare problemi di progettazione. Per fare il testing ci sono
due approcci:
o testing tradizionale sui security control: OWASP ha un documento su questo, come fare
testing del login, dell’autenticazione, dell’encryption.
o risk-based testing: se abbiamo trovato un componente che può avere una vulnerabilità,
faccio un testing guidato dal rischio; quindi, mi baso sulla threat analysis fatta prima.

12) ESTABLISH A STANDARD INCIDENT RESPONSE PROCESS: cosa succede se troviamo una
vulnerabilità? Chi deve vedere gli aggiornamenti? Come distribuirli? Bisogna darsi un piano prima di
rilasciare un prodotto per ridurre la finestra temporale della vulnerabilità.

Altri cicli di sviluppo sicuro simil SDL:

 Security Touchpoints
 SAFECode: da un enfasi sull’uso di pratiche di scrittura codice sicura dandoci una serie di consigli su
come usare Java, C++, etc. Dice le API sicure e quelle non sicure; suggerisce quali linguaggi e quali
framework utilizzare in base a quello che dobbiamo fare.
L’anno scorso Biden ha fatto un decreto sulla cyber security:

Agile and DevOps


Oggi siamo nel mondo Agile e DevOps. Agile significa fondamentalmente velocizzare il processo di
sviluppo, rilasciando spesso il software, fino a più volte al giorno e lavorare per cicli brevi per rilasciare
spesso nuove versioni. Agile si basa spesso su user stories.

SAFECode ha pubblicato una guida su come fare agile sicuro:


- security-focused stories: ovvero consideriamo 36 storie sicure, che sono dei punti di partenza di sicurezza
- per queste storie sono detti quali sono i threat coinvolti (OWASP Top 10), cosa fare per risolvere la storia
- 17 cose da fare per gestire il ciclo di sviluppo in modo sicuro

Un esempio di queste 36 voci: “come sviluppatore voglio che i buffer siano tutti controllati”. Poi dice chi le
deve risolvere: il progettista, lo sviluppatore. Poi dice cosa fare: fare analisi statica, oppure un compilatore
con funzionalità contro buffer overflow, oppure evitare l’uso di primitive non sicure.

Quelle 17 voci di cose da fare, possono essere ad esempio:

Microsoft ha pubblicato una sua versione, DevOps: ci dice di introdurre analisi statica e dinamica e
integrarle; fare in modo che i tool siano facili da usare; evitare i falsi positivi; mantenere al sicuro le
credenziali, quindi evitare che il codice abbia hard-coded password, usare dei tools appositi e non metterle
insieme al sorgente; fare lo scanning dei file di configurazione; monitoring continuo. Il monitoring continuo
ci serve soprattutto per gestire i falsi positivi.
Esempio: GitHub, se andiamo nella configurazione di un progetto troviamo queste due opzioni:

“secret scanning” significa che GitHub manda un alert se trova nel sorgente una password di qualche tipo
(ovviamente non è che le trova completamente tutte) però chi gestisce gitHub sa com’è fatto JDBC e sa
quali sono le funzioni che usano password. Fa analisi statica per trovarle.

“push protection”, ogni volta che proviamo a fare un push lui se trova un problema simile al precedente,
non ce lo fa fare: se carichiamo una credenziale e vogliamo cancellarla da GitHub è una mazzata in fronte
perché c’è la cronologia dei commit, ci sono delle cash, tantissimi punti dove viene copiata.

Sempre riguardo GitHub sappiamo che è interessato al DevSecOps, ovvero lo sviluppo sicuro, dove le
attività di testing non sono fatte solo nella fase di rilascio ma vengono anticipate il più possibile. Shifting
left, significa anticipare le attività di security.

Consigli per la DevSecOps: ci sono degli standard di maturity model che elencano le cose da fare per
arrivare ai vari livelli di sicurezza. Quindi in un’azienda che non sa fare security, osservano l’immagine,
vediamo cosa serve per arrivare al primo livello e partiamo da lì e così via. È un vero e proprio mini-tool che
analizza le nostre repository per dirci dove siamo forti e dove no.
GitHub ha pubblicato una guida per arrivare al livello 1 di security utilizzando GitHub: “usate l’analisi statica
di default, abilitate i tool di fuzzing, e fate degli scan almeno sul branch master periodicamente e non ad
ogni commit, ad esempio una volta al mese o una volta ogni due settimane”.

Lezione 15 06/05 (12-Attackers)


Nella sezione del materiale del corso su Teams, ho caricato un documento (note-tool-malware-
analysis.docx) con alcune indicazioni sul materiale da scaricare per le prossime esercitazioni.

Inizieremo a usare questo materiale nella esercitazione dell'11 Maggio. Entro quel giorno, per favore
scaricate la macchina virtuale, e installate il framework Metasploit sul sistema operativo host del
vostro computer (cioè non dentro la macchina virtuale).

Vi chiederei anche di portare con voi il vostro computer o tablet alla lezione del 6 Maggio (questo
venerdì). Non si tratterà di una esercitazione, ma consulteremo insieme dei documenti.

Cyber Threat Intelligence


Uno dei task fissi di chi fa security è analizzare i malware e per questo motivo oggi cominciamo la malware
analysis. È un task molto tecnico che prevede di analizzare codice macchina, sorgente per ricavare
informazioni utili sull’attacco. Ma prima, dobbiamo capire cosa vogliamo scoprire da quest’analisi, quindi
prima di entrare nel tecnico, capiamo cosa si intende per attacker modeling.
Dobbiamo capire questi attacchi in cosa consistono: dai primi attacchi degli anni ’90 ad oggi hanno subito
molte variazioni, ma esistono dei metodi e degli approcci per ragionare in che modo viene portato un
attacco e come studiarli. Capiamo quali sono le fasi di un attacco e come si fa a caratterizzarne uno stesso.

Introduciamo un concetto di “intelligence”: si intende capire il nemico, ed è un concetto molto ampio, non
si tratta solo di informazioni tecniche di basso livello, ma vogliamo capire qual è la strategia dell’attaccante,
i suoi obiettivi, le risorse che ha a disposizione. Se conosciamo queste informazioni è più facile combattere il
nemico (intelligence theory).

Per dare una definizione più formale, l’intelligence, è la capacità di un’organizzazione di prevedere un
cambiamento (qualcosa di ostile che ci viene rivolta contro) in tempo utile per fare qualcosa.
A noi interessa l’intelligence nel contesto cyber, per cui negli ultimi anni si è introdotto il concetto di CTI
(Cyber Threat Intelligence), che consiste nel collezionare quest’intelligenza per fare operazioni pro-attive
contro un attaccante, cioè vogliamo conoscere il nemico prima che faccia qualcosa contro di noi.

Bisogna raccogliere dei dati e fare delle considerazioni, ma da dove li prendiamo questi dati? La CTI può
partire dall’OSIntellingence ovvero, l’attaccante va a vedere quali sono i domini che abbiamo registrato, i
dati che pubblichiamo sul sito, tutto quello che è pubblico. Noi possiamo applicarla a noi stessi per capire
cosa gli attaccanti sanno di noi. Una volta raccolti i dati, li mettiamo tutti insieme e possiamo ottenere una
threat intelligence, in modo da raffinare le informazioni raccolte, capire magari quali punti specifici sono
stati attaccati in modo da cercare di prevenire attacchi futuri.

Intelligence è un ambito molto teorico ma vediamo gli attributi chiave che devono essere:

 Timely: che mi deve arrivare in tempo utile per fare qualcosa


 Analyzed: questo discorso del raffinare i dati
 Predittivo: mi deve permettere di anticipare eventuali tipi di attacchi futuri
 Contestualizzato
 Accurato
 Rilevante

Potremmo quindi definire brevemente l’intelligence come dati di alta qualità. Oggi viene gestita con tool e
protocolli ben definiti e tipicamente la threat intelligence contiene:

 Quali sono gli attori coinvolti.


 Le motivazioni e gli obiettivi tipici di quel gruppo, quindi a quali assets potrebbero essere interessati
o cosa potrebbero minacciare.
 TTPs/Campaigns, ovvero quale tecniche usano per attaccare.
 Tutto quello che contribuisce dal punto di vista tecnico per caratterizzare quell’attacco (indirizzi IP o
domini), che sono prove che dimostrano che un determinato attaccante è coinvolto (ovvero le
impronte lasciate dagli attaccanti)
Tipi di CTI
Esistono 3 livelli diversi di intelligence, abbiamo l’intelligence di lungo termine (o strategico) e per strategia
si intende il ragionare a lungo termine mentre la tattica invece è quello che io faccio a breve termine. Ad
esempio, il giorno in cui è apparso il primo ransomware, le aziende hanno dovuto iniziare a pensare come
proteggersi da questa novità: questo è qualcosa che fa riferimento a lungo termine.

Chi si occupa di questa parte è la dirigenza “Board” o “C-Suite”.

Poi abbiamo l’intelligence tattica, qui siamo a livello più tecnico: per esempio, informazioni sulle
metodologie, i tools e le tecniche utilizzate dagli attaccanti.

Poi abbiamo un’intelligence di ancora più di basso livello, in cui saremo noi all’inizio della nostra carriera,
detto anche di breve termine, che riguarda le informazioni per fare monitoraggio.

Tutte e tre i gruppi in figura si scambiano informazioni tra di loro.

Threat actors
Ne abbiamo già parlato, con threat actors facciamo riferimento a gruppi che si occupano spionaggio, di
crimini sul web. Invece con advanced persistent threat (APT), ci riferiamo a gruppi strategici che sono delle
vere e proprie aziende di cybercrimine, gruppi avanzati.

Come si procede per usare la CTI? Innanzitutto, si fa un piano, si raccolgono dei dati su cui fare delle analisi.
Ovviamente, l’approccio potrebbe essere “ok attivo tutti i log possibili, raccolgo dati all’infinito”, ma non è
detto che sia l’approccio migliore; è un approccio buono perché raccolgo, ma avrei problemi di volume, il
costo di storage sarebbe imponente, ma soprattutto, dovrei essere sicuro di aver raccolto tutto quello che
mi serve e non solo cose superflue. Quindi vi è prima una fase di auto-analisi per capire se il monitoraggio
che stiamo facendo bisogna modificare qualcosa.
Tutte le operazioni sui file critici, sui database, tutti i punti critici sono ben osservati? Se sì, poi ci chiediamo
quali sono gli attaccanti più probabili e a cosa sarebbero interessati.

Ci sono aziende che sono miei clienti e che possono essere attaccate? Magari faccio software per Enel,
vogliono attaccare Enel, ma attaccano prima me che sono un’azienda più piccola. Poi cerco di capire se
aziende simili a me in passato hanno subito degli attacchi ed eventualmente come si sono comportati.

Fase numero due, preparation and collection, per raccogliere dati ci possiamo auto-scansionare le reti al
fine di capire quali sono le informazioni pubbliche nostre all’esterno; oppure possiamo raccogliere gli
indicators of compromise ed uno in particolare sono i malware (ad esempio, gli allegati malevoli che
vengono inviati attraverso phishing: li monitoro una volta che scopro che uno è sospetto, cerco di
analizzarlo) utilizzo un approccio proattivo piuttosto che filtrarli semplicemente.

Andare per blacklist sarebbe troppo riduttivo, perché una volta messo qualcosa in una blacklist l’attaccante
crea qualcosa di nuovo per ri-provarci superando il blocco. Invece se opero così io analizzo per evitare
proprio di riceverlo nuovamente.

Nel caso di un malware, vediamo quali indicators of compromise estraiamo:

 Indirizzi IP
 Nomi di dominio
 Valori di hash (impronta digitale dell’attacco che ho ricevuto)
 Artefatti host/network

Facciamo un esempio: Bad Rabbit è un ransomware


Se vediamo le prime tre righe abbiamo che queste sono indirizzi http, ci viene sostituito la “tt” con la “xx”
per evitare che i link siano attivi. Il primo rappresenta il link del riscatto, il secondo ed il terzo vi hanno fatto
uno studio del malware e il quarto è l’hash che lo rappresenta.

Fonti dove viaggia questo tipo di threat intelligence è VirusTotal che contiene un database di virus, il quale
scannerizza file e ci fornisce una serie di informazioni sul file. AlienVault è molto simile che contiene
campioni di malware, testimonianze degli utentis, screenshot

Lo scenario è molto ricco: ci sono questi siti web come VirusTotal, oppure piattaforme che non sono
pubbliche, ad esempio MISP che è un progetto opensource. Tipicamente le aziende di un certo settore, si
“federano” tra loro, e MISP permette di scambiare le informazioni di threat intelligence, così se io vengo
attaccato ho che gli altri ricevono le informazioni da me in modo da evitare che accada anche a loro.

Prodotti commerciali di threat intelligence feeds:

Tra questi vediamo Digital Shadow, dall’immagine vediamo come siano pregni di informazioni. Vengono
mostrati gli attacchi più recenti, le cybergang più attive e gli attacchi alla quale la mia rete è o è stata
sottoposta.

Sulla sinistra le dashboard colorate mostrano tutto in maniera più schematica.


È come se aprissimo “Repubblica.it” per la threat intelligence: si vedono gli eventi nel mondo, e ci sono dei
grafi che indicano delle percentuali ottenute da analisi della nostra rete, etc.

Piramide del Dolore


N.D.R. Potrei mettere la storia della mia vita, ma eviterò e metterò la roba seria.

Un concetto importante è che gli IOCs non sono tutti quali, ovviamente c’è una gerarchia d’importanza e
per questo esiste la “piramide del dolore” per gli attaccanti. Ci sono gli hash, gli indirizzi IP, i domini, fino
ad arrivare su ai TTPS.

Se blocchiamo un certo tipo di IOC, ad esempio un hash values, abbiamo che per un attaccante è un
impatto di tipo minore e può superarlo facilmente; quindi, la piramidi del dolore fa riferimento allo sforzo
che un attaccante deve fare per modificarsi e aggirare. Se riusciamo a scoprire quali sono i tools che
l’attaccante usa nei suoi malware, io posso usare tecniche più potenti per cui l’attaccante avrà molta più
difficoltà a raggirarmi. Se scopriamo i TTPs (Tecniche, Tattiche e Procedure) l’attaccante deve inventarsi
completamente un altro attacco (li vedremo nel dettaglio).

Ora per il resto della lezione ci concentreremo sulla parte di processing and exploitation; quindi, dopo aver
raccolto IOC di basso livello saliremo in alto nella piramide, si utilizzano dei framework per strutturare le
strategie:
Cyber Kill Chain (CKC) model
Il primo, Cyber Kill Chain, è un modello per descrivere gli attacchi: secondo questo modello, ogni attacco è
fatto da sette fasi (anche se qualcuna ogni tanto può essere saltata).

1) Reconnaissance: è uno studio preliminare in cui si raccolgono informazioni sulla vittima.


2) Weaponization: creo un payload malevolo
3) Delivery: invio di questo payload malevolo
4) Exploitation: in qualche modo questo payload viene eseguito e riesce ad infettare la vittima.
Ovviamente ci deve essere una vulnerabilità da qualche parte, sia nel software o nell’ingenuità
degli utenti.
5) Installation: questo payload è soltanto l’inizio del malware vero e proprio, non è detto che tutto il
malware sia nel payload. Quello che c’è nel payload è soltanto una prima frazione del malware, poi
magari una volta installato il payload parte il download del malware, questo per non notare le
grosse dimensioni.
6) Command and Control: controllo del nodo in cui è presente il malware.
7) Azioni sull’obiettivo

Diamond model
Il secondo modello, Diamond model, è un modello molto ad alto livello ed è realizzato da quattro elementi:
 Avversari
 Capability (si intendono i metodi usati, la parte strategica)
 Infrastruttura (si parla di tutto quello che è tecnologico, a differenza della capability: tool, server,
indirizzi e-mail)
 Vittima

Viene usato nei report ad alto livello per descrivere degli attacchi: si parte da uno qualunque di questi
quattro punti e bisogna navigare questo diamante.

Facciamo un esempio concreto: Stuxnet è l’attacco alle centrali nucleari iraniane. Questo malware proviene
da Israle e dagli Stati Uniti. Il malware è arrivato tramite pen USB (infrastruttura). Sono state usate delle
vulnerabilità per compromettere vari nodi (capability). La vittima era la centrale iraniana.
Un altro esempio è quello di WannaCry

MITRE att&ck
L’ultimo modello, ma quello più utilizzato, è MITRE ATT&CK e molto dettagliato e guidato soprattutto
rispetto ai precedenti. È una specie di banca dati che elenca tutte le tecniche e le tattiche usate dagli
attaccanti.

A cosa serve? Abbiamo molti rettangoli bianchi, altri blu, altri gialli. La legende ci da due nome di gruppi APT

(Si tranquilli non si legge niente, se volete c’è il link)

Ci sono rettangoli di vario colori perché dalla legenda vediamo la presenza di due gruppi che hanno delle
tecniche differenti:

 Carbanak azzurro.
 FIN7 rosso.

Ad esempio, la terza colonna è “persistent” ed indica tutte le tecniche che un attaccante utilizza per restare
in modo persistente in una rete, se sono segnate in rosso vuol dire che è usata da FIN7. È importante
studiarlo in quanto è la lingua per parlare tra analisti; esiste in diverse edizioni, mobile, standard o per
sistemi industriali.
In totale sono 12 colonne, quindi abbiamo 12 tattiche (attività malevole) all’interno delle quali abbiamo
decine di tecniche:

Esiste anche la matrice di pre-attack, che contiene quello che viene fatto prima di un attacco.

Un lateral movement significa che dopo aver infettato un nodo, mi muovo per arrivare ad un’altra vittima,
passando per altri nodi (Ad esempio fare lo scanning con nmap)

Ogni tecnica ha delle sotto-tecniche, identificate da numeri. Per ogni tecnica troviamo le mitigation, quindi
come proteggerci, prodotti software utili per fare quell’attacco, gruppi APT famosi legati a quello.

=> il sito in figura serve per utilizzarlo.


Passiamo adesso a fare insieme un esercizio. Formbook serve per rubare le password o informazioni vari,
scritto in C e assembly. Lo scopo dell’esercizio è estrapolare la cyber threat intelligence da quest’articolo.

Iniziamo a sottolineare le parti importanti.

Iniziamo capendo che parte attaccando il nostro browser e anche se utilizziamo https, lui legge le password
prima di utilizzarlo. Anche se le password non sono scritte dalla tastiera, lui le legge lo stesso, quindi
probabilmente le legge dalla memoria. Fa screenshot, fa dei logging e credential stealing.

Quindi, se andiamo a vedere le 12 tattiche: quale sceglieremmo? Credential Access e Collection.


Andiamo a vedere le tecniche legate a queste tattiche. Andiamo nella colonna credential access: cosa
troviamo che ci può interessare?

Andando avanti dobbiamo completare l’analisi da soli usando sempre MITRE ATT&CK, aiutiamoci con
l’analisi usando il link del navigator. Ovviamente nel testo cerchiamo delle keyword come persistence,
execute, gather, e send oppure menzionano elementi tecnici come DLL e Registry key.
Il testo inizia dalla slide 52.

Lezione 17 11/05 (12-Lab-Attackers)


Oggi esercitazione su quello che abbiamo iniziato a vedere venerdì scorso, parliamo di malware e
ovviamente wajù questa roba è pericolosa non ci scherzate. Non usatele per danneggiare gli altri.

Astaroth malware
Quello che vedremo oggi è una campagna che, nonostante sia chiamata tecnica fileless, presenta dei file
ma nel senso che impiega già i programmi presenti nel SO.

Per capire meglio vediamo un esempio su Linux. Immaginiamo che un malware debba scaricare un file da
internet e invece che scrivere un programma che fa il download, il programma chiama una shell e fa un
wget senza creare una socket o che.
In più questo tipo di attacco è più difficile da rilevare proprio perché non vi è un exe, che spesso sono i primi
ad essere bloccati. Altri nomi con il quale è conosciuto questo tipo di tecnica è: living off the land binaries
(LOLBins) oppure Living off the land binaries, scripts and libraries (LOLBAS).

L’obiettivo dell’esercizio di oggi è replicare il malware che usa queste tecniche, nello specifico le prime tre
presenti in questa lista:

 Alternate Data Streams (ADS), è una funzione del File System e con questa funzionalità posso
nascondere un file nel FS senza che qualcuno lo veda.
 ExtExport.exe
 BITSAdmin, serve per fare manutenzione
 Windows Management Instrumentation (WMI), non lo useremo ma per chi vuole si può
approfondire.

Ma vediamo di capire come funziona l’attacco. Come al solito partiamo dal fatto che sarà arrivata una mail
di phishing o un altro attacco di social engineering, la vittima clicka sul file .lnk (collegamento windows),
l’attaccante ha una sua macchina predisposta all’attacco con un server web con la porta 80 e la porta 4444
aperte.

La vittima esegue il programma che prende il nome di “dropper” che scarica ed esegue una prima parte del
malware chiamato “stager” dalla porta 80, poi lo stager scarica a sua volta il DLL (vedremo nelle prossime
lezioni che cosa è). La DLL viene eseguita e realizza la connessione alla reverse shell.

Quindi per l’attacco dobbiamo prima scrivere un file .bat -> che genera un .vbs -> che genera un .lnk ora il
lnk viene lanciato e scarica stager.cmd (che scriveremo noi) sulla porta 80 e il .dll. Il .vsb nasconde i file nel
File System e crea a sua volta un altro lnk che esegue il .dll.

Si è un bordellozzo ma sulle slide sta scritto benissimo.

Per alcuni, se si presenta il problema con msfvenom per creare la DLL, usate il comando:
payload/windows/x64/meterpreter/reverse_tcp.

Se metasploit non viene trovavo o non funzionano i comandi, controllare che non sia stato messo in
quarantena dall’antivirus.
Lezione 18 13/05 (13-BasicMalware)
Oggi contrariamente a quanto visto ieri non costruiremo un malware ma lo analizzeremo ma per quale
motivo? Mettiamo caso che veniamo attaccati, se non ne capiamo la struttura non potremo mai essere
in grado di fronteggiare il malware. Tale operazione prende il nome di malware analysis

Malware analysis
Un malware è un software che viene utilizzato come strumento durante intrusioni informatiche (ad
esempio creare una reverse shell che consente all’attaccante di avere il controllo della macchina vittima). La
malware analysis la possiamo definire come “autopsia del software malevolo”, quindi scopriamo di essere
attaccati, non sempre in tempo reale anzi proprio il contrario, allora vogliamo dissezionare il malware per
vedere come sconfiggerlo o ridurre l’impatto dell’incidente (vogliamo scoprire quali macchine sono state
attaccati, quali dati, a chi sono stati inviati i nostri dati).

La settimana scorsa abbiamo introdotto la threat intelligence e in questo momento vogliamo capire qual è
il legame tra malware e threat intelligence? Noi vogliamo fare l’analisi del malware per scoprire qualcosa
sulla threat intelligence. Quindi ad esempio, dato un file malevolo, vogliamo capire se esso è specifico per la
nostra azienda o se fosse un attacco di massa; oppure, cosa comporta questo file malevolo; oppure, come
faccio a rilevare la presenza di questo file anche su altri computer della mia azienda; oppure, capire se
questo file ha informazioni sull’attaccante.

Secondo il NIST un malware è ciò che compromette le tre proprietà CIA della security.

Esistono tanti tipi di malware (un po’ come i pokemon e modestamente il mio pc li ha presi tutti): alcuni
sono generici altri hanno un significato specifico.

 TROJAN, richiama il cavallo di Troia che contrariamente a quello che si pensa appartiene all’Eneide
(n.d.r): è un programma apparentemente utile ma che al suo interno ha del codice malevolo.
Quindi è un malware che si nasconde in altri programmi.
 VIRUS e WORM, entrambi si replicano sulle macchine; il worm si replica in maniera automatica tra
le macchine, mentre il virus ha bisogno di replicarsi manualmente (ad esempio con l’utilizzo di una
chiave USB)
 DOWNLOADER (o dropper): è un codice malevolo che ha come scopo l’operazione di scaricare un
altro codice malevolo
 BACKDOOR: è un programma che apre un porta (ad esempio TCP) permettendo a qualcuno di
entrare sulla macchina infettata, fornendone l’accesso.
 BOTNET: è un programma che recluta la macchina infettata e la fa partecipare ad altri attacchi, ad
esempio negli attacchi DoS
 ROOTKITS: è un programma che cerca di fornire all’attaccante l’accesso, ed il mantenimento, in
modalità amministratore alla macchina attaccata.

Spesso però i malware possono appartenere anche a più categorie contemporaneamente di quelle citate.
In base agli obiettivi, distinguiamo due tipi di malware:

 Mass malware (“a strascico”), non hanno un obiettivo specifico, infettano tutto quello che possono
infettare; fanno affidamento su servizi vulnerabili e laddove sono installati, attaccano tali servizi
senza avere una vittima specifica. Questa è la tipologia che solitamente viene riconosciuta dagli
antivirus.
 Targeted malware e a questa categoria si fa spesso riferimento quando si parla di APT, si cerca una
specifica vittima e quindi sono malware ad hoc. Sono molto problematici perché gli antivirus non
sono pronti a riconoscerli.

Cosa faremo? Vedremo vai approcci di malware analysis, oggi ci focalizzeremo sulla basic static analysis,
ovvero vogliamo guardare i file malevoli e faremo considerazioni senza eseguirli. Ovviamente un’analisi
statica non basta, ma è una prima contromisura guardare solo l’eseguibile.

Poi c’è la basic dynamic analysis, ovvero eseguiamo il malware su un computer e vediamo cosa succede; è
una cosa pericolosa da fare quindi va fatto in un ambiente sicuro. Non è semplice da fare perché spesso chi
scrive i malware cerca di nascondere le informazioni per essere analizzato.

Poi faremo (nelle prossime lezioni), l’advanced static analysis, facendo proprio del reverse engineering:
andremo nel codice macchina per ricostruire il file cosa voleva fare. Richiede però delle skills avanzate.

Infine, esiste l’advanced dynamic analysis (non la vedremo), con la quale apriamo il malware durante la sua
esecuzione, come se facessimo operazioni di debug. Non è niente di fantascientifico ma semplicemente
non abbiamo tutto questo tempo.

Basic Static Analysis: Antivirus


La prima cosa da fare se ricevo un file malevolo e non sono sicuro della sua pericolosità, è utilizzare subito
un antivirus. Abbiamo già visto anche come usare gli hash di un file malevolo; oppure altre tecniche che
prevedono di vedere gli header dell’eseguibile malevolo, le sue stringhe oppure le funzioni utilizzate dallo
stesso.

Per quanto riguarda i tool che vedremo, sono presenti nella macchina virtuale che ci ha fornito lui.

Analizziamo gli antivirus, essi si basano sulle signature ovvero se Il file malevolo contiene un URL per pagare
un riscatto; la presenza di quell’URL è una sorta di impronta digitale di quel malware e noi potremmo
rilevare la presenza di quel malware in altri computer, cercando quell’URL. Quindi la signature è la stringa
“magica” che ci consente di capire se un file malevolo si trova anche in altri computer.
In certi casi le signature sono molto precise, ad esempio “www.malware.com”, oppure altre volte sono
approssimate e non molte precise.

Quali sono i problemi di questo approccio? Perché un antivirus non basta? Perché un attaccante può
modificare il codice in modo tale da non far riconoscere la signature.
VirusTotal consente di vedere se un file viene riconosciuto come malevolo o no.

Per quanto riguarda invece le stringhe dell’eseguibile immaginiamo di avere un programma scritto in C con
la printf e strcpy abbiamo che le parti in grigio sono dentro il file eseguibile.

Se noi aprissimo l’exe, troveremo in rappresentazione ASCII le parole “ciao” ed “hello” e possiamo leggerle
proprio per effettuare malware analysis. Per leggere le stringhe possiamo proprio usare i programmi Strings
e BinText, che dovrebbero essere già presenti sulle macchine, le stringhe ASCII sono fatte così: ogni
carattere è un byte.

In Windows ci sono anche le stringhe Unicode, che non è una codifica, ma è uno standard per enumerare i
caratteri. Nello specifico Unicode usa due byte per carattere:

Facciamo un esempio, immaginiamo di lanciare il tool strings su un programma chiamato bp6.ex_ e


otteniamo quanto mostrato di seguito:
Innanzitutto, come fa Il tool a trovare le stringhe nel programma? È molto selettivo, non vediamo le
istruzioni macchina ma qualcosa simile all’inglese. Il tool va a vedere byte per byte se il valore è un
carattere stampabile in ASCII. Se trova tre valori consecutivi stampabili, la riconosce come una stringa e la
stampa. (I valori 4,1,3,2,5 sono delle note del libro)

Nell’esempio in alto riconosciamo un indirizzo IP, nomi come GDI32.DLL (che è una DLL di Windows), il
nome della funzione SetLayout (serve a stampare finestre, quindi probabilmente è un programma grafico),
e poi troviamo una stringa che ci fa capire che vengono utilizzate delle e-mail. L’operazione di far uscire
dei dati quando li rubiamo è chiamato exfiltration

Packed/obfuscated malware
Quando applichiamo Strings o qualche altra analisi sull’eseguibile abbiamo che spesso le stringhe
potrebbero essere offuscate, parliamo quindi di “malware impacchettati”.

Abbiamo un programma malevolo, ci sono delle stringhe, un packer (un tool che crea un malware packed)
prende l’exe e lo offusca, come se lo comprimesse, per cui la stringhe non sono più rappresentate come nel
file originale.

In questo si ottiene una sorta di compressione e non riusciamo a riconoscere le stringhe, ma come si fa ad
eseguire se non si riconoscono le istruzioni. Quindi come funziona? Il packed program è fatto di due parti:
una parte offuscata (Packed executable), ed una parte molto piccola che è in grado di decomprimere la
parte offuscata (wrapper program). In questo modo, l’eseguibile viene decompresso nella RAM ed
eseguito.

Esiste un tool PEiD che riconosce i malware impacchettati: quindi se analizziamo un exe e non troviamo
delle stringhe, e ci sono altri sintomi che vedremo tra poco, possiamo pensare di avere difronte un malware
impacchettato, allora ricorriamo a questo tool che ha un piccolo database di firme che gli consentono di
riconoscere i Wrapper Program.

Questo tool però è diventato famoso in negativo, perché spesso per dare in pasto i malware, finisce che li
esege e ovviamente prima abbiamo detto che effettuare l’esecuzione di un malware può comportare
numerosi problemi.

PE Executable files
Innanzitutto, impariamo allora come sono fatti i file exe? I file exe di Windows hanno tutti quanti lo stesso
formato, che si chiama PE (Portable Executable) ed è fatto dalle seguenti informazioni:

 Un PE header iniziale
 Info sul codice
 Tipo di applicazione
 Librerie di funzioni richieste
 Spazio richiesto

L’exe, se lo aprissimo con NotePad vedremmo dei bytes, che non hanno molto significato, la prima metà di
questi bytes prende il nome di header mentre l’altra parte è definita sections (un header e tanti sections).
All’interno di queste sections, troviamo diverse cose: codice eseguibile, stringhe, imports.

Cosa sono gli imports? Tipicamente i nostri programmi non sono mai autosufficienti, nel senso che
utilizzano librerie o devono chiamare funzioni esterne. Ad esempio, supponiamo che nel nostro main,
nell’header, abbiamo “urlmon.dll” questa è una libreria utilizzata dal nostro main. Quando viene lanciato
l’exe, il sistema operativo va sul disco, trova “urlmon.dll” e lo linka dinamicamente. Quando viene avviato
l’exe, nella RAM viene copiato sia il contenuto dell’exe ma viene copiato anche il contenuto della libreria
dll, e poi devono essere collegati tra loro tramite un puntatore in modo tale che l’exe possa chiamare le
funzioni della libreria.

In questo modo possiamo capire ad esempio, che il nostro exe importa una funzione per scaricare file: ma
se l’exe è un programma per ascoltare la musica sul disco e non online, e scopro che c’è questa funzione,
c’è qualcosa che non va. Quindi ci può essere utili l’analisi di questi imports.

Mercoledì vedremo proprio i tool che vediamo durante la spiegazione, uno di questi è Dependency walker
che mostra gli import e gli export, più tutte le funzioni usate all’interno del programma dalla DLL (PI).

Alcune DLL sono di utilizzo comune, non sono sinonimo di qualcosa di malevolo.
Ad esempio Kernel32.dll serve ad allocare i file, uccidere i processi; oppure User32.dll serve per la grafica;
oppure abbiamo quelle che ci consentano a collegarci sulla rete; oppure Ntdll.dll solitamente non è
importate quindi forse se la vediamo dovremmo preoccuparci, perché implica operazioni avanzate che
solitamente i programmi non fanno.
Sia gli exe che i dll possono importare ed esportare; quindi, una dll può importare un’altra dll. Con
“esportare” intendiamo che può essere chiamata da qualcuno. Fondamentalmente in windows DLL e EXE
sono identici, cambia solo un bit nell’header.

Quali sono altri trucchi degli attaccanti per evitare l’analisi? Se l’attaccante non vuole fare vedere che usa
una determinata DLL che fa, come lo nasconde nell’header? Usa Kernel32, che permette di usare la
funzione “LoadLibary()” che carica una DLL senza dichiararla nell’exe. Di seguito vediamo il caso di prima,
dove nell’eseguibile troviamo come stringa il nome di una DLL.

Facciamo un esempio concreto: PotentialKeylogger.exe. Già solo il nome è un allarme ovviamente,


analizziamo questo con il tool Dependancy Walker e troviamo tutto quello di seguito:

Il fatto che abbiamo tanti import e tanti DLL è indice del fatto che il file non è impacchettato, se fosse
stato packed tutto questo non lo avremmo visto perché il Wrapper Program è molto piccolo ed importa
molto poco. Spesso di tutti gli import che vediamo sono pochi quelli che ci servono per comprendere le
informazioni utili, ovviamente non ci frega niente di “open/close dialogue()” che aprono e chiudono le
finestrelle.

Vediamo che ci sono funzioni FindFirstFile e FindNext che sono delle funzioni che permettono di cercare file
nel File system; oppure GetCurrentProcess e GetProcess vedono quali programmi sono in esecuzione sul
sistema; poi abbiamo delle funzione sui registri (RegisterClass oppure RegisterHotKey che dice a Windows
di innescare qualcosa quando si premono le combinazioni tipo control + shift + qualcosa); il programma usa
la GUI, si apre la finestrina nera della shell, ma non si apre nessuna interfaccia, questo potrebbe essere un
indizio perché magari questo file mi consente di sniffare le finestre del computer quando premo le
combinazioni che vengono riconosciute con il RegisterHotKey. Inoltre, la funzione SetWindowsHookExW (gli
hook sono delle funzioni che possiamo scrivere nel programma ma che non vengono mai chiamate dal
nostro stesso programma ma le può chiamare il SO quando preme una particolare configurazione di tasti)
prevede quando si muove il mouse o si clicca un tasto della tastiera, vengono chiamate due funzioni dal SO.

Molte funzioni hanno questi nomi strani, che includono “ExW” o “ExA” ma che significano?

 “ex” è un suffisso e significa che la funzione sta deprecando un’altra funzione che è diventata
obsoleta. Ad esempio, windows usava una funzione CreateWindows() che è divenuta deprecata e
adesso si usa CreateWindowEx() che rende obsoleta la precedente. Questo serve per essere
compatibili.
 “W” oppure “A” sempre usati come suffisso, indica il tipo di stringhe che la funzione prende in
ingresso (A sta ad indicare “ASCII”, W sta ad indicare ad esempio “unicode”, in quanto indica che la
funzione accetta “stringhe a caratteri larghi”).

Vediamo adesso un altro esempio, questa volta analizzando il file exe vediamo:

Questo potrebbe indicare un packed program, in quanto ci sono troppi pochi import.

PE Sections
Cerchiamo di approfondire la struttura di un PE file, quale altre informazioni utili ci può dare? Il file è fatto
dall’header ed altri sezioni. La parte “text” racchiude il codice macchina; data include i dati; rdata
racchiudere gli import e i dll; rsrc sta per “risorse” e racchiude come deve apparire l’esecuzione.

Per vedere queste sezioni e l’header usiamo PEview. A sinistra c’è una vista che ci fa vedere gli header e le
sezioni. Gli header sono divisi in più parti:

1. Le prime due parti sono legacy, quindi le ignoreremo spesso. Windows deriva da DOS e una parte è
dedicata ai DOS proprio. Se clicchiamo su quelli moderni chiamati NT_headers troviamo
informazioni come: quando è stato compilato file, che è un file a 32 bit, ci dice che il PE analizzato è
un exe in quanto è indicato il codice 0002.
2. Se clicchiamo su HEADER test, troviamo le informazioni sulla sezione testo: ci dice la dimensione
della sezione. La cosa interessante è che ci sono due dimensioni:
o Virtual size
o Raw data size

Che significa? Virtual size è la dimensione di quella sezione quando è caricata nella RAM, quindi
lanciamo il programma e il programma occupa tale dimensione. Invece, Raw data, ci dice quella
sezione quanto occupa sul disco. Si può vedere che sono dimensioni leggermente diverse, il che è
normale, il problema lo abbiamo quando sono molto diverse.

Cosa possiamo avere di anomalo? I file packed hanno delle sezioni che risultano essere vuote: per
esempio, nella figura in basso, vediamo che nella sezione “Size of raw data” l’eseguibile non ha un’area
testo, non ha un’area dati, ma invece nella RAM queste ci sono. Questo perché è packed e l’area testo
viene creata decomprimendo qualcosa. Probabilmente, l’eseguibile impacchettato si trova nella
sezione .sdfuok; il Wrapper legge questa sezione ed effettua la decompressione e quindi abbiamo l’area
testo e le altre. Guardare le sezioni allora ci fornisce degli indizi sui packed:

Per la sezione rsrc, ad esempio del programma calcolatrice: dentro l’exe c’è un’immagine con i bottoni
disegnati così. Al posto di questa roba un eseguibile potrebbe mettere un payload compresso. Quindi la
sezione resource è molto usata per nascondere codice in aree che non dovrebbero avere codice.

Esempi di tool per analisi statica sono Resource Hacker che ci consente di analizzare queste resource.

Altro tool è PEstudio, che contiene un po’ tutto quello che abbiamo detto. In realtà un valore interessante
di questo tool è entropy: l’entropia è una metrica di randomicità di un dato. Se ho un dato con tutti i bit
uguali, l’entropia è zero; se tutti i bit sono diversi tra loro, se non c’è nessuna ripetizione, l’entropia è 8
(perché un byte sono 8 bit). Perché è utile questo valore? Perché in un eseguibile solitamente l’entropia è
bassa, in quanto c’è una certe regolarità nelle operazioni e nelle stringhe in inglese. Se non ci sono
ripetizioni, e l’entropia dunque è alta, potrebbe essere sintomo di file packed.

Basic Dynamic Analysis


Supponiamo ora di aver fatto quest’analisi statica, abbiamo degli indizi ma il file è offuscato perché
compresso e non riusciamo a leggerlo, cosa facciamo? Lanciamo il programma, e vediamo ad esempio cosa
c’è nella RAM: facciamo quella che viene definito basic dynamic analysis.
Guardando nella RAM possiamo trovare il percorso dove vengono salvati i log del keylogger, oppure
provare a decifrare gli indirizzi IP, i domini che magari erano offuscati e che attraverso l’analisi statica non
riusciavamo a vederli.

Ci sono delle limitazioni in quest’analisi dinamica: alcuni malware non si attivano in una macchina virtuale
e quindi mandarli semplicemente in esecuzione non ci consente di vederlo in esecuzione. PER L’AMOR DEL
CIELO FATE L’ANALISI IN UN AMBIENTE SICURO, quindi macchina virtuale scollegata da internet e al
massimo collegato in modalità host-only. QUALCUNO PENSI AI BAMBINI. VirtualBox consente di utilizzare
gli snapshot, ovvero una istantanea dello stato della macchina in un determinato istante temporale. Infatti,
se avviamo il malware e questo rompe completamente il disco, dopo aver completato l’analisi io posso
tornare indietro nel tempo e fare in modo di poter ripristinare la situazione precedente alla rottura.

Utilizzeremo diversi tool:

Il primo, Process Monitor, monitora il disco, i thread, i processi e tutto quello che c’è nel sistema Windows.
Usa la RAM per salvare tutti questi dati, è un tool che si riempie velocemente infatti va usato per intervalli
di tempo non troppo lunghi.

Ci consente di attivare dei filtri, per sniffare solo i registri, solo la rete. Ci consente di identificare le
connessioni per capire se si sta parlando con un server remoto.

Possiamo focalizzarci su certe specifiche system calls; oppure possiamo fare dei filtri ad hoc per vedere le
modifiche non di tutto il disco ma andiamo a monitorare le modifiche di specifiche cartelle sia in maniera
black list che white list. I filtri non cancellano i dati si possono sempre recuperare.
Un altro tool è Process Explorer, che ci consente di vedere i processi in esecuzione è come il task manager
di windows, non salva molti dati ma fa vedere le dll e altre cose come mostrato nell’immagine.

Se ad esempio abilitiamo le DLL entrando nella DLL Mode e andiamo nel menù, possiamo vedere le DLL
caricate da un programma e quindi anche quelle non presenti nell’header; oppure ci consente di verificare
le firme dell’eseguibile; possiamo vedere le strings sia sul disco sia in memoria visto che lo stiamo
eseguendo. Nel caso in cui tra l’immagine di un processo e quello che c’è in memoria ci siano delle
differenze sostanziali, potrebbe essere un indizio che qualcosa non quadra: questa tecnica si chiama
process replacement (ma la vedremo più avanti) o process hollowing.

Oppure Process Explorer ci consente, attraverso l’utilizzo di quella che viene chiamata handle table di
vedere tutti i file aperti dal processo, nell’esempio sulle slide sono aperte tre chiavi del registro e vediamo
anche i “mutant” che sono i mutex in Linux. Perché dovrei guardare i semafori/mutex? Perché se un
eseguibile malevolo viene lanciato più volte, le esecuzioni dei malware non si devono pestare i piedi tra
loro.

Un altro tool è Regshot che ci consente di comparare due snapshots del registro, tutto.

Durante le analisi dinamiche dobbiamo spegnere la rete per evitare che il malware si diffonda, ma non
dobbiamo spegnerla del tutto, perché magari ci interessa di capire cosa succede nei confronti della rete.
Allora si crea una fake network per vedere come il malware si interfaccia con l’esterno, se tenta di inviare
qualcosa tramite la rete.

Il tool INetSim, ad esempio, simula i protocolli più tipici e ad esempio fa finta di essere un server SMTP;
quindi, fa finta di ricevere una e-mail e il malware ci casca e prova a mandare quest’email che viene
intercettata.
Come abbiamo detto è un finto internet, quindi il finto server, chiama finti siti internet quindi il malware
potrebbe capirlo; però intanto le richieste sono uscite.

Questo tool riesce a fare fesso addirittura Nmap vedendo i porti ed i servizi aperti.

Sandboxing
Fatta poi sia l’analisi statica che dinamica, ci sono aziende che offrono la possibilità di eseguire i file exe e
fanno un report di cosa è stato trovato. Si parla in tal caso di sandbox.

Se apriamo i report, possiamo vedere che qualcun altro ha collegato questa scansione con altre scansioni:
abbiamo altre versioni di questo eseguibile.

Nell’Incident Response leggiamo le tecniche del MITRE utilizzate in quest’attacco: nell’esempio è stato
analizzato un ransomware. Ci consente di vedere anche gli host che vengono contattati:

Lezione 19 18/05 (13-Lab-BasicMalware)


Promemoria importantissimo NON METTETE MANO A NIENTE FINCHé NON SALVATE LO SNAPSHOT
DELLA MACCHINA VIRTUALE, questo sia perché il malware può fare danni importanti sia perché
non è facile ripulire a mano. Alcuni tool sono proprio pensati per funzionare in questo modo, cioè usa
e getta. OVVIAMENTE LO SNAPSHOT CANCELLA I FILE NUOVI CHE ABBIAMO CREATO MAGARI PER
STUDIARE IL MALWARE, DOBBIAMO SALVARLI E SELEZIONARE I FILE DA PROTEGGERE.

Sulle slide c’è come fare uno snapshot, essi sono incolonnati perché ognuno è un aggiornamento del
precedente e quindi ogni snapshot successivo include tutti i file della versione precedente più i nuovi
salvati.

Per quanto riguarda l’esercizio ci fornirà dei programmi su gitHub dei quali non sappiamo molto e
dobbiamo capire cosa fa il malware e che caratteristiche ha. Per renderlo più stimolante vi è un quiz da
compilare.

Basic Static Analysis Lab


Il primo esercizio richiede i file Lab01-01.exe e Lab01-01.dll che vanno insieme e si trovano nel .rar e di
solito sono protetti da password proprio per evitare l’avvio improprio. Tra le varie domande abbiamo:

1. Ovviamente facciamo l’hash del file e vediamo se qualcuno l’ha già analizzato e cosa si sa
2. Quando è stato compilato, se è un packed malware
3. Ci sono indizi su che tipo di malware è?
4. È un po’ prematuro ma mettiamoci nell’ottica di un antivirus, quali caratteristiche troviamo del
malware che ne permettono un successivo riconoscimento?

Link di riferimento https://github.com/rnatella/swsec/tree/main/malware/malware-basic

Una stringa a cui prestare attenzione è quella al minuto 41:21 chiamata “Kerne132.dll” perché Kernel32.dll
è la seria e buona e quella segnalata è praticamente un misleading name.

Lezione 20 20/05 (14-ReverseEngineeringX86)


Oggi faremo qualche richiamo sull’architettura dei calcolatori e sull’assembler per collegarlo poi con
l’analisi del malware.
Ovviamente sappiamo qual è la differenza tra codice sorgente, codice macchina e assembly, i l codice
sorgente è quello che scriviamo con C/C++ ecc; il codice macchina è quello che capisce il processore,
ovvero byte nella memoria; il codice assembly è una versione più leggibile del codice macchina.

Perché facciamo questo richiamo? Quando riceviamo un eseguibile malevolo noi non abbiamo il codice
sorgente e una delle tecniche per capire cosa fa è andare a guardare il codice macchina. Facciamo un
esempio:

Chiamiamo una funzione, MessageBox (la printf di Windows che apre un pop-up, con titolo “hello word!” e
messaggio “a simple…”). Tipicamente il processore Intel quando chiama una funzione prevede un’istruzione
di call (che è un’istruzione di salto, e come parametro ha un indirizzo e quindi salta a quell’indirizzo), e poi
delle istruzioni di push (ognuna per i parametri passati alla funzione, quindi ne abbiamo quattro).

Noi partiremo da qui, dal codice macchina:

Con dei tool, che si chiamano disassemblatori, da quello che sta sotto otteniamo quello che sta sopra a
sinistra (push, push, push, push, call), e a tal punto proviamo a ricostruire quello che c’è sopra a destra
(MessageBox).

Assembler
Per fare l’occhio, consideriamo un programma identico a quello di prima con il MessageBox e ExitProcess
ed infine le due stringhe:

Invece degli indirizzi numerici, per rendere più leggibile un programma assemby sia quando dobbiamo
scrivere, sia quando facciamo reverse engineering, non mettiamo dei numeri grezzi ma delle parole
simboliche chiamate “label”.
L’obiettivo di un assemblatore è quello di passare da sinistra ad un file oggetto, che abbiamo detto essere
il contenuto di PE. Il PE è un vettore di byte, che viene preso dal disco e messo nella RAM.

Inizialmente il file oggetto è vuoto, l’assemblatore dipinge il contenuto di questo file oggetto e la salva su
disco e poi quando viene lanciato il programma l’immagine viene caricata dal disco alla memoria.

L’assemblatore ragiona leggendo un’istruzione alla volta e inserendo il codice macchina all’interno del file
oggetto che contiene l’istruzione da eseguire (6A = push) e poi inserisce i byte (in questo caso zero).

Andiamo avanti così: seconda istruzione e pezzetto di byte che vengono messi subito dopo quelli di prima è
un’istruzione di 5 byte, il 68 rappresenta la push, e poi ci vuole l’indirizzo della stringa (4 byte). In questa
fase l’assemblatore non sa ancora dove sta la stringa quindi ci mette i punti interrogativi. E quindi si
procede così fino alla fine:
Questa fase si chiama “prima passata” dove si mette quello che si può nel file oggetto. Ad un certo punto,
arriviamo nell’area dati, ovvero abbiamo istruzioni che non usano registri ma che mettono dati
nell’immagine di memoria. Ad esempio, il nostro programma deve utilizzare una stringa, come “Hello
world”, che deve essere messa da qualche parte. Per esempio, in questo caso il valore arriva alla posizione
0x403000 (61=A, 20=spazio ecc..). Allora l’assemblatore impara che esiste questa stringa MsgString e si
segna il suo indirizzo in una struttura dati che si chiama symbol table.

Quando abbiamo finito l’eseguibile, l’assemblatore si fa un altro giro e riempie i buchi in sospeso e abbiamo
la “seconda passata”. Quindi 0x403000 viene inserito nel suo apposito spazio (vediamo che viene scritto al
contrario perché è little endian).

Il vettore viene compresso nel file PE e quello che c’è sul disco è mostrato di seguito:

Il caricatore prende questo PE e lo carica nella RAM. Le sezioni nella RAM non sono necessariamente della
stessa dimensione del file PE, però fondamentalmente avviene una copia.

Disassembly
Per fare l’analisi, noi utilizziamo dei tool che sono “disassemblatori”; quindi, qualcosa che fa il lavoro
opposto di questo appena visto, cioè partono dal binario e ricostruiscono l’assembler. Noi utilizziamo IDA
Pro.
Noi ci focalizziamo su Intel x86, quindi 32 bit e poi se avremo tempo vedremo la x64. La scelta è dettata
anche per la semplicità degli esercizi.

X86 ISA
Intel x86 è un processore CISC (abbiamo quindi operazioni complesse, molte tipologie di istruzioni,
abbiamo molti modi di indirizzamento, gli operandi possono essere di tantissimi tipi). L’ottica era quella di
occupare meno memoria (abbiamo codifica a lunghezza variabile) e rendere le istruzioni più leggibili.

Ci sono due sintassi per l’assembly Intel:

1. AT&T, è simil-motorola, abbiamo i $ e i %


2. SINTASSI INTEL, è quello maggiormente utilizzato dai tool e useremo questa

Non impariamo troppo a memoria i comandi o che, purtroppo ogni tool disassembla e mostra in maniera
diversa. L’esempio di seguito fa 2 + 2

L’assemblatore ad ogni istruzione abbina un indirizzo, questo per non utilizzare i valori numerici e si
utilizzano le label, che sono degli alias degli indirizzi. Si possono scrivere in due modi:

 Le mettiamo all’inizio
 Mettiamo i due punti e accapo

Global esporta il simbolo “_main” per fare collegamento e alcune label sono visibili al caricatore del sistema
operativo e ad altri programmi che vogliono chiamare questo eseguibile.

Tutti i programmi sono organizzati in sezioni: tipicamente ci sono il testo, i dati, la rdata (che serve per la
re-location) e il resource (dove ci sono immagini e cose grafiche).

Vediamo la section .data: viene detto all’assemblatore che deve essere messo il valore 2 in memoria che è
una double world (cioe 4 byte).
I tipi che possiamo usare su Intel possono essere:

Come funziona invece la move? Viene presa da destra verso sinistra, il contrario del motorola, e quindi la
destinazione sta a sinistra.

Abbiamo 4 byte da memorizzare, è totalmente irrilevante in quale ordine li disponiamo, ma ogni processore
usa il suo, e Intel è little endian. È importante perché quando facciamo l’analisi e troviamo un indirizzo:

Abbiamo il local host, ma nell’assembly lo leggiamo al contrario.

Per gli operandi mettiamo i numeri o i nomi dei registri e le parentesi quadre sono come l’asterisco in C. Si
prende quindi un qualcosa, un registro, e lo si usa come un indirizzo dove prelevare un dato. Una cosa è
scrivere “eax” e un’altra è scrivere “[eax]”.

Quindi se scrivo “eax” copio il contenuto del registro (Ad esempio il valore 3); se invece mettiamo le
parentesi quadre, viene trattato come un puntatore (il processore va in memoria all’indirizzo [eax] prende
quello che c’è in quella locazione e lo copia).

Registri
I registri hanno questi nomi:
Adesso vediamo quelli più importanti.

 I registri di dati, quelli generali con le lettere, sono a 32 bit ma possiamo anche utilizzarne solo un
pezzo. Di solito usati per conservare dati o indirizzi di memoria, sono intercambiabili e alcuni sono
particolari come EAX e EDX che vengono rispettivamente usati per moltiplicazione e divisione.
Quello che vedremo più spesso è EAX che contiene il valore di ritorno delle funzioni.

Alcuni registri hanno dei significati speciali per alcune istruzioni, noi possiamo usarli come vogliamo ma
alcune istruzioni danno dei ruoli particolari. Ad esempio, “ECX” viene chiamato loop counter: esistono
istruzioni che ci fanno leggere una stringa intera, per dire quanto è lunga una stringa, la lunghezza la
mettiamo in ECX.

Un esempio di istruzione CISC è movsd: è un’istruzione senza operandi perché gli operandi sono impliciti e
vengono presi direttamente il valore di esi ed il valore di edi per fare il trasferimento. Quindi deve essere
preceduta da due istruzioni di esi e edi.

Poi ci sono delle istruzioni particolari come rep, che non è un istruzione macchina ma è una parola chiave
e può essere messa insieme ad altre istruzioni. Quando l’assemblatore lo vede capisce che deve assemblare
movesb in maniera diversa, ovvero l’operazione va ripetuta come se facesse un loop. Quante volte deve
essere ripetuta? 13 volte, perché è il contenuto del registro ecx.

 Il registro di stato è un registro speciale, che contiene vari bit separati gli uni dagli altri che si
chiamano flag e ci fanno capire cosa sta accadendo nel processore:

Ad esempio, c’è un bit, ZF (Zero flags) che ci dice se l’ultima operazione ha dato valore zero. Sono
quindi bit aritmetici, ad esempio memorizzano il riporto, il segno.
Mentre, l’EIP (Extended Instruction Pointer) dice al processore qual è l’istruzione da eseguire, infatti è
l’obiettivo degli attacchi di buffer overflow.

L’istruzione “lea” è una simil-move. Quando noi scriviamo “mov eax,[ebx+8]”, la move non trasferisce il
valore di ebx, ma trasferisce il contenuto della memoria all’indirizzo indicato (cioè all’indirizzo di ebx+8 c’è
qualcosa, lo prendo e lo trasferisco). Invece, se scriviamo lea non va in memoria, mette il valore del
puntatore in eax.

L’istruzione lea viene sfruttata per l’aritmetica dei puntatori per fare operazioni come le moltiplicazioni, più
velocemente. Facciamo un esempio per essere più chiari:

Il valore di eax con la move è 0x20. Perché ebx è 0x00B30040, se a questo aggiungiamo otto, otteniamo
0x00B30048, e troviamo proprio il valore 20. Invece, il valore di eax con lea: troviamo 0x00B30048.

Un’altra parola che vedremo spesso è PTR, ovvero pointer. Quanto noi facciamo un’istruzione come
riportato nella slide, dove copiamo un valore in un certo indirizzo abbiamo che normalmente il valore viene
considerato come una double word, quindi scriviamo 4 byte in memoria.

Se invece vogliamo scrivere esattamente un byte, possiamo usare il prefisso BYTE PTR
Abbiamo le operazioni matematiche

La NOP è la no operation, l’operazione nulla, che ci serve come zona di lancio per i nostri attacchi quando
non conosciamo la destinazione di un salto con precisione.

Lo stack è una zona di memoria cresce all’inverso, cioè cresce verso l’altro. Abbiamo visto anche la
questione del frame dove ogni chiamata ritaglia un pezzo di stack che contiene i parametri, i valori locali di
una funzione e l’indirizzo di ritorno

Vedremo due operazioni condizionali: vedremo test e compare i quali sono due modi di Intel per fare la
comparazione tra due operandi. Test effettua la AND tra i due valori che gli diamo, la cmp ne fa la
sottrazione.

Perché? In molti programmi troviamo test eax,eax, serve per capire se quel registro ha il valore 0, in
quanto essendo la AND, se facciamo la AND tra due valori che sono tutti zeri, il risultato è zero. Invece, la
cmp la utilizziamo per capire se una variabile è maggiore di un’altra, e quindi facciamo la sottrazione e
vediamo il risultato.

Poi abbiamo jump zero (jz) o jump not zero (jnz), tipicamente seguono la cmp o test e servono per fare i
cicli, gli if, etc.

Reverse Engineering
Il nostro obiettivo non è scrivere codice assembly, ma fare reverse engineering dei programmi. In
particolare, non vogliamo ricostruire tutti i singoli registri o chi copia cosa, sarebbe uno spreco di tempo.
L’obiettivo è avere una figura di alto livello di cosa fa il programma.

A noi interessa conoscere la matrice del MITRE, ci interessa capire l’attacco, per cui nell’analisi del reverse
engineering, cercheremo di capire dentro un assembler quali sono i codici di programmazione del C che
l’attaccante ha messo (vogliamo capire se volesse fare una connessione, se volesse aprire un file). Quindi
dobbiamo rapidamente capire dall’assembler se stiamo guardando un while, un for, etc.

Vediamo allora come capire dall’assembler quali di queste operazioni ad alto livello stiamo analizzando.

Variabili globali e variabili locali, costrutti


Come appaiono le variabili globali nell’assembler?

Possiamo immaginare che nell’area dati ci siano delle istruzioni come viste prima, il disassemblatore mette
delle etichette che sono un po’ strane ad una rapida occhiata (ad esempio dword_40CF60). Questo capita
perché il disassemblatore non conosce il codice sorgente utilizzato dall’autore, quindi quando lo
utilizziamo, produce delle etichette finte che riassumono il tipo di label (se è una double world, un byte) e
l’indirizzo (ad esempio dword_40CF60). I tool consentono, man mano che si fa l’analisi di rinominare
l’etichetta.

Nell’esempio in alto, abbiamo 2 variabili globali, le sommiamo e le stampiamo. Per farlo abbiamo due mov,
che ci consentono da leggere dalla memoria globale, quindi stiamo leggendo variabili globali.
Poi abbiamo la push dei parametri e la printf. Nella push a 0040101A la stringa “aTotallD” dove si trova nel
programma? È il contenuto della printf e la “a” davanti a TotalID indica ASCII. Dopo il “;” si vede la stringa
originale che stiamo passando.

Invece le variabili locali le riconosciamo perché sono sullo stack e allora troviamo [ebp - 4], quindi troviamo
delle sottrazioni. Quando vediamo questa cosa stiamo avendo a che fare con variabili di tipo locali, in
questo caso la somma di uno e due che stanno nella memoria.

OSS: è sbagliata la figura, sono sbagliati i valori, perché avremmo dovuto avere 1 e 2, e non 0 e 1
I disassemblatori però le fanno vedere così, mettono il “+” e non il “-“, però in realtà è una sottrazione
perché lo stack cresce sottraendo.

Dopo le variabili, andiamo a considerare gli IF STATEMENTS:

Quando dobbiamo fare un IF, dobbiamo fare almeno due salti “if … else”. Se l’if è “x==qualcosa”, il primo
salto è l’opposto cioè se x è diverso da qualcosa salta, altrimenti va avanti e poi salta fuori.

Noi non lavoreremo a questo livello: i disassemblatori, ricostruiscono un control flow graph.
Il disassemblatore vede che ci sono le istruzioni di salto, vede dove sono e le spezza in più blocchettini, in
questo caso 4, e li collega così:

 Prima dell’if (il nodo radice del grafo),


 Dopo dell’if (la destinazione)
 I due rami dell’if (i possibili risultati del controllo, cioè true o false).

Nell’esempio, prendiamo due varabili locali (perché c’è + var8), le compariamo e jnz (jump not zero) e vede
se le variabili sono uguali.

Al minuto 1:08:00 apre IDA pro e mostra il suo funzionamento. Una volta aperto il file crackme-121-1.exe ci
troviamo avanti tantissime cose ma procediamo per gradi. L’immagine centrale è il main e i parametri
classici, sostanzialmente è vuoto e c’è solo una chiamata alla funzione “_main_0”.

Se spostiamo il cursore su un simbolo ne otteniamo l’anteprima e premendo invio mi porta al pezzo di


codice corrispondente. Il nostro obiettivo è trovare la password per crackare il programma, bene cerchiamo
proprio come se stessimo leggendo un programma. Il primo punto da cui partire è proprio dalla stringa
“fail” quando sbagliamo la password.
Per cercare le stringhe andiamo in View/Strings (Shift+ f12) e ci apre una nuova tab con tutte le stringhe del
programma con lunghezza e posizione in memoria. Se clicchiamo sulla stringa interessata ci mostra
l’assembler, se vogliamo possiamo anche cambiare nome alla label per renderla più esplicativa.

Qualcuno si potrebbe chiedere: Ma che è questa barra qua sopra?

La barra lunga è il vettore di byte dell’eseguibile, il file PE, poi il blocchetto azzurro è l’area .texts mentre la
successiva è la .datas. Per vedere meglio basta come al solito passare il cursore.

Torniamo al nostro problema per trovare la stringa fail, dopo averla trovata nella zona dell’assembler,
usiamo tasto destro su “aFail” e usiamo la funzione di cross-reference Xrefs graph to (ovvero si parla punti
nel codice che usano questa label).

Nel caso non riuscissimo a navigare tra le varie opzioni ci basta tornare nel _main_0 e vedere che sopra la
condizione di fail la password è “topsecret”.

Per quanto riguarda i for e i cicli sono i più contorti di tutti, questo di seguito è il classico ciclo che stampa i
numeri da 0 a 99.

Tipicamente sono tre pezzi: abbiamo l’incremento, la condizione di uscita e il body del for e sono collegati
al ciclo.

Il ciclo si ferma con il JUMP che c’è nel mezzo. Come ci appare nel grafo? Ovviamente troveremo un loop
nel grafo ma possiamo anche cercare l’inizializzazione dell’indice, la condizione (che riconosciamo con una
freccia che esce dal loop, la freccia true). Dentro al loop troviamo il body e l’incremento.
Invece, per un while: è molto più semplice, somiglia al FOR, in quanto entro nel while e poi c’è un salto
all’indietro. Se all’inizio del blocco un certo controllo mi dice di uscire, il ciclo si ferma.

A noi interessa sempre la condizione del while per capire quando un malware si attiva e quando si blocca.
Vedremo un esercizio su questo la prossima volta.

Convenzioni per le chiamate di funzioni


Probabilmente la parte più importante perché le chiamate DLL e quelle di sistema sono spesso le più
indicative. Abbiamo sempre a che fare con un chiamato e un chiamante, ovvero un codice che chiama un
altro codice.

Dobbiamo distinguere i modi possibili per passare i parametri in ingresso e uscita:

1. Il primo approccio è quello più accademico “cdecl”: i parametri vengono messi sullo stack e
quando la funzione finisce il chiamante decrementa lo stack. Il risultato viene poi messo in un
registro EAX. Quindi fa tutto il chiamante, prima fa cresce lo stack e poi lo fa decrescere.

2. Stdcall possiamo considerarlo come regole di chiamata, abbiamo che i parametri vengono messi
sullo stack, ma il decremento viene fatto dal chiamato e non dal chiamante.
3. Fastcall è l’approccio più utilizzato in Windows prevede che i parametri sono in parte passati sui
registri. Se la funzione prende pochi parametri si usano solo i registri altrimenti se i parametri
sono tanti, alcuni parametri sono passati nei registri e altri nello stack. Anche in questo caso il
chiamante non fa il clean dello stack.

Altro avvertimento: potrebbe capitare che diversi compilatori fanno chiamate in modo diverso. Ad
esempio, la stessa chiamata con visual studio e gcc:

Il programma è lo stesso ma vediamo due risultati diversi. Per questo è importante avere un buon
assemblatore, in quanto quelli migliori riescono a capire anche quale compilatore è stato utilizzato.

Ricapitoliamo allora il protocollo di chiamata per Intel x86:

1. Inizialmente lo stack è vuoto. Il chiamante può usare lo stack per salvare i suoi registri (non è uno
step obbligatorio).
2. Nel caso cdecl , i parametri vengono pushati sullo stack oppure vengono copiati sui registri,
sempre in base alla convenzione. Il base pointer rimane fisso, lo stack pointer sale.
3. Poi c’è il momento della chiamata, l’istruzione call con l’indirizzo della chiamata. A tal punto viene
inserito l’indirizzo di ritorno sullo stack.

4. Nell’Intel, le funzioni chiamate iniziano sempre con due istruzioni: push ebp e mov ebp , esp che
fanno la configurazione del base pointer. Nell’immagine di sopra il base pointer si trovava in
fondo, adesso viene assegnato con esp e così rimarrà per tutta la chiamata. Ci dobbiamo salvare il
vecchio ebp per ripristinarlo alla fine.
5. Il chiamato fa anche lui il salvataggio dei registri perché poi li deve ripristinare.
6. Fa ulteriori push se deve mettere le variabili locali. Tipicamente troviamo “sub” questo perché
stiamo spostando lo stack pointer e non stiamo scrivendo valori sulle variabili locali. Nell’esempio
abbiamo sub esp,8 e l’8 è una sottrazione cumulativa: abbiamo 2 variabili di 4. Questo è quello
che rende difficile l’analisi perché non sappiamo se sono due variabili di 4 o una di 8.

Facciamo un esercizio: cosa fa questo programma?


Questo programma vede se esiste già un mutex con un certo nome, se non esiste lo va a creare:

Può servire per capire se un malware è stato già avviato, per non far andare più esecuzioni dello stesso
malware in conflitto.

Array e strutture
Qualche parola veloce su array e strutture, in questo esempio in figura abbiamo un array locale ed uno
globale.

Un intero sono 4 byte, quindi l’array “a” di 5, sarà di 20 byte, perché 5*4 e avremo quindi una sub di 20.
Come li riconosciamo nell’assembly? È qui che entra in gioco il modo dell’indirizzamento, troviamo ebp
seguito da diverse espressioni. L’array “a” è quello rosso (perché c’è ebp), l’array blu è “b” in questo
esempio in figura abbiamo un array locale ed uno globale. (perché c’è dword_40A000).

Quello locale ci dice che l’array parte da ebp+var_14, e poi c’è un terzo addendo “ecx*4”. “ECX” è l’indice
dell’array, moltiplicato per 4 perché ogni elemento è di 4 byte. Quindi per trovare la posizione del primo
elemento mi sposto di 4, per il secondo di 8 e così via. Per quello globale vale una cosa simile ma stavolta
l’indirizzo base non è ebp ma quello nelle parentesi quadre.

Le struct sono molto toste però servono e sono usate nelle system call di windows per le socket. Troviamo
un puntatore ad una struttura ed in essa troviamo diverse cose, nel caso delle socket possiamo trovare
indirizzi ip; porte; etc.
Nell’assembler cosa troviamo? Immaginiamo una struct con un array, un carattere e un double. Crea con
malloc() un area di memoria dove mette questa struttura e passa la funzione test. La funzione test prende il
puntatore “q” e va a mettere i valori nella struct.

Capiamo le cose salienti, ad esempio la scrittura del carattere y:

in C il carattere è un solo byte, infatti, le istruzioni in rosso presentano “byte ptr” quindi la dimensione del
trasferimento mi dà un suggerimento sul tipo di dato che sto trasferendo. Il char si trova dopo un array di
20 byte (14h è 20), quindi dopo l’array scriviamo un byte (in particolare 61 è la lettera A in ASCII).
Poi ci sono istruzioni floating point, e la sintassi dell’array, bisogna farci l’occhio.

Poi abbiamo le liste linkate:


Come capiamo la lora presenza? In assembly troviamo una cosa come questa “curr = curr -> next” ed è un
codice che sta scorrendo una lista linkata, in assembly troviamo una cosa come quella sottolineato in rosso.
Viene letto scritto un valore in eax, viene incrementato e poi viene risalvato.

X86-64
Cosa c’è da sapere sui 64 bit? È retrocompatibile, cioè su un 64 bit possiamo lanciare un 32 bit. Cosa
cambia? I registri si allargano:

Ed abbiamo dei registri in più, da R8 a R15. Alcuni registri cambiano nome, tutti possono essere accessi
all’ultimo byte.
Una cosa utile da sapere sono gli indirizzi canonici: è vero che il processore è a 64 bit, ma non si usano 64
fili per gli indirizzi, ma se ne usano 48. Vengono ignorati gli altri 12, tale cosa è chiamata forma canonica e il
processore non permette il non uso della forma canonica. Quindi devono essere un’estensione in segno
sono o tutti 0 o tutti 1. Queste cose le abbiamo viste quando abbiamo fatto il buffer overflow.

Facciamo un esempio:
La parte in rosso sono i primi 12 bit, che non sono usati quindi devono essere o tutti 0 o tutti 1. Se il
48esimo bit è 1, i primi 12 sono 1 (analogamente per lo zero).
L’ultimo esempio in figura non è un’estensione valida: il processore genera un’eccezione.
Questa cosa l’abbiamo vista nell’esercizio del buffer overflow.

Inoltre, lo stack si utilizza di meno, e non si tende a fare pop-push-pop-push ma si fa push di tutto e poi si
utilizza.

Il primo esercizio richiede i file Lab01

Lezione 21 25/05
Analyzing Windows Malware (15-WindowsMalware)
Oggi ci focalizziamo su windows malware e vediamo alcuni aspetti specifici che vengono sfruttati di questo
SO, l’ottica sarà osservarle per trovarle tramite reverse engineering. In fine vorremo scrivere delle signature
anti-malware per proteggerci.

Windows fortunatamente fa attenzione alla retrocompatibilità e quindi quello che studiamo funziona
ancora ed è valido. Divideremo la discussione in due parti:

 La prima sulle Windows API ovvero quelle che si usano se si scrive un’applicazione per windows, ad
esempio aprendo e chiudendo le finestre.

Questo di seguito è uno stile di windows per cui non usiamo i tipi nativi del C ma dei typedefs che prendono
i nomi elencati e che ricalcano l’architettura Intel.

Lasciamo anche la tabella con i tipi più ricorrenti, alcuni da tenere d’occhio sono “Handles (H)” che ha
molto a che fare con le primitive del SO e Callback; che riguarda i puntatori a funzione cioè quando noi
chiamiamo il SO e indichiamo una funzione del nostro programma che il SO dovrà chiamare a sua volta.
Praticamente il meccanismo di hocking visto le lezioni scorse.
Notazione Hungerese
Prevede che quando creiamo una variabile di tipo handle nel nome mettiamo una lettera iniziale che si
riferisce al tipo.

Handles
A proposito degli handles, essi sono una sorta di puntatore ad oggetti anche se non sono puntatori perché
sono dei typedef a degli interi. Questo concetto si generalizza a tutti gli oggetti del SO e di questo numero
noi non faremo somme/sottrazioni o che, poche cose ci facciamo:

 Salviamo in una variabile.


 Lo passiamo senza modificarlo a chiunque lo richieda, magari tramite un read/write/etc.

Vediamo un esempio con hwnd che è un handle di tipo finestra.


Tutta la roba del costruttore della finestra non ci interessano, il succo è che questo valore numerico non lo
modifichiamo e ogni volta che vogliamo farci qualcosa lo prendiamo e lo mettiamo nella chiamata di
sistema.

Altro esempio, creazione/apertura/scrittura di un file e abbiamo un handle che chiamiamo “hFile” e


chiaramente possiamo verificare se l’apertura è andata a buon fine o meno (Sezione dell’IF). In fine nella
sezione WriteFile gli passiamo l’handle.

Noi abbiamo già visto gli oggetti e gli handle, dove? Quando abbiamo utilizzato i tool di Process Explorer e
WinObj ed infatti, quando cliccavamo su un processo, mostrava gli handle usati. Nel gergo di windows
anche i processi ed i thread sono oggetti.

La cartella che più ci interessa di WinObj è BaseNamedObjects perché qui vanno a finire gli handle che
usiamo con le windows API. Il File System di Windows si trova in :\Windows ed è orientato al disco ma i
percorsi sono ancora compatibili e viene tradotto con \GLOBAL??\.....
Infine, l’ultima cartella importante è \Device dove sono posizionati i device fisici. Per concludere tutte le
informazioni sui percorsi di Windows abbiamo che tutti quelli che iniziano con “\\“ sono percorsi di rete, e
quindi non fanno parte della gerarchia classica. Dopo il doppio slash c'è il nome del server e poi il percorso
dentro di esso.

DLLs (Dynamic Link Libraries)


Abbiamo visto che le API di windows vengono dalle DLL, ad esempio il keylogger usava kernel32.dll e lì c’era
la funzione per sniffare la tastiera ed altre cose. Il fatto che un malware carichi una dll è un indicatore forte
di cosa fa; infatti, moltissimi tendono a nasconderlo ma come fanno?

Esistono diversi meccanismi di caricamento, una è l’uso della funzione LoadLibrary() che mettiamo
all’interno del nostro programma C; così facendo non viene mostrata la dll negli import. Il contro è che se io
vedo la funzione intuisco comunque la situazione. Un altro modo è quello di aprire la DLL come se fosse un
file

Le DLL sono molto simili agli EXE, quindi hanno area codice/area dati/import/etc e poi c’è una funzione
speciale chiamata DllMain. Tale funzione viene tipicamente chiamata quando viene chiamata la Dll, le altre
due occasioni nella quale viene chiamata sono:

 Avvio un nuovo thread, come se fosse un costruttore


 Quando un thread finisce

API nativa
Sappiamo che esiste una Dll un po’ speciale chiamata “Ntdll.dll” perché tutte le altre, come Kernel32 etc, la
chiamano per arrivare al SO. Il compito di questa Dll è appunto fare le chiamate di sistema quindi con un
passaggio da User mode a Kernel mode, mentre la libreria non fa ciò. Normalmente questa Dll non verrà
mai usata dai programmatori, infatti non è nemmeno documentata ma viene usata dal malware.

Normalmente gli antivirus si inseriscono tra l’applicazione e le Dll monitorando un po’ tutto. Per questo
motivo il malware, per non essere rilevato, fa il giro lungo passando per Ischia e andando su Ntdll.ll.

Ma perché è utile chiamarlo? Oltre ad evitare l’antivirus si possono fare cose più sofisticate ad esempio sul
link nella slide c’è una spiegazione per trovare il pid di un processo senza chiamare la syscall
Domanda: perché l’antivirus non lo spostiamo più giù nella gerarchia? Si potrebbe fare ma perderemo altre
informazioni, soprattutto quelle di alto livello.

Per chiudere questa parte vediamo come possiamo, ipoteticamente, caricare la ntdll.dll di nascosto. Ci
creiamo il percorso poi chiamiamo due API per caricarlo, la CreateFileA la uso per aprire il file come se fosse
un file di testo ASCII poi si abbina il file ad un buffer di memoria.

Il resto di oggi sarà tutto su varie API di windows e come vengono usate.

Malware behavior - Downloaders


È il primo pezzo di malware a funzionare e serve proprio per scaricare altro malware, ci sono tre modi
principali per usare la rete windows:

 Socket
 WiniNet
 Oggetti COM

Per le prime, le socket, abbiamo che esse sono identiche a Linux ma cambiano piccolissimi dettagli e dalle
slide abbiamo l’elenco. Se il malware fa da server chiamerà le seguenti funzioni:
Mentre la connect, più altre tre segnate in rosso sono quelle chiamate dal client.

In un disassemblatore vedremo un sacco di righe di codice ma noi dobbiamo essere resistenti al rumore e
notare solo quelle chiamate importanti (che sono riportate in grassetto sulla slide).

Poi vediamo anche la API WinINet, assomiglia a quella di Java, per http e FTP più altri protocolli applicativi.
Non creiamo una socket ma facciamo direttamente una richiesta, ad esempio “InternetReadFile” legge i
dati da un file scaricato.

Component Object Model (COM)


Ultimo modo, però molto usato, per usare i servizi di Windows sono gli oggetti COM che permettono al
nostro programma di visitare un sito come se fosse aperto tramite Internet explorer. Sono quindi delle
API speciali che ci permettono di controllare delle componenti di windows, inizialmente i COM erano stati
creati per Microsoft office per scambiare i pezzi tra powerpoint ed excel.
Vediamo un esempio con un malware che vuole aprire un sito internet. Partiamo dalla stringa
“internetExplorer.Application” e da questa vogliamo ottenere un numero detto class identifier che
passiamo a varie API; focalizziamoci poi sulla parte finale.

Ma perché usiamo questo invece delle socket? Perché sono più comode e perché il traffico che viene da
qui è INDISTINGUIBILE da quello di un vero utente, quindi se il traffico viene sniffato non si nota nulla.

Poi ci sono ulteriori dettagli sugli COM identifier che troviamo nelle cartelle del registro di windows.
Malware behavior - Backdoors
Servono per dare accesso ad un attaccante ad una macchina vittima, abbiamo visto la reverse shell l’altra
volta. Come capiamo che abbiamo a che fare con una backdoor? Facciamo reverse Engineering, la prima
funzione da valutare è “CreateProcess” tale funzione è come la fork di Linux più complicata.

“StartupInfo” è il parametro più importante perché, per la reverse shell dobbiamo creare un processo che
non deve stampare nulla a video ma deve stampare sulla socket. Vediamo il codice per creare una remote
shell ottenuta dal disassemblatore:

Partiamo dalla chiamata sospetta di CreateProcessA e cerchiamo di capire se serve per creare una reverse
shell, andiamo quindi a ritroso sui parametri ignorando quelli inutili. Cerchiamo poi StartupInfo, che è una
struct con tre parametri:

 StdError
 StdOutput
 StdInput

Andiamo ulteriormente a ritroso per trovare “eax” e vediamo che è il valore di una socket.
Altra cosa che potremmo trovare, invece di CreateProcess(), è CreateThread() che ha come parametri quelli
elencati di seguito. Il malware per fare una reverse shell crea due thread figli, uno per leggere dalla socket e
l’altro per mandare i dati alla socket.

Ma se è simile perché usare un’altra funzione? Perché il malware non solo comunica con il server ma cerca
anche di nascondersi offuscando il traffico.

Vediamo qualche esempio concreto di backdoor.

RATs (Remote Administration Tools)


Oggi si parla spesso di RAT che sono delle backdoor molto potenti e quando un computer viene infettato
abbiamo che esso si comporta come se fosse un estremo di connessione, inviando un messaggio all’unità di
command and control (C2). Da questo momento in poi il client può mandare i comandi alla macchina
infettata.

Vediamo un esempio di Quasar RAT, openSource e quindi possiamo scriverci il nostro malware per infettare
le cose. Se apriamo l’applicativo vediamo tutte le macchine infettate che si sono collegate al nostro PC.

Le botnet sono una collezione di hosts compromessi, i pc così compromessi sono chiamati bot o zombi. Lo
scopo di una botnet è quello di essere il più esteso possibile per avviare campagne di spam o diffusioni
malware; un altro obiettivo è sicuramente quello di performare un attacco DdoS.

Ma che differenza c’è tra RATs e Botnet?


Malware behavior – Credential Stealers
Ne abbiamo già visto uno con il keylogger, ma non è l’unico per il furto di credenziali poiché lo si può
performare in altri due modi:

 Aspettare che l’utente acceda per rubargli le credenziali sniffandolo.


 Scaricare i dati conservati, come le password hashate

Per il primo caso vediamo un esempio, forse obsoleto perché risale ad Windows XP, attraverso un
meccanismo noto come GINA (Graphical Identification and Authentication). Quando noi logghiamo in
windows facciamo partire un processo chiamato “winlogon.exe”, che poi vediamo nella lista dei processi di
windows, e può essere programmato tramite registro per chiamare una dll di autenticazione; di solito
quella di default. Si può però modificare tale registro per far chiamare un’altra Dll, tale meccanismo si
chiama GINA.

Un attaccante potrebbe modificare il registro mettendo il percorso di una Dll malevola, che sniffa la
password, e poi richiama la Dll ufficiale performando una specie di man-in-the-middle.

Mentre per il secondo punto si parla di hash dumping e performano sempre degli attacchi al sistema di
autenticazione di windows. In pratica quando un utente si è già autenticato abbiamo che le credenziali di
autenticazione sono memorizzate temporaneamente in un processo molto importante, LSASS.exe.

Un attaccante che è già entrato nel computer e vuole rubare le password cerca di attaccare questo
processo prendendo la memoria dello stesso e la salva sul disco. In particolare, una vittima prediletta di
questo tipo di attacchi sono gli elementi contenuti nel riquadro rosso.
SAM Server contiene i così detti NTLM Hashes ma capiamo cosa sono; abbiamo già studiato la struttura PKI
(public Key Infrastructure) che viene usata molto da windows infatti il KDC, a seguito dell’autenticazione
può usufruire di due meccanismi: rimandare indietro l’hash (oppure NTOWF), oppure la coppia TGT e la sua
chiave.

L’invio dell’hash è ormai obsoleto e viene usato ogni volta che vogliamo accedere alla rete locale per
leggere una risorsa. Ora se qualcuno ruba il codice può accedere al File server attraverso un replay attack.

Il succo del discorso è che esistono decine di tool, come Pwdump, che quando lanciati in windows rubano
gli hash permettendo l’accesso alla rete locale. Infatti, Pwdump inietta una dll nel processo di Lsass la quale
viene poi richiamata per leggere all’interno del processo Lsass, per vedere gli hash.
Vediamo adesso l’ultima forma di credential stealing, il Keylogging che si può effettuare sia:

 A livello kernel, non lo vedremo


o Funziona come un driver della tastiera
o Difficile da identificare con applicazioni in user mode
o Spesso fa pare dei rootkit
 A livello user space
o Utilizza Windows API
o Implementato attraverso hooking e polling

Qual è la differenza tra gli ultimi due meccanismi? Nel primo caso un programma passa al kernel di
Windows il puntatore alla funzione “SetWindowsHookEx”, e ciò avviene ogni volta che viene premuto un
tasto. Il secondo approccio invece è proprio il classico polling, quindi si richiede costantemente l’attenzione.
Cosa troviamo quando facciamo reversing engineering? Ricordiamolo, l’ottica è sempre quella di trovare
particolari funzioni e poi i nomi dei parametri che vengono passati.

Malware behavior – Persistence (nei registri di windows)


Una volta che un malware ha ottenuto l’accesso al sistema si fa spesso in modo che ci rimanga, questo è il
concetto di persistenza. Per ottenere ciò bisogna fare anche in modo che quando si riavvia il pc esso non
cancelli la presenza del malware, è quindi tipico per il malware usare il registro di windows per mantenere
la propria configurazione.

Ma che cos’è il registro di windows? Fondamentalmente è un database dove mettiamo:

 Informazioni per il boot e configurazioni del SO


 Security database, come il SAM visto prima
 Configurazioni dell’utente

Per visualizzarlo usiamo il tool Regedit, le cartelle che vediamo sulla sinistra sono le sei root keys e se ci
navighiamo dentro troviamo delle entry (delle foglie) formate da un nome + un valore; ogni variabile ha un
tipo.

Ma cosa significano queste root keys? Le prime due sono le più importanti e più usate, la prima racchiude
tutte le variabili del registro che hanno a che fare con l’intero computer; mentre il secondo tutte le
configurazioni dell’utente loggato in questo momento

Il terzo, CLASSES_ROOT contiene gli oggetti COM.


I tipi dei valori all’interno dei registri sono quelli mostrati di seguito, non facciamoci ingannare da
“REG_Binary” perché non è il valore binario ma come codice binario.

Ma cosa ci interessa di questo registro? Un po’ tutto purtroppo, ci si potrebbe perdere ma una prima voce
sono le “Run Key” e tutte le variabili qui presenti vengono lanciate appena loggati.

Visto che normalmente ci sono decine diverse di elementi il prof suggerisce l’uso di un tool noto come
Autoruns, il quale accentra in un solo tool tutte le run keys possibili ed immaginabili.

Abbiamo poi visto l’altro tool chiamato Procmon che ci consente di vedere tutti gli accessi al registro e
abbiamo visto che ci sono tre funzioni:

1. RegOpenKeyEx, quando voglio aprire un registro per modificarlo o visualizzarlo


2. RegSetValueEx, aggiunge un nuovo valore ad un registro e setta il valore
3. RegGetValue, ritorna il dato per il tipo di richiesta nel registro

Quindi quando vediamo nel codice macchina qualcosa di simile, attenzione! Dobbiamo cercare di capire
cosa viene modificato.
Ci sono tanti modi di scrivere nel registro, uno di questi è .REG file che sono dei piccoli script per la
modifica.

Come detto non ci sono solo le run keys ma un altro meccanismo sono le APPINIT DLLs. Sono sempre chiavi
di registro, si trovano “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\
Windows” e sono una stronzata bella e buona. Praticamente ogni volta che avviamo un programma grafico
in windows (e che quindi usa User32.dll) AUTOMATICAMENTE carica le dll che sono qui elencate.

Un altro modo è attraverso winlogon.exe che si trova nella cartella “Software”, questa chiave qui viene
usata ogni volta che ci logghiamo e vengono chiamate tutte le DLL elencate qui dentro. Per non
demonizzare sempre, in realtà delle utilità ci sono infatti se vogliamo personalizzare il blocca schermo o il
log in per motivi amministrativi; bisogna mettere la Dll qui dentro.

Services
I servizi di windows sono un altro modo per raggiungere la persistenza, ricordiamolo sono dei processi che
girano in sottofondo ed infatti non li vediamo subito una volta aperto task manager. Per vedere come
funzionano usiamo ProcessExplorer che evidenza i servizi con il colore rosé.

Il codice dei servizi proviene da un Dll


Tra tutti questi c’è un processo particolarmente fastidioso: scvhost.exe questo perché è un processo
contenitore e non ha un compito specifico. Per riconoscere i servizi malevoli dobbiamo andare sempre nel
registro e poi dentro una cartella nota come “ScvHost” troviamo dei gruppi, dentro i gruppi troviamo i
servizi che vi appartengono.

Poi cercando il nome del servizio troviamo anche la Dll associata.

Concludendo, nel nostro malware dobbiamo fare caso se ci sono funzioni che modificano i servizi come:

 OpenSCManager, ritorna un handler al Service Control Manager


 CreateService, aggiunge un nuovo servizio al SCM e può specificare quale servizio avviare
automaticamente a tempo di boot
 StartService, si usa solo se il servizio è pensato per avviarsi manualmente.

Trojan system Binaries


Ultima tecnica di persistenza, cavalli di troia. Alcuni malware prendono Kerne32.dll e lo modificano perché
molto usata.

Vediamo l’assembly della libreria, sulla sinistra, e il codice modificato attraverso un salto.
Come preveniamo questa cosa? Tutti quelli visti prima erano su registro, questo modifica i file di sistema
e quindi dobbiamo garantirne l’integrità attraverso il file hashing.

Un’altra tecnica per la persistenza è il DLL Load-Order Hijacking ovvero una tecnica che non richiede né di
modificare le entry dei registri né di usare un trojan. Semplicemente quando un .Exe viene lanciato,
windows cerca le dll importate nel seguente ordine:

Vediamo un esempio di ipotetico attacco:

Supponiamo di avere un file “explorer.exe” che si trova in C:\Windows\explorer.exe ed importa una dll
chiamata “ntshrui.dll” localizzata in C:\Windows\System32. Tale dll non è una nota e una malevola
chiamata allo stesso modo, ma messa in C:\Windows verrà chiamata prima.
Windows non è ovviamente stupido, come meccanismo di protezione fa si che le Dll più importanti saltino
questo procedimento. Tale operazione è chiamata KnownDll registry key.

Malware behavior – Privilage Escalation


A questo ci tiene molto. Ne abbiamo già parlato, vuol dire che qualcuno è entrato nel sistema ma vuole
acquisire più privilegi; abbiamo:

 Escalation orizzontale
 Escalation verticale

Quali sono i metodi di privilage escalation? I più famosi sono:

 Furto di credenziali
 Vulnerabilità ed exploit del SO
 Malconfigurazioni del SO
 Social engineering.

In windows esistono due tipi di utenti: quelli locali, che lavorano su un singolo computer, e quelli di dominio
che lavorano su una LAN.

Gli utenti che vengono normalmente visti in un computer windows sono, per il locale:

In particolare, quando installiamo windows il primo che configuriamo è anche l’amministratore. Mentre i
System account sono usati dai componenti dei SO, come i servizi. L’utente più privilegiato dal sistema è
proprio system.

Dove entrano in gioco gli utenti e gli amministrators? Tipicamente per accedere ad una risorsa possiede un
security descriptor (quello che vediamo andando in proprietà) e sappiamo, perché LA CASOLA CI ASCOLTA,
che per gestire i permessi associati ad una risorsa si usano le Access Control List (ACL).

Quando lanciamo un processo esso eredita dall’utente un Access Token, il SO ogni volta che apriamo un
file/registro va a prendere l’user e controlla se è scritto nella ACL.
Poi c'è un'altra considerazione da fare sui privilegi, rispetto alle ACL, che appartengono agli utenti e non agli
oggetti e quindi Windows gestisce come due meccanismi diversi. I privilegi sono usate per tutte le altre
operazioni privilegiate presenti nel SO.

Ma quindi come fa il malware a sfruttare i privilegi, e come ci proteggiamo? Consideriamo un windows XP,
senza protezioni, e di usarlo con un account amministratore; lanciamo internet e scarichiamo un malware e
lo lanciamo. Purtroppo, essendo tutti privilegiati succede un casino:
Ad oggi c’è un nuovo meccanismo di funzionamento per evitare la situazione di sopra chiamato integrity
levels dove, i processi non sono tutti uguali e alcuni si avviano con un livello basso di integrity. Anche il
malware scaricato ha bassa priorità e quindi non potrà modificare o attaccare processi con priorità più alte
della propria.

Un altro meccanismo è l’user account control (UAC), introdotto da Windows Vista, e funziona così: Ci si
logga sul pc e anche se siamo amministratori abbiamo che i processi non ereditano i nostri privilegi; quindi,
si crea un secondo token chiamato restricted. La risposta del sistema era il pop up.

Nei framework di attacco useremo tecniche di UAC Bypass i vari link sulle slide aiutano a vedere i modi
possibili di utilizzo. Oppure un’altra funzionalità è la access token manipulation.
Il più potente e malefico di tutti è il debug privilage che provoca la possibilità, ad un programma, di leggere
e scrivere la memoria di qualunque altro programma nel sistema. Di default questo privilegio è disattivato e
i malware cercando di attivarlo per poter poi operare, per farlo usano la funzione “OpenProcessToken”.

Lezione 22 27/05 (15-Lab-WindowsMalware)


Prima di iniziare l’esercizio completiamo un attimo qualche discorso lasciato in sospeso di ieri, soprattutto il
discorso dell’autenticazione in windows, l’altra cosa ancora è vedere la process injection. Infine, vediamo
l’esercizio di reverse engineering.

Abbiamo detto che c’è questo processo LSASS che contiene un piccolo database contenenti hash value e
kerberos ticket. Sono due meccanismi validi di autenticazione, solo che NTLM è più vecchio ed ancora usato
mentre Kerberos viene usata nei sistemi più moderni.

NTLM funziona sul concetto di cifratura simmetrica e funziona così: si prende la password dell’utente e si
calcola l’hash attraverso il MD4, il server quando riceve una richiesta di una risorsa invia una challenge che
verrà cifrata dall’utente con la chiave simmetrica. Il server non riuscirà a decifrare, visto che non ha la
chiave ma invia la richiesta al domain controller che darà la decisione finale sulla cosa.

Tale meccanismo è abbastanza robusto anche allo sniffing ma come sempre no al replay.
Kerberos rende la cosa un po’ più robusta, nonostante si basa sull’utilizzo di chiavi simmetriche, perché
introduciamo una password ed un hash anche per il domain controller. Kerberos riduce l’uso dell’hash e
introduce il concetto di ticket, che ha una scadenza.

Al minuto 13 fa vedere come rubare le password di sistema usando una shell con privilegio di admin. Come
tool usa mimikatz che lavora bene su kerberos e sugli hash.

Malware behavior – Rootkits


Li salta ma chi vuole può vedersela

Malware behavior – Launchers


Vediamo infine la process injection che fa parte dei malware chiamati launchers o loaders. Il compito di un
Launchers è quello di lanciare un pezzo di malware, tale pezzo verrà lanciato in maniera nascosta e per
fare ciò esistono diverse tecniche, una di queste è proprio la process injection/dll injection.

Vediamo un esempio, immaginiamo di avere un malware che fa da launcher e poi una dll malevola ed infine
nel nostro sistema abbiamo l’eseguibile explore.exe. Il compito del malware è andare su internet. Se il
malware provasse ad andare su internet potrebbe venire bloccato dal firewall, magari attraverso una regola
di filtraggio di IP tables, a questo punto la process injection modifica la memoria dell’explore e vi copia
dentro la dll malevola; quest’ultima poi scaricherà quanto dovuto.
Per poter fare ciò dobbiamo avere per forza i privilegi di debug, come visto prima. Nello specifico il
processo A attaccante effettua la chiamata alla funzione OpenProcess(), che apre un processo come se
fosse un file, che ritorna un handle. Una volta recuperato l’handle si chiamano le tre funzioni elencate sotto
nell’immagine, questo per creare un thread non nel processo A ma nel processo B vittima.

Al minuto 23:20 mostra un esempio live su macchina virtuale attraverso un programma chiamato Xenos e
Xenos64, questo è importante perché dipende se vogliamo iniettare un processo a 32 o 64 bit.

Per lanciare una dll si utilizza il comando rundll 32 nome ¿ , nome¿ .

Link di suggerimento per gli esercizi https://book.hacktricks.xyz/windows-hardening/stealing-credentials

https://github.com/rnatella/swsec/tree/main/malware/malware-windows

Nel caso cerchiate la password per lo zip esso è “malware”. Se durante l’esercizio non vi trovaste con
determinate cose potrebbe essere dovuto al fatto che la nostra versione di IDA pro è quella studenti, quindi
tranquilli; nello specifico si rifà alla prima domanda

Il prof vede una cosa del genere, noi ignoriamo _DllMain visto che non lo riusciamo a vedere.
Lezione 22 01/06 (16-MalwareDetection)
Oggi è l’ultima lezione, la prossima sarà su un seminario dei CC (common criteria). Per la fine ci lascerà un
laboratorio (16-Lab-MalwareDetection) da fare da soli e nel caso chiedere a lui.

Malware Detection – Signature


Torniamo adesso al lato dei buoni cercando di capire come proteggerci dai software malevoli, nello
specifico parliamo delle malware signatures/indicators ovvero l’impronta digitale di un malware; questo
indicatore serve proprio per marcare e capire se un file è malevolo oppure no.

La prima categoria di signatures sono le così dette host based signatures che analizzano pezzi di codice,
file/exe/dll o codice all’interno di un documento, e se questo programma potenzialmente malevolo
modifica o crea certi file, la nostra firma potrebbe riguardare proprio questi file.

L’ottica del discorso è questa: supponiamo che scopriamo durante la malware analysis che il nostro virus
modifica una certa run key, e quindi sia il nome della variabile che il percorso possono far parte della host
based signatures e quindi sapere che viene usato già in altri attacchi. Una volta individuato detect possiamo
fare response e applicare politiche di quarantena.

La seconda categoria sono le network signatures che concettualmente sono identiche, solo che non sono
applicate a degli eseguibili ma al traffico di rete. Per esempio, un certo pacchetto ha una lunghezza strana,
oppure che il contenuto di quel pacchetto contenga dello shell code. Quello che faremo in più, rispetto ad
un corso di reti, è unire l’analisi del traffico con la malware analysis per creare delle regole migliori.

Ovviamente quando scriviamo una regola dobbiamo concentrarci su cosa il malware fa sul sistema e non
le caratteristiche del malware stesso.

Piccola panoramica su dove si usano i network indicator, abbiamo parlato di IDS ma anche i firewall e i
router che filtrano gli indirizzi IP e le porte TCP e UDP. Oppure ovviamente i proxy e i server DNS.

Noi vedremo molto i network indicators chiamati content based, questo perché le regole possono essere
fatte riguardanti proprio le caratteristiche del file oppure del traffico; ma le regole più potenti vanno sul
contenuto.
Una buona practise è non eseguire il malware subito, ma per scrivere delle buone regole è necessario
partire dai sistemi già avviati e catturare del traffico reale delle macchine.

Il vantaggio principale nell’osservare la rete reale è osservare la comunicazione del malware con l’esterno e
quindi, ipoteticamente, risalire all’indirizzo del server C2. Anzi il principale vantaggio è che l’analisi passiva
del traffico non può essere rilevata dall’attaccante, rispetto ad effettuare un’analisi attiva, e questo ci
permette di ottenere ulteriori dettagli sul malware.

Vediamo un esempio di indicator, giusto per capire con cosa abbiamo a che fare. Abbiamo detto che il
malware cerca di contattare dei domini associati all’infrastruttura di controllo, oppure utilizza degli indirizzi
IP; oppure ancora avere traffico di tipo http.

Chiariamo meglio l’aspetto di come un attaccante, quando il malware attiva e si esegue, capisce che sta
venendo analizzato:

 Non mandare un malware indiscriminatamente ma inviarlo a specifiche reti, e se nota che c’è
qualcosa che non va subito capisce chi è. Tale tecnica è conosciuta come spear phishing e-mail
 Quando il malware viene eseguito logga la propria attività malevola, un sito famoso è PasteBin
dove si possono mettere dei testi in maniera non rintracciabile.
 Oppure tramite unused DNS.
Strategie di OPSEC più sicure
Dopo aver avviato un malware siamo in fase di analisi ed esso effettua una richiesta all’attaccante, per
impedire che l’attaccante scopra dov’è stato eseguito il malware possiamo anonimizzare l’indirizzo IP.
Magari attraverso Tor, oppure attraverso VM dedicate per la ricerca usando delle connessioni SIM oppure
usare macchine Cloud.

Quando abbiamo a che fare con un indirizzo o un dominio e vogliamo capire se sia malevolo o no, possiamo
usare Google o qualunque altro motore di ricerca. Però un’avvertenza: quando mettiamo un url in Google
egli potrebbe decidere di aggiungere l’url nel suo database, questo provoca il così detto crawling e questo
potrebbe essere notato dall’attaccante.

Altro modo ancora per ottenere informazioni sul dominio è cercare il registro dei domini, che è un
protocollo distribuito, e chiedere ai server attraverso i comandi come whois o dig ma attenzione perché ci
esponiamo anche noi. È possibile anche il contrario, da un indirizzo IP ricostruire quali domini vi puntano.

Per non esporci più di tanto esistono dei siti specializzati nel fare richieste al DNS etc come DomaniTools,
RobTex, etc.

Content-based Network indicators


Torniamo a come scrivere le firme e ci basiamo sull’esempio precedente, quello con la GET http.
Ipotizziamo di aver isolato quella richiesta http e di voler scrivere una malware signature per quel
messaggio, quello che vediamo nell’immagine è il formato SNORT.

Brevemente, è una regola che analizza tutto il traffico http, senza destinazione sorgente e destinazione, e il
primo messaggio “TROJAN Malicious…” serve a noi per dare il nome alla regola mentre la regola vera e
propria inizia a “content” e le regole di scrittura sono simili al grep di Linux.

La regola controlla il modo in cui l’user agent è scritto, 0d e 0a è il ritorno a capo e questo è fondamentale
perché altrimenti se capitasse “user agent” in mezzo a del testo questo verrebbe filtrato dalla regola.
A sentimento, è una buona regola? No, manco la chiavica perché basta un user agent diverso e non
funziona più. L’autore di questa regola ha scritto questo perché ha notato che nella sua rete gli unici due
user agent del malware erano: Wefa7e and Wee6a3 e quindi l’espressione regolare è diventata

Una cosa del genere può far scattare falsi allarmi tramite il software Webmin, che altro non fa che generare
traffico con l’user agent che si vuole. Il prodotto è open source e per nulla malevolo. Quindi bisogna
modificare nuovamente la regola, adesso ci sono due sotto-regole ed il contenuto è che non deve essere
Webmin

Ora vediamo un caso più reale, immaginiamo di aver monitorato la rete e aver osservato tutti questi valori
sulla destra dell’immagine. Sembra che la regola vista sopra sia ancora valida, sono tutte lunghe 6 caratteri
e sono tutte lettere e numeri; in più le lettere sono sempre tra a-f e non a-z. Ipotizziamo siano valori
esadecimali.
Se avessimo avuto quest’altro caso di seguito non sarebbe stata una buona regola, perché ci sono User
agent lunghi 7 e altri non iniziano sempre con We. Una cosa tipica che si verifica quando facciamo la
malware analysis, ed eseguiamo sul nostro computer un malware, è che se usiamo una macchina virtuale
avremmo visto una cosa come quella segnata con i due host. Il malware fa tante richieste ma l’user agent è
sempre lo stesso perché il malware invia sempre lo stesso messaggio.

Combinare tecniche di analisi statica e dinamica


L’analisi dinamica consiste nello sniffare queste informazioni, ma ci lascia sempre una sensazione di
incertezza. A questo punto quello che possiamo fare è prendere il traffico di rete, ma prendere pure
l’eseguibile attraverso i vari tool.

Giusto una premessa, nel fare l’analisi statica non vogliamo verificare la copertura del codice perché non
vogliamo una conoscenza totale dello stesso. Quindi osservando la tabella ci fermiamo più o meno nel
mezzo.
Vediamo un esempio con un messaggio simile a quello di prima, ovvero dei messaggi di Beaconing (Si
pronuncia Bicon perché è un trasmettitore, è non Bagon il pokemon o Bacon la pancetta) quindi dei
messaggi che l’host compromesso manda al server C2.

Abbiamo appena detto che questo messaggio, tipicamente incorpora informazioni sull’host; quindi, se lo
lanciamo su host diversi il messaggio cambierà. Vogliamo capire cosa contiene e come si forma tale
messaggio.

Tipicamente quanto visto prima, ovvero il malware si inserisce in un protocollo già esistente, non è un caso
raro, infatti, l’uso di http è un classico. Nell’immagine di seguito, ad occhio, vediamo subito qualcosa di
strano, infatti, l’user agent è enorme e non contiene spazi.
Il malware per cercare di passare inosservato si fa furbo e cerca di mandare un user agent fisso e valido. Ad
esempio così:

Però una volta capito lo si blocca e finisce qui, ma sapientemente scelgono diversi user agent da assegnare
a rotazione o a caso rendendo più difficile il filtraggio. Oppure come detto le lezioni scorse alcuni malware
usano la libreria COM per costruire richieste con lo stesso codice del browser usato. In questo caso l’user
agent non è un buon indicator, perché ovviamente questo appartiene al computer infettato.

Tecniche di offuscamento

Supponiamo adesso un altro malware, dove l’user agent è tranquillo e l’attaccante ha usato il percorso
nella GET. Supponiamo che l’URI che abbiamo sia malevolo e che abbiamo preso molto traffico da tanti
nodi, i valori sono casuali o sono regolari? Ci sono parti regolari, lunghezza simile e si ripete spesso 58.
Questi appena visti si chiamano Beacon message e vengono costruiti prendendo:

 Informazioni sull’host e la configurazione


 Nascondere nei messaggi rubati

Facciamo un ulteriore analisi sul messaggio di sopra, mettiamo che il messaggio di sopra sia stato ottenuto
usando tre funzioni:

 GetTickCount
 Random
 Gethostbyname

Quindi il nostro URL ha una parte randomica, una parte che è l’host del computer e l’altra sono i secondi
nel quale il pc è acceso. Alcuni malware possono usare delle API di network di basso livello per mimare
traffico comune, questo però richiede più lavoro manuale e creare situazioni di errore come queste:

Analizzando il binary code del malware capiamo che ci sono 4 valori randomici, i primi 3 sono
dell’hostname e il restante è il GetTickCount; infine unisce con i “:”

Ma vediamo per bene come funziona nello specifico la formazione, i malware tipicamente trasformano i
dati attraverso dell’encoding prima di inviarlo sulla rete. Il fatto che i primi 4 valori siano casuali già ci aiuta
sulla formazione dell’espressione regolare; vediamo come il malware parte dai valori esadecimali/lettere e
prende gli ASCII.

Quindi chi scrive malware alla fine fa qualche conversione a casaccio, senza capire o essere troppo
intelligenti. Il nostro scopo è comprendere quali sono le regole per poter poi fare la signature
Finiamo per adesso le tecniche di offuscamento con il data encoding, l’obiettivo non è rendere il messaggio
indecifrabile ma semplicemente il non essere rilevabili da un occhiata rapida dell’uomo. Un altro motivo
deriva anche dal fatto di dover inserire il codice per fare la codifica nel malware e quindi non si può
appesantire troppo lo stesso.

Unendo infine tutte le informazioni raccolte prima creiamo una signature “perfetta” con l’espressione
regolare creata a seguito dell’analisi. Notiamo anche una cosa particolare di questa firma, vi è la presenza
prima di un controllo sul numero 58 e poi di tutta la firma. Perché? L’espressione regolare è pesante da
calcolare e quindi se l’url non contiene il 58 iniziale io non l’analizzo proprio.

Base64
Vediamo un altro esempio nel quale un attaccante una un’infrastruttura già esistente; quindi, non usa un
server C2 preso dal nulla ma ha infettato magari un sito web famoso tipo Repubblica.it . Quello di seguito è
codice HTML ma notiamo subito qualcosa di strano, quello con “!--"è un comento contenente una parola in
base64; ovvero longsleep. È un comando che il malware deve fare sulla macchina vittima.
Qui siamo in una situazione contraria al Beacon, prima abbiamo visto il messaggio beacon che parlava al
server qui invece, è il server che sta parlando con una botnet. Questo è molto stealty perché non parliamo
noi.

Ma come funziona il base64, è una codifica che usa solo caratteri esadecimali, l’obiettivo è tramutare
qualunque tipo di byte (anche i non stampabili) in un qualcosa stampabile. Ad esempio, i ritorno a capo o
qualsiasi cosa non visibile.

Vediamo un esempio, quando facciamo l’upload di un file su un sito web noi facciamo la PUT, in alcuni casi
il file viene codificato in base64 (lo vediamo nel body) del messaggio.

Vediamo la trasformazione, si prendono 3 caratteri alla volta e cerchiamo di codificare la parola


“attaccante”. Ogni lettera ha un carattere ASCII e ogni cifra del numero decimale sono 3 byte (24 bit) e i bit
vengono presi a gruppi di 6 invece che 8. Per cui convertiamo ATT in 16/21/17/20. Si usano 6 bit perché ci
sono 64 caratteri stampabili.
Q/V/R/U sono i valori sull’alfabeto. Ovviamente non lo faremo a mano ma ci sono i siti che fanno la
conversione, la particolarità è che le stringhe devono essere sempre multipli di 3.

La domanda ora sorge spontanea, come facciamo a capire da un .EXE che ci troviamo a lavorare con
qualcosa a base64? Tra gli esercizi che ci ha dato vediamo che tra le sue stringhe ha una stringa come quella
riportata nell’immagine, il suo compito è proprio fare la conversione e per questo si chiama indexing
string.

Domanda, ma se noi mettessimo questa stringa come mostrata di seguito nella regola avremmo fatto una
buona regola? E no, altrimenti non abbiamo imparato niente.

Quindi apriamo il malware e cerchiamo quali sono le stringhe possibili che l’exe può ricevere. Usiamo IDA e
cerchiamo di capire la parte del codice che si occupa di fare la conversione della base64. In questo esempio
specifico abbiamo che la stringa funziona con una ciclo for + switch case.

Quindi vediamo cinque alternative diverse, una per ogni comando (le stringhe sono tagliate perché
indicano solo la parola “run” o connect”).
Un modo più robusto per scrivere queste regole è farlo come nell’esempio. Si smonta la regola in due parti:

 Una parte del prefisso che è sempre uguale “? Ed espressione base64”


 Ci sono poi 5 regole dove si fissa solo la prima parte e l’ultima parte

Ora non conviene unire il tutto alla regolar expression di prima, ma ci conviene spezzarla in due regole
diverse, questo sia per la dimensione e quindi lentezza sia perché sarebbe poi aggirabile.

La prospettiva dell’attaccante
In conclusione, come ogni sviluppatore software, anche l’attaccante ha difficoltà ad aggiornare il software e
renderlo poi compatibile con i sistemi che cambiano in continuazione. Ogni cambiamento necessario
dovrebbe essere minimale e quindi le nostre regole se sono robuste resistono a questi tipi di cambiamenti.

Al minuti 1:37:50 fa vedere il laboratorio ed il suo funzionamento.


Lezione 24 08/06 (16-Lab-MalwareDetection)
Ripetiamo che venerdì ci sarà un seminario, non fa parte del programma però è utile per vedere cosa fanno
le aziende nel nostro settore.

La prima esercitazione riguarda la scrittura di una regola su YARA, nelle slide ci sono vari esempi e i task
sono segnati alla slide 16 sui lab. yarGen crea un database di quasi 1 gb ma suggerisce la scrittura delle
regole e nello specifico nel file creato c’è già la risposta alle domande; ma come ragiona il tool? Di base
mette dei metadati e non fanno parte della regola, poi suggerisce 5 stringhe ma non le mette tutte nella
regola se no sarebbe accurato e fa un controllo con le stesse stringhe sul Database.

Il controllo sul DB serve per evitare falsi allarmi. Le stringe sono chiamate $s* se sono stringhe generiche
mentre %x* se specifiche.

In sigma quando le regole sono impilate in una selection sono tutte in AND tra loro mentre se ci sono i “-“
allora le regole sono in OR. Le regole di SIGMA sono meno specifiche di quelle di YARA, le quali sono fatte
ad-hoc sul file exe di un certo malware; mentre per sigma esse sono fatte per i logger che poi devono
inoltrare le informazioni ad altri sistem. Nel caso vediamo in sigma |endswith allora significa che abbiamo
sostituito il “*” e quindi non ci interessa il percorso.

Potrebbero piacerti anche