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.
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.pysitting inC:\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 poolsawesomeworkers[.]org:8880andauto[.]c3pool[.]org:13333.If this is your fleet, do these first:
- Hunt Event ID 4688 for
python.exe/pythonw.exerunning from anywhere underC:\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.execold.- 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
- Foothold - something with admin rights drops a full payload directory into
C:\Windows\SystemHealth\Update\, masquerading as a Windows component. - Execution - a tiny VBScript (
win32check.vbs) launches a bundled, portable Python interpreter againstSysMon.pywith no visible window. - Orchestration -
SysMon.py(Pyarmor-protected) tags the host with amachine_id.txtand unpacks three payloads it brought along. - Objective A - mining - XMRig 6.26 starts hashing Monero to attacker-controlled pools.
- Objective B - stealing - a toolkit called
getthemrips browser credentials, crypto wallets and seed phrases, Discord tokens, then can spread over SMB. - Objective C - phishing - a toolkit called
nigthrows 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:
win32cryptpluscryptographyandpycryptodome-> the exact kit for decrypting Chromium’s DPAPI-protected cookies and saved passwords.- A file called
english.txtcontaining 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.
| Tactic | Technique | ATT&CK ID | What it did here |
|---|---|---|---|
| Defense Evasion | Masquerading (match name/location) | T1036.004/.005 | C:\Windows\SystemHealth\Update, SysMon.py posing as a Windows/Sysmon component |
| Defense Evasion | Obfuscated/packed code | T1027.002 | Pyarmor 9.1.7 across every stage |
| Execution | Command/scripting: Python | T1059.006 | Bundled portable CPython 3.11 runs the payloads |
| Execution | Command/scripting: Visual Basic / WSH | T1059.005 | win32check.vbs launches pythonw.exe hidden |
| Impact | Resource hijacking | T1496 | XMRig 6.26 Monero miner |
| Credential Access | Credentials from web browsers | T1555.003 | DPAPI decrypt of Chromium cookies/passwords |
| Credential Access | Steal app access token | T1528 | Discord token theft |
| Collection | Data from local system | T1005 | Crypto wallets + BIP-39 seed phrases |
| Collection | Input capture: keylogging | T1056.001 | keyboard library |
| Collection | Clipboard data / clipper | T1115 | pyperclip address swap |
| Collection | Screen capture | T1113 | Pillow/pyautogui |
| Defense Evasion | Impair defenses | T1562.001 | defender_utils.py |
| Lateral Movement | SMB / admin shares (PsExec) | T1021.002 | pypsexec + smbprotocol |
| Exfiltration | Over C2 / web protocols | T1041 / T1071.001 | requests + 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 do | Essential Eight | What to hunt for |
|---|---|---|---|
| Python via bundled interpreter (T1059.006) | App-control the interpreter; deny execution from C:\Windows\SystemHealth\ and user-writable paths | Application 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 paths | Application Control (ML1) | 4688: wscript.exe -> pythonw.exe parent/child |
Masquerade in %WINDIR% (T1036) | Tighten ACLs so only signed installers write to protected dirs | Restrict Admin Privileges | File-creation audit on C:\Windows\ subfolders for off-baseline writers |
| Impair defenses (T1562.001) | Defender Tamper Protection on; alert on exclusion changes | User Application Hardening | Defender exclusion edits / Set-MpPreference; service stop |
| Lateral movement via SMB/PsExec (T1021.002) | Least privilege; restrict admin-share write + service creation | Restrict Admin Privileges | 7045/4697 service install; ADMIN$ writes |
| Valid/admin accounts (T1078) | Phishing-resistant MFA; separate admin accounts | Multi-factor Authentication | 4624/4625 anomalies; new admin-group adds (4728/4732) |
| HTTP/WebSocket C2 (T1071.001) | Default-deny egress; block mining pool ports | No clean E8 home | Egress 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.exerunning from anywhere underC:\Windows\. This should essentially never be legitimate. - 4688 -
wscript.exeorcscript.exespawningpythonw.exe. - Sustained high CPU from a process under
...\SystemHealth\Update\xmrig-v2\. - File creation of
machine_id.txt,config.json, orwin32check.vbsunderC:\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-profitstoolchain stamp (below).
Indicators of Compromise
| Type | Indicator | Notes |
|---|---|---|
| SHA256 | 1cd3af05fbd21d06205d693de3f8c5e604a4853ed55a2e6f7e3b588278a0ccae | SysMon.py orchestrator |
| SHA256 | a75937c8b6db9bb6925a2e1f8cabb23f921e02252ae01c6760a343c726b1b664 | XMRig config.json |
| SHA256 | 768e3085ee1d9c90a35d6c95a7250a0f09387fa83b1b23d74e09e776ffa7df68 | getthem.zip (stealer toolkit) |
| SHA256 | 4d0138f5010d4ce7aabac633c3b1f947e0f295c6920ce82f7643971684312a65 | nig.zip (phishing/keylogger toolkit) |
| SHA256 | 274bc4f3447253bfcdc4a5cd1727f1e2bb19ca1d5a5d45fcf209994914bdad82 | wallet_collector.py |
| SHA256 | 147852c1d363c57fedc8c89b2ee6713951dccfd2b06442b749e8e088261b72f2 | fakemet.py (fake MetaMask) |
| Wallet | 88biyCZR3vaKtYrtUWzBxbd94NuwvW15tGxNfpCjDxMPKo2XQ2a6CEbLixDuZY3uaoXzNoU4vSsKJPw1EQrJL6ejJ2sngbX | Monero - cross-victim correlator |
| Domain | awesomeworkers[.]org:8880 | Attacker xmrig-proxy |
| IP | 2[.]57[.]241[.]65:8880 | Attacker mining proxy |
| Domain | auto[.]c3pool[.]org:13333 | Public pool, failover |
| Path | C:\Windows\SystemHealth\Update\ | Staging directory |
| File | machine_id.txt, win32check.vbs | Bot ID + hidden launcher |
| String | gamer-pc | Static 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