A Tour of the Worm

Donn Seeley
Department of Computer Science
University of Utah

EXTRAIT


Le soir du 2 novembre 1988, un programme auto répliquant a été lancé sur Internet (1). Ce programme (un ver) a envahi des VAX et des ordinateurs Sun-3 faisant tourner des versions de l'UNIX de Berkeley et les a utilisé pour infecter encore plus d'ordinateurs (2). En l'espace de quelques heures ce programme s'est répandu à travers les Etats-Unis, infectant des centaines de milliers de machines et les rendant inutilisables à cause de l'encombrement que provoquait son exécution. Ce document présente une chronologie de cet événement ainsi qu'une description détaillée du fonctionnement du ver, basée sur une version en C du ver obtenue en le décompilant.

There is a fine line between helping administrators protect their systems and providing a cookbook for bad guys. [Grampp and Morris, "UNIX Operating System Security"]

Le 3 novembre 1988 allait devenir le Jeudi Noir. Les administrateurs système de tout le pays venaient pour travailler et découvrirent ce jour là que leurs réseaux d'ordinateurs étaient soumis à de forts traitements. S'ils parvenaient à s'identifier et à générer un statut du système, ils voyaient une douzaine ou une centaine de processus de "shell" (interpréteur de commande). S'ils essayaient de tuer les processus, ils s'apercevaient que de nouveaux processus apparaissaient plus vite qu'ils ne pouvaient les tuer. Redémarrer l'ordinateur n'avait pas plus d'effet : au bout de quelques minutes la machine était à nouveau surchargée par ces mystérieux processus.

Ces systèmes avaient été envahis par un ver. Un ver et un programme qui se propage tout seul sur un réseau, utilisant les ressources d'une machine pour en attaquer une autre (Un ver n'est pas tout à fait similaire à un virus qui est un fragment de programme qui s'insère dans d'autres programmes). Le ver profitait de trous dans la sécurité de systèmes qui utilisaient BSD UNIX 4.2 ou 4.3 ou des dérivés comme SunOS. Ces trous lui permettait de se connecter aux machines d'un réseau, de contourner leur authentification, de se reproduire (se recopier) puis d'attaquer toujours plus de machines. Les chargements massifs étaient dû aux multitudes de vers qui essayaient de propager l'épidémie.

L'Internet n'avait jamais été attaqué de cette façon auparavant, bien qu'il y avait beaucoup de chance pour qu'une telle attaque soit en préparation. La plupart des administrateurs n'étaient pas familiers avec le concept de ver (à l'opposé des virus, qui sont une plus grande menace au monde des PCs) et il leur a fallu du temps avant qu'ils comprennent ce qui se passait et comment gérer ça. Le but de ce document est d'expliquer aux gens ce qui s'est passé et comment cela est arrivé, afin qu'ils soient mieux préparés lorsque cela se reproduira. Le comportement du ver sera étudié en détail, à la fois pour montrer exactement ce qu'il faisait et ne faisait pas et aussi pour montrer les dangers des futurs vers. Comme les informations sont bien connues, que des milliers d'ordinateurs possèdent leurs copies du vers, il semble improbable que ce document puisse causer des dommages, même si cela reste troublant. Les opinions sur le sujet et les autres problèmes seront offertes plus tard.

2. Chronologie

Remember, when you connect with another computer, you're connecting to every computer that computer has connected to. [Dennis Miller, on NBC's Saturday Night Live]

Here is the gist of a message I got: I'm sorry. [Andy Sudduth, in an anonymous posting to the TCP-IP list on behalf of the author of the worm, 11/3/88]

Beaucoup de détails sur la chronologie des attaques ne sont pas encore disponibles. La liste suivante représente la date et l'heure des différents évènements connus.

2/11 à 18 : 24 (approx.)
C'est la date et l'heure à laquelle les fichiers du ver ont été trouvés sur la machine prep.ai.mit.edu, un VAX 11/750 du Laboratoire d'Intelligence Artificielle du MIT. Ces fichiers ont étés retirés plus tard, et l'heure exacte a été perdue. Pendant deux semaines il fut impossible de se loger sur prep. Le système ne gardait pas de logs et les disques n'étaient pas sauvegardés sur cassette : une cible parfaite. Un certain nombre de "visiteurs" (des personnes utilisant des comptes publiques) a été remarqué ce soir là. Ces utilisateurs auraient du apparaître dans les logs de sessions, mais regardez ce qui suit.

2/11 à 18 : 24
Première infection connue sur la cote ouest : rand.org à Rand Corp, Santa Monica.

2/11 à 19 : 04
csgw.berkeley.edu est infecté. Cette machine est un des routeurs principaux du réseau de UC Berkeley. Mike Karels et Phil Lapsley ont découvert l'infection peu de temps après.

2/11 à 19 : 54
mimsy.umd.edu est attaqué par le biais de son serveur finger. Cette machine se trouve au département informatique de l'Université de Maryland.

2/11 à 20 : 00 (approximatif)
Les Suns du laboratoire d'I.A. du MIT sont attaqués.

2/11 à 20 : 28
Première attaque sendmail sur mimsy.

2/11 à 20 : 40
L'équipe de Berkeley se rend compte des attaques Sendmail et Rsh, remarque les erreurs sur les services telnet et finger et commence à désactiver ces services.

2/11 à 20 : 49
cs.utah.edu est infecté. Ce VAX 8600 est la machine principale du département informatique de l'Université d'Utah. Les évènements suivant décrivent les attaques à Utah et sont représentatives des autres infections à travers le pays.

2/11 à 21 : 09
Première attaque sendmail sur cs.utah.edu.

2/11 à 21 : 21
La moyenne de charge sur cs.utah.edu atteint 5. La "moyenne de charge" est une valeur générée par le système qui représente le nombre moyen de jobs dans la fille d'attente durant la dernière minute ; une charge de 5 sur un VAX 8600 dégrade de façon notable les temps de réponse alors qu'une charge de plus de 20 est une dégradation drastique. A 9 heures du soir, la charge est généralement entre 0.5 et 2.

2/11 à 21 : 41
La moyenne de charge de cs.utah.edu atteint 7.

2/11 à 22 : 01
La moyenne de charge de cs.utah.edu atteint 16.

2/11 à 22 : 06
Le nombre maximum de processus différents en cours d'exécution (100) est atteint sur cs.utah.edu ; le système est inutilisable.

2/11 à 22 : 20
Jeff Forys d'Utah tue les vers sur cs.utah.edu. Les machines Sun de Utah sont infectées. A 22 : 41 la re-infection provoque une moyenne de charge de 27 sur cs.utah.edu.

2/11 à 22 : 49
Forys redémarre cs.utah.edu.

2/11 à 23 : 21
La re-infection fait grimper la moyenne de charge à 37 sur cs.utah.edu malgré les efforts répétés de Forys pour tuer les vers.

2/11 à 23 : 28
Peter Yee du Centre de Recherche de NASA Ames envoie un message d'avertissement à la liste de diffusion TCP-IP : "Nous sommes en ce moment sous les attaques d'un VIRUS Internet. Il a frappé UC Berkeley, UC San Diego, Lawrence Livermore, Stanford, et NASA Ames." Il suggère de stopper les services telnet, ftp, finger, rsh et SMTP. Il ne mentionne pas rexec.

3/11 à 00 : 34
Andy Sudduth de Harvard poste anonymement un avertissement sur la liste TCP-IP : "Il doit y avoir un virus perdu sur l'Internet." C'est le premier message qui explique (brièvement) comment l'attaque finger marche, décrit comment se protéger de l'attaque SMTP en recompilant sendmail, et mentionne de façon explicite l'attaque rexec. Malheureusement le message de Sudduth est bloqué à relay.cs.net alors que le routeur est éteint pour combattre le ver. Le message ne sera délivré que deux jours après. Sudduth révéla être l'auteur du message le 5 novembre.

3/11 à 02 : 54
Keith Bostic envoie un correctif pour sendmail au newsgroup comp.bugs.4bsd.ucb-fixes et à la liste TCP-IP. Ces correctifs (et les suivants) sont aussi envoyés aux administrateurs des systèmes importants à travers le pays.

3/11 tôt la matin
Les logs de session wtmp sont mystérieusement effacées de prep.ait.mit.edu.

3/11 à 05 : 07
Edward Wang de Berkeley remarque et averti de l'attaque finger mais son message ne parvient à Mike Karel's que 12 heures plus tard.

3/11 à 09 : 00
Le congrès annuel de Berkeley Unix se déroule à UC Berkeley. 40 ou plus des plus importants administrateurs systèmes et hackers sont en ville à discuter, quand la catastrophe arrive chez eux. Plusieurs personnes qui avaient décidé de prendre l'avion jeudi matin sont immobilisées par la crise. Keith Bostic passe la plus grande partie de la journée au téléphone au bureau du Groupe de Recherche en Systèmes Informatiques à répondre aux appels d'administrateurs systèmes paniqués venant de tout le pays.

3/11 à 15 : 00 (approximatif)
L'équipe du MIT Athena appelle Berkeley pour lui expliquer le fonctionnement du bug des serveurs finger.

3/11 à 16 : 26
Dave Pare arrive dans les bureaux du Berkeley CSRG ; le désassemblage et la décompilation se font petit à petit avec les outils spécialisés de Pare.

3/11 à 18 : 00 (approximatif)
Le groupe de Berkeley expédie des calzones (sorte de pizzas pliées). Les gens font des va et vient ; les bureaux sont bondés, l'excitation est au plus fort.

3/11 à 19 : 18
Keith Bostic envoie un correctif pour le serveur finger.

4/11 à 06 : 00
Les membres de l'équipe Berkeley, avec le ver presque entièrement désassemblé et largement décompilé, prennent finalement quelques heures de sommeil avant de retourner au travail.

4/11 à 12 : 36
Theodore Ts'o du Projet Athena du MIT annonce publiquement que le MIT et Berkeley ont complètement désassemblé le ver.

4/11 à 17 : 00 (approximatif)
Une courte présentation du ver est faite à la fin du congrès Berkeley Unix.

8/11
Déroulement du meeting du National Computer Security Center afin de discuter du ver. Il y a environ 50 participants.

11/11 à 00 : 38
Les sources du ver, entièrement décompilées et commentées sont installées à Berkeley.

3. Vue d'ensemble

Que faisait exactement le ver pour provoquer une telle épidémie ? Le ver est composé d'une routine d'amorçage (bootstrap en Anglais. C'est la partie du ver qui est exécutée et qui se charge de placer une copie du ver sur les machines infectées) de 99 lignes écrites en langage C, plus un gros fichier objet qui change selon la plate-forme (VAX ou Sun-3). De toute évidence ce fichier objet a été généré à partir de sources en C, il était donc naturel de décompiler le langage machine en C ; nous avons maintenant plus de 3200 lignes de code C commenté qui peut se recompiler et qui est quasi-complet. Nous sommes obligés de commencer l'étude du ver par une rapide vue d'ensemble des objectifs du ver, suivi d'une discussion en profondeur sur les différents comportements du ver révélés lors de la décompilation.

Les activités du ver se divisent en deux catégories : l'attaque et la défense. L'attaque consiste à trouver des machines (et des comptes) à pénétrer, puis à exploiter des trous de sécurité sur des machines distantes pour y placer une copie du ver et la mettre en marche. Le ver obtient des noms d'hôtes en regardant dans les fichiers système /etc/hosts.equiv et dans /.rhosts, dans les fichiers des utilisateurs comme le .forward et rhosts, en analysant les données de routage dynamique produites par le programme netstat et enfin en générant des adresses IP aléatoires sur le réseau local. Il les classe par ordre de préférence, commençant d'abord par le fichier /etc/hosts.equiv parce qu'il contient des noms de machines locales qui sont susceptibles d'accepter des connexions sans authentification. La pénétration d'un système distant peu s'effectuer d'une des trois façons suivantes. Le ver peut profiter d'un bug dans le serveur finger qui lui fera télécharger le code malveillant puis le lui faire exécuter. Le ver peut utiliser une brèche dans le service de messagerie SMTP sendmail, lui faire exécuter des commandes dans un shell et télécharger le code à travers une connexion mail. Si le ver pénètre un compte local en devinant son mot de passe, il peut utiliser les services de commande à distance rexec et rsh pour attaquer des machines qui partagent le même compte. Dans tous les cas, le ver s'arrange pour obtenir un shell distant qu'il utilisera pour se propager, se compiler et exécuter la routine de 99 lignes. Cette routine établie sa propre connexion avec le ver local afin de récupérer les différents fichiers dont elle a besoin, et en regroupant ces différents morceaux un nouveau ver est créé et la procédure d'infection recommence encore et encore. Les tactiques de défense se classent en trois catégories : empêcher la détection de l'intrusion, interdire l'analyse du programme et identifier les autres vers. La méthode la plus simple pour le ver de se cacher est de changer son nom. Quand il se déclenche, il efface sa liste d'arguments (argv) et fixe l'argument numéro zéro à sh, le faisant passer pour un innocent interpréteur de commande. Il utilise la commande fork() pour changer son PID, et ne reste jamais très longtemps avec le même ID. Ces deux tactiques ont pour objectif de camoufler la présence du ver dans les listings d'état du système (logs, etc.) Le ver essaie de laisser le moins de traces possibles, au démarrage il met en mémoire tous les fichiers dont il a besoin et efface les copies qui pourraient révéler sa présence. Il empêche aussi la génération de fichiers core, si bien que si le ver plante, il ne laisse pas de preuve de son passage à travers la présence de dumps core. Cette dernière tactique est aussi crée pour empêcher l'analyse du programme : cela empêche un administrateur d'envoyer un signal qui forcerait le programme à générer un fichier core. Cependant il y a d'autres méthodes d'obtenir un fichier core, si bien que le ver remplace minutieusement certains octets en mémoire (des caractères) pour être extrait plus difficilement. Les copies sur le disque sont encodées en répétant un xor sur une séquence de 10 octets ; les chaînes de caractères static sont encodées octets après octets en effectuant un xor avec la valeur hexadécimale 0x81, à l'exception d'une liste privée qui est encodée avec le code hexadécimal 80. Si les fichiers du ver sont toutefois capturés avant que le ver ne les ai effacés, les fichiers objets ont été chargé de telle façon que la plupart des entrées non essentielles de la table des symboles ont été effacées, empêchant de comprendre le rôle d'une routine en se basant sur son nom. Le ver fait aussi un effort trivial pour stopper les programmes qui voudraient profiter de ses communications ; en théorie un site bien attaqué peut empêcher l'infection en envoyant un message aux ports sur lequel le ver écoute, si bien que le ver fait bien attention à tester les connexions en établissant un court échange de "chiffres magiques" aléatoires.

Quand on étudie un programme malicieux comme celui-là, il est tout aussi important de faire la liste de ce qu'il ne fait pas que la liste de ce qu'il fait. Ce ver n'efface pas de fichiers systèmes : il efface juste les fichiers qu'il a crée durant le processus d'amorçage. Ce programme ne tente pas de mettre hors d'état de marche le système en supprimant des fichiers importants. Il n'efface pas les fichiers de log ou ne va pas perturber d'opérations normales si ce n'est en consommant des ressources systèmes. Le ver n'altère pas de fichiers existants : ce n'est pas un virus. Le ver se propage en se copiant et en se compilant lui-même sur chaque système ; il ne modifie aucun programme pour parvenir à ce résultat. A cause de sa méthode d'infection, il ne possède pas les privilèges suffisant qui lui permettraient de modifier des programmes. Le worm n'installe pas de chevaux de Troie : sa méthode d'attaque est uniquement active, il n'attend jamais qu'un utilisateur tombe dans un piège. Une partie de la raison pour laquelle il n'installe pas de troyen est qu'il ne peut pas se permettre de perdre son temps à attendre l'exécution d'un exécutable par l'utilisateur - il doit se reproduire avant qu'il soit découvert. Enfin, le ver ne transmet, ni n'enregistre les mots de passes décryptés : sauf pour sa liste statique de ses passwords préférés, le ver ne passe pas les mots de passes crackés à de nouveaux vers ni ne les renvoie vers une machine centrale. Cela ne veut en aucun cas dire que les comptes pénétrés par le ver sont sûr simplement parce que le ver n'a communiqué les passwords à personne, évidemment, si le ver peut deviner le mot de passe d'un compte alors des personnes pourront certainement le faire. Le ver n'essaye pas de récupérer les privilèges du super utilisateur : alors qu'il fait tout pour pénétrer des comptes, il n'a a aucun moment besoin des privilèges particuliers pour se propager, et ne fait aucun usage spécial de ces privilèges même s'il les obtient. Le ver ne se propage pas à travers uucp, x.25, DECNET ou BITNET : il requiert spécifiquement TCP/IP. Le worm n'infecte pas de systèmes System V à moins qu'ils aient été modifié pour utiliser les programmes réseaux de Berkeley comme sendmail, fingerd et rexec.

4. Etude détaillée

Passons maintenant aux détails : nous devons suivre le fil principal du ver puis examiner les structures de données avant d'étudier chaque phase séparément.

4.1 Le processus principal

Quand le ver commence son exécution dans le main(), il fait bien attention aux initialisations, aux moyens de défense et de camouflage. La toute première opération qu'il effectue est de changer son nom en sh. Il réduit le temps durant lequel le ver est visible en changeant le nom de son processus en un nom étrange comme x9834753. Il initialise alors le générateur de nombres aléatoires, en s'appuyant sur l'heure courante, désactive les core dumps, et s'arrange pour mourir quand une connexion distante échoue. Une fois tout ceci effectué, le ver gère la liste de ses arguments. Il commence par rechercher l'option -p $$, où $$ représente l'ID de son processus père ; cette option indique au ver qu'il doit prendre soin de s'effacer plus tard. Il lit alors chacun des fichiers qui lui ont été passé en argument ; si l'option précédente est activée, il efface chaque fichier qu'il vient de lire. Si le ver n'a pas récupéré le fichier source d'amorçage l1.c comme argument, il se termine silencieusement ; c'est probablement un moyen de ralentir les personnes qui sont en train d'étudier le ver. Si l'effacage des traces est activé, le ver ferme alors ses descripteurs de fichier, se coupant momentanément de son ver père distant, et efface certains fichiers. (Un de ceux-ci, /tmp/.dumb, n'est à aucun moment crée par le ver, cette suppression semble venir d'une version précédente du ver, et n'aurait pas été retirée du programme.) Le ver met alors sa liste d'arguments à zéro, là encore pour contrecarrer le programme de statut système ps. L'étape suivante est l'initialisation de la liste des interfaces réseaux du ver ; ces interfaces sont utilisées pour trouver des réseaux locaux et chercher des adresses alternatives pour la machine en court. Enfin, si l'option -p est passée, le ver re-initialise son groupe de processus et tue les processus qui l'ont aidé à se lancer. La dernière action qu'effectue le ver dans la fonction main() est d'appeler une fonction que nous avons nommé doit(), qui contient la boucle principale du ver.

doit()
{
	lance le générateur de nombres aléatoires en se basant sur l'heure
	attaque de machines : gateways, réseaux locaux, réseaux externes
	checkother();
	send message();
	for(;;)
	{
		cracksome();
		other_sleep(30);
		cracksome();
		changement de l'identifiant de processus
		attaque de machines : gateways, hôtes connus, réseaux distants, réseaux locaux
		other_sleep(20);
		if (12 heures se sont passés)
			vider la liste des machines
		if (pleasequit && nextw > 10)
			exit(0);
	}
}

Pseudo code "C" pour la fonction doit()

doit() lance un court prologue avant d'entrer dans la boucle principale. Il fixe (inutilement) le générateur de nombres aléatoires avec l'heure courante, tout en sauvant l'heure afin qu'il puisse déterminer depuis combien de temps il tourne. Le ver commence alors sa première infection. Il attaque tout d'abord les passerelles (gateways) qu'il a trouvé avec la commande de surveillance réseau netstat ; s'il ne parvient pas à infecter l'une de ces machines, alors il cherche des machines sur le réseau local avec un numéro d'hôte aléatoire, ensuite il fait la même chose mais avec les machines se trouvant de l'autre côté de la passerelle, dans tous les cas il s'arrête dès qu'il a trouvé une machine. (Remarquez que cette séquence d'attaque diffère de la séquence utilisée par le ver une fois que ce dernier est entré dans la boucle principale.)

Après ces premières tentatives d'infection, le worm appelle la routine checkother() pour vérifier qu'un autre ver ne se trouve pas sur la machine locale. Dans cette routine le ver agit comme un client qui communique avec un ver préexistant qui joue le rôle de serveur ; ils peuvent alors s'échanger des messages de "contrôle de la population" après quoi l'un des deux vers va éventuellement s'éteindre.

Une routine étrange est lancée juste avant d'entrer dans la boucle principale. Cette routine que nous avons appelé send_message() n'envoie en fait rien du tout. Il semblerait quelle soit faite afin qu'une copie du ver sur 15 envoie un datagramme de 1 octet sur un port de la machine ernie.berkeley.edu, qui est localisée à l'UC Berkeley, au Computer Science Department. Il a été suggéré que ceci n'était qu'une feinte désignée à faire porter l'attention sur ernie, et non sur la véritable machine de l'auteur. Puisque la routine a un bug (elle met en place un socket TCP mais essaye d'envoyer un paquet UDP), rien n'est envoyé au final. Il est aussi possible que cela soit une feinte plus recherchée, destinée à être découverte seulement par les décompileurs ; si cela était le cas, ce ne serait pas le seul obstacle que l'auteur a délibérément placé sur notre chemin. Dans tous les cas, les administrateurs de Berkeley n'ont détecté aucun processus écoutant sur le port 11357 de ernie, et nous n'avons trouvé aucun code dans le ver qui écoute sur ce port, indépendamment de la machine.

La boucle principale débute avec l'appel à une fonction que nous avons baptisé cracksome() pour le cassage de mots de passe. Le cassage de mots de passe est une activité que le ver fait constamment et de manière incrémentale. Il fait une pause de 30 secondes pour voir s'il y a des copies du ver qui se sont introduites sur la machine locale, et puis retourne au cracking. Ensuite il fork (crée un nouveau processus fonctionnant comme une copie du même ver) et l'ancien processus se termine ; cela sert à changer les numéros d'I.D. des processus et de rendre plus difficile la découverte du ver avec la commande d'information système ps. Le ver retourne alors à l'étape d'infection, en essayant (par ordre de préférence) les routeurs, les machines présentes dans un fichier comme /etc/hosts.equiv, les hôtes aléatoires de l'autre côté du routeur et ceux des réseaux locaux. Comme précédemment, s'il parvient à infecter une nouvelle machine, il marque cette machine dans une liste et arrête l'infection pour le moment. Après l'infection, le ver passe deux minutes pour regarder une nouvelle fois s'il n'y a pas de nouvelles copies du ver ; cela est fait parce qu'un nouvel hôte distant infecté peut essayer de réinfecter l'hôte local. Si 12 heures se sont passées et que le ver est toujours actif, il suppose qu'il n'a pas eu de chance car des machines ou des réseaux devaient être hors service ou éteints, et il réinitialise sa table d'adresses si bien qu'il peut reprendre au début. A la fin de la boucle principale le ver regarde s'il peut mourir en fonction des résultats de contrôle de la population, et si c'est le cas, et s'il a obtenu une quantité suffisante de mots de passe cassés, il quitte.

4.2 Structures de données

Le ver maintient au moins quatre structures de données intéressantes, et chacune est associée à un ensemble de routines.

La structure object est utilisée pour contenir des copies de fichiers. Les fichiers sont encryptés en utilisant la fonction xorbuff() au moment où ils sont en mémoire, si bien qu'un dump du ver ne révèle rien d'intéressant. Les fichiers sont copiés sur le disque du système distant avant de commencer un nouveau worm, et les nouveaux worms mettent ces fichiers en mémoire et suppriment les copies du disque comme tâche de démarrage. Chaque structure contient un nom, une longueur et un pointeur vers un buffer. La fonction getobjectbyname() récupère un pointeur vers une structure d'objet nommée ; pour différentes raisons, elle n'est appelée que pour lancer le fichier source d'amorçage.

La structure interface contient des informations sur la configuration des interfaces réseaux de la machine en cours. Cela est principalement utilisé pour trouver des réseaux locaux. Cette structure est composée d'un nom, d'une adresse réseau, d'un masque de sous réseau et de quelques flags. La table des interfaces est initialisée une fois au démarrage.

La structure host est utilisée pour garder une trace des statuts et des adresses des machines. Les machines sont ajoutées dynamiquement à cette liste, au fur et à mesure que le ver rencontre de nouvelles sources de noms d'hôtes et d'adresses. Cette liste peut être explorée à la recherche d'une adresse ou d'un nom particulier, avec une option pour insérer une nouvelle entrée si la recherche n'a rien donnée. Les bits d'indication (flags) sont utilisés pour indiquer si l'hôte est une passerelle, s'il a été trouvé par l'intermédiaire d'une table système comme /etc/hosts.equiv, si le ver a considéré pour différentes raisons que cette machine était impossible à attaquer, et si l'hôte a déjà été infecté avec succès. Les bits pour "infection impossible" et "infecté" sont effacés toutes les 12 heures, et les hôtes à faible priorité sont effacés pour être à nouveau accumulés plus tard. La structure peut contenir jusqu'à 12 noms (alias) et jusqu'à 6 adresses réseaux distinctes pour chaque machine.

Dans nos sources, ce que nous avons appelé la structure muck (saleté, saloperie, cochonnerie) est utilisée pour garder trace des comptes dans un but de cracking de passwords. (Nous lui avons décerné le nom muck pour des raisons sentimentales, bien que pw ou act auraient été plus parlant.) Chaque structure contient un nom de compte, un password crypté ou décrypté (si possible) plus le répertoire personnel de l'utilisateur (home directory) ainsi que des informations personnelles tirées du fichier password.

4.3 Croissance de la population (Propagation)

Le ver possède un mécanisme qui semble avoir été crée pour limiter le nombre de copies du ver qui tournent sur un système donné, mais en dehors de cela notre compréhension de pourquoi ce ver a été crée est limitée. Cela n'empêche évidemment pas la surcharge d'un système bien que cela accélère de manière évidente l'infection pour rendre les premières copies indétectables. Il a été suggéré qu'une simulation des fonctions de contrôle de la population du ver pourrait révéler plus de détails sur sa création et nous serions intéressé d'écrire un papier sur une telle simulation.

Le ver utilise une technique client-et-serveur pour contrôler le nombre de copies exécutées sur la machine courante. Une routine checkother() est appelée au démarrage. Cette fonction essaye de se connecter à un serveur en écoute sur le port TCP 23357. La tentative de connexion retourne immédiatement si aucun serveur n'est présent, mais se bloque si un est disponible ou occupé ; un ver serveur lance régulièrement son code serveur pendant les opérations qui prennent du temps si bien que la queue des connexions n'augmente jamais beaucoup. Une fois que le client a échangé des nombres magiques (magic numbers) avec le serveur sous la forme d'une authentification triviale, le client et le serveur jouent à la courte paille pour déterminer qui survivra. Un ou-exclusif entre les bits de poids faibles respectif des nombres aléatoires du client et du serveur est effectué. Si le résultat vaut 1 le serveur est gagnant, dans le cas contraire le client sort vainqueur. Le perdant active son drapeau (flag) pleasequit qui lui permet éventuellement de quitter en fin de boucle principale. Si à un moment un problème survient - une lecture du serveur échoue, ou qu'un mauvais nombre magique est retourné - le ver client quitte la fonction, devenant un ver qui n'agira jamais en tant que serveur et en conséquence ne s'implique pas dans la croissance de la population. Un test est effectué au tout début de la fonction pour détecter un éventuel serveur cataleptique. Après ce test 1 ver sur 7 abandonne le contrôle de la population. De cette manière le ver achève le jeu de population dans checkother() dans l'un de ces trois états : programmé pour mourir après un certain temps, avec le drapeau pleasequit activé ; fonctionnant en tant que serveur avec la possibilité de perdre le jeu ultérieurement ; et immortel, à l'abris des dangers du contrôle de la population.

Une routine complémentaire, other_sleep(), exécute la fonction de serveur. Cette fonction utilise l'appel système select() de Berkeley comme chronomètre et accepte durant un laps de temps de quelques secondes les connections venant des clients. Au début de la fonction il vérifie s'il dispose d'un port de communication avec lequel accepter des connexions, si ce n'est pas le cas, il laisse passer le laps de temps prédéfini et retourne. Dans le cas contraire il boucle sur select(), diminue le temps restant après qu'il ai servi un client jusqu'à ce qu'il ne reste plus de temps et que le ver retourne. Quand le serveur obtient un client, il effectue l'inverse du protocole client, décidant éventuellement s'il doit continuer ou quitter. other_sleep() est appelé à partir de beaucoup d'endroits différents du code, si bien que les clients n'attendent jamais bien longtemps.

Au vu du système élaboré du ver à contrôler la réinfection, qu'est ce qui le rend si rapide à se reproduire sur une machine au point de la submerger ? Un des coupables est le test du "1 sur 7" dans checkother() : les vers qui sautent la phase de client deviennent immortels, et ainsi ne risquent pas de disparaître lors du duel. Une autre cause de surcharge de la mémoire est le fait que quand un ver décide qu'il a perdu, il peut encore effectuer beaucoup de taches avant de réellement quitter. La routine du client n'est même pas lancée jusqu'à ce que le ver nouveau-né tente d'infecter au moins une machine distante, et même si le ver perd le duel, il continue de s'exécuter à la fin de la boucle principale, et là encore il ne quitte pas tant qu'il n'a pas fait le tour de la boucle plusieurs fois, étant bloqué dans le cassage des mots de passes. Enfin, les nouveaux vers perdent tout l'historique des infections réalisées par leurs parents, si bien que les enfants d'un ver sont constamment en train d'essayer de réinfecter les machines de leur père, ainsi que les enfants d'autres vers. Si on rassemble toutes ces données il n'y a en fait aucune surprise qu'au bout de une ou deux heures d'infections, une machine soit entièrement dévouée la l'exécution des vers.

4.4 La recherche de nouvelles machines à infecter

Une des caractéristiques du ver est que toutes ses attaques sont actives, jamais passives. Par conséquent le ver n'a jamais à attendre qu'un utilisateur vienne le lancer sur une autre machine contrairement à la plupart des Chevaux de Troie - il doit chercher des ordinateurs par ses propres moyens.

Le ver dispose d'une liste bien distincte de priorités quand il fait la chasse aux machines. Ses victimes préférées sont les passerelles ; la fonction hg() essaie d'infecter toutes les machines qu'elle suppose être des passerelles. C'est seulement une fois que toutes les passerelles sont considérées comme étant soit infectées soit inattaquables que le ver s'en prend à d'autres hôtes. hg() appelle la fonction rt_init() qui lui donne une liste de passerelles ; cette liste a été générée à partie du résultat de la commande de surveillance réseau Netstat. Le ver prend bien soins de sauter le loopback ainsi que les interfaces réseau locales (dans le cas où la machine en cours est un routeur) ; il mélange ensuite aléatoirement la liste et ajoute les 20 premiers routeurs au tableau des machines afin d'accélérer les recherches initiales. Il teste alors chaque routeur dans l'ordre jusqu'à ce qu'il trouve un hôte qu'il puisse infecter, où qu'il n'ai plus d'hôtes à disposition.

Après avoir pris soin des routeurs, la priorité suivante du ver concerne les machines trouvées en scannant les fichiers systèmes. Au début du cassage de mot de passes, les fichiers /etc/hosts.equiv (qui contient le nom des machines qui peuvent se connecter à la machine locale sans authentification) et /.rhosts (qui contient les noms de machines auxquelles sont accordées certains privilèges de login) sont examinés, tout comme les fichiers .forward de chaque utilisateur (qui donnent la liste de toutes les machines auxquelles un mail est retransmis). Ces hôtes sont marqués (drapeau) de telle façon qu'ils puissent être scannés avant les autres. La fonction hi() se charge alors d'attaquer ces machines.

Une fois que les machines les plus rentables ont été utilisées, le ver commence à chercher les machines qui ne sont pas recensées dans des fichiers. La routine hl() étudie les réseaux locaux : elle teste les adresses des machines locales, en concaténant l'adresse du réseau avec une valeur aléatoire. ha() effectue la même tâche pour les machines externes, à la recherche d'adresses alternatives aux passerelles. Un code spécial gère la règle d'ARPAnet qui consiste à mettre le chiffre IMP dans les bits de poids faible de l'adresse et le port IMP actuel de la machine dans les bits de poids fort de l'adresse. Une fonction qui effectue différentes "enquêtes" aléatoires et que nous avons nommé hack_netof(), semble posséder un bug qui empêche le ver d'attaquer des machines sur les réseaux locaux ; cela peut bien sûr être dû à une mauvaise interprétation de notre part, mais dans tous les cas la recherche de machines à travers les fichiers systèmes devrait être suffisante pour couvrir toutes ou presque toutes les machines locales.

Le cassage de password est un autre générateur de nom de machines, mais comme il est traité séparément de l'habituel plan d'attaque présenté ici, le sujet sera étudié plus loin avec les autres calculs concernant les mots de passes.

4.5 Failles de sécurité

The first fact to face is that Unix was not developed with security, in any realistic sense, in mind... [Dennis Ritchie, "On the security of Unix"]

Cette section traite des services TCP utilisés par le ver pour pénétrer des systèmes. Il est injuste d'utiliser la citation précédente alors que l'implémentation des services que nous allons étudier a été distribuée par Berkeley plutôt que Bell Labs, mais le sentiment est tout à fait approprié. Pendant très longtemps la balance entre la sécurité et la facilité d'utilisation des systèmes Unix a penchée en faveur de la facilité d'utilisation. Comme Brian Reid l'a dit à propos de l'intrusion de Stanford il y a deux ans : "La commodité accordée à un programmeur va à l'encontre de la sécurité, parce que cette commodité devient celle de l'intrus si le compte du programmeur est compromis." La leçon tirée de cette expérience semble être oubliée par la plupart des personnes, mais pas par l'auteur du worm.

4.5.1 Rsh et rexec

These notes describe how the design of TCP/IP and the 4.2BSD implementation allow users on untrusted and possibly very distant hosts to masquerade as users on trusted hosts. [Robert T. Morris, "A Weakness in the 4.2BSD Unix TCP/IP Software"]

Rsh et rexec sont des services réseau qui offrent des interpréteurs de commande à distance. Rexec utilise une authentification par mot de passe ; rsh s'appuie sur des fichiers décrivant les connexions entrantes privilégiées et leurs permissions. Deux vulnérabilités sont exploitées par le ver - la probabilité qu'une machine distante qui possède un compte similaire à un compte local possède aussi le même password, permet l'infiltration par le biais de rexec, et la probabilité qu'un tel compte distant inclura la machine locale dans ses fichiers de droits rsh. Ces deux vulnérabilités sont vraiment des problèmes de laxisme ou de confort des utilisateurs et sont dues à l'administrateur système et non à un bug actuel, mais elles doivent être prises comme des facteurs d'infection tout comme une faille de sécurité involontaire.

La première utilisation de rsh faite par le ver est très simple : il recherche un compte distant avec le même nom que celui qui est (secrètement) en train de faire tourner le ver sur la machine locale. Ce test fait partie du plan standard de piratage effectué pour chaque hôte ; si il échoue, le ver laisse la place à finger, puis sendmail. Beaucoup de sites comme Utah étaient déjà protégés contre cette attaque triviale en ne fournissant pas de shells distants à des pseudo-utilisateurs comme daemon ou nobody.

Une utilisation plus sophistiquée de ces services est faite dans la procédure de cassage de mots de passe. Une fois qu'un password est découvert, le ver essaie aussitôt de s'introduire chez l'hôte où se trouve le compte cassé. Il lit alors le fichier .forward de l'utilisateur (qui contient les adresses vers lesquelles les mails sont relayés) ainsi que son fichier .rhosts (qui contient la liste des machines, et optionnellement les noms d'utilisateurs sur ces machines, qui ont un droit d'accès à la machine locale avec rsh sans avoir à passer par l'habituelle saisie de mot de passe), essayant chacune de ces machines jusqu'à ce qu'il réussisse à s'y introduire. Chaque cible est attaquée de deux manières différentes. Le ver contacte d'abord le serveur rexec de l'hôte distant et lui donne le nom du compte qu'il a trouvé dans le fichier .forward ou le fichier .rhosts suivit du password qu'il a cassé auparavant. Si cela échoue, le ver se connecte au serveur rexec local avec le nom d'utilisateur local et contacte le serveur rsh de sa cible. Le serveur rsh distant va permettre la connexion si le nom de la machine qui vient de se connecter apparaît dans le fichier /etc/hosts.equiv ou dans le fichier privé .rhosts de l'utilisateur.

Renforcer la sécurité de ces services réseaux est bien plus problématique que fixer un bug de finger ou sendmail, malheureusement. Les utilisateurs n'aiment pas être obligé de taper leurs passwords quand ils se connectent à une machine de confiance et, de même, ils n'aiment pas avoir à se souvenir des mots de passes qu'ils utilisent pour chaque machine auxquelles ils ont à se connecter. Certaines solutions peuvent s'avérer catastrophique, ainsi un utilisateur qui gère trop de mots de passe est suspectible de les noter quelque part.

4.5.2 Finger

gets was removed from our [C library] a couple days ago. [Bill Cheswick at AT&T Bell Labs Research, private communication, 11/9/88]

Le hack probablement le plus ingénieux dans le worm est le fait qu'il utilise le service TCP finger comme alternative pour pénétrer un système. Finger fait le compte-rendu des utilisateurs d'une machine, en signalant habituellement des détails comme le nom complet de l'utilisateur, où se trouve son bureau, le numéro de son extension de téléphone etc. La version de Berkeley (3) du serveur finger est un programme vraiment trivial : il lit la requête demandée par l'ordinateur connecté puis lance le programme local finger en lui passant cette même requête en argument, il renvoie ensuite le résultat de ce programme. Malheureusement le serveur finger lit la requête en utilisant gets(), une routine de la librairie C Standard, qui date de l'ère des temps et qui ne vérifie pas que les 512 octets de mémoire réservés sur la pile pour la requête ne sont pas débordés. Le ver fournit au serveur finger une requête de 536 octets de longueur, la plus grosse partie de la requête est du code machine VAX qui demande au système d'exécuter l'interpréteur de commande sh et les 24 octets supplémentaires représentent juste ce qu'il faut pour écraser la stack frame (correspond à l'environnement d'une fonction avec ses paramètres, ses variables locales, l'ancien environnement...) de la routine principale du serveur. Quand la routine principale du serveur quitte, le pointeur de retour vers la fonction appelante est supposé être restauré à partir de la pile, mais le ver y a écrit une adresse qu'il a choisi et qui pointe vers le code VAX situé dans le buffer. Le programme saute sur le code du ver et lance l'interpréteur de commande, dont le ver se sert pour placer sa routine de démarrage.

Il n'a pas été étonnant de voir, peu après l'annonce faite sur l'utilisation de cette caractéristique de gets() par le ver, bon nombre de personnes remplacer toutes les instances de gets() dans leur code système par une version qui vérifiait la taille du buffer. Certains sont même aller jusqu'à retirer gets() de la librairie, bien que la fonction soit apparemment mandaté par le standard ANSI C à venir (4). Jusque ici personne n'avait prétendu avoir été victime du bug dans le serveur finger avant l'incident du worm, mais en Mai 1998, des étudiants de l'UC Santa Cruz auraient apparemment passé des sécurités en utilisant un serveur finger différent, mais avec un bug similaire. L'administrateur système de l'UCSC remarqua que le serveur finger de Berkeley possédait un bug semblable et envoya un mail à Berkeley, mais la gravité du problème n'a pas été prise à sa juste valeur à ce moment là (Jim Haynes, private communication).

Note additionnelle : le ver est méticuleux pour certaines choses mais ne l'est pas pour d'autres. D'après ce que nous pouvons dire, il n'y avait pas de version de l'intrusion par finger pour Sun-3 bien que le serveur Sun-3 était tout aussi vulnérable que celui de VAX. Peut-être que l'auteur disposait des sources VAX mais pas des sources Sun ?

4.5.3 Sendmail

[T]he trap door resulted from two distinct 'features' that, although innocent by themselves, were deadly when combined (kind of like binary nerve gas). [Eric Allman, personal communication, 11/22/88]

L'attaque sendmail est probablement la plus délaissée dans l'arsenal du ver, mais cela n'a pas empêché qu'un site en Utah ait été sujet à environ 150 attaques sendmail le Jeudi Noir. Sendmail est le programme qui fournit le service mail SMTP sur les réseaux TCP pour les systèmes Berkeley UNIX. Il utilise un protocole simple en texte pur pour accepter des mails venant de sites distants. Une des caractéristiques de sendmail est qu'il permet de distribuer les messages à des processus plutôt qu'aux fichiers de boîtes de messageries ; cela peut être utilisé par (disons) le programme de vacances qui informe les expéditeurs que vous êtes en vacances et que vous êtes temporairement dans l'incapacité de répondre à leurs messages. Normalement cette fonction n'est disponible que pour les destinataires. Malheureusement, une petite ouverture a accidentellement été crée quand deux anciennes failles de sécurité ont été fixé - si sendmail est compilé avec l'option DEBUG, et que à l'exécution l'expéditeur demande à sendmail de passer en mode debug en lui envoyant la commande debug, il accorde à l'expéditeur le droit de passer une séquence de commandes à la place d'un nom d'utilisateur comme destinataire. Hélas, la majorité des versions de sendmail sont compilées avec DEBUG, entre autres celle que Sun expédie dans sa version binaire. Le ver imite une connexion SMTP à distance, remplissant le champ nom de l'expéditeur par /dev/null et en prenant soins de placer une chaîne malicieuse comme destinataire. Cette chaîne lance une commande qui efface l'en-tête du message et passe le corps à un interpréteur de commande. Le corps contient une copie de la source de la routine de démarrage du ver ainsi que les commandes nécessaires à sa compilation et à son exécution. Une fois que le ver en a fini avec le protocole et a fermé la connexion avec sendmail, le code de démarrage sera fabriqué sur l'hôte distant et le ver local attend sa connexion afin qu'il puisse compléter le processus de fabrication du nouveau ver.

Bien sûr ce n'est pas la première fois qu'une inadvertante brèche ou "trappe" comme celle-là est trouvée dans sendmail, et ce ne sera probablement pas la dernière. Dans sa conférence "Turing Award", Ken Thompson a dit : "Vous ne pouvez faire confiance à du code que vous n'avez pas totalement crée vous-même (Spécialement le code d'entreprises qui emploient des personnes comme moi.)" En fait, comme l'a dit Eric Allman, "[V]ous ne pouvez même pas faire confiance à du code que vous avez entièrement créé vous même." Le problème basique de la confiance envers les programmes systèmes n'est pas près de se résoudre.

4.6 Infection

Le ver utilise deux routines favorites quand il décide qu'il veut infecter une machine. Une routine que nous avons baptisé infect() est utilisée à partir de fonctions de recherche d'hôtes comme hg(). infect() commence par vérifier qu'il n'est pas en train d'infecter la machine locale, une machine déjà infectée ou une machine précédemment attaquée mais qu'il n'avait pas été incapable de pénétrer ; les états "infecté" et "immunisé" sont signalés par des drapeaux sur la structure d'une machine quand l'attaque a respectivement réussie ou échouée. Le ver s'assure ensuite qu'il peut obtenir une adresse pour sa cible, la marquant comme étant immunisée si il n'y parvient pas. Vient alors une série d'attaques : d'abord par rsh à partir du compte qui fait fonctionner le ver, puis par finger, et enfin par sendmail. Si infect() échoue, il marque la machine comme étant immunisée.

L'autre routine d'infection se nomme hul() et elle est appelée à partir du code de cassage de mots de passe, une fois qu'un mot de passe a été trouvé. hul(), comme infect(), s'assure qu'elle ne réinfecte pas une machine, puis vérifie une adresse. Si un éventuel nom d'utilisateur distant est trouvé dans les fichiers .forward et .rhosts, le ver vérifie qu'il est valide - il ne doit pas contenir de caractères de ponctuation ou de caractères de contrôle. Si un nom d'utilisateur distant est indisponible le ver se sert du nom d'utilisateur local. Une fois que le ver possède un nom d'utilisateur et un password, il contacte le serveur rexec de sa cible et essaie de s'authentifier. S'il y parvient, il procède à la phase de démarrage ; dans le cas contraire, il tente une approche légèrement différente - il se connecte au serveur rexec local avec le nom d'utilisateur local et son mot de passe, puis il utilise cet interpréteur de commande pour récupérer un shell chez sa victime avec rsh. Cela fonctionnera si la cible considère l'hôte local comme étant de confiance (dans son fichier /etc/hosts.equiv), ou qu'elle considère le compte local comme étant sûr (marqué dans son fichier .rhosts). La fonction hul() ne prend pas en compte le drapeau d'immunité de infect() et ne fixe pas ce drapeau elle-même, cela s'explique par le fait que hul() peut réussir là ou infect() échoue (l'une fait une recherche sur les comptes d'utilisateurs, l'autre sur les machines).

infect() et hul() font toutes les deux appel à une routine, que nous avons appelé sendworm(), pour faire leur sale boulot (5). sendworm() recherche le fichier source de démarrage l1.c dans sa liste d'objets, puis elle utilise la routine makemagic() afin de récupérer un canal de communication (un socket), un numéro de port réseau aléatoire, ainsi qu'un numéro magique pour l'authentification. (makemagic() possède un effet secondaire intéressant - il recherche l'adresse d'une machine en essayant de se connecter à son port TCP telnet, cela produit un message de log particulier de la part du serveur telnet.) Si makemagic() s'est réalisé correctement, le ver commence à envoyer des commandes à l'interpréteur de commandes distant qui a été lancé lors de l'attaque effectuée juste avant. Il change de répertoire pour un endroit non-protégé (/usr/tmp), et y met la source d'amorçage, faisant appel à l'éditeur de flux UNIX sed pour copier le flux entrant. La source d'amorçage est compilée puis lancée sur le système distant, ensuite le ver lance une routine nommée waithit() qui attend que la routine de démarrage distante le contacte sur le port choisi.

L'amorçage est vraiment simple. On lui passe l'adresse de la machine d'origine, un numéro de port TCP ainsi qu'un nombre magique en arguments. Au démarrage, le code d'amorçage s'efface lui même de façon à ne pas être détecté dans le système de fichier, puis il appelle fork() pour créer un nouveau processus. L'ancien processus subsiste, permettant au ver d'origine de continuer son travail. Le code d'amorçage récupère ses arguments puis les mets à zéro pour les cacher du programme de statut système ; il est alors prêt à se connecter sur le réseau à son ver père. Quand la connexion est établie, la routine y fait passer le nombre magique, que le père va comparer avec sa propre copie. Si le père valide ce nombre, il envoie ensuite une série de noms de fichiers et de fichiers que le code d'amorçage écrit sur le disque. Si un problème survient, le code efface tous ces fichiers et quitte. Finalement la transaction se termine, et la routine d'amorçage fait appel à un shell pour terminer le travail.

Pendant ce temps, la fonction waithit() du père attend jusqu'à deux minutes que la routine d'amorçage la contacte ; si l'amorçage ne parvient pas à rappeler, ou si l'authentification échoue, le worm décide d'abandonner et signale un échec. Si une connexion est établie, le worm expédie tous ses fichiers suivis par un indicateur de fin de fichier (eof). Il se met en pause quatre secondes, histoire qu'un shell distant soit lancé, puis il donne les commandes pour créer un nouveau ver. Pour chaque fichier présent dans sa liste, le ver tente de créer un objet exécutable, typiquement chaque fichier contient du code pour un type d'ordinateur particulier, et cette création échouera jusqu'à ce que le ver trouve le bon type d'ordinateur. Si le ver père parvient finalement à générer un ver fils exécutable, il le fait se détacher avec l'option -p pour tuer le shell, puis ferme la connexion. La machine cible est marquée comme étant "infectée". Si aucun des objets n'a produit un ver fils utilisable, le père efface les déchets et waithit() retourne un code d'erreur.

Quand un système se fait submerger par les vers, le répertoire /usr/tmp peut se remplir avec les restes de fichiers comme conséquence à un bug dans waithit(). Si la compilation d'un ver prend plus de 30 secondes, le code de re-synchronisation signalera une erreur mais waithit() ne parviendra pas à effacer les fichiers qui ont été créés. Sur l'une de nos machines, 13 Mo de données représentant un ensemble de 86 fichiers se sont accumulés en 5.5 heures.

4.7 Cassage de mot de passe

Un algorithme de cassage de password paraît être une fonctionnalité lente et encombrante à placer dans un worm, mais celui ci rend cette tache persistante et efficace. Le ver est aidé par des statistiques regrettables conçernant les choix typiques de mots de passe. Dans cette partie nous étudierons comment le ver choisi des passwords à tester et comment la routine d'encryption de password UNIX a été modifiée.

4.7.1 Deviner les mots de passe

For example, if the login name is "abc", then "abc", "cba", and "abcabc" are excellent candidates for passwords. [Grampp and Morris, "UNIX Operating System Security"]

La divination des mots de passe par le ver se fait en quatre étapes. La première étape rassemble des informations sur les mots de passe, alors que les étapes restantes représentent de moins en moins des sources de passwords potentielles. La routine centrale de cracking est appelée cracksome(), et elle contient un switch (aiguillage) sur chacune de ces quatre étapes.

La routine qui implémente la première étape a été baptisée crack_0(). Le rôle de cette routine est de collecter des informations sur les machines et les comptes. Elle est lancée une seule fois ; l'information récupérée persiste toute la durée de vie du worm. Son implémentation est simple et sans détours : elle lit les fichiers /etc/hosts.equiv et /.rhosts pour les machines à attaquer, puis lit le fichier password à la recherche de comptes d'utilisateurs. Pour chaque compte, le ver garde en mémoire le nom, le mot de passe crypté, le répertoire personnel (le home) ainsi que le champ d'information sur l'utilisateur. Comme première vérification rapide, il regarde les fichiers .forward dans le répertoire home de chaque utilisateur et mémorise tous les noms de machines qu'il y trouve, les marquant de la même façon que les précédentes.

Nous n'avons pas fait preuve d'originalité en nommant la fonction de l'état suivant crack_1(). crack_1() cherche tous les mots de passe qui peuvent être cassés de façon triviale. Il s'agit des mots de passe qui peuvent être devinés simplement à partir des informations présentes dans le fichier password. Grampp et Morris ont effectué une étude sur plus de 100 fichiers password qui montre que entre 8 et 30 pourcents des mots de passe sont littéralement les même que le nom de l'utilisateur ou avec de petites variations. Les essais du ver sont un peu plus durs que cela : il teste le mot de passe vide, le nom de compte, le nom de compte répété (concaténé à lui-même), le prénom (extrait du champ d'information concernant l'utilisateur, avec la première lettre mise en minuscule), le nom de famille ainsi que le nom de compte retourné. Il traite jusqu'à 50 comptes par appel à cracksome(), mémorisant sa place dans la liste de comptes et passant à l'étape suivante lorsqu'il est à court de comptes à tester.

L'étape suivante est gérée par crack_2(). Dans cette étape le ver compare une liste de mots de passe favoris, un password par appel, avec tous les mots de passe cryptés qui étaient présents dans le fichier password. Cette liste contient 432 mots, la plupart sont des mots tirés du dictionnaire anglais ou des noms propres ; il est fort probable que cette liste soit générée à partir de fichiers de mots de passe dérobés puis cassés par l'auteur, sur son temps libre et sur sa machine personnelle. La variable globale nextw est utilisée pour compter le nombre de mots de passe testés, et c'est ce compteur (en plus de la perte au jeux de contrôle de la population) qui décide si le ver quitte à la fin de la boucle principale - nextw doit être supérieure à 10 afin que le ver puisse se terminer. Puisque le ver passe normalement 2,5 minutes à chercher des clients durant l'exécution de la boucle principale et fait appel à cracksome() deux fois lors de cette période, il semblerait que le ver doit faire au minimum 7 passages dans la boucle principale, ce qui lui prend plus de 15 minutes au total (6). Il faudra 9 heures au ver pour scanner sa propre liste de mots de passe et procéder à l'étape suivante.

La dernière étape est conduite par crack_3(). Cette fonction ouvre le dictionnaire présent sur les systèmes UNIX, se trouvant à /usr/dict/words, et lit chaque mot l'un après l'autre. Si un mot est en lettres capitales, le ver teste aussi une version en minuscules. Cette recherche peut durer une éternité : cela prendrait environ quatre semaines au ver de traiter un dictionnaire comme le notre.

Quand le ver sélectionne un mot de passe potentiel, il le passe à la routine que nous avons nommé try_password(). Cette fonction fait appel à la version du ver de la fonction d'encryption de password UNIX crypt() et compare le résultat avec le mot de passe crypté de la cible actuelle (le compte traité à ce moment). Si les résultats concordent, ou si le ver a découvert que le password était une chaîne vide (pas de password), le ver sauvegarde le mot de passe en clair et attaque les machines possédant ce compte. Une routine que nous avons baptisé try_forward_and_rhosts() lit les fichiers .forward et .rhosts des utilisateurs, appelant la fonction hul() décrite précédemment pour chaque compte trouvé.

4.7.2 Cryptage plus rapide des mots de passe

The use of encrypted passwords appears reasonably secure in the absence of serious attention of experts in the field. [Morris and Thompson, "Password Security : A Case History"]

Il y a malheureusement des experts en cryptographie qui ont apporté une attention toute particulière à des implémentations rapides de l'algorithme d'encryption d'UNIX. Le système d'authentification par password d'UNIX fonctionne sans garder un seul mot de passe dans sa version lisible (en clair) sur le système, et fonctionne même sans empêcher les utilisateurs de lire les mots de passe cryptés présents sur le système. Quand un utilisateur tape son mot de passe en clair, le système l'encrypte en utilisant la routine standard de la librairie, crypt(), puis compare le résultat avec la sauvegarde du password crypté. L'algorithme d'encryption a été fait de telle façon qu'il soit impossible à inverser, empêchant alors de retrouver un password en analysant juste la version chiffrée, et il est aussi long à faire marcher, si bien que les tests de mots de passe prendront beaucoup de temps. L'algorithme d'encryption des passwords UNIX est basé sur le Standard d'Encryption de Données Fédéral (DES = Data Encrytion Standard). Pour le moment personne ne sait comment inverser l'algorithme en un laps de temps raisonnable, et alors que des puces d'encodage rapide en DES sont disponibles, la version UNIX de l'algorithme est légèrement modifiée de sorte qu'il est impossible d'utiliser une puce de DES standard pour l'implémenter.

Deux problèmes relatifs à l'implémentation UNIX du DES se sont atténués. La vitesse des ordinateurs augmente continuellement - les machines actuelles sont généralement plusieurs fois plus rapides que les machines qui étaient à disposition quand le système de password actuel fut inventé. A coté de ça, des méthodes ont été découvertes au niveau logiciel permettant d'accélérer le DES. Les mots de passe UNIX sont maintenant bien plus vulnérables à une recherche continuelle (attaque exhaustive ou dite de force brute), particulièrement si les mots de passes cryptés sont déjà connus. La version de la routine UNIX crypt() du ver s'exécute plus de neuf fois plus rapidement que la version standard lorsque nous avons effectué les tests sur notre VAX 8600. Alors qu'il faut 54 secondes à la version standard de crypt() pour encrypter 271 passwords sur notre 8600 (le nombre de mots de passe actuellement présents dans notre fichier password), la version du worm met elle, moins de 6 secondes.

L'algorithme crypt() du ver semble se baser sur un compromis entre le temps et la mémoire : le temps nécessaire à l'encryption d'un mot de passe contre l'important espace des tables supplémentaires nécessaires pour extraire des performances de l'algorithme. Curieusement, une amélioration de l'exécution est en fait d'économiser un peu de mémoire. L'algorithme traditionnel d'UNIX stocke chaque bit du password dans un octet, alors que l'algorithme du ver entasse les bits dans deux mots de 32 bits. Cela permet à l'algorithme du ver d'utiliser les champs de bits et les opérations de décalage (shift) sur les données du mot de passe, ce qui est bien plus rapide. Parmis les autres accélérations, on trouve les boucles déroulantes, les tables de combinaisons, des masques et des décalages précalculés, ainsi que les permutations initiales et finales toutes les 25 exécutions du DES modifié que l'algorithme d'encryption des mots de passe utilise. L'amélioration la plus importante est le résultat des permutations de combinaisons : le ver se sert de tableaux étendus qui sont indexés par groupe de bits plutôt que par un seul bit comme dans l'algorithme standard. La version rapide de crypt() réalisée par Matt Bishop fait tout cela et précalcule même plus de fonctions, la rendant deux fois plus rapide que celle du ver mais nécessitant environ 200 Ko de données initialisées contre les 6 Ko utilisées par le ver ou les 2 Ko utilisées par le crypt() normal.

Comment les administrateurs systèmes peuvent-ils faire face à ces implémentations rapides de crypt() ? Une des suggestions qui a été présentée pour tromper les méchants est l'idée de fichier password 'shadow'. Dans ce système, les mots de passe cryptés sont cachés plutôt que public, obligeant le cracker à casser un compté privilégié ou utiliser l'unité centrale de la machine et son algorithme d'encryption (lent) pour attaquer, avec le danger supplémentaire qu'un test sur un mot de passe soit logé et l'attaque découverte. L'inconvénient des fichiers shadow est le fait que si un méchant contourne, d'une façon ou d'une autre, les protections de ce fichier qui contient les mots de passe actuels, tous les mots de passe doivent être considérés comme cassés et devront être remplacés. Une autre proposition qui a été faite est de remplacer l'implémentation UNIX du DES par l'implémentation la plus rapide disponible, mais l'appeler 1000 fois ou plus à la place des 25 fois utilisées dans le code UNIX de crypt(). A moins que le nombre de tours de boucle se stabilise d'une façon où d'une autre à la vitesse maximum offerte par le CPU, cette méthode ne fait que repousser l'heure fatidique jusqu'à ce que le cracker trouve une machine plus performante. Il est intéressant de noter que Morris et Thompson ont mesuré le temps de mise en œuvre de l'ancien algorithme d'encryption m-209 (non-DES) des mots de passe utilisé dans les versions précédentes de UNIX sur le PDP-11/70 et ont observé qu'une bonne implémentation prenait seulement 1,25 millisecondes par encryption, ce qu'ils ont jugé insuffisant ; actuellement un VAX 800 utilisant l'algorithme DES de Matt Bishop requiert 11,5 millisecondes par encryption, et des machines 10 fois plus rapides que le VAX 8600 et à un prix plus attractif devrait être bientôt disponibles (si elles ne le sont pas déjà !).

5. Opinions

The act of breaking into a computer system has to have the same social stigma as breaking into a neighbor's house. It should not matter that neighbor's door is unlocked. [Ken Thompson, 1983 Turing Award Lecture]

[Creators of viruses are] stealing a car for the purpose of the joyriding. [R.H. Morris, in 1983 Capitol Hill testimony, cited in the New York Times 11/11/88]

Je ne compte pas donner de points de vue définitifs sur la moralité de l'auteur du ver, l'éthique relative à la publication d'informations de sécurité (full-disclosure ou non) ou les besoins de la communauté informatique en matière de sécurité, puisque des personnes plus (ou moins) qualifiées que moi sont constamment en train de troller sur ces sujets dans les différents newsgroups et les mailing-lists. Par respect envers le mythique administrateur système lambda qui a dû être perdu au milieu de toute ces informations et désinformations, je vais tenter de répondre de répondre à quelques une des questions les plus importantes de façon circonscrite mais utile.

Le ver a t-il causé des dégâts ? Le ver n'a pas détruit de fichiers, na pas intercepté de courriels privés, n'a pas corrompu de bases de données ni installé de chevaux de Troie. En revanche il faisait la concurrence au niveau du temps d'utilisation du CPU avec les processus normaux de l'utilisateur, et en fin de compte les écrasait. Il épuisait les ressources système limitées tels que la table des fichiers ouverts ou la table des processus, faisant ainsi échouer les processus par manque de ressources. Il amenait certaines machines à crasher en les faisant fonctionner à la limite de leurs capacités, provoquant des bugs qui ne seraient pas apparus dans des conditions normales. Il obligea des administrateurs à redémarrer une ou plusieurs fois leurs machines pour effacer les vers du système, mettre fin aux sessions des utilisateurs et aux jobs qui tournaient depuis longtemps. Il força des administrateurs à éteindre leurs passerelles réseau, incluant les passerelles entre d'importants réseaux de recherche nationaux, qui faisaient leur possible pour isoler le ver ; cela générait des délais allant jusqu'à plusieurs jours dans le cas d'échange de courrier électronique, obligeant certains projets à repousser leurs dates limites et faisant perdre à d'autres du précieux temps de travail. Il força le personnel technique de tout le pays à annuler leurs importantes recherches et travailler 24 heures par jour à essayer de contrôler et éradiquer les vers. Dans au moins une institution, la direction eu tellement peur qu'elle effaça tous les disques de leur installation qui étaient en ligne au moment de l'infection, et restreigna le rechargement des fichiers aux données pour lesquelles elle était sûre qu'elles n'avaient pas été modifiées par un élément étranger. Une des conséquences fut la dégradation de la bande passante à travers les passerelles qui continuaient de faire fonctionner le ver après infection et qui utilisaient la majorité de leurs capacités à le transférer d'un réseau à l'autre. Le ver pénétra des comptes d'utilisateurs, les faisant ainsi passer comme dangereux pour le système alors qu'en fait ils n'étaient pas responsables. Il est vrai que le ver aurait pu être bien plus dangereux qu'il ne l'a été : dans les dernières semaines, différentes failles de sécurité ont été découvertes qui auraient pu être utilisées par le ver pour détruire des systèmes. Peut-être devrions-nous nous considérer heureux d'avoir échappé à des événements qui auraient pu se révéler bien plus catastrophiques, et peut-être nous devrions nous montrer reconnaissant d'avoir appris autant sur les faiblesses présentes dans nos systèmes de défense, toutefois je pense que nous devrions échanger notre reconnaissance avec d'autres personnes que l'auteur de ce ver.

Le ver était-il malicieux ? Certaines personnes ont suggéré que le worm était une innocente expérience dont le contrôle avait échappé à son créateur, et qu'il n'était pas sensé se propager si rapidement et aussi fortement. Nous pouvons trouver des preuves dans le code du ver qui permettent de soutenir et aussi de détruire cette hypothèse. Il y a un certain nombre de bogues dans le ver qui semblent être le résultat d'une programmation faite à la va-vite ou négligée. Par exemple, dans la routine init(), il y a un appel à la fonction block zéro bzero() qui utilise le block lui-même alors qu'il devrait utiliser l'adresse de ce block en tant qu'argument. Il est tout aussi possible qu'une erreur soit à l'origine du manque d'efficacité des mesures de contrôle de la population prises par le ver. Il peut paraître tout à fait plausible qu'une version de développement du ver se soit "perdue" accidentellement, et que éventuellement son auteur avait l'intention de tester la version finale dans des conditions bien précises, dans un environnement duquel il n'aurait pas pu s'échapper. D'un autre côté il y a des preuves considérables que le ver a été crée de telle façon à ce qu'il se reproduise vite et se propage sur de grandes distances. On pourrait avancer que les routines de contrôle de la population sont faibles parce que l'auteur en a décidé ainsi : il y a un compromis entre propager le ver le plus vite possible et le faire se reproduire assez pour être détecté et stoppé. Un ver existera pour un certain laps de temps et effectuera une certaines quantité de tâches même s'il pert au lancé de dés imaginaire (duel décrit plus haut qui détermine quel ver continue à fonctionner) ; qui plus est, 1 ver sur 7 devient immortel et ne peux pas être supprimé au lancé des dés. Il est plus qu'évident que le ver a été conçu pour entraver les efforts destinés à le stopper même une fois qu'il a été identifié puis capturé. Il est certainement arrivé à ses fins sur ce point là, puisqu'il a fallu au moins un jour avant que le dernier mode d'infection (relatif au serveur finger) soit découvert, analysé et annoncé publiquement ; le ver s'est montré très performant pour ce qui est de se reproduire à ce moment, même sur les systèmes sur lesquels le problème debug de sendmail était corrigé et rexec éteint. Pour finir, il est évident que l'auteur du worm a délibérément introduit le worm sur un site qui ne lui appartenait pas, et qui était à moitié ouvert pour accueillir d'éventuels utilisateurs extérieurs, abusant donc de l'hospitalité de ce serveur. Il semble même avoir été plus loin dans l'abus de la confiance qui lui était donnée en effaçant un fichier de log qui aurait pu révéler des informations qui auraient permis de faire le lien entre son site (serveur) personnel et l'infection. Je pense que l'innocence revient à la communauté des chercheurs plutôt qu'au créateur du ver.

Est-ce que une publication sur les détails du worm ne va pas nuire à la sécurité ? Dans un sens, le ver lui-même a résolu le problème : il s'est publié lui-même en s'envoyant à des centaines ou des milliers de machines à travers le monde. Bien sûr une personne mal intentionnée qui désire utiliser les astuces du ver devrait passer par les même efforts que nous avons du traverser afin de comprendre le programme, mais là encore il ne nous a fallut qu'une semaine pour décompiler le programme, bref même s'il faut beaucoup de courage pour hacker le ver, il est clair que ce n'est pas difficile pour un programmeur décent. L'une des astuces les plus efficaces du ver annonçait son arrivée - le gros du hack sendmail est visible dans le fichier de log, et quelques minutes d'étude des sources permettent de découvrir le reste de l'astuce. L'algorithme rapide utilisé pour les mots de passe par le ver pourrait s'avérer utile à des méchants, mais il existe au moins deux autres implémentations plus rapides disponibles depuis un an ou plus, bref ce n'est pas vraiment secret ou original. Enfin, les détails du ver ont suffisamment bien été ébauchés sur différents newsgroups ou listes de diffusions que les principaux exploits du ver sont de la connaissance générale. Je crois qu'il est bien plus important de comprendre ce qu'il s'est passé, afin que nous puissions réduire les chances que cela se reproduise à nouveau, plutôt que de passer notre temps dans un effort futile à couvrir l'affaire aux yeux de tous, sauf aux yeux des méchants. Des correctifs pour les sources et les binaires sont largement diffusés, et toute personne utilisant un système vulnérable doit immédiatement récupérer ces correctifs, si cela n'est pas déjà fait.

6. Conclusion

It has raised the public awareness to a considerable degree. [R H Morris, quoted in the New York Times 11/5/88]

Cette citation est l'un des euphémismes de l'année. L'histoire du ver a fait la couverture du New York Times et d'autres quotidiens pendant plusieurs jours. Il a été le sujet de prédilection des télévisions et des radios. Même la bande dessinée Bloom County s'est moquée de cet événement.

Notre communauté n'avait jamais été autant sous les projecteurs auparavant, et aux vues des responsabilités, cela nous a effrayé. Je ne vous donnerait pas de banalités fantaisistes sur comment cette expérience va nous changer, mais je pense que plus ces articles seront ignorés, moins nous serons en sécurité, et j'ai le sentiment qu'une meilleure compréhension de cette crise qui est derrière nous, nous aidera à mieux gérer la prochaine. Espérons que nous aurons autant de chance là prochaine fois que nous en avons eu cette fois çi.

Remerciements

Personne ne doit être tenu responsable pour les inexactitudes ici excepté moi, mais il y a vraiment beaucoup de personnes à remercier pour avoir aidé à décompiler le ver et pour avoir aidé à documenter cette épidémie.
Dave Pare et Chris Torek étaient au centre de l'action durant les longues nuits passées à Berkeley, et ils avaient le soutien et les conseils de Keith Bostic, Phil Lapsley, Peter Yee, Jay Lepreau et un millier d'autres. Glenn Adams et Dave Siegel ont fourni de bonnes informations sur l'attaque du laboratoire d'I.A. du MIT, alors que Steve Miller me donna les détails sur Maryland, Jeff Forys sur Utah, et Phil Lapsley, Peter Yee et Keith Bostic sur Berkeley. Bill Cheswick m'envoya quelques anecdotes de AT&T Bell Labs. Jim Hayes me donna le résumé détaillé des problèmes de sécurité qui se sont présentés à l'UC de Santa Cruz. Eric Allman, Keith Bostic, Bill Cheswick, Mike Hibler, Jay Lepreau, Chris Torek et Mike Zeleznik ont fourni beaucoup de commentaires et de critiques intéressantes. Merci à vous tous, ainsi qu'à tous ceux que j'ai oublié de mentionner.

Le document de Matt Bishop "A Fast Version of the DES and a Password Encryption Algorithm", (c) 1987 by Matt Bisbop and the Universities Space Research Association, a été utile pour (légèrement) comprendre le mystère du DES pour moi. Toute personne désirant comprendre le hack du DES par le worm devrait d'abord jeter un coup d'oeil à ce document.

Les documents suivants sont référencés pour les différentes citations et pour d'autres matériaux :

Data Encryption Standard, FIPS PUB 46, National Bureau of Standards, Washington D.C., January 15, 1977.

F. T. Grampp and R. H. Morris, "UNIX Operating System Security," in the AT&T Bell Laboratories Technical Journal, October 1984, Vol. 63, No. 8, Part 2, p. 1649.

Brian W. Kernighan and Dennis Ritchie, The C Programming Language, Second Edition, Prentice Hall: Englewood Cliffs, NJ, (C) 1988.

John Markoff, "Author of computer 'virus' is son of U.S. Electronic Security Expert," p. 1 of the New York Times, November 5, 1988.

John Markoff, "A family's passion for computers, gone sour," p. 1 of the New York Times, November 11, 1988.

Robert Morris and Ken Thompson, "Password Security: A Case History," dated April 3, 1978, in the UNIX Programmer's Manual, in the Supplementary Documents or the System Manager's Manual, depending on where and when you got your manuals.

Robert T. Morris, "A Weakness in the 4.2BSD Unix TCP/IP Software," AT&T Bell Laboratories Computing Science Technical Report #117, February 25, 1985. This paper actually describes a way of spoofing TCP/IP so that an untrusted host can make use of the rsh server on any 4.2 BSD UNIX system, rather than an attack based on breaking into accounts on trusted hosts, which is what the worm uses.

Brian Reid, "Massive UNIX breakins at Stanford," RISKS-FORUM Digest, Vol. 3, Issue 56, September 16, 1986.

Dennis Ritchie "On the Security of UNIX," dated June 10, 1977, in the same manual you found the Morris and Thompson paper in.

Ken Thompson, "Reflections on Trusting Trust," 1983 ACM Turing Award Lecture, in the Communications of the ACM, Vol. 27, No. 8, p. 761, August 1984.


Détails

(1)
Internet est un réseau logique constitué d'un ensemble des réseaux physiques, tous fonctionnant grâce à la classe IP des protocoles réseaux.

(2)
VAX et Sun-3 sont des modèles d'ordinateurs construits respectivement par Digital Equipment Corp. et Sun Microsystems Inc. UNIX est un produit Registered Bell of AT&T Trademark Laboratories.

(3)
En réalité, comme la majorité du code de la distribution de Berkeley, le serveur finger venait d'un peu partout ; dans ce cas, il semble que le MIT en soit la source.

(4)
Référez-vous par exemple à l'Appendice B, section 1.4 de la seconde édition de The C Programming Language par Kernighan et Ritchie.

(5)
Une petite exception : l'attaque sendmail n'utile pas sendworm() puisqu'elle doit gérer le protocole SMTP en plus de l'interpréteur de commande, mais le principe reste le même.

(6)
Pour ceux qui aiment les détails : le premier appel à cracksome() sert à lire les fichiers systèmes. Le ver fait au moins un appel à cracksome() une seconde fois pour s'attaquer aux mots de passe faibles. Cela constitue au moins un passage dans la boucle principale. La troisième fois, cracksome() teste un des passwords de sa liste favorite à chaque appel ; le ver la quitte s'il échoue au jeu de dès et si plus de dix mots ont été testé, cela constitue donc au moins six boucles, deux mots sur chaque boucle pour cinq boucles pour atteindre dix mots, puis une autre boucle pour passer ce nombre. Au total on atteint un minimum de 7 boucles. Si chacune des 7 boucles prend un laps de temps maximum à attendre des client cela demandera un minimum de 17,5 minutes, mais la vérification des 2 minutes peut quitter plus tôt si un client se connecte et que le serveur pert le challenge, en conséquence 15,5 minutes d'attente en plus du temps d'exécution constituent le cycle de vie minimum. Sur cette période un ver attaquera au moins 8 machines lors de la routine d'infection d'hôtes, et testera environ 18 mots de passe pour chaque compte, attaquant plus d'hôtes si des comptes sont cassés.