On this page
On May 19, two WindowsTelemetry archives were linked to a PoisonX tag and a source report from Naoki Takayama / @mopisec (opens in new tab) describing a VERSION.dll sideloading chain, a vulnerable driver step, and C2 at 101.32.190[.]202:8080. Static analysis of both archives produced the same decoded scheduler payload, the same decoded RAT payload, and the same C2. The chain below follows the loader, installer, BYOVD path, and RAT core stage by stage.
Sample overview
| Archive SHA256 | Outer format | Tag | First seen (UTC) |
|---|---|---|---|
| 0fb45474ca58bd67220f79b0e3b07f940270c371ba56e27d3e2b99bf4dbb5174 | WindowsTelemetry.zip | PoisonX | 2026-05-19 04:32 |
| b892981af3ca699d13f07ddcf75c2df62c1543b071278b4cc1ac0993d8b9dc01 | WindowsTelemetry.rar | PoisonX, RustyStealer | 2026-05-19 06:57 |
ReversingLabs filed the two archives as Win64.Trojan.Midie and Win64.Trojan.Ravartar respectively; neither name has a Malpedia entry. The working label across both is PoisonX, matching the source report that shipped with them and the MalwareBazaar tag. That label held through analysis: both archives decode to the same scheduler, same RAT binary, and the same C2 at 101.32.190[.]202:8080.
The PoisonX name has public baggage. Maltrail tracks PoisonX Stealer as an alias alongside Ailurophile Stealer and MrAnon Stealer (opens in new tab), with older infrastructure such as poisonx[.]in, poisonx[.]net, 103.116.8[.]66, and 179.43.171[.]201; that same trail now lists this case's C2. Local artifacts contain none of those older domain, IP, Ailurophile, or MrAnon markers, so the name overlap is background, not proof of shared code ancestry. A separate PoisonX.sys / PoisonKiller BYOVD disclosure (opens in new tab) describes a different PID-kill driver using IOCTL 0x22E010; the embedded driver carved from these archives is GLCKIo/WinIo-style through \Device\GLCKIo with IOCTL base 0x80102040, exposing port I/O, physical memory map/unmap, and rdmsr/wrmsr primitives.
Stage 0: DLL sideloading
The outer archive provides:
- dashost.exe, a legitimate Microsoft binary that imports VERSION.dll.
- A malicious VERSION.dll placed in the same directory.
- scheduler.cache and cache.db as encrypted payload artifacts.
When dashost.exe runs, Windows loads the local VERSION.dll through standard DLL search-order hijacking. The malicious VERSION.dll decodes its own embedded strings with XOR 0x7a, then reads scheduler.cache, rolling-XOR-decodes it, manual-maps it into memory, and calls its entrypoint.
Stage 1: The scheduler
The scheduler (decoded PE, 64-bit) acts as installer, persistence mechanism, Defender tamper utility, BYOVD orchestrator, and RAT loader.
Rolling-XOR encoding
Both .cache and .db files use the same encoding scheme:
- Byte 0: XOR seed
- Payload starts at offset 1
- Each payload byte is XORed with
(seed + byte_index) & 0xff
| Archive | Seed |
|---|---|
| 0fb | 0x29 |
| b892 | 0x4a |
A second layer of the same rolling XOR is used for the embedded driver blob inside the scheduler. The driver blob (0x4918 bytes at file offset 0x14369) uses seed -10 (0xf6) and produces a valid MZ PE when decoded.
Persistence and installation
The scheduler creates a deployment directory at ProgramData\Microsoft\WindowsTelemetry and copies dashost.exe, VERSION.dll, scheduler.cache, and cache.db into it. It registers services under the names "WinHealthSvc" and "Windows Diagnostics Service", and adds a Run key entry. System-wide and user-level mutexes (Global\SysMtx_51FB4B7B, Global\UsrMtx_EAB7CD0B) prevent multiple instances.
Defender tampering
Before the driver path, the scheduler disables Windows Defender controls:
- Process and path exclusions for the deployment directory
- Real-time protection policy values set to disabled
- MAPS/SpyNet submission policy changed
- Security Center notification flags disabled
- PowerShell Add-MpPreference fallback commands for cases where direct registry manipulation fails
BYOVD: embedded driver and callback removal
The scheduler carries an encrypted GLCKIo/WinIo-style vulnerable driver signed with a 2014 compile timestamp (PDB: D:\tmp\GLKIo_git\x64\Win7Debug\Drv.pdb).
| Driver property | Value |
|---|---|
| Device | \Device\GLCKIo |
| Symlink | \DosDevices\GLCKIo |
| IOCTL base | 0x80102040 |
| IOCTL surface | port I/O, physmem map/unmap, rdmsr, wrmsr |
| Size | 18,712 bytes |
The scheduler:
- Disables
VulnerableDriverBlocklistEnableunderSYSTEM\CurrentControlSet\Control\CI\Config(REG_DWORD 0). - Writes the decrypted driver to disk as an
EneTmp*named .sys. - Creates and starts the service via SCM.
- Enumerates loaded kernel modules via NtQuerySystemInformation (class 0x0b) and locates the kernel base.
- Parses ntoskrnl.exe exports for PsSetCreateProcessNotifyRoutine, PsSetCreateThreadNotifyRoutine, and PsSetLoadImageNotifyRoutine.
- Follows relative branch/call targets in the export stubs to find the callback arrays.
- Uses the driver's physical memory map IOCTL (0x80102040) to map kernel pages, writes zero over matching callback entries, then unmaps with IOCTL 0x80102044.
The scanner walks up to 0x40 entries per callback array and targets entries whose callback module name matches an embedded list. The effect is that security product kernel callbacks are silently removed, not blocked at the usermode level.
Loading the RAT
After the BYOVD path completes, the scheduler loads cache.db, decodes it with the same rolling-XOR scheme (seed varies by archive), and calls its exported entrypoint (StartPayload).
Stage 2: RAT core
The decoded cache.db is a 64-bit PE exporting StartPayload. It is the command-and-control layer with a custom protocol over raw TCP.
C2
- 101.32.190[.]202:8080
Passive enrichment showed the IP in Hong Kong (AS132203, Tencent/Aceville infrastructure). Shodan indexed SMB (445), RDP (3389), and WinRM (5985) on the same host but did not see 8080 open. NTLM data from RDP/WinRM points to Windows Server 2019 / Windows 10 build 10.0.17763 hosts. VirusTotal shows 3 malicious and 1 suspicious detections for the IP. GreyNoise, ThreatFox, Feodo, FireHOL, IPsum, and Pulsedive do not currently cluster it as known malicious infrastructure.
Protocol
The 12-byte packet header:
| Offset | Size | Field |
|---|---|---|
| 0x00 | 4 bytes | Magic: 10FX (0x58463031) |
| 0x04 | 4 bytes | Payload length (little-endian DWORD) |
| 0x08 | 4 bytes | Packet type (little-endian DWORD) |
| 0x0c | varies | Payload |
Recovered packet types:
| Type | Name | Behaviour |
|---|---|---|
| 0x00 | Echo/ack | Sends received body back as type 0 |
| 0x02 | Shell command | Copies payload to string, starts ShellExecThread |
| 0x08 | Binary input | Calls HandleBinaryInput |
| 0x10 | JSON task dispatch | Calls DispatchJsonTask |
| 0x21 | Encrypted plugin | Calls ParseAndLoadPayload |
| 0x30 | SOCKS tunnel | Calls Socks5HandleTunnelData |
Connection lifecycle
StartPayload resolves APIs dynamically, initializes direct syscall helpers, binds Winsock, and loads any cached plugins from disk. It connects to the hardcoded C2, sends a registration JSON block as packet type 1, spawns a heartbeat thread, enters a receive loop, and reconnects after a 5-second sleep on disconnect.
Built-in JSON task surface
The dispatcher (DispatchJsonTask) first checks for a mapped plugin handler. If one exists, the plugin handles the task. If no plugin is mapped, the built-in switch takes over. Plugin payloads were not present in the recovered archives, so the built-in set below is what was confirmed in this build:
| Task | Function | Payload fields |
|---|---|---|
| SHELL_EXEC | cmd.exe /c via ShellExecThread | command: string |
| GET_PROCS | Process enumeration | none |
| KILL_PROC | TerminateProcess | pid: number |
| FREEZE_PROC | NtSuspendProcess | pid: number |
| UNFREEZE_PROC | NtResumeProcess | pid: number |
| GET_SERVICES | EnumServicesStatus | none |
| CONTROL_SERVICE | Start/stop/pause/resume | serviceName, action |
| GET_WINDOWS | EnumWindows | none |
| CONTROL_WINDOW | Close/minimize/maximize/restore/hide/show | handle, action |
| GET_SYS_SUMMARY | Host fingerprint | none |
| GET_STARTUP_ITEMS | Startup enumeration | none |
| GET_SOFTWARE_LIST | Installed software (Uninstall key) | none |
| GET_NET_CONNECTIONS | GetExtendedTcpTable/UdpTable | none |
| SWITCH_DISPLAY | Select monitor for screen capture | displayId: number |
| START_CAMERA | Stub - returns "not implemented" | none |
| STOP_CAMERA | Stub - returns {"ok":true} | none |
| PLUGIN_QUERY | Lists loaded plugin names and count | none |
| SOCKS5_START | Initializes SOCKS relay state | none |
| SOCKS5_STOP | Stops SOCKS relay and cleans up | none |
| SELF_UPDATE_PAYLOAD | Base64-decode and replace binary | data: base64 string |
| SELF_RESTART | Triggers restart flow | none |
| CHANGE_SERVER_IP | Patches C2 address in cache.db | ip, port |
Additional task names present in the binary but routed through the plugin system include HDESK_CREATE, HDESK_DESTROY, HDESK_START_STREAM, HDESK_MOUSE, HDESK_KEY, HDESK_RUN, HDESK_SCREENSHOT, START_KEYLOGGER, STOP_KEYLOGGER, GET_KEYLOG_HISTORY, CRYPTO_SWAP_START, CRYPTO_SWAP_STOP, CLEAN_TRACES, WIPE_CREDENTIALS, ELEVATE, GET_DRIVES, GET_DISPLAYS, CLIPBOARD_SET, and TG_ENUM. These require a plugin DLL to be loaded before they function. No plugin payload was recovered in this case.
Screen capture
Screen capture is built into core, not plugin-dependent. It uses GDI/GDI+ to capture the selected display as JPEG. Frames can optionally be compressed with RtlCompressBuffer. The marker byte at frame start is 0 for uncompressed or 1 for compressed, followed by the original uncompressed length DWORD.
SOCKS5 relay
SOCKS tunnelling is multiplexed over the existing RAT C2 connection. The implant maintains a 64-slot tunnel table. Tunnel frames are carried inside packet type 0x30:
| Tunnel offset | Size | Field |
|---|---|---|
| 0x00 | 4 bytes | Tunnel ID |
| 0x04 | 1 byte | Command: 0x01 connect, 0x02 relay, 0x03 close |
| 0x05 | varies | Payload |
For connect commands, the implant supports SOCKS IPv4 (type 0x01) and domain (type 0x03) targets. It calls getaddrinfo, creates a socket with a 10 second timeout, and connects directly from the infected host. No HTTP CONNECT, TLS wrapping, or domain fronting was observed in the relay path.
Plugin format
Encrypted plugins arrive as packet type 0x21 and are parsed by ParseAndLoadPayload:
| Offset | Field |
|---|---|
| 0 | Plugin name length (1-31) |
| 1..name_len | Plugin name string |
| name_len+1 | XOR key length |
| next key_len | XOR key |
| next 4 bytes | Encrypted payload length (LE) |
| remaining | Encrypted payload (repeating XOR: payload[i] ^= key[i % key_len]) |
After decryption the plugin is loaded from memory as a reflective DLL and the decrypted copy is zeroed before freeing.
Detection
A cluster YARA rule covering this chain is available:
The public YARA rule covers four parts of this case: VERSION.dll sideloader artifacts, raw rolling-XOR cache blobs, the decoded BYOVD scheduler PE, and the decoded 10FX RAT core.
IOCs
Network
- 101.32.190[.]202
- 101.32.190[.]202:8080
Host artifacts
- ProgramData\Microsoft\WindowsTelemetry
- WindowsTelemetry (directory and filename pattern)
- WinHealthSvc (service name)
- Windows Diagnostics Service (service name)
- Global\SysMtx_51FB4B7B (mutex)
- Global\UsrMtx_EAB7CD0B (mutex)
- dashost.exe (side-loaded binary)
- VERSION.dll (sideload payload)
- scheduler.cache (encrypted stage 1)
- cache.db (encrypted RAT payload)
- EneTmp*.sys (written vulnerable driver at runtime)
Registry/behavioural
- HKLM\SYSTEM\CurrentControlSet\Control\CI\Config\VulnerableDriverBlocklistEnable = 0 (DWORD)
- Defender exclusion policies for the deployment directory
- Real-time protection disable values under Windows Defender policy
- Security Center notification suppression
- \Device\GLCKIo (driver device object)
- \DosDevices\GLCKIo (driver symlink)
Protocol
- 10FX packet magic at TCP layer
- 12-byte header: magic DWORD, length DWORD, type DWORD
- Rolling XOR encoding with per-file seed byte
Hashes (all SHA256)
- 0fb VERSION.dll: 62431e499db7c6a02e93c5f9c79fbcff954144db1b016695d3f34f30c89d0b44
- b892 VERSION.dll: 0ea1335fefc490622dae07b1a5936a539fa4152f89b64f4b270c8e23846deba6
- Decoded scheduler: c07573810f5f4578315681ca9108ada8a56eefc1b4786b4e93b54b7abf4b028c
- Decoded RAT: 0f841b7bddf9788589fce191bb3e7f9f52ec76adb67ff8c360618df8745ee320
- Embedded driver: 38c18db050b0b2b07f657c03db1c9595febae0319c746c3eede677e21cd238b0