Backdoor on Node.js: why, why and how it works

    Recently, colleagues from Yandex shared with us a sample of an interesting Trojan, which we reported in this news. Such a malware does not come across often, so we decided to disassemble it in more detail, and at the same time talk about why we rarely encounter such samples.

    The Trojan is a multi-component backdoor written in JavaScript and using Node.js. to run it. Its main elements are worker and updater, which are downloaded and installed into the system by the bootloader. The payload can be any, but in this case, the Trojan installs the xmrig miner. At the time of the study, the developer used the miner to mine the TurtleCoin cryptocurrency.

    MonsterInstall is distributed through sites with cheats for popular video games. Most of these resources belong to the developer of the Trojan, but we found several more infected files on other similar sites. The owner of one of them regularly monitors the updates of competitors and replenishes his resource with fresh content. To do this, he uses the parser.php script, which through a proxy searches for new cheats on

    Proxy parse done, total: 1
    Use sox
    Error: CURL error(#52), attempts left: 10
    Use sox
    Posts found: 30!
    [33mPage Satisfactory: Трейнер +8 vCL#96731 {} already in base[0m
    [33mPage Borderlands: The Pre-Sequel - Трейнер +28 v1.2019 {LinGon} already in base[0m
    [33mPage Borderlands - Game of the Year Enhanced: Трейнер +19 v1.0.1 {LinGon} already in base[0m
    [33mPage Star Wars: Battlefront 2 (2017): Трейнер +4 v01.04.2019 {MrAntiFun} already in base[0m
    [36mPage Far Cry 5: Трейнер +23 v1.012 (+LOST ON MARS/DEAD LIVING ZOMBIES) {} added 2019-Apr-09[0m
    [36mPage Fate/Extella Link: Трейнер +13 v04.09.2019 {} added 2019-Apr-09[0m
    [36mPage Superhot: Трейнер +3 v2.1.01p { MrAntiFun} added 2019-Apr-09[0m
    [36mPage Dawn of Man: Трейнер +7 v1.0.6 {} added 2019-Apr-08[0m
    [36mPage Borderlands 2: Трейнер +14 v06.04.2019 {MrAntiFun} added 2019-Apr-08[0m
    [36mPage Borderlands: The Pre-Sequel - Трейнер +17 v06.04.2019 {MrAntiFun} added 2019-Apr-08[0m
    [36mPage Tropico 6: Трейнер +9 v1.01 {MrAntiFun} added 2019-Apr-08[0m
    [36mPage Operencia: The Stolen Sun - Трейнер +20 v1.2.2 {} added 2019-Apr-08[0m
    [36mPage Enter the Gungeon: Трейнер +6 v2.1.3 {MrAntiFun} added 2019-Apr-07[0m
    [36mPage The Guild 3: Трейнер +2 v0.7.5 {MrAntiFun} added 2019-Apr-07[0m
    [36mPage Dead Effect 2: Трейнер +8 v190401 {MrAntiFun} added 2019-Apr-07[0m
    [36mPage Assassin's Creed: Odyssey - Трейнер +26 v1.2.0 {FLiNG} added 2019-Apr-07[0m
    [36mPage Assassin's Creed: Odyssey - Трейнер +12 v1.2.0 {MrAntiFun} added 2019-Apr-06[0m
    [36mPage Super Dragon Ball Heroes: World Mission - Трейнер +11 v1.0 {FLiNG} added 2019-Apr-05[0m
    [36mPage Tropico 6: Трейнер +7 v1.02 97490 {} added 2019-Apr-05[0m
    [36mPage Risk of Rain 2: Трейнер +10 Build 3703355 {} added 2019-Apr-05[0m
    [36mPage Sid Meier's Civilization 6 - Rise and Fall: Трейнер +12 v1.0.0.314 {MrAntiFun} added 2019-Apr-05[0m
    [36mPage Sid Meier's Civilization 6 - Gathering Storm: Трейнер +12 v1.0.0.314 {MrAntiFun} added 2019-Apr-05[0m
    [36mPage Sid Meier's Civilization 6: Трейнер +12 v1.0.0.314 {MrAntiFun} added 2019-Apr-05[0m
    [36mPage Borderlands GOTY Enhanced: Трейнер +16 v1.0 {} added 2019-Apr-05[0m
    [36mPage Borderlands Game of the Year Enhanced: Трейнер +13 v1.00 {MrAntiFun} added 2019-Apr-04[0m
    [36mPage Assassin's Creed: Odyssey: Трейнер +24 v1.2.0 (04.04.2019) {} added 2019-Apr-04[0m
    [36mPage Sekiro: Shadows Die Twice - Трейнер +24 v1.02 {FLiNG} added 2019-Apr-04[0m
    [36mPage Hearts of Iron 4: Трейнер +24 v1.6.2 {MrAntiFun} added 2019-Apr-04[0m
    [36mPage Wolcen: Lords of Mayhem - Трейнер +5 v1.0.2.1 {MrAntiFun} added 2019-Apr-04[0m
    [36mPage Devil May Cry 5: Трейнер +18 v1.0 (04.03.2019) {} added 2019-Apr-04[0m
    Parse done

    On the developer's sites there is a large selection of cheats, but the same archive will be returned for all the links. If you try to download any of the files from the malware site, the user will receive Trojan.MonsterInstall. Some parameters of the Trojan can be guessed from the download link:


    • name - the name of the archive and exe in the archive;
    • link - a link to the file that the user wanted to download (is wired in data.json);
    • password - password for the archive.

    Suppose we select the desired cheat and download the password-protected 7zip archive with the promising name “ExtrimHack.rar” from the site of the Trojan’s developer. Inside it there is an executable file, a configuration file, a 7zip library, as well as a bin archive with native C ++ libraries and scripts launched using the Node.js binar.

    Example archive content:

    • 7z.dll;
    • data.bin;
    • data.json;
    • ESP cheat for COP GO.exe.

    When the executable is launched, the Trojan will install all the components necessary for its operation, and also download the cheat necessary for the user, using the information from the data.json file with parameters.

    Example contents of data.json:

    To exclude several copies of its process from working, the Trojan creates the “cortelMoney-suncMutex” mutex and installs it in the “% WINDIR% \ WinKit \” directory. Then it checks to see if it is in the registry ([HKLM \\ Software \\ Microsoft \\ Windows Node]). If so, it reads its parameters and compares the version with the one specified in data.json. If the version is current, it does nothing further and ends.

    After that, the Trojan unpacks the contents of data.bin in% WINDIR% \\ WinKit \\ and installs the service to start start.js.

    Content of data.bin:
    • Daemon;
    • node_modules;
    • 7z.dll;
    • msnode.exe;
    • start.js;
    • startDll.dll;
    • update.js;
    • updateDll.dll.

    At the same time, msnode.exe is an executable file Node.js with a valid digital signature, and the node_modules directory contains the libraries “ffi”, “node-windows” and “ref”.

    The startDll.dll library is loaded in start.js and its mymain export is called, in which it reads its parameters from the registry, starts "% WINDIR% \\ WinKit \\ msnode% WINDIR% \\ WinKit \\ update.js" and stops the service "Windows Node." The update.js script, in turn, loads the updateDll.dll library and calls its mymain export. Nothing complicated.

    In updateDll.dll, the Trojan will begin to check your Internet connection. To do this, he will send requests to,, every 10 seconds, until all three return 200 code. Then send to s44571fu server[.] bget [.] ru / CortelMoney / enter.php POST request with configuration data:


    In this case, for basic authorization, a pair of “cortel: money” is used, and the User-Agent is set to “USER AGENT”. For basic authorization of subsequent requests, login: password will be used, which will be reported by the server.

    The server responds with json like this:

        "login": "240797",
        "password": "tdzjIF?JgEG5NOofJO6YrEPQcw2TJ7y4xPxqcz?X",
        "updaterVersion": [0, 0, 0, 115],
        "updaterLink": "http:\/\/\/CortelMoney\/version\/0-0-0-115-upd.7z",
        "workerVersion": [0, 0, 3, 0],
        "workerLink": "http:\/\/\/CortelMoney\/version\/0-0-3-0-work.7z"

    As you can see, the server’s response contains versions of the main elements of the Trojan. If the current version of updater on the user's device is older than the one reported by the server, the Trojan downloads the file from the specified link and unpacks the archive into the directory "% WINDIR% \\ WinKit \\\\", where instead the value of the updaterVersion parameter from the server response will be indicated .

    The Trojan unpacks the worker file into the% WINDIR% \\ WinKit \\ SystemNode \\ directory, and then launches "% WINDIR% \\ WinKit \\ SystemNode \\ sysnode% WINDIR% \\ WinKit \\ SystemNode \\ main.js".

    The contents of the archive with the worker:
    • node_modules;
    • 7za.exe;
    • codex;
    • main.js;
    • sysnode.exe.

    The Trojan then removes the Windows Node Guard service, and then creates it again, replacing the executable file with the Windows Node service file. In the same way, it re-creates the Windows Node service, replacing the executable with daemon \\ service.exe.

    Next forms service.xml with parameters:

    Updater is installed in the "C: \ Windows \ Reserve Service" directory, registered by the service and launched by the Node.js. binary. It also consists of several js scripts and native C ++ libraries. The main module is main.js.

    The contents of the archive with updater:
    • main.js;
    • start.js;
    • crypto.dll;
    • network.dll;
    • service.exe.

    The first thing the Trojan finds the current date, send a request to, or . He uses the information received a little later. It then decrypts the contents of the bootList.json file using the crypto.dll library.

    Decryption Algorithm:
    key = '123'
    s = ''
    for i in range(len(d)):
        s += chr((ord(d[i]) - ord(key[i % len(key)])) & 0xff)

    Gets a list of management servers from there:

    Then the Trojan reads information from the registry:
    function getInfo()
    var WindowsNodeInfo = new Object();
    WindowsNodeInfo.mainId = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "mainId");
    WindowsNodeInfo.login = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "log");
    WindowsNodeInfo.password = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "pass");
    WindowsNodeInfo.source = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "source");
    WindowsNodeInfo.updaterVersion = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "updaterVersion");
    WindowsNodeInfo.workerVersion = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Windows Node", "workerVersion");
    var ReserveSystemInfo = new Object();
    ReserveSystemInfo.workerVersion = windowsLib.getStringRegKey("HLM\\SOFTWARE\\Microsoft\\Reserve System", "updaterVersion");
    var myInfo = new Object();
    myInfo.windowsNode = WindowsNodeInfo;
    myInfo.reserveSystem = ReserveSystemInfo;
    return JSON.stringify(myInfo);

    Then it adds the HTTP header of the basic authorization corresponding to the “cortel: money” pair, and sends it with a POST request to the previously decrypted management server.
    In response, the server receives:

        "data": {
            "updaterVersion": [0, 0, 0, 1],
            "updaterLink": "/upd.7z",
            "updaterVerify": "£ñß(\u0012Ä\ti¾$ë5ž»\u001c²\u001c\fÙ=±÷ö‚´èUnŽÐÂBÔ\n\u001dW6?u½\u0005Œn\u000fp:üÍ\u0019\u0000\u000bSý«\u00137÷\u0013”’ì¥û§s7F\u0016ó\\\u000f%6ñê\"7î<ýo䃃0Æ%tñÅv­‚S¡\r\u001e•ÅÆ¡¿N)v\\f8\u0004F\fUS¯‰³§ oIõŒiÆîGݪ\u0017êH/8Ö1-°™[P5E7X‡Fø%SŠXÕ6Oþ=Vô‰…ˆ:.3Œ‚i\u000eÁù9Ã&¾ŒM\u001eÛªé$\u0006#IèÞÛ\u0018À\u001b^è,ÁòÑCTXb\u001d$ç\u0004„ð¶0UVÕ»e\u001f\b\u001e¡Ä¼è+Fjúÿoâz\r!çô3xØs—_\u000b\u0017\u001fY]\u0001¥j^û\\W",
            "dateTime": 1534868028000,
            "bootList": [{
                "node": "",
                "weight": 10
            }, {
                "node": "",
                "weight": 10
        "dataInfo": "I`ù@ÀP‘ÈcÊÛ´#ièÒ~\u0007<\u0001Ýìûl«ÔÆq\u0013àÛ\u0003\b\u0017ÑLÁ}ÿژDS']\u0003bf\u0003!¿Cð¸q¸ÖÜ’B¢CÄAMˆÀA¤d\u001c5¨d-\u0013‰\u0011ѼF‘|SB[¬°(ܹÈÒÜ £L\u00071¾:`\u001bŒìýKõ\"²Ÿ¸$´3™UºÅ¨J†¨cƒf¿}r;Öeì¶x‰ØKt¥‹„47a\u001e¸Ô‡ˆy\u0006•\u001b\u0004‚‹„„•ó\u001a\u0019\nu>¨)bkæ…'\u00127@é‹7µæy3ÈNrS’Mð‡\u0018\u0019¾òÓ[Žå5H‡ƒ·¦k‘¿ÉŠ&PÂÈîåÚ~M\u0010ðnáH擪xÃv cד\u0013…T…ïÑÝ\tœŽ\u0018†Æ\u00148$”Ôî"

    And here comes the current date, obtained earlier. The Trojan checks it against the dataTime parameter passed by the server. If the difference between the dates is more than a week (in terms of milliseconds), the Trojan will not execute the commands. The dataInfo parameter also contains the data signature (data field), it is checked using the public key wired in main.js. And the “bootList” parameter contains a list of servers, which the Trojan encrypts and stores in “bootList.json”.

    After that, the Trojan checks its version against the one specified in the updaterVersion parameter. If the installed version is not lower than the latest one, the Trojan launches “upd \\ upd.exe” by passing the parameter “main.js”. If the version from the server’s response is newer, then the Trojan downloads the update archive using the link from the “updaterLink” parameter upd.7z, checks its signature and unpacks it. Then it writes to the registry the version of the update [HKLM \\ SOFTWARE \\ Microsoft \\ Reserve System] 'updaterVersion', after which it again launches “upd \\ upd.exe”, passing it with the parameter “main.js”.

    The Trojan’s Worker checks that one of the components has already been installed, and makes decisions about whether to install applications. First, he creates the MoonTitleWorker mutex, then decrypts the codeX file with an XOR with the string “xor” and executes it. After that forms json with information:
    {"userId": id, "starter": [], "worker": [], "source": [], "osInfo": {"isX64": True, "osString": "Windows 7 Enterprise"}}

    Sends this information with a POST request to http: //[.] xyz: 1001 / getApps (for the sake of decency, we do not specify the domain name, but it can be found here .) The server response may contain information about the applications that need to be installed.

    Answer example:
        "body": {
            "apps": [{
                "hash": "452f8e156c5c3206b46ea0fe61a47b247251f24d60bdba31c41298cd5e8eba9a",
                "size": 8137466,
                "version": [2, 0, 0, 2],
                "link": "xmr-1-64.7z",
                "path": "%pf%\\Microsoft JDX\\64",
                "runComand": "%path%\\moonlight.exe start.js",
                "name": "xmr64"
        "head": "O~¨^Óå+ßzIçsG¬üS„ʶ$êL–LùθZ\f\u0019ÐÐ\u000e\u0004\u001cÀU¯\u0011š)áUÚ\u001flß²A\u001fôÝÔ숱y%\"DP»^¯«FUâ\u001cÔû\u001dµ´Jï#¬ÌȹÎÚª?\r—]Yj·÷õ³—\u001e°ÖÒ\\鉤d’BT\u0019·¦FõVQ°Aç’)\u001cõªµ¦ýûHlb͸þ}éŒ\u0000jvÔ%S;Ã×þA\u0011ߑI[´\u0004ýÚ\u0007Z:ZÂ\n–ñz#ÈBö›²2\u0007ÎJw±èTVoŸå\bÖR3½ù;ƒó\u0011É̀ÅÖàð06ÓeÕþ­ˆ”7ٚ\u0011•»”˜¢5µgôÛc˜&L\u000fê.?!Çæ}¨\u001e՗J#A¼_Ì\u0015càñb"

    If the device does not have such an application, the Trojan downloads it by sending a POST request to the URL http: //[.] xyz: 1001 /, where instead the value of the “link” parameter will be indicated for the corresponding application from the server response. If there is such an application, but an older version, it is updated to the current one.

    The Trojan checks the size and hash of the downloaded file with the information specified by the server in the hash and size parameters. If the data converges, it moves the file along the path from the path parameter and runs the command specified in the runCommand field. Information about the downloaded application is stored in the registry [HKLM \\ SOFTWARE \\ Microsoft \\ MoonTitle \\ apps \\].

    At the moment, the xmrig miner is being set using the backdoor. Depending on the capacity of the system, the Trojan downloads an xmr-1.7z or xmr-1-64.7z archive. In start.js, it loads the xmrig.dll library and calls mymain export, where it deploys its environment variables and kills the processes:
    • % sys32_86% \\ xmr;
    • % sys32_86% \\ xmr64;
    • % pf_86% \\ Microsoft JDX \\ 32 \\ windows-update.exe;
    • % pf_86% \\ Microsoft JDX \\ 64 \\ windows-update.exe.

    If the xmrig.exe file is located nearby, the Trojan loads it into the memory of the current process, erases the MZ signature, decrypts it using XOR with 0x39, and then saves its dump in the dump file. If the Trojan finds the “dump” file in the same directory, it decrypts it in the same way, launches windows-update.exe and embeds the decrypted payload into it.

    The Trojan collects and sends by POST request to the URL: cherry-pot [.] Top / RemoteApps / xmr / main.php the following system information: {"action": "enter", "architecture": "INTEL", "cpuAES" : true, “cpuCache”: 8192, “cpuSpeed”: 3392.0, “cpuThreads”: 2, “cpuVendorString": "Intel® Core (TM) i5-4690S CPU @ 3.20GHz \ u0000", "hightPages": false, " login ":" null "," password ":" null "," ramPhysicalSize ": 3071," xmrigVersion ": [2,10,0]}

    In response, the server sends the miner configuration:
    {"maxCpuLoad":1000,"minCpuLoad ":0,"algo":"cryptonight-pico/trtl","av":0,"background":false,"donate-level":1,"max-cpu-usage":75,"retries":5,"retry-pause":5,"cpu-priority":1,"pools":[{"url":"","keepalive":true,"nicehash":true}]}

    After the Trojan saves the configuration in config.json, it will automatically start and start mining.

    MonsterInstall has other modifications. For example, in addition to cheats for games, the Malvari developer distributed it under the guise of a Chrome browser installer and a program for checking files. In later versions of the Trojan, the developer thought about security and added string encryption, as well as the need to enter a password for some files. In addition, the bootloader of one of the versions of the Trojan even has a link to a license agreement hosted on the Trojan’s developer’s domain.

    (Questions about the legal force of such agreements, unfortunately, are beyond the scope of this article, but if you would be interested to read material on this topic, let us know in the comments).


    Node.js is not the most practical solution for virus writers. If the size of such a Trojan can be small, then the Node.js binding (executable file and libraries) will be significantly “heavier” than the standard malvari. What dictated this choice? As a rule, developers choose tools that they are familiar with. Therefore, even in the case of the breeders, the choice of technology is more a matter of personal preference. However, Node.js has its advantages, one of which is a valid signature. In the system, such a process will be signed as Node.js, which rarely raises suspicions.

    Summing up, it can be noted that, despite the interesting choice of tools, this did not give the backdoor developer any significant advantage. It is unlikely that in the future we will see more malware using Node.js.

    As usual, we share indicators of compromise .

    Also popular now: