Mirai variant leveraging CVE-2023-1389

Introduction

On Feb 18 2024, our systems logged an activity from an endpoint on the internet trying to the hit the path /cgi-bin/luci

Initial research on the internet quickly gave hints that this was an attempt to exploit CVE-2023-1389 , an unauthenticated remote code execution on TP-Link archer routers.

The payload triggers on the victim router the download of a script http://45.142.214.108/tenda.sh

The script tenda.sh tries to fetch a variety of static binaries, compiled for various architectures, before trying to run them with the argument tplink

Judging by the naming convention of the aggressor, it is tempting to assume Russian-language proficiency due to the keyword blyat.

This mode of operation is generally associated with mirai based IoT botnets. This particular variant seems to have been augmented to exploit CVE-2023-1389 on internet facing devices.

Analysis of static binary.

The x86_64 compatible binary was downloaded for analysis.

Dynamic analysis

To guide our static analysis, the binary was first run with strace to have a global understanding of which syscalls it invokes, and thus what it attempts to do initially on infected systems.

Upon detonation, the binary greets us with a friendly message.

However, strace logs are more explicit.

root@stracerev:~# cat strace.log
execve("./faggot", ["./faggot"], 0x7ffea8865950 /* 15 vars */) = 0
mmap(0x200000, 1048576, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, 0, 0) = 0x200000
readlink("/proc/self/exe", "/root/faggot", 4096) = 12
mmap(0x400000, 1200128, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x400000
mmap(0x400000, 69680, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x400000
mprotect(0x400000, 69680, PROT_READ|PROT_EXEC) = 0
mmap(0x511000, 1264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x11000) = 0x511000
mprotect(0x511000, 1264, PROT_READ|PROT_WRITE) = 0
mmap(0x512000, 74952, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x512000
munmap(0x101000, 1180904)               = 0
ioctl(0, TCGETS, {c_iflag=ICRNL|IXON|IXOFF|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD|HUPCL, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE, ...}) = 0
ioctl(1, TCGETS, {c_iflag=ICRNL|IXON|IXOFF|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD|HUPCL, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE, ...}) = 0
unlink("./faggot")                      = 0
rt_sigprocmask(SIG_BLOCK, [INT], NULL, 8) = 0
rt_sigaction(SIGCHLD, {sa_handler=SIG_IGN, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x40c648}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGTRAP, {sa_handler=0x4061d0, sa_mask=[TRAP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x40c648}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
open("/dev/watchdog", O_RDWR)           = -1 ENOENT (No such file or directory)
open("/dev/misc/watchdog", O_RDWR)      = -1 ENOENT (No such file or directory)
chdir("/")                              = 0
socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(41386), sin_addr=inet_addr("20.0.0.9")}, [16]) = 0
close(3)                                = 0
brk(NULL)                               = 0x167e000
brk(0x167f000)                          = 0x167f000
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
bind(3, {sa_family=AF_INET, sin_port=htons(43213), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
listen(3, 1)                            = 0
time(NULL)                              = 1708270866 (2024-02-18T15:41:06+0000)
getpid()                                = 645
getppid()                               = 642
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1730067285
prctl(PR_SET_NAME, "suh7eqbodkb74e1"...) = 0
write(1, "faggot got malware'd", 20)    = 20
write(1, "\n", 1)                       = 1
fork()                                  = 646
exit(0)                                 = ?
+++ exited with 0 +++
root@stracerev:~#
  • Immediately after detonation, the binary sample unlinks itself from the filesystem, (line 14 of strace logs)

  • From lines 18, 19 of strace log, the sample tries to open /dev/watchdog in READ & WRITE mode, suggesting that it intends to write to /dev/watchdog.

    In normal circumstances, periodic writes to /dev/watchdog is the task of the watchdog daemon, and this is done to prevent the kernel from resetting .

    This is one more known procedures of mirai based botnets, to prevent their infected routers from rebooting because the malware primarily resides in memory.

  • Lines 23 - 24 show that the sample tries to make a reverse IP look to 8.8.8.8 with ip address of the host system.

  • Its last actions are to open a bind socket listening on 127.0.0.1:43213, and fork itself, probably to continue listening in the background.

Static analysis

  • Hexdump of the file shows from first few bytes that it is UPX encoded.

  • So the binary is decoded first

  • Following the unpacking, a few familiar strings appear

    They are primarily mirai’s M-SEARCH ssdp flood attack, /dev/watchdog paths opened during detonation, and the perculiar debugging sentence of the author.

Counter-measures

Based on the strings gathered from the unpacked binary, the following rule was defined for detection.

rule MAL_MIRAI_x86_64_LINUX_FEB_18_2024 {
  meta:
    description = "Mirai x86_64 yara rule"
    author = "Permafr0st security"
    md5 = "d476e0c2a8e4e4d90f2eaa15c36d8a90"
    sha256 = "5d37a4c89f2e567807e2033f8c8e9cfdb75ee6ec426d58ffd930e7fdbe066157"

  strings:
    $str1 = "M-SEARCH * HTTP/1.1"
    $str2 = "ST: urn:dial-multiscreen-org:service:dial:1"
    $str3 = "USER-AGENT: Google Chrome/60.0.3112.90 Windows"
    $str4 = "service:service-agent"
    $str5 = "/dev/watchdog"
    $str6 = "/dev/misc/watchdog"
    $str7 = "got malware'd"
    $str8 = "/usr/sbin/tcpdump"
    $str9 = "/usr/sbin/tshark"
    $str10 = "/usr/sbin/wireshark"
    $str11 = "/usr/sbin/dumpcap"
    $str12 = "/usr/sbin/ettercap"
    $str13 = "/usr/sbin/dsniff"
    $str14 = "/usr/sbin/ngrep"
    $str15 = "/usr/sbin/tcpflow"
    $str16 = "/usr/sbin/windump"
    $str17 = "/usr/sbin/netsniff-ng"
    $str18 = "/usr/bin/tcpdump"
    $str19 = "/usr/bin/tshark"
    $str20 = "/usr/bin/wireshark"
    $str21 = "/usr/bin/dumpcap"
    $str22 = "/usr/bin/ettercap"
    $str23 = "/usr/bin/dsniff"
    $str24 = "/usr/bin/ngrep"
    $str25 = "/usr/bin/tcpflow"
    $str26 = "/usr/bin/windump"
    $str27 = "/usr/bin/netsniff-ng"

  condition:
    ( 4 of them ) and $str7
}

Reference