This post is also available in: 日本語 (Japanese)

Executive Summary

As soon as the proof-of-concept (PoC) for CVE-2020-9054 was made publicly available last month, this vulnerability was promptly abused to infect vulnerable versions of Zyxel network-attached storage (NAS) devices with a new Mirai variant – Mukashi.

Mukashi brute forces the logins using different combinations of default credentials, while informing its command and control (C2) server of the successful login attempts. Multiple, if not all, Zyxel NAS products running firmware versions up to 5.21 are vulnerable to this pre-authentication command injection vulnerability. The vendor advisory is also available.

You can test to see if a Zyxel NAS device is vulnerable here.

This vulnerability has a critical rating (i.e CVSS v3.1 score of 9.8) due to its trivial-to-exploit nature. It’s not surprising that the threat actors weaponize this vulnerability and start wreaking havoc in the Internet of Things (IoT) realm. It was initially discovered via the sale of its exploit code as a 0-day i.e. while it was still unreported to the vendor. This initial discovery also mentioned “the exploit is now being used by a group of bad guys who are seeking to fold the exploit into Emotet”.

This blog includes a walkthrough of the entire killchain, including images and IoCs.

Vulnerability Analysis

The executable weblogin.cgi doesn’t properly sanitize the username parameter during authentication. The attacker can use a single quote ‘ to close the string and a semicolon ; to concat arbitrary commands to achieve command injection. Since weblogin.cgi accepts both HTTP GET and POST requests, the attacker can embed the malicious payload in one of these HTTP requests and gain code execution.

Exploit in the Wild

The first incident happened at 19:07 (UTC) on March 12, 2020 and was caught on our Next-Generation Firewall. As shown in Figure 1 and 2 below, this threat actor attempted to download a shell script to the tmp directory, execute the downloaded script, and remove the evidence on a vulnerable device.

Upon execution, the zi script downloads different architectures of Mirai bot, runs the downloaded binaries, and removes the binaries. All these binaries were not available on VirusTotal at the time of discovery — 4 out of 8 are in VirusTotal at the time of writing.

New Mirai Variant – Mukashi

Mukashi is a bot that scans the TCP port 23 of random hosts, brute forces the logins using different combinations of default credentials, and reports the successful login attempt to its C2 server. Like other Mirai variants, Mukashi is also capable of receiving C2 commands and launching DDoS attacks.

When it’s executed, Mukashi prints the message “Protecting your device from further infections.” to the console. The malware then proceeds to change its process name to dvrhelper, suggesting Mukashi may inherit certain traits from its predecessor.

Prior to carrying out its intended operation, Mukashi binds to the TCP port 23448 in order to ensure only a single instance is running on the infected system.

The malware decodes a couple of strings on the fly during its initialization. These decoded strings, as shown in the following table, include credentials as well as C2 commands. Unlike its predecessors that use conventional xor encryption, Mukashi uses a custom decryption routine to encrypt these commands and credentials. A decryption script is provided in the appendix.

/cmdline .udprand .udpbypass .udphex user tsgoingon /proc/ .udpplain .tcpbypass default daemon zlxx. /status .http .tcp admin juantech Zte521 /maps /exe killallbots root 123456 hunt5759 /proc/self/cmdline None ./ guest solokey samsung self POST killer dvr2580222 xc3511 vizxv ping GET scanner support 12345 .udp / world guest xmhdipc

Table 1. Decoded credentials and commands

When the malware performs credential brute-force attacks, Mukashi uses well known default passwords like t0talc0ntr0l4! and taZz@23495859, in addition to the decoded credentials that it has decoded before the scanning phase. Figure 3, below, shows the initiation traffic captured when Mukashi was scanning the random hosts, and Figure 4 shows the malware’s attempt to brute-force authentication.

Upon successful login attempt, Mukashi reports the working combination of the credentials to its C2 server 45[.]84[.]196[.]75 on TCP port 34834.

The message has the following format – <host ip addr>:23 <username>:<password>. The following figure shows an example of such a message.

Once the malware is up and initialized, it sends a beacon back to its C2 server 45[.]84[.]196[.]75 listening on TCP port 4864, notifying its C2 server that it’s ready for command. An example of the beacon is shown in Figure 6 below. The beacon has the following format: <name>.<input argument>. The <name> substring depends on the return value of a socket creation; if the socket is successfully created, then <name> is root, else it’s default. The <input argument> substring is the input argument passed to the binary when it’s being executed. If no input argument is provided, the beacon string would be None.

Mirai’s and its variants’ DDoS attack mechanics (e.g UDP, TCP, UDP bypass, and TCP bypass) have already been analyzed in-depth, and Mukashi’s DDoS capabilities are no different from these variants. The presence of DDoS defense bypass confirms our speculation from earlier that Mukashi includes certain capabilities from the dvrhelper variant — Mukashi also possesses the anti-DDoS-defense capabilities. The following table shows the C2 commands that Mukashi supports.

PING scanner .udpplain .tcp killallbots .udp .udpbypass .tcpbypass killer .udprand .udphex .http

Table 2. C2 commands

The attack_parsing() function is responsible for processing C2 command strings that Mukashi receives from its C2 server. In addition to the type of command and target address, the C2 command strings include relevant information like SYN flag, ACK flag, URG flag, PSH flag, Rst flag, time field, destination port value, and length value that Mukashi needs to construct the packet header. If destination port value is not available, Mukashi chooses a random port. And if the length of the packet is not specified, Mukashi uses the default value 1458.

Even though there are numerous Mukashi binaries compiled for different architectures, they are pretty much the same capabilities-wise — except that the x86 version doesn’t have the cleaner() function that allows it to kill processes by process command line, specific strings, and permissions. The following figures show how the x86 version is different from the arm7 version.

Conclusion and Mitigation

Updating the firmware is highly recommended to keep the attackers at bay. The latest version of the firmware is available for download. Complex login passwords are also advised to prevent brute forcing.

Palo Alto Networks customers are protected from the vulnerability by the following products and services:

Next-Generation Firewalls with threat prevention license can block the attacks with best practice via threat prevention signature 57806 .

. WildFire can stop the malware with static signature detections.

IoCs

File (Sha256)

8c0c4d8d727bff5e03f6b2aae125d3e3607948d9dff578b18be0add2fff3411c (arm.bot)

5f918c2b5316c52cbb564269b116ce63935691ee6debe06ce1693ad29dbb5740 (arm5.bot)

8fa54788885679e4677296fca4fe4e949ca85783a057750c658543645fb8682f (arm6.bot)

90392af3fdc7af968cc6d054fc1a99c5156de5b1834d6432076c40d548283c22 (arm7.bot)

675f4af00520905e31ff96ecef2d4dc77166481f584da89a39a798ea18ae2144 (mips.bot)

46228151b547c905de9772211ce559592498e0c8894379f14adb1ef6c44f8933 (mpsl.bot)

753914aa3549e52af2627992731ca18e702f652391c161483f532173daeb0bbd (sh4.bot)

ce793ddec5410c5104d0ea23809a40dd222473e3d984a1e531e735aebf46c9dc (x86.bot)

a059e47b4c76b6bbd70ca4db6b454fd9aa19e5a0487c8032fe54fa707b0f926d (zi)

Network

45[.]84[.]196[.]75:34834 (Report Successful Login Attempt)

45[.]84[.]196[.]75:4864 (Command and Control)

0[.]0[.]0[.]0:23448 (Singleton)

Previous activity

The table below includes hashes for samples of the same variant, hosted at the same IP, however we are missing evidence of whether they were distributed by exploitation of CVE-2020-9054 or not.

First Seen URL SHA256 2020-03-04 45.84.196[.]75/bins/arc.corona 3e8af889a10a7c8efe6a0951a78f3dbadae1f0aa28140552efa0477914afd4fd 2020-03-04 45.84.196[.]75/bins/arm5.corona 213cdcf6fd5ca833d03d6f5fa0ec5c7e5af25be8c140b3f2166dccccf1232c3e 2020-03-04 45.84.196[.]75/bins/m68k.corona 4f1fe9dc48661efe2c21b42bd5779f89db402b5caa614939867508fa6ba22cd6 2020-03-04 45.84.196[.]75/bins/arm6.corona 0f7fb7fb27ce859b8780502c12d16611b3a7ae72086142a4ea22d5e7eaa229bc 2020-03-04 45.84.196[.]75/bins/mips.corona 9a983a4cee09e77100804f6dae7f678283e2d2ff32d8dbcf356ef40dcdff8070 2020-03-04 45.84.196[.]75/bins/arm.corona 060547ee0be2d5e588e38d1ad11e1827ba6ce7b443b67e78308571e9d455d79b 2020-03-04 45.84.196[.]75/bins/ppc.corona dcb52fbd54fd38b6111670554a20a810b9caccc0afce7669ba34fc729afe2049 2020-03-04 45.84.196[.]75/bins/mpsl.corona 60be483526d1ae9576617907b80a781296404220affcf01d47e9e2bfa2cdc55f 2020-03-04 45.84.196[.]75/bins/x86.corona 12d3d391462f7b66985f216dbca330ac13a75263d0f9439692fd53065eeb5657 2020-03-04 45.84.196[.]75/bins/arm7.corona 0c016ce7576b5c041ea1e36e8561214dee85d7ce87a50bb092def026881183f4 2020-03-04 45.84.196[.]75/bins/spc.corona 4e21b2547a8fc15b1435441fa6567b4626dfa3049c2dd6911b333449dd6756fd 2020-03-04 45.84.196[.]75/bins/sh4.corona 049a1570e76c025d431997fb7a9963d465959a6c470eeeab4ac8420f6e3829a6 2020-03-09 45.84.196[.]75/bins/arc.bot 3df226be94f99ece7875032e41b025b5a19152e1d63bd0cda2af204f667cd140 2020-03-09 45.84.196[.]75/bins/arm5.bot 768430ee908a6fc5fa6d5785b2ec15cd334fbc302d98ee3045aa44c2137a7a35 2020-03-09 45.84.196[.]75/bins/arm6.bot 228eac174dcf166c97a7baa854ab3803ade9934915ef701dd0634f033ca252fe 2020-03-09 45.84.196[.]75/bins/arm7.bot eac71fd11ebb70ab256afa417e6621de0b66ec4830eb229b04192f9f866037ca 2020-03-09 45.84.196[.]75/bins/arm.bot 1734610c5d09be7a0e4459f8bd2a9373ae3da8812165f08733b3a5efdd38ff29 2020-03-09 45.84.196[.]75/bins/m68k.bot b6e859812efecce70041ad5fda2b4881b1b1a89e6ae982cb43af67b301640620 2020-03-09 45.84.196[.]75/bins/mips.bot 8f047170fceb05164429968ae24839f1419e58e30fd10057ab14291bfe0945c1 2020-03-09 45.84.196[.]75/bins/mpsl.bot 7dbd6923a425d3464318e22c3bd88ea1e8f2d0ae914ac29664f95cef5cb4d748 2020-03-09 45.84.196[.]75/bins/ppc.bot 635d7bb69b758cb7df9b9fcab9de7671139fdbe3f03f79299476706cfe54553d 2020-03-09 45.84.196[.]75/bins/sh4.bot d400cb7c2bb69011c8b21d8f24da08ac31cc55ee88b45f21cf4e4a1683548e38 2020-03-09 45.84.196[.]75/bins/spc.bot 83022c991d5da2725b8e39128862e5ae987d53846e0539655ab66f7ed3355a6b 2020-03-09 45.84.196[.]75/bins/x86.bot bca0cffe842196be283d28572d7c43a53c1e5e5a231ad3d7969aa40965e2406b 2020-03-10 45.84.196[.]75/bins/arc.bot a3a674b3481e3b9e5e12b332f4508134db6405f59d3c8dc74aaa4943c84fafb6 2020-03-10 45.84.196[.]75/bins/arm5.bot c9c546967620830745796b87993e9b89d3405e0a8cc083f09bfbf08675ef87ba 2020-03-10 45.84.196[.]75/bins/arm6.bot 72d44204ad26a974b1bdbed2970955670ce2697bfe99e697eb7df255cccea0be 2020-03-10 45.84.196[.]75/bins/arm7.bot 62ad931aa37a227211ccb1d89050630c9122e2d24eecef824416e913f578f969 2020-03-10 45.84.196[.]75/bins/arm.bot be1d0f53d7647a46047102ffdc063d06be511ffc9832a72cca1420ac2811f807 2020-03-10 45.84.196[.]75/bins/m68k.bot 46d868913a330e5b36673c229240dc971b535f95f091fc9bd9c9fa315c7cf838 2020-03-10 45.84.196[.]75/bins/mips.bot 7b0176099dd032a5c2d6834e8840af78f91332a0b7cee000746bcaec5fbb3e9b 2020-03-10 45.84.196[.]75/bins/mpsl.bot 940fa7d9ef770a3e70c5f227a0ad1aaac88071f3c4879a2c92e7c155d9626d73 2020-03-10 45.84.196[.]75/bins/ppc.bot 514e5ca58df6ba22708046cd034af05e3a88f80da893e4d7e2124137086468b0 2020-03-10 45.84.196[.]75/bins/sh4.bot af6a51c012062078d6fcf112b3e4239eb029fc895f5f74fb5e40eb0b71fe67ce 2020-03-10 45.84.196[.]75/bins/spc.bot 3ae3b155c274edb389fe9d06bf9349bfd829c0e55db34238c3a8f53da16b4d98 2020-03-10 45.84.196[.]75/bins/x86.bot 5060a00c235566726cdf0e0a07f022cdbf2f59cff636f37b19576bf98ea70027 2020-03-12 45.84.196[.]75/bins/arc.bot 906d945b00465b1b7f6a828eb47edc0e875e745b7638258afbe8032d4c2d6ac6 2020-03-12 45.84.196[.]75/bins/arm5.bot 27f26c710b4d461396749acfbe8fadc57ba19dcb70b1e1890599ca938c0d6aec 2020-03-12 45.84.196[.]75/bins/arm6.bot 162add056aef065ff0e19242ca8674698586b295b2f75c03f9f22a14f6e16ff3 2020-03-12 45.84.196[.]75/bins/arm7.bot 948776a3c50a8e6a2f58f27f29095b63f7bbc0f8b5aeb08c6a4ba27558b13a0d 2020-03-12 45.84.196[.]75/bins/arm.bot 3061fd4a4a57e8c1948c30728f82a82213a1907ee8fccb7037dd1649e1c51e0e 2020-03-12 45.84.196[.]75/bins/m68k.bot 941e2833d313d33e53db5416718ba4c68609ac0537d3f16bf600c0bee2f562d0 2020-03-12 45.84.196[.]75/bins/mips.bot 8473645820c828758a7655730ab6bd6967c97872687f4b6d5eff769387f59059 2020-03-12 45.84.196[.]75/bins/mpsl.bot 1a4efe25a8f660e44abdb82d84912cf24db7eabfe9ad3c4c12080ca05636d73b 2020-03-12 45.84.196[.]75/bins/ppc.bot dbcd46dabd2fbddb40e17c2f7790950086b0108370d2448ff5fe407a9cd83103 2020-03-12 45.84.196[.]75/bins/sh4.bot 751b0fe6616034a72235c7d3021e3f54f0634b9b5b29fed56cd44843389da0e9 2020-03-12 45.84.196[.]75/bins/spc.bot 5a69a7c079555b53263a64dc0757f2168e255b29bc17ab846aceb2f8d08f3830 2020-03-12 45.84.196[.]75/bins/x86.bot 47f9e2e65b17b937bc32fc6bb5bfbbb0efd2b86305b9d29a976512cbcc049d28

Appendix

IDApython 6.x-7.3 Script

import ida_kernwin from idc import * from idautils import * from idaapi import * def decode_str(encoded_str): if len(encoded_str) == 0: return "" buf = list(encoded_str) result = "" buf[0] = chr(ord(buf[0]) - 2) slen = len(encoded_str) v1 = slen / 2; if v1 > 0: i = v1 while True: if i >= slen: break; buf[i] = chr(ord(buf[i]) - 1); i += 1 v2 = slen / 4; if v2 > 0: j = v2 while True: if j >= slen: break; buf[j] = chr(ord(buf[j]) - 1) j += 1 for k in xrange(0, slen): buf[k] = chr(ord(buf[k]) - 1) v3 = 0 if slen > 24: if slen > 99: v3 = slen / 5 - 3; else: v3 = slen / 5 - 1; else: v3 = slen / 5; l = v3 while True: if l >= slen: break buf[l]= chr(0); l += 1 result = "".join(buf) return result def main(): for addr in XrefsTo(0x080482A0, flags=0): print("[*] addr.frm: {0}".format(hex(addr.frm))) prev_addr = PrevHead(addr.frm) encoded_str = "" if GetMnem(prev_addr) == "push": str_addr = GetOperandValue(prev_addr, 0) elif GetMnem(prev_addr) == "mov": str_addr = GetOperandValue(prev_addr, 1) print("\tstr_addr: {0}".format(hex(str_addr))) encoded_str = GetString(str_addr) print("\tencoded_str: {0}".format(encoded_str)) decoded_str = decode_str(encoded_str) print("\tdecoded_str: {0}".format(decoded_str)) if __name__ == '__main__': main() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import ida_kernwin from idc import * from idautils import * from idaapi import * def decode_str ( encoded_str ) : if len ( encoded_str ) == 0 : return "" buf = list ( encoded_str ) result = "" buf [ 0 ] = chr ( ord ( buf [ 0 ] ) - 2 ) slen = len ( encoded_str ) v1 = slen / 2 ; if v1 > 0 : i = v1 while True : if i >= slen : break ; buf [ i ] = chr ( ord ( buf [ i ] ) - 1 ) ; i += 1 v2 = slen / 4 ; if v2 > 0 : j = v2 while True : if j >= slen : break ; buf [ j ] = chr ( ord ( buf [ j ] ) - 1 ) j += 1 for k in xrange ( 0 , slen ) : buf [ k ] = chr ( ord ( buf [ k ] ) - 1 ) v3 = 0 if slen > 24 : if slen > 99 : v3 = slen / 5 - 3 ; else : v3 = slen / 5 - 1 ; else : v3 = slen / 5 ; l = v3 while True : if l >= slen : break buf [ l ] = chr ( 0 ) ; l += 1 result = "" . join ( buf ) return result def main ( ) : for addr in XrefsTo ( 0x080482A0 , flags = 0 ) : print ( "[*] addr.frm: {0}" . format ( hex ( addr . frm ) ) ) prev_addr = PrevHead ( addr . frm ) encoded_str = "" if GetMnem ( prev_addr ) == "push" : str_addr = GetOperandValue ( prev_addr , 0 ) elif GetMnem ( prev_addr ) == "mov" : str_addr = GetOperandValue ( prev_addr , 1 ) print ( "\tstr_addr: {0}" . format ( hex ( str_addr ) ) ) encoded_str = GetString ( str_addr ) print ( "\tencoded_str: {0}" . format ( encoded_str ) ) decoded_str = decode_str ( encoded_str ) print ( "\tdecoded_str: {0}" . format ( decoded_str ) ) if __name__ == '__main__' : main ( )

IDApython 7.4 Script