At a high level, the purpose of the binary is to act as a network service providing the attacker with access to the machine without needing to login. It does this by opening a socket to listen for IP packets; those that match its criteria are evaluated as commands from the attacker. Some commands involve sending a reply back to the attacker, while other commands appear to be attack functions.
The main loop of the program has the following logic:
for (; 1; usleep(10000) ) { receive datagram packet from socket; verify packet authenticity; decrypt packet data; determine command, and execute request; } |
As a network service, the-binary offers a choice of 12 commands that the user can issue via network packets. In order to be considered a valid command, a packet must meet the following tests:
it is of protocol 0xb.
the first two bytes of the packet data form the word 0x0002 (Intel byte ordering).
the length of the packet is greater than 200.
Sends a reply packet containing information on whether an attack (commands 4, 5, 10, 11, and 12) is currently running, and which one if so. Replies are sent to IPs as directed by command 2.
The relevant information in the packet reply is:
Sets source and destination IP addresses that the-binary uses when creating packets. There are multiple reply modes that can be set, some of which randomize a table of destination IP addresses, but all modes include a way to explicitly set at least one address.
The relevant information in the command packet is:
Byte(s) | Description |
---|---|
decoded[1] | The type of packet. The value must be "1", for replies to command-1 packets. |
decoded[2] | the "reply mode" to use when a command requires sending a reply. Valid reply modes are:
|
decoded[3-6] | In reply mode 0, these four bytes are stored into position 0 of the reply list. In reply mode 1, the four bytes are stored in a random position of the reply list. |
decoded[3-6,7-10,...,39-42] | In reply mode 2, this array of 10 four-byte values is used to populate the reply list on the server. |
Forks a child process which executes a shell command included in the packet. The output is sent is parceled out in small packets that are sent to IPs on the server reply list.
The relevant information in the command packet is:
The relevant information in the reply packet is:
Bytes | Description |
---|---|
decoded[1] | The type of packet. A value of 3 indicates that this is the initial reply to a command-3 packet. A value of 4 indicates that it is a continuation of the reply. |
decoded[2] until NUL byte | The partial output from running the shell command. A type 3 reply is followed by one or more type 4 replies. The strings from the decoded[2] position of each packet should be extracted and concatenated together to get the full output of the shell command. |
Forks and sends out packets to a host, in an infinite loop. We believe these to be implementations of attacks, but time did not permit us to investigate the nature of these attacks.
Forks and starts listening on TCP port 61786 for incoming connections. Connections are accepted and connected to a shell.
Forks and executes a shell command that was included with the packet. The output is discarded.
The relevant information in the command packet is:
Kills any active child process from commands 4, 5, 6, 7, 9, 10, 11, or 12.
Fortunately, both an encoding and decoding function appear within the-binary. After analyzing the encoding function, we determined that it was a simple ROT-23 stream cipher, with feedback. That is to say, the encoding algorithm is as simple as
encoded[0] = decoded[0] + 23; for (pos = 1; pos < len; pos++) { encoded[pos] = encoded[pos-1] + decoded[pos] + 23; } |
decoded[0] = encoded[0] - 23; for (int pos = 1; pos < len; pos++) { decoded[pos] = encoded[pos] - encoded[pos-1] - 23; } |
As a proof of concept, the perl script sniffer was developed to read libpcap capture files. We exercised it on the released snort capture, and observed reasonable output.
The glaring abnormality of the network traffic used to communicate with the-binary is its marking as protocol 11 encapsulated within an IP packet. Standard protocols that you would expect to see on a small network are 1 (ICMP), 6 (TCP), 17 (UDP), and possibly 47 (GRE). A generalized method for detecting a network service such as the-binary would then be to configure a NIDS (such a snort) to trigger alerts on any but the common protocols. Naturally, that must be followed up by customization particular to your local network, lest you go mad with false alerts.
In a pinch, you could also do the following:
# tcpdump -l -n ip and not icmp and not tcp and not udp and not ip proto 47 |
There are a few techniques that obscure analysis of the binary:
The symbols were stripped from the executable. This removes the function and variable names from the binary, which makes even decompiled output into C extremely unreadable.
At the start of the executable, there are two fork() calls in quick succession. This technique is used by programs to make debugging more difficult, since debuggers must try to detect the child and attach to it. The Linux ptrace() facility for debugging allows the child process to start executing before the debugger can intterupt it. This creates a race condition, where the child can quickly call fork() before the debugger has attached, allowing the second child to proceed unmolested.
This technique as implemented has questionable utility, because any analyst would become suspicious about the first child exiting so quickly after it was created.
There are several calls to time(), rand(), and random() sprinkled throughout the program. I believe that these are intended to slow down anyone attempt to use a debugger on the program.
In most cases, copies from one buffer to another were implemented by hand rather than calling memcpy(). Due to the rather interesting way that compilers sometimes reschedule instructions, it can be extremely difficult to recognize a memory copy and its source and target.
The function to decrypt the data within the packets (located at 0x0804A1E8) was particularly obtuse. My initial impression was that this was from programming inexperience, but several other parts of the program cause me to question this. There are a few cases where a memory copy is blended with a sprintf() so that the end result is a proper functioning, but the resulting code is bizarre to contemplate.
There is also a bit of strange code appearing in the routine used to send out packets, located at 0x08048F94. There is a call into the DNS resolver routines, using an argument that couldn't possibly generate valid data, and the results are stored in a static variable that doesn't appear to ever be used elsewhere. This could again be the mark of inexperience, as the programmer tries to find the correct use of the APIs -- leaving the old broken code enabled but impotent. Another possibility is that multiple people have worked on the code, one with limited experience and one with some sophistication.
The network encoding also served to inhibit reverse engineering, because the encoding process had to be decompiled before a sufficiently correct client program could be built to help probe the operation of the-binary under a debugger.
This exploit has several "covert shell" related commands, but also includes commands for attacking hosts. The SANS article Covert Shells details some of the evolution of programs providing covert shells, and names Loki as one of the earliest (released in 1996) demonstrations of covert communications. However, none of the programs listed incorporate an attack feature. More modern distributed denial of service (DDoS) programs (such as TFN and stacheldraht) use covert channels and can attack hosts, but don't offer access to a shell. We were not able, in the time available, to locate any programs that mirrored both aspects of this program (especially in the 1997 time frame).
There is a certain schizophrenia to the code which makes it difficult for me to guess the skill level of the programmer. Very likely, they started with some base program, such as Loki, and made modifications to it. The use of double calls to fork(), and other reverse engineering inhibitors leads me to believe that the programmer is actually fairly sophisticated. And then there are the weird combinations of memory copies which duplicate the work of sprintf() calls... which could be the signs of a floundering neophyte, or a clever masochist. The fact that the decryption routine operates backwards through the buffer (and thus appears to be exactly what a beginner would write when trying to invert the encryption function) and the simplicity of actual encryption algorithm (when similar programs of the time were including Blowfish encryption) indicate the work of an "advanced newbie". My best guess is that multiple people worked on the code, of differing abilities.
Our best guess, based on the compiler and library used to compile the-binary is that it was written some time in 1997. The state of the art in such programs has significantly advanced since then. Tribal Flood Network 2000 (TFN2K) pushed the envelope for minimizing detection by using multiple randomly selected covert channels, as well as using Base64 encoding of its packets, to hide their binary nature. Stacheldraht pursued a more distributed architecture for controlling "slave" computers, thereby increasing the difficulty in tracing back to the attacker.
I believe that the next significant advancement will be in a reorganization of the topology connecting these programs, to further obfuscate the location of the attacker. For instance, Stacheldraht uses a simple two-level "tree" topology, which can easily be improved upon by migrating to an IRC-like "cloud" topology. In this topology, control packets can be sent to any one server and be forwarded to all the other servers.
Another problem with the current implementations is that servers maintain a list of their "neighbor slaves". Once one installation has been detected and analyzed, it can lead to a chain reaction bringing down much of the network of slaves. This can be improved upon by maintaining encrypted lists of neighbors, and not retaining a copy of the decryption key. A careful (and patient) investigator could still debug the application and retrieve the key and lists, but this simple precaution significantly raises the bar on the amount of effort required.
Also, the underground/hacking community should get together and build a single global network of slaves, rather than everyone having their own independent networks. Perhaps a cryptographic-based system could be created so that some people have more "power" (access to more slaves) than others. Toss in vulnerability scanning, auto-exploiting, and auto-upgrade, and the system would probably grow much faster than it could be taken apart, as long as it was maintained.
Diverting back to covert channels, I believe that the Base64 trick used by TFN2K could be improved by skewing the letter frequency to look like a spoken language (where vowels are generally preferred over consonants). And a trick I haven't seen is to open a raw socket and implement the TCP protocol directly (for instance, over port 80). To the casual observer, it would appear as if there were normal web server traffic, and only inspection of the actual stream would reveal that the HTTP protocol isn't within.
<<< Previous | Home | Next >>> |
Analysis | Estimate of Incident Cost |