VioletWorm v4.7 (Violet RAT): The Most Dangerous Payload in a 9-RAT Toolkit
We recovered a .NET assembly from the same multi-stage intrusion that produced PureLogs and PureCrypter. Across six parallel attack chains, the actor deployed nine RAT families. This one is the largest payload, has the widest capability set, and sits on separate infrastructure. The actor's internal label for it was Viooooooo.
Tria.ge classifies this family as violetworm. We use VioletWorm as the canonical name throughout this post and use Violet RAT only where campaign artifacts use that naming.
Sample overview
| Field | Value |
|---|---|
| Family | violetworm (aka Violet RAT) |
| Version | v4.7 |
| Original filename | VioletClient.exe |
| Internal label | Viooooooo / vio |
| SHA256 (Nov10) | d656bcfefa98007fcb3e2be4430a9b24d258c046b0c768ac43699436aceb98e6 |
| Size (Nov10) | 1,432,360 bytes |
| SHA256 (Nov19) | e2c5921e5c354000c38653d05ac3255865d20bc986da34e68246da6f72ec1fed |
| Size (Nov19) | 1,425,920 bytes |
| SHA256 (Nov24) | cbd4cd4c42b3ee0bfb99a254a97ff2c2aacc47d83e08601ae8cdf3b333f07806 |
| Size (Nov24) | 1,029,120 bytes |
| Type | .NET assembly (PE32, Mono) |
| Framework | .NET Framework 4.0 |
| Campaign overlap | SERPENTINE#CLOUD (Securonix) -- shared tooling and TTPs, but Securonix reporting does not mention VioletWorm |
| Assessment | Most dangerous payload in the toolkit |
The assembly is the inner payload of a multi-stage loader chain. Python droppers handle delivery. AES-256-CBC or RC4 protects the outer layer. Donut v0.9.2 shellcode handles CLR bootstrap. The final stage runs in memory through reflective assembly loading.
Three builds were recovered. The Nov10 build is the primary analysis target. The Nov19 build is a recompile with the same configuration and command set. The Nov24 build is distinct: different C2 endpoint (vigroup2125.duckdns.org:2125), different XOR keys, and a different mutex. It was recovered from an unobfuscated developer artifact.
Classification and Tria.ge corroboration (2026-02-25)
Tria.ge gives independent classification and runtime support for the three payload assemblies and three loader scripts recovered in this intrusion.
| Sample | Type | Tria.ge ID | Score | Family | Status |
|---|---|---|---|---|---|
| VioletWorm Nov10 payload | payload | 260225-st7zrsbz6d | 10 | violetworm | reported |
| VioletWorm Nov19 payload | payload | 260225-st8ajabz6e | 10 | violetworm | reported |
| VioletWorm Nov24 payload | payload | 260225-st8w3abz6f | 10 | violetworm | reported |
| VioletWorm Nov10 loader | loader | 260225-syqahsb15d | 3 | - | reported |
| VioletWorm Nov19 loader | loader | 260225-syq7tab15f | 3 | - | reported |
| VioletWorm Nov24 loader | loader | 260225-syrtcab15g | 3 | - | reported |
All three payload submissions were classified as violetworm with score 10 across static and behavioral tasks. Loader submissions still showed behavior but did not receive family attribution. A Tria.ge search for family:violetworm currently shows samples dating back to 2025-11-28T02:07:01Z (251128-cjy14sylcr).
Tria.ge task telemetry corroborated both C2 endpoint sets already recovered from reversing:
45.58.143.254:7575for Nov10/Nov19 payloads (vijdklet.duckdns.org)- Tria.ge runtime endpoint
213.227.152.82:2125for the Nov24 payload (vigroup2125.duckdns.org); breach-report infrastructure attribution for this domain remainsTBD
Tria.ge sample links:
- Nov10 payload: https://tria.ge/260225-st7zrsbz6d
- Nov19 payload: https://tria.ge/260225-st8ajabz6e
- Nov24 payload: https://tria.ge/260225-st8w3abz6f
- Nov10 loader: https://tria.ge/260225-syqahsb15d
- Nov19 loader: https://tria.ge/260225-syq7tab15f
- Nov24 loader: https://tria.ge/260225-syrtcab15g
Architecture: massive command dispatcher with C2-delivered plugins
VioletWorm (Violet RAT) is a command dispatcher. The main binary contains an 8,000-line IL handler with 120 decoded command branches. Capabilities such as ransomware, HVNC, keylogging, file management, and credential theft are implemented in plugin DLLs delivered by C2 at runtime. The binary loads those plugins with Assembly.Load(), instantiates Class1, and calls methods through VB.NET late binding (NewLateBinding.LateCall).
This makes it structurally closer to PureLogs than it first appears. Both receive capabilities from C2. The difference is scale. PureLogs has a handful of commands and a small dispatcher. VioletWorm covers remote shell, file manager, keylogger, screenshot streaming, active window monitoring, screen control, HVNC, remote desktop, webcam/mic/audio capture, DDoS, clipboard hijacking, crypto clipping, password stealing, network recon, TCP connection monitoring, Discord grabbing, ransomware, process injection, USB spreading, ngrok tunneling, live chat with the victim, AV enumeration, UAC elevation, and Windows Defender tampering.
At 1.4 MB, it is the largest payload in the toolkit -- 10x larger than AsyncRAT (52 KB) and 3x larger than VenomRAT (70 KB). The bulk is the dispatcher itself and 11 large base64-encoded data blobs stored in the .NET User Strings heap. These blobs are initialized in the config class constructor but never referenced by the main binary's code -- they are likely accessed via reflection by C2-delivered plugins at runtime.
String encoding scheme
All strings in the .NET User Strings (#US) heap are base64 encoded. VioletWorm uses two encoding tiers:
2-layer encoding (plaintext strings): plaintext -> base64 -> base64. These cover configuration fields, command names, registry paths, and file paths. Decoding two layers of base64 yields the plaintext directly.
3-layer encoding (XOR-obfuscated strings): plaintext -> XOR(key) -> base64 -> base64 -> base64. These cover C2 infrastructure, sensitive command identifiers, and User-Agent strings. Three layers of base64 yield XOR-encrypted bytes that require the key to decode.
XOR key derivation
The key is self-contained in the binary. String index [24] in the #US heap stores the key through the same triple-base64 encoding:
Stored: ZVc1T1JVNUpUQT09
1x base64: eW5ORU5JTA==
2x base64: ynNENIL
Key: ynNENIL (7 bytes: 0x79 0x6e 0x4e 0x45 0x4e 0x49 0x4c)
XOR decryption is byte-wise with key recycling: plaintext[i] = ciphertext[i] ^ key[i % 7].
Verification: the known plaintext uninstall XOR'd with the key matches the bytes stored (after triple-base64) at string index [64]. Once the key is recovered, every XOR-encoded string in the binary decodes immediately.
C2 protocol
Transport
HTTP POST over TCP. No TLS. The C2 address and port are XOR-encoded in the binary.
| Parameter | Value |
|---|---|
| C2 domain | vijdklet.duckdns.org |
| C2 port | 7575 |
| Protocol | HTTP POST |
| Content-Type | application/x-www-form-urlencoded |
| Field separator | XSXSXSX |
| Keepalive | PING? / PONG |
Field separator
All C2 messages use XSXSXSX as the initial field separator in the POST body. Command arguments, file paths, and data chunks are concatenated with this delimiter. The separator is initialized from a 2-layer base64 string in the config, but it is mutable -- the C2 server rotates it mid-session. Since the AES encryption key is derived from MD5(UTF8.GetBytes(separator)), rotating the separator also rotates the encryption key.
Keepalive
The RAT sends PING? at regular intervals. The server responds PONG. If the keepalive fails, the client reconnects.
User-Agent rotation
Six hardcoded User-Agent strings rotate across requests:
| # | Platform |
|---|---|
| 1 | iPhone / Safari / iOS |
| 2 | Windows / Chrome / Edge |
| 3 | Mac / Safari / macOS |
| 4 | Linux / Firefox |
| 5 | iPad / Safari / iPadOS |
| 6 | Windows / alternate |
All six are XOR-encoded (3-layer base64). The rotation makes traffic pattern matching harder -- each request appears to come from a different browser and operating system.
Microsoft URL whitelist
VioletWorm checks outbound URLs against a whitelist of Microsoft domains. If the destination matches any of the following, the request is excluded from interception:
windowsupdatewinatp-gw-cuswatsonmsedgego.microsoft.comactivation.sls
These are telemetry and update endpoints. Whitelisting them prevents the RAT from intercepting legitimate Windows traffic.
Configuration
Extracted fields from the decoded binary:
| Field | Value | Source |
|---|---|---|
| C2 domain | vijdklet.duckdns.org | XOR-encoded, index [18] |
| C2 port | 7575 | XOR-encoded, index [19] |
| Version | Violet v4.7 | Base64, index [37] |
| Tag | <Violet> | Base64, index [21] |
| XOR key | ynNENIL | Base64, index [24] |
| Mutex (single-instance) | aXTyo1HpFXkKUYoL | Base64, index [25] |
| Mutex GUID #1 | 3d847c5c-4f5a-4918-9e07-a96cea49048d | Base64, index [43] |
| Mutex GUID #2 | 89c43fcf-5e52-4be7-a719-a26139ce636a | Base64, index [50] |
| USB spreader | USB.exe | Base64, index [22] |
| Separator (initial) | XSXSXSX | Base64, index [20]; rotated by C2 at runtime |
| PasteUrl | %PasteUrl% (placeholder, unused) | Base64, index [0] |
| Install filename | 3d847c5c-4f5a-4918-9e07-a96cea49048d.exe | Derived from mutex GUID |
| Cleanup script | WinTempClean32.bat | Base64, index [41] |
| Tunneling | \ngrok.exe | Path reference |
Persistence targets:
HKEY_CURRENT_USER\SOFTWARE\-- Run key persistenceHKEY_LOCAL_MACHINE\software\classes\-- COM/class registration abuse
Three mutex values serve different purposes. aXTyo1HpFXkKUYoL is the primary single-instance lock -- despite looking like an AES-128 key, IL tracing confirms it's only used in new Mutex(false, "aXTyo1HpFXkKUYoL", out bool). The first GUID (3d847c5c...) doubles as the install filename. The second GUID (89c43fcf...) is a secondary instance check.
Capabilities
The dispatcher has 120 command branches, each decoded from double-base64 + XOR-encoded constants in the IL. Below are the ones with enough context to describe. The full command map is in the reference documentation.
Remote shell
| Command | Function |
|---|---|
shellfuc | Open interactive shell (CMD.EXE) |
runnnnnn | Execute command in shell |
closeshell | Terminate shell session |
File manager
| Command | Function |
|---|---|
showfolderfile | List directory contents |
creatnewfolder | Create directory |
creatfile | Create file |
downloadfile | Exfiltrate file to C2 |
Execute | Run file |
Rename | Rename file |
viewimage | View image file |
Delete / DelP | Delete file or path |
7zIT / 7zzip | Compress files with 7zip |
The file manager includes a bundled 7zip integration (7zip\7z.exe) for compressing files before exfiltration.
Keylogger
| Command | Function |
|---|---|
KL | Start keylogger |
KLget / KLGET | Retrieve captured keystrokes |
closeKL | Stop keylogger |
Keylog data is stored under HKEY_CURRENT_USER\SOFTWARE\ -- the same registry path used for persistence.
Screen control and HVNC
| Command | Function |
|---|---|
HVNC / HvNcX | Hidden VNC session (invisible remote desktop) |
hvncxdis | Disconnect HVNC |
shwup | Display fake "Windows Update" screen overlay |
hidup | Remove the fake overlay |
BSOD | Trigger Blue Screen of Death |
While the victim sees "Installing updates, please wait..." the operator has full control of the hidden desktop.
Webcam, microphone, and audio
| Command | Function |
|---|---|
WBCM | Webcam capture |
MICL | Microphone listener |
Wsound | System audio capture |
These three are the only cached plugins. On first use, the C2 sends the plugin DLL and the RAT stores the raw bytes in a static field. Subsequent calls reload from cache instead of re-requesting from the C2.
Screenshot capture
| Command | Function |
|---|---|
RSS | Start remote screenshot stream |
RSSDis | Stop screenshot stream |
Screenshots are captured as JPEG (image/jpeg) with configurable region bounds (X, Y, Width, Height). The stream commands suggest live screen monitoring, not just one-off captures.
Active window monitoring
| Command | Function |
|---|---|
ACT | Start active window tracking |
ACTG | Get active window title |
killAct | Stop active window tracking |
Passive surveillance -- tracks which application the victim is using without capturing keystrokes. The operator can watch for banking sites, email clients, or crypto wallets opening and then trigger other modules (keylogger, screenshot, HVNC) at the right moment.
DDoS
| Command | Function |
|---|---|
DDosS | Start DDoS attack |
DDosT | Stop DDoS attack |
An unusual capability for a RAT deployed in a targeted intrusion. The infected machines can be enrolled as DDoS nodes on demand.
Clipboard and crypto clipper
| Command | Function |
|---|---|
Cilpper | Enable clipboard monitor / crypto clipper |
clss | Clear clipboard |
The crypto clipper monitors the clipboard for cryptocurrency wallet addresses and silently replaces them with attacker-controlled addresses. The dispatch command is misspelled -- Cilpper instead of Clipper. A correctly-spelled copy exists at string index [104], but the dispatcher uses the XOR-encoded misspelling at index [103].
Network reconnaissance and sniffer
| Command | Function |
|---|---|
NetDisCV | Network discovery scan |
SnifStrt | Start network sniffer |
SniffKll | Stop sniffer |
TCPV | View active TCP connections |
TCPG | Get TCP connection details |
Ransomware
| Command | Function | Plugin method | Args |
|---|---|---|---|
ENC | Encrypt target file | ENC(hwid, path) | 2 |
DEC | Decrypt target file | DEC(hwid, path) | 2 |
RENC | Recursive directory encryption | ENC(hwid, ...params) | 6 (5 ByRef) |
RDEC | Recursive directory decryption | DEC(hwid) | 1 |
All four commands are confirmed in the IL command dispatcher. Like every other capability, the actual encryption logic lives in a C2-delivered plugin DLL.
The first argument to every call is a machine fingerprint: MD5(ProcessorCount + UserName + MachineName + OSVersion + DriveSize), output as a 32-character lowercase hex string. This HWID is the primary cryptographic input to the plugin -- RDEC receives it as its only argument, meaning the plugin needs nothing else from the RAT to reverse the encryption. The actual encryption algorithm and key derivation are inside the C2-delivered plugin DLL, which we do not have.
ENC and DEC are single-file operations: the C2 sends a plugin DLL and a target path. RENC and RDEC operate on directory trees and are governed by a state machine. A guard flag prevents concurrent execution:
RENCarrives -- flag set to 1 (encrypting), plugin loaded,ENC()called with 6 arguments (5 ByRef, allowing the plugin to return status data)- Plugin returns -- flag set to 2 (encryption complete)
RDECarrives -- only executes if flag is exactly 2, then callsDEC(hwid), resets flag to 0
This means the operator must complete encryption before decryption is possible. The state machine enforces the ransom workflow: encrypt first, decrypt only after.
Persistence and AV evasion
| Command | Function |
|---|---|
install | Install persistence (via plugin) |
uninstall | Remove persistence (via plugin) |
update | Update RAT binary |
WDKillerNew | Disable Windows Defender |
WDPL | Windows Defender plugin (separate from WDKillerNew) |
AntiiReset | Survive system reset |
PSleep | Prevent system sleep |
askuac | Prompt for UAC elevation |
bot | Bot control |
admin | Admin privilege check |
kill | Kill process |
JMar / JMarKLL / SysKLL | Process killing variants |
Before persisting, the RAT queries Select * from AntivirusProduct via WMI (namespace \root\SecurityCenter2) to enumerate installed security products.
It also sets ShowSuperHidden to 0 under Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced. This hides protected OS files in Explorer -- the RAT's dropped files become invisible even with "Show hidden files" enabled.
WDKillerNew kills Windows Defender. AntiiReset survives system reset attempts.
USB spreading
| Command | Function |
|---|---|
USB.exe | USB worm payload name |
WinBIt32-* | USB worm variant name pattern |
The USB spreader creates .lnk shortcut files on removable drives using WScript.Shell COM automation. Each shortcut has its TargetPath set to cmd.exe with arguments like /c start [payload] & start explorer [folder] & exit -- the payload runs, then the original folder opens so the victim sees what they expected. Icon paths are read from HKEY_LOCAL_MACHINE\software\classes\folder\defaulticon\ so the shortcuts look like normal folders.
XWorm v3.1 from the same campaign uses an identical USB.exe spreading mechanism.
Ngrok tunneling
| Command | Function |
|---|---|
ngrok | Ngrok tunnel connection |
InstallN | Install ngrok binary |
Ngrok provides reverse proxy tunneling for C2 redundancy. If the primary DuckDNS domain is blocked, the operator can route C2 traffic through ngrok.
Live chat
| Command | Function |
|---|---|
LLCHAT | Start live chat session |
Xchat | Extended chat |
SNote | Send note to victim |
Real-time text chat between the operator and the victim. The operator can impersonate IT support, demand ransom, or tell the victim to take specific actions mid-intrusion.
Other commands
| Command | Function |
|---|---|
hrdp / hrdp+ / RD- / RD+ | Remote desktop (connect, extended, disconnect, reconnect) |
PassR / PC# | Password recovery |
Email | Email credential theft |
GrabberDC | Discord token theft |
CookieST / coki | Browser cookie stealing |
injRun | Inject and run payload in target process |
PE | PE file execution |
Pvbnet / vbb / cbb | VB.NET payload execution variants |
script | Script execution |
regfuc | Registry operations |
openhide | Hidden browser instance via internetexplorer.application COM |
NETINS | Network install |
FURL | URL fetch |
UPtoS | Upload to server |
MapsPLU | Maps plugin |
JustFun | Unknown (likely nuisance function) |
### / $$$ / ^^^& / ||| | Signal/control codes |
Plugin loading architecture
Every plugin DLL arrives from the C2 server. The loading path is:
- AES decrypt the incoming TCP stream (AES-128-ECB, key = MD5 of the separator)
- UTF-8 decode the plaintext bytes
- Split by the separator (
XSXSXSX) --A[0]= command name,A[1]= plugin DLL bytes (base64) - Base64 decode
A[1]to raw bytes Assembly.Load()the decoded bytes, get modules, get types,CreateInstance("Class1")NewLateBinding.LateCall()the target method on Class1
IL disassembly confirms 46 plugin loader call sites in the command handler. 43 load the plugin directly from C2 command data (A[1]). Three -- webcam, microphone, and audio -- cache plugin bytes in static fields and reload from cache on subsequent calls. All three cached plugins call method CON on Class1.
Separator rotation
The separator is not static. Six stsfld write instructions in the command handler update it to values received from the C2 server, which means the AES key also changes mid-session.
Infrastructure
The Nov10/Nov19 builds use vijdklet.duckdns.org:7575, which resolves to 45.58.143.254 (Sharktech, Amsterdam NL). This is the same IP hosting two PureLogs variants:
| RAT | Domain | Port |
|---|---|---|
| VioletWorm v4.7 (Violet RAT, Nov10/Nov19) | vijdklet.duckdns.org | 7575 |
| VioletWorm v4.7 (Violet RAT, Nov24) | vigroup2125.duckdns.org | 2125 |
| PureLogs (Mvfsxog.dll) | ydspwie.duckdns.org | 9045 |
| PureLogs (Qdjlj.dll) | nhvncpure*.duckdns.org | 6757-6759, 8075-8076 |
The Nov24 build uses a second domain (vigroup2125) on a different port (2125). The naming convention is consistent: vi- prefix for VioletWorm domains, like vijdklet.
Three RAT groupings ran on separate infrastructure. The Sharktech Amsterdam server was split from the ServerAstra Budapest cluster (91.219.238.167) that hosted four commodity RATs (DcRat, AsyncRAT, VenomRAT, Remcos). XWorm V3.1 ran on a third server (12.202.180.133, AT&T, Baltimore MD). The layout separates operations: VioletWorm and PureCoder tools shared one server, commodity RATs shared another, and XWorm remained isolated.
As of February 2026, port 7575 (VioletWorm) is still accepting connections on 45.58.143.254.
Build comparison
| Property | Nov10 build | Nov19 build | Nov24 build |
|---|---|---|---|
| SHA256 | d656bcfe...aceb98e6 | e2c5921e...72ec1fed | cbd4cd4c...f07806 |
| Assembly size | 1,432,360 bytes | 1,425,920 bytes | 1,029,120 bytes |
| Shellcode size | 1,464,594 bytes | 1,722,111 bytes | 1,067,778 bytes |
| Loader encryption | AES-256-CBC + 2x XOR | RC4 (per-file key) | AES-256-CBC + 2x XOR |
| Injection method | explorer.exe APC | In-process CFUNCTYPE | explorer.exe APC |
| C2 domain | vijdklet.duckdns.org | vijdklet.duckdns.org | vigroup2125.duckdns.org |
| C2 port | 7575 | 7575 | 2125 |
| XOR dispatch key | ynNENIL | ynNENIL | XSRSXSX |
| Mutex | aXTyo1HpFXkKUYoL | aXTyo1HpFXkKUYoL | H3n0qlXPeIv1umQI |
The Nov10 and Nov19 builds share the same C2 endpoint and XOR key -- the attacker recompiled between waves (assembly size changed by 6,440 bytes) but didn't change the configuration. The Nov24 build is a distinct compilation: different C2 endpoint (vigroup2125 instead of vijdklet), different XOR keys, different mutex, and 400 KB smaller. It was recovered from an unobfuscated developer artifact (1uunov24.py), one of only two unobfuscated loaders in the entire campaign.
Deployment history
| Wave | Loader filename | Encryption | Injection | C2 |
|---|---|---|---|---|
| Wave 2 (Nov 10) | BKSNOLazyNov10_Viooooooo.py | AES-256-CBC + 2x XOR | explorer.exe APC | vijdklet:7575 |
| Wave 3 (Nov 10) | BKNOLazyNov10_Viooooo.py | AES-256-CBC + 2x XOR | explorer.exe APC | vijdklet:7575 |
| Wave 4 (Nov 19) | 1vio-obf.py | RC4 key NOpzga4k | In-process CFUNCTYPE | vijdklet:7575 |
| Nov 24 | 1uunov24.py (dev artifact) | AES-256-CBC + 2x XOR | explorer.exe APC | vigroup2125:2125 |
| Wave 5 (Nov 19) | OBKSLazyNov20_vio.py | RC4 key NQzn2pMo | In-process CFUNCTYPE | vijdklet:7575 |
| Wave 5 (Nov 19) | WBKSLazyNov20_vio.py | RC4 key Hs3TFDx1 | In-process CFUNCTYPE | vijdklet:7575 |
| Dec 2 | 1Dec2vio.py | Kramer .pyc RC4 | In-process CFUNCTYPE | vijdklet:7575 |
| Wave 7 (Dec 16) | via Shoopify.bat | Same payload | Same | vijdklet:7575 |
VioletWorm was deployed in every wave from Wave 2 onward. Eight separate loaders with different encryption keys deliver the RAT across two C2 endpoints. The Nov24 1uunov24.py is an unobfuscated developer artifact -- one of only two in the campaign -- and is the only loader that delivers the vigroup2125 build.
Attribution
VioletWorm (also tracked as Violet RAT) is a separate MaaS product, not part of the PureCoder suite (PureLogs, PureCrypter, PureHVNC, PureMiner, PureRAT). The threat actor purchased tools from at least two MaaS vendors and deployed them together.
The C2 IP 45.58.143.254 is shared exclusively with PureLogs variants from the same campaign. No other RATs in the toolkit use this server. The attacker put VioletWorm and the PureCoder tools on one server (Sharktech Amsterdam) and the commodity RATs on another (ServerAstra Budapest). That's an operational choice by the buyer, not a developer-level connection between the tools.
VioletWorm is delivered through the same Python multi-stage loader infrastructure used by the other RATs in this campaign: same Donut shellcode version, same AES/XOR scheme, same injection methods, and the same Kramer obfuscator in later waves.
The attacker's internal label Viooooooo follows the same pattern as Ploggggggg (PureLogs), Asyncccc (AsyncRAT), Venommm (VenomRAT), and Annorii (DcRat) -- repeated last characters as padding. VioletWorm was always deployed alongside PureLogs. Both appear in the same loader sets, staging directories, and batch launchers.
VirusTotal classification
Neither sample had been submitted to VirusTotal before this analysis. On first submission, both scored 40-41/71. No vendor identifies it by name -- the dominant label is Gen:Variant.MSILHeracles, a generic Bitdefender heuristic for suspicious .NET binaries. ESET classifies both as MSIL/XWorm.L. IL comparison against an XWorm client confirms this is correct -- VioletWorm is a fork of XWorm with an expanded command set and different string encoding.
XWorm lineage
We compared VioletWorm's IL disassembly (27,623 lines) against an XWorm client sample (11,893 lines, SHA256 ced525930c76834184b4e194077c8c4e7342b3323544365b714943519a0f92af) across 10 structural dimensions. Five produced byte-identical IL sequences. Four were structurally similar. One differed.
Byte-identical IL:
- AES implementation: Both use
RijndaelManaged+MD5CryptoServiceProvider+ldc.i4.2(CipherMode.ECB) with no IV. The encrypt, decrypt, and config-decryption methods match opcode-for-opcode. - HWID generation: Both build a 5-element object array (
ProcessorCount,UserName,MachineName,OSVersion,DriveInfo.TotalSize), concatenate it, and pass it to an MD5 hash function. The IL from offset0000through004eis identical in both binaries. Both use"Err HWID"as the fallback string. - Mutex creation:
new Mutex(false, name, out bool). The close/dispose sequence (null-check,Close(), set null) also matches byte-for-byte. - C2 socket setup:
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)withReceiveBufferSizeandSendBufferSizeboth set to0xc800(51,200 bytes), followed byBeginReceivefor async I/O. Identical from the constructor through the buffer configuration. - Language: Both are VB.NET, not C#. Both reference
Microsoft.VisualBasicas their first external assembly, useOperators.CompareStringfor string comparisons, and include identicalMy.MyApplication/My.MyComputer/My.MyProjectscaffolding from the VB compiler.
Structurally similar:
- Plugin loading: Both use
Assembly.Load(byte[])for in-memory plugin execution. XWorm searches for a type named"Plugin"with a method named"Run". VioletWorm searches byFullNamesuffix and usesCreateInstance(). - Command dispatch: Both use a long if/else chain comparing
A[0]against command constants viaCompareString. XWorm stores commands as plaintext strings (~20 commands). VioletWorm XOR-encodesA[0]with keyynNENILand compares against double-base64 constants (120 commands). - Class structure: 1:1 mapping between core classes -- settings/config, entry point, connection handler, command dispatcher, utilities (crypto + HWID + mutex), persistence. Same functional decomposition, different names.
- Config class: Both use a single static class with a
.cctorthat initializes all fields. XWorm stores AES-encrypted base64 values (14 fields). VioletWorm stores 2-layer base64 values (30+ fields, withGooglesuffix appended to all names).
Different:
- String encoding: XWorm encrypts config strings with AES-ECB using a key stored in the binary. VioletWorm replaced this with 2-layer base64 encoding (simpler, less secure). VioletWorm also added anti-decompiler marker classes (
ObfuscatedByGoliath,NineRays.Obfuscator,NetGuard,dotNetProtector,YanoAttribute,Xenocode) that XWorm lacks.
The fork relationship is clear. The VioletWorm author took XWorm's codebase, replaced string encryption with base64 encoding, added XOR command encoding, expanded the command set from about 20 to 120, and added HTTP-based C2 address updates before socket connection. Core infrastructure remained the same: AES, HWID, mutex, sockets, and VB.NET late binding.
Vendor reporting
None of the existing campaign reports cover VioletWorm specifically. The closest are:
| Vendor | Report | Coverage |
|---|---|---|
| Securonix | Analyzing SERPENTINE#CLOUD | Campaign with overlapping tooling and TTPs (does not mention VioletWorm) |
| Check Point Research | Under the Pure Curtain | PureCoder developer analysis (does not mention VioletWorm) |
| Fortinet | PureHVNC Deployed via Python Multi-stage Loader | Loader chain, PureHVNC component |
| eSentire | Quartet of Trouble | PureLogs delivery via TryCloudflare tunnels |
Indicators of compromise
Network
| Indicator | Type | Context |
|---|---|---|
vijdklet.duckdns.org | Domain | C2 server (Nov10/Nov19) |
vigroup2125.duckdns.org | Domain | C2 server (Nov24) |
45.58.143.254 | IPv4 | C2 IP (Sharktech, Amsterdam NL) |
45.58.143.254:7575 | Endpoint | Tria.ge behavioral endpoint (Nov10/Nov19 payloads) |
213.227.152.82:2125 | Endpoint | Tria.ge behavioral endpoint (Nov24 payload) |
7575/tcp | Port | C2 port (Nov10/Nov19) |
2125/tcp | Port | C2 port (Nov24) |
XSXSXSX | String | HTTP POST field separator |
PING? / PONG | String | Keepalive pattern |
Host
| Indicator | Type | Context |
|---|---|---|
d656bcfefa98007fcb3e2be4430a9b24d258c046b0c768ac43699436aceb98e6 | SHA256 | Nov10 assembly |
e2c5921e5c354000c38653d05ac3255865d20bc986da34e68246da6f72ec1fed | SHA256 | Nov19 assembly |
cbd4cd4c42b3ee0bfb99a254a97ff2c2aacc47d83e08601ae8cdf3b333f07806 | SHA256 | Nov24 assembly |
<Violet> | Mutex | RAT mutex tag |
3d847c5c-4f5a-4918-9e07-a96cea49048d | GUID | Mutex / install filename |
89c43fcf-5e52-4be7-a719-a26139ce636a | GUID | Secondary mutex |
VioletClient.exe | Filename | Original assembly name (from PE headers) |
ynNENIL | String | XOR dispatch key (Nov10/Nov19) |
XSRSXSX | String | XOR dispatch key (Nov24) |
aXTyo1HpFXkKUYoL | String | Mutex (Nov10/Nov19) |
H3n0qlXPeIv1umQI | String | Mutex (Nov24) |
USB.exe | Filename | USB spreader |
WinTempClean32.bat | Filename | Cleanup script |
\ngrok.exe | Path | Tunneling binary |
Behavioral
- HTTP POST traffic to port 7575 with
XSXSXSXfield separator - Tria.ge payload behavioral signature:
Suspicious use of AdjustPrivilegeToken - Tria.ge loader behavioral signatures:
Suspicious use of SetWindowsHookEx,Modifies registry class,Enumerates physical storage devices PING?/PONGkeepalive cycle- Rotating User-Agent strings across 6 platform profiles per session
- Registry writes under
HKEY_CURRENT_USER\SOFTWARE\andHKEY_LOCAL_MACHINE\software\classes\ - Hidden VNC session creation
.lnkfile creation on removable drives (USB spreading)- ngrok process execution
- Windows Defender tampering (
WDKillerNew) - File encryption operations matching ransomware behavior
- WMI query
Select * from AntivirusProduct(namespace\root\SecurityCenter2) ShowSuperHiddenregistry value set to 0 underExplorer\Advancedtaskkill.exe /pidfor targeted process termination- JPEG screenshot capture with region bounds
Conclusion
Nothing about this binary is hard to reverse. The XOR key is in the binary, the encoding is stacked base64, and the C2 protocol is plaintext HTTP POST with no TLS. The difficulty isn't obfuscation -- it's that the interesting code isn't here. The 8,000-line command handler is a switchboard. Every capability worth analyzing lives in a plugin DLL that only exists on the C2 server, delivered at attack time and loaded once into memory. We can map every command the RAT accepts, but we can't see what those commands actually do once the plugin takes over.
The 11 encrypted blobs in the config class make this worse. They're initialized in the constructor, never referenced by the dispatcher, and don't decrypt with any key in the binary. They're there for the plugins to read -- and without the plugins, they stay opaque.
This post is the third in the series. PureLogs covered the plugin stager and crypto-stealing fat client. PureCrypter covered the loader infrastructure. Remcos Banking Fraud & AutoIt Persistence covers the AutoIt-based persistence chains that kept everything alive -- including the discovery that PureHVNC shares exact C2 infrastructure with PureLogs. VioletWorm is the payload the attacker put on the Sharktech Amsterdam server alongside the information stealer, and the one they deployed in every wave from November onward.
Kirk
I like the internet. Want to get in touch? kirk@derp.ca