Post

A Fake "SystemHealth" Service, a Pyarmor Wall, and Three Ways to Get Paid

A fake SysMon.py in C:\Windows\SystemHealth runs a Pyarmor-locked Python bundle: an XMRig Monero miner, a credential and wallet stealer, and a fake-wallet phisher.

A Fake "SystemHealth" Service, a Pyarmor Wall, and Three Ways to Get Paid

The views and opinions expressed in this post are my own and do not represent those of my employer. This is a personal blog where I share research and things I’m learning.

TL;DR

A file called SysMon.py sitting in C:\Windows\SystemHealth\Update\ looks like Sysinternals Sysmon doing Windows housekeeping. It’s neither. It’s a Pyarmor-protected Python orchestrator at the centre of a crimeware bundle with three revenue streams running at once: an XMRig Monero miner (wallet and pools recovered in the clear), a custom infostealer (getthem) that rips browser credentials, crypto wallets and seed phrases, and a fake-wallet phishing/keylogger/clipper kit (nig). Both toolkits also carry PsExec/SMB for lateral movement. What’s interesting is the triage: Pyarmor encrypts every module body, but the honest module names and the bundled support libraries give up the whole capability map without decrypting a line. The miner config defangs to pools awesomeworkers[.]org:8880 and auto[.]c3pool[.]org:13333.

If this is your fleet, do these first:

  • Hunt Event ID 4688 for python.exe/pythonw.exe running from anywhere under C:\Windows\ - that should essentially never be legitimate.
  • Block unapproved executables and scripts from user-writable and system paths (Application Control ML1) - it stops the bundled interpreter and xmrig.exe cold.
  • Treat a “just a coinminer” finding here as the thread to pull: the credential/wallet theft and SMB lateral movement are the real incident.

Full indicators and two YARA rules are at the bottom.

A file called SysMon.py that very much isn’t Sysmon

A sample landed on my desk this week with the kind of name that’s designed to make a tired analyst’s eyes slide right past it: SysMon.py, sitting in C:\Windows\SystemHealth\Update\. If you only glanced at it, you’d think “Sysinternals Sysmon, Windows health updates, fine” and move on. That’s the whole idea.

It is not fine. It’s a Python script wrapped in commercial obfuscation, sitting at the centre of a little crimeware operation that wants to do three things to a victim’s machine at once: mine Monero, empty their browsers and crypto wallets, and pop fake wallet windows to phish whatever it couldn’t steal outright. Whoever built this wasn’t subtle, but they were thorough, and they did one genuinely annoying thing that I want to talk about, because it’s a pattern you’ll keep running into: they shipped the whole thing under Pyarmor, so the actual logic is encrypted and you can’t just read it.

So this is a post about working a sample where the code fights back, what you can still recover when the source is locked, and the part that actually matters: what a defender does about a Python miner-stealer that runs out of a fake Windows folder. Let’s dig in.

The attack at a glance

  1. Foothold - something with admin rights drops a full payload directory into C:\Windows\SystemHealth\Update\, masquerading as a Windows component.
  2. Execution - a tiny VBScript (win32check.vbs) launches a bundled, portable Python interpreter against SysMon.py with no visible window.
  3. Orchestration - SysMon.py (Pyarmor-protected) tags the host with a machine_id.txt and unpacks three payloads it brought along.
  4. Objective A - mining - XMRig 6.26 starts hashing Monero to attacker-controlled pools.
  5. Objective B - stealing - a toolkit called getthem rips browser credentials, crypto wallets and seed phrases, Discord tokens, then can spread over SMB.
  6. Objective C - phishing - a toolkit called nig throws up fake MetaMask/Phantom windows, keylogs, and watches the clipboard for crypto addresses to swap.

One loader, three revenue streams, all running as a fake health service.

How it works

Stage 1 - Hide in plain sight, then bring your own Python

Two things make this hard to spot. First, the masquerade: the directory is C:\Windows\SystemHealth\Update and the script is SysMon.py. Nothing there is a real Windows component - it’s a costume. Second, and cleverer, the malware brings its own Python. The folder contains a complete portable CPython 3.11 runtime - python.exe, pythonw.exe, python311.dll, the lot - plus pip packages like requests and psutil. The victim doesn’t need Python installed; the attacker shipped it. That’s “bring-your-own-interpreter”, and it sidesteps any assumption that “we don’t run Python here, so we’re fine”.

The launch itself is a one-liner of VBScript whose entire job is to start pythonw.exe (the windowless Python) so nothing flashes on screen.

Stage 2 - The Pyarmor wall

Here’s the annoying part. Every malicious .py in this operation opens like this:

1
2
3
# Pyarmor 9.1.7 (trial), 000000, non-profits, 2026-05-31T22:31:22.337809
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\x0b\x00\xa7\r\r\n...')

Pyarmor is a legitimate commercial tool for protecting Python source. The original code is compiled to bytecode, encrypted, and only decrypted at runtime by that pyarmor_runtime native module using a key bound to the runtime. For us that means: no plaintext. String and entropy sweeps across all of it returned exactly zero URLs, IPs, or keys out of the protected modules. The C2 addresses, the exfil endpoints, the Defender-tamper commands - all sealed inside the blob. You don’t get those statically without dynamically unpacking on a matching Windows box.

Now, that sounds like a dead end, but it isn’t, for two reasons. The attacker still had to ship unprotected supporting material - config files, library bundles, wordlists - and those are wide open. And the module names themselves weren’t obfuscated. So even with the bodies encrypted, the filenames plus the bundled libraries tell you what each component is for. More on that in a second - it’s the whole trick to triaging a sample like this.

One nice tell while we’re here: the Pyarmor licence stamp is identical across every stage - 9.1.7 (trial), 000000, non-profits - and the build timestamps cluster tightly, with wallet_collector.py compiled at 21:54 and SysMon.py at 22:31 on the same evening. Same toolchain, same person, one build session. That’s not attribution - I can’t and won’t tell you who - but it’s strong evidence this is one operator’s kit, not a mash-up.

Stage 3 - Stream one: the miner (fully recovered)

The miner config wasn’t protected at all - it’s a plain XMRig config.json, and it gives up everything (defanged below):

1
2
3
4
5
6
"pools": [
  { "coin": "monero", "url": "awesomeworkers[.]org:8880", "user": null, "rig-id": "gamer-pc" },
  { "coin": "monero", "url": "2[.]57[.]241[.]65:8880",    "user": null, "rig-id": "gamer-pc" },
  { "coin": "monero", "url": "auto[.]c3pool[.]org:13333",
    "user": "88biyCZR3vaKtYrtUWzBxbd94NuwvW15tGxNfpCjDxMPKo2XQ2a6CEbLixDuZY3uaoXzNoU4vSsKJPw1EQrJL6ejJ2sngbX" }
]

Two pools point at attacker-run proxies (user: null means the wallet lives server-side), and the failover is public C3Pool with the Monero wallet right there in the clear. max-threads-hint is set to 75%: greedy, but leaving a little headroom so the machine doesn’t grind to an obvious halt.

Stage 4 - Stream two: getthem, the stealer

This is where reading the bundle pays off. The getthem archive carries modules named wallet_collector.py, discord_tokens.py, password_manager_scanner.py, autofill_collector.py, and defender_utils.py. The bodies are encrypted, but look at what’s bundled around them:

  • win32crypt plus cryptography and pycryptodome -> the exact kit for decrypting Chromium’s DPAPI-protected cookies and saved passwords.
  • A file called english.txt containing exactly 2,048 words - that’s the BIP-39 seed-phrase wordlist. You only ship that if you’re scanning for and validating crypto wallet seed phrases.
  • smbprotocol + pypsexec -> SMB-based remote execution, i.e. lateral movement. A stealer that can also PsExec its way onto the next box over.

So without decrypting a single function, the capability map is clear: browser credential theft, wallet and seed-phrase theft, Discord token theft, Defender tampering, and network spread.

Stage 5 - Stream three: nig, the wallet-phishing kit

The third archive has modules fakemet.py, fakeph.py, and fakeex.py, and the bundled libraries again give the game away. It carries tkinter/tk_tools/pymsgbox (for drawing fake GUI windows - fakemet = fake MetaMask, fakeph = fake Phantom), the keyboard library (a keylogger), pyperclip (a clipboard clipper, the classic swap-the-copied-crypto-address trick), and Pillow/pyautogui (screenshots). It’s a surveillance and wallet-phishing package: if it can’t decrypt your wallet, it’ll just draw a convincing “unlock your wallet / re-enter your seed phrase” box and ask you for it.

Techniques observed (MITRE ATT&CK)

The following techniques have been mapped to MITRE ATT&CK for future reference, derived from the observed behaviour and the bundled capability libraries.

TacticTechniqueATT&CK IDWhat it did here
Defense EvasionMasquerading (match name/location)T1036.004/.005C:\Windows\SystemHealth\Update, SysMon.py posing as a Windows/Sysmon component
Defense EvasionObfuscated/packed codeT1027.002Pyarmor 9.1.7 across every stage
ExecutionCommand/scripting: PythonT1059.006Bundled portable CPython 3.11 runs the payloads
ExecutionCommand/scripting: Visual Basic / WSHT1059.005win32check.vbs launches pythonw.exe hidden
ImpactResource hijackingT1496XMRig 6.26 Monero miner
Credential AccessCredentials from web browsersT1555.003DPAPI decrypt of Chromium cookies/passwords
Credential AccessSteal app access tokenT1528Discord token theft
CollectionData from local systemT1005Crypto wallets + BIP-39 seed phrases
CollectionInput capture: keyloggingT1056.001keyboard library
CollectionClipboard data / clipperT1115pyperclip address swap
CollectionScreen captureT1113Pillow/pyautogui
Defense EvasionImpair defensesT1562.001defender_utils.py
Lateral MovementSMB / admin shares (PsExec)T1021.002pypsexec + smbprotocol
ExfiltrationOver C2 / web protocolsT1041 / T1071.001requests + websocket (endpoints encrypted)

Why this matters

Cryptomining alone is a billing problem - wasted electricity and a hot CPU. But mining is the least of what this bundle does. On a single infection, an attacker walks away with the browser-saved passwords, the session cookies, the Discord token, and the expensive one: any crypto wallet and seed phrase on the box, with a fake-wallet popup as backup if the wallet was encrypted. Then, because both toolkits carry PsExec and SMB, the same kit can step sideways to the next machine.

And the install path tells you something important: dropping files into C:\Windows\ requires administrative rights. So by the time this is running, something already had admin on the host. The miner is the noise; the credential theft and the lateral movement are the part that turns one sticky-fingered download into an incident. Treat a “just a coinminer” finding here as the thread you pull, not the conclusion.

What defenders can do

Technique (ATT&CK)What to doEssential EightWhat to hunt for
Python via bundled interpreter (T1059.006)App-control the interpreter; deny execution from C:\Windows\SystemHealth\ and user-writable pathsApplication Control (ML1)4688: python.exe/pythonw.exe with an image path under C:\Windows\
WSH launcher (T1059.005)Constrain or disable wscript.exe; block script hosts from user pathsApplication Control (ML1)4688: wscript.exe -> pythonw.exe parent/child
Masquerade in %WINDIR% (T1036)Tighten ACLs so only signed installers write to protected dirsRestrict Admin PrivilegesFile-creation audit on C:\Windows\ subfolders for off-baseline writers
Impair defenses (T1562.001)Defender Tamper Protection on; alert on exclusion changesUser Application HardeningDefender exclusion edits / Set-MpPreference; service stop
Lateral movement via SMB/PsExec (T1021.002)Least privilege; restrict admin-share write + service creationRestrict Admin Privileges7045/4697 service install; ADMIN$ writes
Valid/admin accounts (T1078)Phishing-resistant MFA; separate admin accountsMulti-factor Authentication4624/4625 anomalies; new admin-group adds (4728/4732)
HTTP/WebSocket C2 (T1071.001)Default-deny egress; block mining pool portsNo clean E8 homeEgress to pool ports / attacker hosts

Application control is the big lever here, and it bites hard. This entire operation depends on running an unapproved interpreter and a pile of unapproved executables out of a folder under C:\Windows. The Essential Eight Maturity Model is explicit at Maturity Level One that “Application control restricts the execution of executables, software libraries, scripts, installers, compiled HTML, HTML applications and control panel applets to an organisation-approved set”, and that it is “applied to user profiles and temporary folders” (Essential Eight Maturity Model, November 2023). A bundled python.exe and xmrig.exe are exactly that - unapproved executables - and a properly scoped allowlist never lets them start. See Implementing Application Control (November 2023). Hunt the gap with process-creation logging (Event ID 4688) for any Python or script host whose image path sits under C:\Windows\.

Restrict administrative privileges is the other load-bearing one, twice over. The drop into %WINDIR% needed admin, and both toolkits carry PsExec for lateral movement. If standard users can’t write to protected directories and ordinary accounts can’t create services or write to ADMIN$ on their neighbours, you’ve broken both the install and the spread. See Restricting Administrative Privileges (November 2023), and for the lateral-movement story, Detecting and Mitigating Active Directory Compromises (September 2024). Watch for service-install events (7045) and admin-share writes from hosts that have no business doing either.

Defender tampering maps to user application hardening - turn on Tamper Protection so a defender_utils module can’t quietly add exclusions or stop the service, and alert when the exclusion list or AV service state changes (see the Hardening Microsoft Windows 11 Workstations guidance, September 2025). The credential and wallet theft doesn’t have a neat preventive E8 home once code is running on the box - which is the point: stop the code running (application control) and the theft never starts. Layer phishing-resistant MFA (Implementing Multi-Factor Authentication, November 2023) so stolen passwords and cookies are worth less when they’re replayed.

Finally, the C2 and mining traffic has no clean Essential Eight home - it’s a network-architecture problem. Default-deny egress, block known mining pool ports, and treat outbound to freshly-seen hosts on :8880/:13333 as the anomaly it is.

Hunting and detection summary

  • 4688 - python.exe/pythonw.exe running from anywhere under C:\Windows\. This should essentially never be legitimate.
  • 4688 - wscript.exe or cscript.exe spawning pythonw.exe.
  • Sustained high CPU from a process under ...\SystemHealth\Update\xmrig-v2\.
  • File creation of machine_id.txt, config.json, or win32check.vbs under C:\Windows\SystemHealth\.
  • 7045 / 4697 service installs and ADMIN$ writes from workstations (PsExec lateral movement).
  • Defender exclusion-list changes, Set-MpPreference, or AV service stop/disable.
  • Egress to awesomeworkers[.]org, 2[.]57[.]241[.]65, auto[.]c3pool[.]org, or Monero pool ports (3333/5555/7777/8880/13333/14444).
  • YARA on the Pyarmor 9.1.7 (trial), 000000, non-profits toolchain stamp (below).

Indicators of Compromise

TypeIndicatorNotes
SHA2561cd3af05fbd21d06205d693de3f8c5e604a4853ed55a2e6f7e3b588278a0ccaeSysMon.py orchestrator
SHA256a75937c8b6db9bb6925a2e1f8cabb23f921e02252ae01c6760a343c726b1b664XMRig config.json
SHA256768e3085ee1d9c90a35d6c95a7250a0f09387fa83b1b23d74e09e776ffa7df68getthem.zip (stealer toolkit)
SHA2564d0138f5010d4ce7aabac633c3b1f947e0f295c6920ce82f7643971684312a65nig.zip (phishing/keylogger toolkit)
SHA256274bc4f3447253bfcdc4a5cd1727f1e2bb19ca1d5a5d45fcf209994914bdad82wallet_collector.py
SHA256147852c1d363c57fedc8c89b2ee6713951dccfd2b06442b749e8e088261b72f2fakemet.py (fake MetaMask)
Wallet88biyCZR3vaKtYrtUWzBxbd94NuwvW15tGxNfpCjDxMPKo2XQ2a6CEbLixDuZY3uaoXzNoU4vSsKJPw1EQrJL6ejJ2sngbXMonero - cross-victim correlator
Domainawesomeworkers[.]org:8880Attacker xmrig-proxy
IP2[.]57[.]241[.]65:8880Attacker mining proxy
Domainauto[.]c3pool[.]org:13333Public pool, failover
PathC:\Windows\SystemHealth\Update\Staging directory
Filemachine_id.txt, win32check.vbsBot ID + hidden launcher
Stringgamer-pcStatic miner rig-id across victims

Detection rules

rule PyArmor_SystemHealth_Crimeware_Toolchain
{
    meta:
        description = "Pyarmor 9.1.7 trial 'non-profits' id 000000 - SysMon/getthem/nig toolchain"
        author = "Luke Wilkinson"
        date = "2026-06-06"
    strings:
        $pa  = "from pyarmor_runtime_000000 import __pyarmor__"
        $hdr = "__pyarmor__(__name__, __file__, b'PY000000"
        $lic = "Pyarmor 9.1.7 (trial), 000000, non-profits"
    condition:
        $hdr and ($pa or $lic)
}

rule SystemHealth_Miner_Stealer_FS
{
    meta:
        description = "Host artefacts of fake SystemHealth\\Update miner+stealer+wallet-phish bundle"
        author = "Luke Wilkinson"
    strings:
        $d  = "SystemHealth\\Update" ascii wide
        $w  = "88biyCZR3vaKtYrtUWzBxbd94NuwvW15tGxNfpCjDxMPKo2XQ2a6CEbLixDuZY3uaoXzNoU4vSsKJPw1EQrJL6ejJ2sngbX" ascii wide
        $r  = "gamer-pc" ascii wide
        $m1 = "wallet_collector" ascii wide
        $m2 = "discord_tokens" ascii wide
        $m3 = "fakemet" ascii wide
        $m4 = "machine_id.txt" ascii wide
    condition:
        $w or ($d and 2 of ($r,$m1,$m2,$m3,$m4))
}

Closing

The thing I want you to take from this one isn’t the miner or even the stealer - it’s how much you can recover from a sample that’s been “protected”. Pyarmor locked the bodies, sure, but the attacker still had to ship a BIP-39 wordlist, a plaintext miner config, and a stack of honestly-named modules wrapped in tell-tale libraries. Read the supporting cast and the story writes itself, even when the lead won’t talk. The bit I couldn’t get - the exfil C2 - is sitting in that encrypted blob waiting for a dynamic unpack on a Windows box, which is a job for another evening.

And if you only do one thing after reading this: go check whether an unapproved python.exe could start out of C:\Windows in your environment right now. If the answer is yes, that’s the whole game.

Stay curious, and watch your %WINDIR%.


On methodology: the investigation is mine. The reverse engineering and analysis assembly were carried out with AI workflows (Claude, primarily). I reviewed every finding. Errors are mine - ping me on X or Instagram if you spot something off.

References

  • MITRE ATT&CK: T1036, T1027.002, T1059.006, T1496, T1555.003, T1528, T1056.001, T1115, T1562.001, T1021.002, T1071.001 - https://attack.mitre.org
  • ASD/ACSC Essential Eight Maturity Model (November 2023) - https://www.cyber.gov.au/business-government/asds-cyber-security-frameworks/essential-eight/essential-eight-maturity-model
  • ASD/ACSC Implementing Application Control (November 2023) - https://www.cyber.gov.au/business-government/protecting-devices-systems/hardening-systems-applications/system-hardening/implementing-application-control
  • ASD/ACSC Restricting Administrative Privileges (November 2023) - https://www.cyber.gov.au/business-government/protecting-devices-systems/system-administration/restricting-administrative-privileges
  • ASD/ACSC Detecting and Mitigating Active Directory Compromises (September 2024) - https://www.cyber.gov.au/about-us/view-all-content/advisories/detecting-and-mitigating-active-directory-compromises
  • ASD/ACSC Implementing Multi-Factor Authentication (November 2023) - https://www.cyber.gov.au/business-government/protecting-devices-systems/hardening-systems-applications/system-hardening/implementing-multi-factor-authentication
This post is licensed under CC BY 4.0 by the author.