Back to reports

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

PublishedApril 1, 2026
serpentine-clouddcratxenoratdonutchaskeygerman-targetingwebdavcloudflare-tunnel

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:

  1. Downloading revive.bat into the Windows Startup folder for persistence
  2. Downloading and extracting raise.zip to %LOCALAPPDATA%\vours\
  3. Downloading a Python 3.10 embedded distribution directly from python.org
  4. 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:

PayloadXOR Key (hex)
fraps.bin (dcRAT)4c5f09035d19fece478a2a90a8001f43144863f4e980fc9c4ddb676ea99f6839
frexs.bin (XenoRAT)0a9260f02b3cd336d1f8f2f9cf6349d766b1ef73f241bc9a26c90cb701150f62

After XOR decryption, so.py performs Early Bird APC injection. The technique:

  1. Create a suspended explorer.exe process via CreateProcessW with the CREATE_SUSPENDED flag
  2. Allocate memory in the suspended process with VirtualAllocEx (PAGE_EXECUTE_READWRITE)
  3. Write the decrypted shellcode with WriteProcessMemory
  4. Queue an Asynchronous Procedure Call to the suspended thread with QueueUserAPC
  5. 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:

PayloadSHA256Family
fraps_payload.exe0e3b61878f50b78c5b28b9c9d2067c3c157e23c163a48dc346555ad61d992f96dcRAT (.NET 4.0)
frexs_payload.exee149f0e26afa5460ac5a9aac3688495ae7cb4642c8ec6e5f40ac63e0bafae00cXenoRAT 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.exe delay -- 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:

PortService
80IIS 10.0 (default page)
135MSRPC
445SMB
3389RDP
5985WinRM

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)EventEvidence
Feb 8WebDAV staging VM createdVM creation timestamp 2026-02-08T18:00:17Z
Mar 2KISS Loader (so.py) first compiled/distributedReversingLabs first-seen date
Mar 4C2 server provisionedRDP certificate issued 2026-03-04, IIS default page configured
Mar 8WebDAV share configureddesktop.ini timestamp on WebDAV server
Mar 24-25All attack files stagedFile timestamps on WebDAV: LNK, scripts, ZIP, encrypted payloads
Mar 25Payload encryption finalized.bin and .json key file timestamps
Mar 31so.py submitted to MalwareBazaarJAMESWT_WT submission
Apr 1Breakglass analysisThis 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.exe with trycloudflare.com in the command line
  • python.exe or python3.10.exe spawned from %TEMP% or %LOCALAPPDATA%
  • Multiple explorer.exe instances (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[.]182 on 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)

FileSHA256
Rechtsdokumente.pdf.lnk85123630e0931cc73e435b3c73f1b006c78ffad8740cbb3f3aa0db0a933cf77c
brown.wsh629d6e302f4484b2d486edeb17e83ae9dbe1136d64b4a85cf4ab20da9d5185db
assist.js0d2ec6d6f93833610a21ef861cd2754729456a8df2342f00b6d600c3766c6708
job.bat329c639007c6a2d3ae76bc7a19bfda829c63898d1541234bd50a68f42cf08916
revive.batf72f4db6308b73dead82d86ce7edfa678da9e6c05d436376e4bffceecbae70e4
raise.zipe1ded7ea4ae682eedd390117e189324854c29056eee13849f32c8f33db678468
so.py (KISS Loader)5cab6bf65f7836371d5c27fbfc20fe10c0c4a11784990ed1a3d2585fa5431ba6
fraps.bine92d781e34354663ede64ec9b370c154941af4112c21bc4778f1f8e44dfb7920
fraps.jsonbe242d005f0e360178c504063978701d5d014d255cc0ba45fa2e79418cf35f6e
frexs.bine2b7571eb1107da726287227ecfeb1b7ba8700fb894576624d1367242c7aac60
frexs.jsoned6e23051675fb0d96b06791c59a340c7528df4f85349fb64982ef16ce635fc3

Decrypted Payloads

FileSHA256Family
fraps_payload.exe0e3b61878f50b78c5b28b9c9d2067c3c157e23c163a48dc346555ad61d992f96dcRAT (.NET 4.0)
frexs_payload.exee149f0e26afa5460ac5a9aac3688495ae7cb4642c8ec6e5f40ac63e0bafae00cXenoRAT v1.8.7 (.NET 4.8)
Donut code sectionde824519240067bbb9e7b3aa97e8da43e1f98743fb0905d0bf465d62ed34d43eCustom Donut loader

Network Infrastructure

IndicatorValueRole
Tunnel domainira-tom-shakespeare-understood[.]trycloudflare[.]comWebDAV payload delivery
C2 IP176.96.136[.]182XenoRAT C2 (LIVE)
C2 hostnameWIN-98T3MU38QEGRDP certificate CN
C2 hostingdataforest GmbH, GermanyASN
C2 RDP cert serial5a:56:19:4a:ad:10:9a:a7:49:1e:b9:ba:07:2c:9d:c6Certificate fingerprint
Build machineec2amaz-t08m3l3AWS EC2 (LNK metadata)
Build SIDS-1-5-21-3415605472-1908877419-3911355496-500Administrator 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

TechniqueIDImplementation
Spearphishing LinkT1566.002LNK distribution via email
Windows Script HostT1059.005brown.wsh, assist.js
Command ShellT1059.003job.bat, revive.bat
PythonT1059.006so.py KISS Loader
Startup FolderT1547.001revive.bat persistence
Registry Run KeysT1547.001dcRAT + XenoRAT persistence
Scheduled TaskT1053.005schtasks onlogon trigger
MasqueradingT1036.005LNK with Edge/PDF icon
Obfuscated FilesT1027XOR + Chaskey-CTR encryption
Process Injection (APC)T1055.004Early Bird into explorer.exe
Disable Security ToolsT1562.001AMSI/ETW/WLDP patching
Virtualization EvasionT1497.001VMware/VBox/Sandboxie checks
Encrypted ChannelT1573.001AES-256 C2 (dcRAT)
Protocol TunnelingT1572Cloudflare 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

Share