SERPENTINE Goes German: Dual RAT Deployment via a Custom Donut Variant With a Non-Standard Chaskey Counter
dcRAT + XenoRAT through a reverse-engineered Donut loader — 12 files, zero detections, IHK.de invoice decoy
A Cloudflare tunnel serving a WebDAV share named "Music." A Windows shortcut disguised as legal documents from the German Chamber of Commerce. Two completely different remote access trojans injected into the same victim through a Python-based process injector that had zero detections on any public sandbox. And underneath it all, a custom variant of the Donut shellcode framework with a non-standard Chaskey cipher implementation that required manual reverse-engineering to crack.
This is the third SERPENTINE#CLOUD wave we have tracked at Breakglass Intelligence. The first two -- a 5-day longitudinal rotation study (post #3699) and a multi-tunnel chain analysis (post #3708) -- documented an operator who rotates Cloudflare tunnel infrastructure daily and targets German-speaking businesses with invoice-themed lures. This wave marks a first: a fully German-language variant, purpose-built for German small business owners, deploying both dcRAT and XenoRAT v1.8.7 through a single infection chain. All 12 campaign files had zero VirusTotal detections at time of analysis.
The XenoRAT C2 server at 176.96.136[.]182 was live when we found it. It still is.
The Lure: "Rechtsdokumente" and the IHK Invoice
The entry point is a 1,351-byte Windows shortcut file: Rechtsdokumente.pdf.lnk. The name translates to "legal documents" -- a generic but effective pretext for German business correspondence. The LNK file uses a Microsoft Edge icon to masquerade as a PDF, a detail that matters because Edge is the default PDF viewer on modern Windows installations. To a busy Mittelstand office manager scanning their downloads folder, this looks like a legal PDF they need to open.
The shortcut's target is not a local file. It invokes wscript.exe against a remote WebDAV path, pulling brown.wsh from the actor's Cloudflare tunnel at ira-tom-shakespeare-understood[.]trycloudflare[.]com. This is the same delivery pattern we documented in posts #3699 and #3708 -- WsgiDAV/4.3.3 behind Cloudflare's free Quick Tunnel service, anonymous read-write access, the WebDAV root display name set to "Music" because the operator is sharing C:\Users\Administrator\Music from their staging VM.
The .wsh file is 118 bytes. It loads assist.js, a 292-byte JScript file that copies job.bat to %TEMP% and executes it hidden. Three stages of indirection before the real work begins, each one trivially small, each one with zero detections.
When job.bat runs, the first thing it does is open a legitimate invoice PDF from IHK.de -- the Industrie- und Handelskammer, Germany's official Chamber of Commerce. The decoy URL points to a real IHK invoice document. This is the social engineering payoff: the victim sees a German business invoice appear in their browser, confirming their expectation that they just opened "legal documents." Meanwhile, job.bat is silently:
- Downloading
revive.batinto the Windows Startup folder for persistence - Downloading and extracting
raise.zipto%LOCALAPPDATA%\vours\ - Downloading a Python 3.10 embedded distribution directly from
python.org - Executing the KISS Loader twice -- once for each RAT payload
The operator downloads Python from the official source. No trojanized interpreter, no supply chain compromise -- they need a clean, trusted Python runtime to execute their loader. The embedded distribution is the minimal version: no pip, no standard library beyond what is included, just enough to run their injection script.
The KISS Loader: Early Bird APC Injection from Python
The loader is called so.py, and the name is apt -- this is Keep It Simple, Stupid engineering. At 12,952 bytes, it is the first sample of this loader observed on MalwareBazaar, submitted on March 31, 2026 by researcher JAMESWT_WT.
so.py takes two arguments: an encrypted binary payload (.bin) and a JSON key file (.json). The key files were generated by PurePythonObfuscator, a custom key generation tool that seeds from secrets.token_bytes(), os.urandom(), time.time_ns(), and os.getpid() to produce 32-byte XOR keys. Each key file includes SHA256 and SHA3-256 integrity hashes of the key material, a level of verification that suggests the operator has been burned by corrupted keys before.
The XOR keys for each payload:
| Payload | XOR Key (hex) |
|---|---|
| fraps.bin (dcRAT) | 4c5f09035d19fece478a2a90a8001f43144863f4e980fc9c4ddb676ea99f6839 |
| frexs.bin (XenoRAT) | 0a9260f02b3cd336d1f8f2f9cf6349d766b1ef73f241bc9a26c90cb701150f62 |
After XOR decryption, so.py performs Early Bird APC injection. The technique:
- Create a suspended
explorer.exeprocess viaCreateProcessWwith theCREATE_SUSPENDEDflag - Allocate memory in the suspended process with
VirtualAllocEx(PAGE_EXECUTE_READWRITE) - Write the decrypted shellcode with
WriteProcessMemory - Queue an Asynchronous Procedure Call to the suspended thread with
QueueUserAPC - Resume the thread with
ResumeThread-- the APC executes before the process entry point
This is T1055.004 in the MITRE ATT&CK framework. The "Early Bird" variant is particularly effective because the APC runs before any EDR hooks are established in the new process. The shellcode executes in a pristine explorer.exe address space, before Windows Defender's userland hooks, before any DLL injection-based security products initialize.
The loader runs twice: once with fraps.bin/fraps.json to deploy dcRAT, once with frexs.bin/frexs.json to deploy XenoRAT. Two separate explorer.exe processes, two separate RATs, two separate C2 channels.
Cracking the Donut: A Custom Chaskey-CTR Implementation
This is where the investigation turned from malware analysis into applied cryptography.
After XOR decryption, each .bin payload contains a Donut shellcode loader -- a framework for packing .NET assemblies into position-independent shellcode. Donut is open-source (TheWover/donut on GitHub), well-documented, and widely used in red team operations. The standard version uses the Chaskey block cipher in CTR mode to encrypt the embedded payload.
The problem: this was not the standard version.
Both payloads share an identical 23,925-byte Donut loader code section. Disassembly confirmed the Chaskey block cipher implementation and the CTR mode of operation. But when we applied the standard Donut decryption -- extract the 16-byte key and 16-byte counter from the instance structure, run Chaskey-CTR, and decrypt -- the output was garbage.
The Instance Structure
The Donut instance is a binary structure embedded in the shellcode. Through disassembly, we mapped the relevant fields:
Offset Field Size
+0x000 len (total size) 4 bytes (uint32)
+0x004 Chaskey key 16 bytes
+0x014 Chaskey counter/IV 16 bytes
...
+0x230 Encrypted data (len - 0x230) bytes
The key and counter were present. The encrypted data started at offset 0x230. The Chaskey block cipher implementation matched the reference. So why didn't it decrypt?
The Non-Standard Counter Increment
Standard Donut increments the CTR counter as a little-endian 128-bit integer. This is the natural byte order on x86 -- you treat the 16-byte counter as a single integer stored least-significant-byte-first, add one, and use the result as the next counter block. This is what every public Donut decryptor implements, and what every Donut analysis blog post describes.
This variant increments the counter as a big-endian 128-bit integer.
The difference is subtle in code but catastrophic in output. In little-endian CTR, incrementing the counter 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 produces 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00. In big-endian CTR, the same increment produces 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 -- the carry propagates from the opposite end.
The first block decrypts correctly in both implementations because the initial counter value is the same. Every subsequent block produces different keystream. By the second 16-byte block, the output diverges completely. A standard Donut decryptor will produce a valid first block followed by random noise -- exactly what we observed initially, and exactly the kind of failure that makes you question whether you have the right key rather than the right implementation.
We identified the big-endian increment through instruction-level disassembly of the Donut loader's CTR routine. The counter update sequence used byte-reversed arithmetic -- bswap instructions bracketing the increment -- confirming a deliberate modification to the standard Donut source.
Successful Decryption
With the corrected counter increment, both payloads decrypted cleanly. The Donut module structure revealed itself:
- Loads
ole32.dll,oleaut32.dll,wininet.dll,mscoree.dll - Patches AMSI (Antimalware Scan Interface) to neutralize Windows Defender's in-process scanning
- Patches WLDP (Windows Lockdown Policy) to bypass application whitelisting
- Initializes the .NET CLR (Common Language Runtime)
- Loads the embedded .NET assembly directly from memory -- no file touches disk
The decrypted assemblies:
| Payload | SHA256 | Family |
|---|---|---|
| fraps_payload.exe | 0e3b61878f50b78c5b28b9c9d2067c3c157e23c163a48dc346555ad61d992f96 | dcRAT (.NET 4.0) |
| frexs_payload.exe | e149f0e26afa5460ac5a9aac3688495ae7cb4642c8ec6e5f40ac63e0bafae00c | XenoRAT v1.8.7 (.NET 4.8) |
Why Two RATs? The Logic of Redundancy
Deploying two different RATs through the same infection chain is not common. It is wasteful in terms of footprint, noisy in terms of behavior, and doubles the C2 infrastructure the operator must maintain. So why do it?
The answer lies in what each RAT does well.
dcRAT (DcRatByqwqdanchun)
dcRAT is an open-source .NET RAT maintained by the Chinese developer qwqdanchun. The variant deployed here is version 3.6.0.0, compiled for .NET Framework 4.0. It is the heavyweight:
- AES-256 encrypted C2 with X.509 certificate pinning -- the C2 server address and configuration are encrypted in the binary using a certificate-based scheme. The encrypted configuration blob:
Abmzajhcyi0m8Af7aoe0VehB2SZfgmw9FY6k0DtXeCJI4PC2jEb629e3jN/s6P6bPTg0VKghIlMUMoU87/jV0g== - MessagePack protocol for C2 communication -- binary serialization, more compact and harder to signature than JSON or XML
- Plugin system with dynamic loading -- the operator can push new capabilities to the implant without replacing it
- AMSI and ETW memory patching for both x86 and x64 -- runtime disabling of Windows telemetry
- VM detection for VMware, VirtualBox, and Sandboxie -- sandbox evasion
- Anti-debug via
CheckRemoteDebuggerPresent - Dual persistence: scheduled tasks (onlogon trigger) and Registry Run key
dcRAT is the operator's primary implant. It provides a full plugin framework for post-exploitation, encrypted C2 that resists network inspection, and layered evasion that targets both automated sandboxes and human analysts.
XenoRAT v1.8.7
XenoRAT is the lighter, faster alternative. Version 1.8.7, compiled for .NET 4.8:
- C2: 176.96.136[.]182 -- hardcoded, no encryption wrapper
- Mutex:
Xeno_rat_nd8912d - Install path:
%LOCALAPPDATA%\XenoManager\ - Startup name:
XenoUpdateManager(Registry Run + Scheduled Task) - Async socket C2 with three distinct receive types
- Active window title capture -- keylogger-adjacent capability
- Self-delete via
choice.exedelay -- forensic cleanup
XenoRAT's value is speed and reliability. If dcRAT gets caught by an EDR update, if the certificate-pinned C2 gets sinkholed, if the plugin system triggers a behavioral detection -- XenoRAT is already running on a separate C2 channel with a simpler, harder-to-break communication model. It is the insurance policy.
The dual deployment also serves an intelligence collection purpose: two RATs means two independent streams of stolen data. If one drops packets or crashes, the other captures what was missed. For an operator targeting German small businesses -- where the window of access before someone calls their IT contractor may be measured in hours -- redundancy is not wasteful. It is operational discipline.
The C2 Server: A Windows Box in Frankfurt
The XenoRAT C2 at 176.96.136[.]182 is a Windows Server running IIS 10.0 on the default HTTP page. When we scanned it, the server responded on five ports:
| Port | Service |
|---|---|
| 80 | IIS 10.0 (default page) |
| 135 | MSRPC |
| 445 | SMB |
| 3389 | RDP |
| 5985 | WinRM |
The RDP certificate, auto-generated when Remote Desktop was enabled, leaks the hostname: WIN-98T3MU38QEG. The certificate was issued on March 4, 2026 and expires September 3, 2026 -- a 6-month validity window that dates exactly when this C2 was provisioned.
The server is hosted by dataforest GmbH in Germany. This is notable: the operator is hosting their C2 in the same country they are targeting. German traffic to a German IP raises fewer flags than German traffic to a VPS in Romania or the Netherlands. Whether this is deliberate OPSEC or coincidence, it is effective.
The server is also vulnerable to CVE-2020-0796 (SMBGhost) -- the critical SMB v3.1.1 compression vulnerability that enables remote code execution. An unpatched Windows Server exposed to the internet with RDP, WinRM, and SMB open is not a hardened C2 installation. This is a disposable box -- stood up quickly, used until burned, then abandoned.
The Operator's Fingerprints
Every campaign leaves traces. This one left more than most.
The AWS Build Machine
The LNK file's embedded metadata contains the Machine ID ec2amaz-t08m3l3 -- the default hostname format for Windows instances on Amazon EC2 (ec2amaz- followed by a random string). The SID is S-1-5-21-3415605472-1908877419-3911355496-500, where -500 confirms the built-in Administrator account.
This operator builds their payloads on AWS EC2 instances. We have seen this before: post #3699 documented a different EC2 hostname (ec2amaz-vjnf8l9) in an earlier SERPENTINE wave. Different instance, same pattern. The operator spins up Windows EC2 instances, crafts their LNK files and batch scripts, stages them to the WebDAV server, and presumably terminates the instance. Cloud-based build environments are good OPSEC -- they leave no persistent forensic artifacts on the operator's real machine. But the LNK metadata survives.
The "Music" Directory
The WebDAV root directory is named "Music" because the operator is sharing C:\Users\Administrator\Music via WsgiDAV. This is the same OPSEC failure we documented in every prior SERPENTINE wave. The operator either does not know or does not care that WsgiDAV's directory listing reveals the share name. It is a small leak, but it confirms: same operator, same staging VM configuration, same habit of using the Music folder as a working directory.
The Cross-Campaign Copy-Paste
Here is a detail that ties this wave to the previous one. The persistence script revive.bat installs itself to %LOCALAPPDATA%\lg07\ -- a directory name from a prior SERPENTINE wave. But the main dropper job.bat extracts payloads to %LOCALAPPDATA%\vours\. Two different install directories in the same chain.
This is a copy-paste artifact. The operator reused revive.bat from a previous campaign configuration (the "lg07" wave) but wrote job.bat fresh for this wave (using "vours" as the new campaign identifier). They forgot to update the directory path in the persistence script. It is the kind of mistake that happens when you are rotating infrastructure daily and reusing components across waves -- exactly the operational tempo we have been tracking.
Campaign Timeline
The forensic evidence allows us to reconstruct the operator's preparation timeline:
| Date (UTC) | Event | Evidence |
|---|---|---|
| Feb 8 | WebDAV staging VM created | VM creation timestamp 2026-02-08T18:00:17Z |
| Mar 2 | KISS Loader (so.py) first compiled/distributed | ReversingLabs first-seen date |
| Mar 4 | C2 server provisioned | RDP certificate issued 2026-03-04, IIS default page configured |
| Mar 8 | WebDAV share configured | desktop.ini timestamp on WebDAV server |
| Mar 24-25 | All attack files staged | File timestamps on WebDAV: LNK, scripts, ZIP, encrypted payloads |
| Mar 25 | Payload encryption finalized | .bin and .json key file timestamps |
| Mar 31 | so.py submitted to MalwareBazaar | JAMESWT_WT submission |
| Apr 1 | Breakglass analysis | This report |
The gap between VM creation (February 8) and attack file staging (March 24-25) suggests the operator maintains long-lived staging infrastructure that is reused across multiple waves. The C2 was stood up on March 4 -- three weeks before payloads were staged -- indicating advance preparation of the receiving infrastructure.
Detection and Hunting Guidance
Immediate Blocks
- DNS/Proxy: Block
ira-tom-shakespeare-understood[.]trycloudflare[.]com - Firewall: Block
176.96.136[.]182(XenoRAT C2)
Filesystem Indicators
%LOCALAPPDATA%\vours\-- payload extraction directory%LOCALAPPDATA%\XenoManager\-- XenoRAT install directory%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\revive.bat-- persistence%LOCALAPPDATA%\lg07\-- cross-campaign persistence path
Process Indicators
wscript.exewithtrycloudflare.comin the command linepython.exeorpython3.10.exespawned from%TEMP%or%LOCALAPPDATA%- Multiple
explorer.exeinstances (Early Bird APC injection creates new suspended processes) - Mutex:
Xeno_rat_nd8912d - Scheduled task with startup name
XenoUpdateManager
Network Indicators
- WebDAV traffic to
*.trycloudflare.com(PROPFIND, GET on .wsh/.js/.bat files) - Outbound connections to
176.96.136[.]182on any port - IIS 10.0 default page response from the C2 IP
Full Indicators of Compromise
File Hashes (SHA256)
Campaign Files (All 0/n VT at time of analysis)
| File | SHA256 |
|---|---|
| Rechtsdokumente.pdf.lnk | 85123630e0931cc73e435b3c73f1b006c78ffad8740cbb3f3aa0db0a933cf77c |
| brown.wsh | 629d6e302f4484b2d486edeb17e83ae9dbe1136d64b4a85cf4ab20da9d5185db |
| assist.js | 0d2ec6d6f93833610a21ef861cd2754729456a8df2342f00b6d600c3766c6708 |
| job.bat | 329c639007c6a2d3ae76bc7a19bfda829c63898d1541234bd50a68f42cf08916 |
| revive.bat | f72f4db6308b73dead82d86ce7edfa678da9e6c05d436376e4bffceecbae70e4 |
| raise.zip | e1ded7ea4ae682eedd390117e189324854c29056eee13849f32c8f33db678468 |
| so.py (KISS Loader) | 5cab6bf65f7836371d5c27fbfc20fe10c0c4a11784990ed1a3d2585fa5431ba6 |
| fraps.bin | e92d781e34354663ede64ec9b370c154941af4112c21bc4778f1f8e44dfb7920 |
| fraps.json | be242d005f0e360178c504063978701d5d014d255cc0ba45fa2e79418cf35f6e |
| frexs.bin | e2b7571eb1107da726287227ecfeb1b7ba8700fb894576624d1367242c7aac60 |
| frexs.json | ed6e23051675fb0d96b06791c59a340c7528df4f85349fb64982ef16ce635fc3 |
Decrypted Payloads
| File | SHA256 | Family |
|---|---|---|
| fraps_payload.exe | 0e3b61878f50b78c5b28b9c9d2067c3c157e23c163a48dc346555ad61d992f96 | dcRAT (.NET 4.0) |
| frexs_payload.exe | e149f0e26afa5460ac5a9aac3688495ae7cb4642c8ec6e5f40ac63e0bafae00c | XenoRAT v1.8.7 (.NET 4.8) |
| Donut code section | de824519240067bbb9e7b3aa97e8da43e1f98743fb0905d0bf465d62ed34d43e | Custom Donut loader |
Network Infrastructure
| Indicator | Value | Role |
|---|---|---|
| Tunnel domain | ira-tom-shakespeare-understood[.]trycloudflare[.]com | WebDAV payload delivery |
| C2 IP | 176.96.136[.]182 | XenoRAT C2 (LIVE) |
| C2 hostname | WIN-98T3MU38QEG | RDP certificate CN |
| C2 hosting | dataforest GmbH, Germany | ASN |
| C2 RDP cert serial | 5a:56:19:4a:ad:10:9a:a7:49:1e:b9:ba:07:2c:9d:c6 | Certificate fingerprint |
| Build machine | ec2amaz-t08m3l3 | AWS EC2 (LNK metadata) |
| Build SID | S-1-5-21-3415605472-1908877419-3911355496-500 | Administrator account |
dcRAT Encrypted Configuration
Abmzajhcyi0m8Af7aoe0VehB2SZfgmw9FY6k0DtXeCJI4PC2jEb629e3jN/s6P6bPTg0VKghIlMUMoU87/jV0g==
AES-256 encrypted with X.509 certificate pinning. Contains C2 address, port, and plugin configuration.
MITRE ATT&CK Coverage
| Technique | ID | Implementation |
|---|---|---|
| Spearphishing Link | T1566.002 | LNK distribution via email |
| Windows Script Host | T1059.005 | brown.wsh, assist.js |
| Command Shell | T1059.003 | job.bat, revive.bat |
| Python | T1059.006 | so.py KISS Loader |
| Startup Folder | T1547.001 | revive.bat persistence |
| Registry Run Keys | T1547.001 | dcRAT + XenoRAT persistence |
| Scheduled Task | T1053.005 | schtasks onlogon trigger |
| Masquerading | T1036.005 | LNK with Edge/PDF icon |
| Obfuscated Files | T1027 | XOR + Chaskey-CTR encryption |
| Process Injection (APC) | T1055.004 | Early Bird into explorer.exe |
| Disable Security Tools | T1562.001 | AMSI/ETW/WLDP patching |
| Virtualization Evasion | T1497.001 | VMware/VBox/Sandboxie checks |
| Encrypted Channel | T1573.001 | AES-256 C2 (dcRAT) |
| Protocol Tunneling | T1572 | Cloudflare tunnel delivery |
The Bigger Picture
SERPENTINE#CLOUD has been active since at least May 2024. Proofpoint first documented it. Cofense named it PythonRatLoader. Securonix gave it the SERPENTINE#CLOUD designation and published a deobfuscation tool for the Kramer framework. Forcepoint, eSentire, and ThreatIntelReport have all contributed analysis.
What makes this wave different is not just the German-language targeting or the dual RAT deployment. It is the encryption. The operator has moved from the Kramer obfuscation framework -- which now has public deobfuscation tooling thanks to Securonix -- to a custom Donut variant with a deliberately modified Chaskey-CTR implementation. The big-endian counter increment is a small change in code but it breaks every existing Donut decryptor. Anyone attempting to analyze these payloads with standard tooling will get a valid first block and garbage thereafter -- just enough to make you doubt your key extraction rather than your decryption implementation.
This is not sophisticated cryptography. Chaskey is still Chaskey, CTR mode is still CTR mode, and the key sits unencrypted in the shellcode. But it is effective anti-analysis. It adds hours to the reverse-engineering timeline, and for a campaign that rotates infrastructure daily, hours matter.
The zero-detection rate across all 12 files reinforces the point. This operator understands the detection landscape. They download a clean Python runtime from python.org. They use PurePythonObfuscator with cryptographic-quality entropy for key generation. They patch AMSI, ETW, and WLDP before loading .NET assemblies. They pin certificates for C2 authentication. And they modify open-source tools just enough to break automated analysis without introducing bugs.
The OPSEC failures -- the Music directory, the EC2 hostname in LNK metadata, the lg07/vours copy-paste -- are not signs of incompetence. They are signs of operational tempo. This actor is running a high-volume campaign with daily infrastructure rotation, and at that pace, small mistakes accumulate. Those mistakes are how we track them.
We will continue tracking SERPENTINE#CLOUD as the campaign evolves.
h/t @smica83 for the tunnel tip that initiated this investigation. KISS Loader sample credit to JAMESWT_WT via MalwareBazaar. All evidence was captured via passive and semi-passive methods. The XenoRAT C2 at 176.96.136[.]182 was live at time of publication.
Breakglass Intelligence | April 1, 2026