You Wanted Free Obfuscation. You Got Free Surveillance: Inside a Trojanized $2,000 Java Obfuscator With DoH C2
A cracked Zelix KlassMaster hides a RAT that resolves C2 via DNS-over-HTTPS through Cloudflare — we cracked the encryption by turning ZKM against itself
Inside a trojanized copy of a $2,000 Java obfuscator that turned its own encryption against every developer who downloaded it -- and how we cracked it by making ZKM decrypt itself.
The Bait
Zelix KlassMaster is the gold standard of Java obfuscation. Fortune 500 companies pay $2,000+ per license to protect their intellectual property. It renames classes, encrypts strings with DES, rewires method calls through invokedynamic dispatch tables, and turns readable Java into something that makes decompilers weep. If you ship commercial Java software and you care about reverse engineering, you've at least considered ZKM.
You've also considered not paying for it.
On April 1, 2026, a 10.18 MB Java Archive appeared on MalwareBazaar, submitted by researcher @smica83. Origin: Hungary. AV detections: zero. The filename was out.jar, and its manifest declared a main class of com.zelix.ZKM -- a complete, functional copy of the Zelix KlassMaster obfuscator, cracked by the group behind leaks[.]tf and distributed through piracy channels.
It worked perfectly. Run it against your Java project and it would obfuscate your code exactly as advertised. Every feature intact. Every option functional. 1,594 class files, 18.6 MB uncompressed, built with JDK 1.8.0_281. A developer who downloaded it would have no reason to suspect anything was wrong.
Except for one extra class buried among the 1,594: ResourceMonitor.class.
The name sounds benign -- like something that tracks memory usage or monitors system resources. It wasn't. Its original source filename, leaked through Java's SourceFile attribute, told a different story: PatchSystem.java. This was a RAT payload, purpose-built to patch into legitimate software. And the very first instruction in ZKM's entry point -- before a single line of obfuscation runs -- spawns it in a background thread.
You wanted free obfuscation. You got free surveillance.
The Shield Becomes the Sword
Here's the part that makes this sample elegant in its cruelty: the RAT was invisible not because of some novel packing technique or custom crypter. It was invisible because it was protected by the very tool it was hiding inside.
ZKM's commercial obfuscation -- the same DES/CBC/PKCS5Padding string encryption, the same invokedynamic reference obfuscation through a 2.4 MB dispatch class, the same control flow mangling that companies pay thousands of dollars for -- was applied uniformly across the entire JAR. The RAT payload received the same protection as ZKM's own legitimate code. Every antivirus engine that tried to analyze out.jar hit the same wall: 1,594 files of commercially obfuscated Java bytecode with no cleartext strings, no recognizable patterns, no signatures to match.
The obfuscation that was supposed to protect software became the malware's own armor.
But the RAT author didn't stop there. On top of ZKM's commercial encryption, the ResourceMonitor class had its own second layer: a custom 7-byte repeating XOR cipher (key: pt9T;c8) protecting 59 encrypted strings in an internal array, plus 61 long constants feeding a parametric decryption routine. Dual-layer obfuscation. To extract the RAT's configuration, you'd need to defeat both.
Cracking ZKM With ZKM
When we pulled the JAR apart, the initial decompilation gave us structure but not secrets. CFR recovered the control flow -- we could see ResourceMonitor spawning threads, making HTTP connections, writing files, executing processes. But every meaningful string was a call to a decryption method. The C2 address, the drop path, the persistence mechanism -- all locked behind a(int, int, int) calls that fed into XOR tables and DES routines.
So we did something simple: we made ZKM decrypt itself.
Java reflection lets you load a class, find its methods, and invoke them with arbitrary arguments -- even if those methods were never meant to be called externally. The encrypted string array and the decryption methods were right there in the bytecode. We didn't need to reverse-engineer the algorithm. We just needed to call it.
We wrote a harness that loaded ResourceMonitor.class into a JVM, located the a(int, int, int) decryption method via reflection, and invoked it with every index in the encrypted string table. The class decrypted its own secrets for us:
Index 0: os.name
Index 4: win
Index 7: user.home
Index 8: AppData
Index 12: qtshadercache-x86_64-little_endian-llp64
Index 13: 874643384254.jar
Index 22: https://cloudflare-dns.com/dns-query
Index 23: POST
Index 24: application/dns-message
Index 36: schtasks.exe
Index 38: MicrosoftEdgeUpdateTaskMachineOA
Index 45: shit
That last one -- index 45 -- was a debug string. Printed to stdout on successful payload execution. A developer's little celebration, left in the production build. We'll come back to that.
The DNS query bytes decoded separately, revealing the C2 domain: download.launcher.mcleaks[.]de. The full configuration was now plaintext. We had everything.
The Kill Chain
Here's what happens when a developer runs java -jar out.jar expecting to obfuscate their code:
ZKM's modified entry point (ZKMProGuardTranslate.main()) fires. Its very first instruction -- before touching the developer's project -- instantiates ResourceMonitor. The constructor checks a static boolean (singleton guard), then spawns a new thread and returns. ZKM proceeds normally. The developer sees their code get obfuscated. Everything looks right.
Meanwhile, the background thread gets to work.
Step 1: Reconnaissance. It reads os.name to determine Windows or Linux. The RAT supports both.
Step 2: Staging. On Windows, it constructs a drop path deep inside the user profile:
%USERPROFILE%\AppData\Local\cache\qtshadercache-x86_64-little_endian-llp64\874643384254.jar
That directory name -- qtshadercache-x86_64-little_endian-llp64 -- mimics Qt's shader cache directory structure. If a developer or sysadmin browses their AppData folder, this looks like leftover cache from a Qt application. On Linux, it's /tmp/cache/874643384254.jar. Either way, the embedded payload gets extracted from the class data and written to disk using CREATE_NEW (first run only -- if the file already exists, it skips this step).
Step 3: C2 Resolution via DNS-over-HTTPS. This is where the sophistication jumps. The RAT does NOT make a traditional DNS query. Instead, it constructs a raw DNS wire-format query for download.launcher.mcleaks[.]de, wraps it in an HTTPS POST request, and sends it to:
https://cloudflare-dns.com/dns-query
Content-Type: application/dns-message
Cloudflare's public DNS-over-HTTPS resolver. The response comes back as an encrypted HTTPS payload containing the A record. No DNS queries hit the network. No DNS logs capture the resolution. Network defenders see only an HTTPS connection to cloudflare-dns.com -- one of the most trusted domains on the internet. Traditional DNS monitoring, DNS sinkholes, corporate DNS filtering -- all completely bypassed.
Step 4: Payload Download. The resolved IP serves additional JARs (PL.jar, csb.jar). The RAT downloads one, reads its MANIFEST.MF to find the Main-Class entry.
Step 5: In-Memory Execution. A URLClassLoader loads the downloaded JAR's main class. Method.invoke() calls its main method with an empty argument array. The second-stage RAT executes entirely in memory -- no additional file touches the disk.
Step 6: Persistence. On Windows, the RAT creates a scheduled task:
schtasks.exe /Create /SC MINUTE /MO 1 /ST 00:05
/TN MicrosoftEdgeUpdateTaskMachineOA
/TR "\"C:\...\javaw.exe\" -jar \"874643384254.jar\" auto"
The task name MicrosoftEdgeUpdateTaskMachineOA is almost identical to the legitimate MicrosoftEdgeUpdateTaskMachineCore that Microsoft Edge installs. It fires every minute, ensuring the RAT survives reboots and process termination. On Linux, it falls back to direct execution.
Then, somewhere in memory: System.out.println("shit");
Mission accomplished.
The MCLeaks Connection
The C2 domain -- download.launcher.mcleaks[.]de -- isn't random. MCLeaks is (or was) a Minecraft alt-account service. It provided stolen or generated authentication tokens that let users play Minecraft without buying the game. The domain mcleaks[.]de resolves to 79.137.206[.]33 (alongside Cloudflare proxies at 172.67.186[.]231 and 104.21.36[.]68), and a wildcard certificate for *.mcleaks.de enables arbitrary subdomains without additional cert issuance.
The subdomain choice -- download.launcher -- tells a story. This RAT wasn't originally built for ZKM. It was built for a trojanized MCLeaks launcher, targeting Minecraft players. The infrastructure was already in place: domain, wildcard cert, C2 server. Trojanizing ZKM was an expansion -- same operator, same backend, new victim pool. Minecraft players who download stolen accounts. Java developers who download stolen tools. Same ecosystem. Same predator.
The SSL certificate for mcleaks[.]de was renewed on March 26, 2026 -- five days before the sample appeared on MalwareBazaar. Active infrastructure, actively maintained.
At time of investigation, download.launcher.mcleaks[.]de had no A record -- the C2 was offline or the operator had pulled the subdomain's DNS entry. But the parent domain infrastructure remained fully operational behind Cloudflare.
Why It Was Fully Undetected
Zero detections across every AV engine on MalwareBazaar. Not one. Here's why:
Commercial-grade obfuscation as camouflage. The RAT lives inside 1,594 files of legitimately obfuscated Java code. Antivirus engines that attempt static analysis hit ZKM's DES string encryption, invokedynamic reference obfuscation, and control flow mangling across every class. The RAT payload (ResourceMonitor.class) is indistinguishable from the legitimate ZKM classes around it -- they're all equally opaque.
No cleartext IOCs. Every meaningful string -- the C2 domain, the drop path, the persistence command, the DoH endpoint -- is encrypted. There are no plaintext URLs, no hardcoded IP addresses, no recognizable command strings for signature engines to match.
Dual-layer encryption. Even if an engine defeats ZKM's DES layer, the RAT's own XOR encryption remains. Two independent encryption schemes means two independent decryption efforts.
Legitimate application wrapping. The JAR's main class is com.zelix.ZKM. It IS a Java obfuscator. Heuristic engines that check for "suspicious Java application" see a legitimate commercial tool with a valid manifest, proper package structure, and expected behavior. The malicious thread is a tiny fraction of the codebase.
DNS-over-HTTPS for C2. Even dynamic analysis struggles. Sandboxes that monitor DNS queries see nothing -- the resolution happens over HTTPS to Cloudflare. Sandboxes that monitor network connections see an HTTPS request to one of the world's most legitimate domains. The actual C2 IP never appears in network logs unless you decrypt and parse the DoH response.
This is what FUD actually looks like. Not a crypter service that lasts a week. A supply chain attack wearing commercial-grade armor.
The Piracy Supply Chain
The crack attribution is explicit: cus.class contains the string "Cracked by hxxps://leaks[.]tf" alongside fake license serials (795566733400000, 6030234600000, 91389587400000) that bypass ZKM's license validation. leaks[.]tf is a platform that distributes cracked commercial software -- developer tools, security products, enterprise applications.
The supply chain works like this: leaks[.]tf (or someone using their platform) cracks ZKM's license check, injects the ResourceMonitor RAT class, applies ZKM's own obfuscation to the entire package (the tool obfuscates itself), and distributes the result through piracy channels -- forums, Telegram groups, warez sites, torrent trackers. The developer downloads what they believe is a cracked ZKM. It works. They use it to obfuscate their projects. They never suspect the tool they're using to protect their code is simultaneously compromising their machine.
There's a secondary concern that's worth stating explicitly: any software obfuscated with this trojanized copy is potentially compromised. The RAT executes before ZKM processes any input. If a developer built and shipped software using this copy, the RAT ran on their build machine. Their development environment, their credentials, their source code -- all potentially exposed. The supply chain attack doesn't stop at the developer. It reaches everything the developer touches.
OPSEC Assessment
The operator's tradecraft is genuinely strong:
- DNS-over-HTTPS for C2 resolution (bypasses all traditional DNS monitoring)
- Dual-layer string encryption (commercial DES + custom XOR)
- Legitimate application wrapping (functional $2,000 commercial tool)
- Deep-hiding drop paths (mimics Qt shader cache)
- Legitimate process names (Microsoft Edge update task, javaw.exe)
- In-memory second-stage execution (fileless after initial drop)
- Cloudflare-proxied domain with wildcard cert
- Cross-platform support (Windows + Linux)
The failures are small but exploitable:
- Debug string left in production:
"shit"printed on successful execution - Source filename leaked:
PatchSystem.javavisible in bytecode attributes - Crack watermark:
leaks[.]tfattribution ties the distribution chain to a known platform - Domain reuse:
mcleaks[.]deconnects this operation to the Minecraft underground ecosystem - Wildcard cert: certificate transparency logs expose all
*.mcleaks.desubdomains
One debug print statement. One forgotten metadata attribute. One watermark the crack group insisted on leaving. That's all it takes.
Indicators of Compromise
File Indicators
| Type | Value |
|---|---|
| SHA256 | cb574adcec44a9b051269d23bd4567b876253c068c3b30835ff38aec85d49d55 |
| MD5 | 70b4dc6765860e58793c85645c690491 |
| SHA1 | 1c5ef7ee0afe774d35325a4133ac26c4f2c02ad2 |
| Filename | out.jar |
| Size | 10,183,894 bytes |
| Drop filename | 874643384254.jar |
Network Indicators
79.137.206[.]33
download.launcher.mcleaks[.]de
mcleaks[.]de
files.mcleaks[.]de
gaseducation.ptr[.]network
hxxps://cloudflare-dns[.]com/dns-query (DoH endpoint, legitimate -- used for C2 resolution)
hxxp://79.137.206[.]33/PL.jar
hxxp://79.137.206[.]33/csb.jar
Behavioral Indicators
| Type | Value |
|---|---|
| Scheduled Task | MicrosoftEdgeUpdateTaskMachineOA |
| Drop Directory | qtshadercache-x86_64-little_endian-llp64 |
| Drop Path (Win) | %USERPROFILE%\AppData\Local\cache\qtshadercache-x86_64-little_endian-llp64\874643384254.jar |
| Drop Path (Lin) | /tmp/cache/874643384254.jar |
| RAT Class | com.zelix.ResourceMonitor |
| Manifest Main-Class | com.zelix.ZKM |
| Process | javaw.exe or java with -jar and 874643384254.jar |
| DoH Traffic | HTTPS POST to cloudflare-dns.com with application/dns-message content type |
| XOR Key | pt9T;c8 (7 bytes: 112, 116, 57, 84, 59, 99, 56) |
| Debug Output | "shit" printed to stdout |
| Crack String | "Cracked by hxxps://leaks[.]tf" |
MITRE ATT&CK
| ID | Technique | Evidence |
|---|---|---|
| T1195.002 | Supply Chain Compromise | Trojanized pirated ZKM obfuscator |
| T1204.002 | User Execution: Malicious File | Victim runs JAR expecting ZKM |
| T1053.005 | Scheduled Task | MicrosoftEdgeUpdateTaskMachineOA persistence |
| T1027 | Obfuscated Files or Information | Dual-layer: ZKM DES + custom XOR |
| T1036.005 | Masquerading: Match Legitimate Name | Task name, class name, drop path |
| T1071.004 | Application Layer Protocol: DNS | DNS-over-HTTPS via Cloudflare |
| T1090.002 | Proxy: External Proxy | DoH through Cloudflare as resolution proxy |
| T1105 | Ingress Tool Transfer | Downloads PL.jar/csb.jar from C2 |
| T1129 | Shared Modules | URLClassLoader in-memory JAR execution |
Detection
YARA (abbreviated -- full rules available in the GHOST investigation artifacts):
rule Trojanized_ZKM_ResourceMonitor {
meta:
author = "GHOST - Breakglass Intelligence"
date = "2026-04-01"
description = "Trojanized ZKM obfuscator with ResourceMonitor RAT"
hash = "cb574adcec44a9b051269d23bd4567b876253c068c3b30835ff38aec85d49d55"
strings:
$manifest_zkm = "Main-Class: com.zelix.ZKM" ascii
$class_rm = "com/zelix/ResourceMonitor" ascii
$source_patch = "PatchSystem.java" ascii
$crack_leaks = "leaks.tf" ascii wide
$drop_qt = "qtshadercache-x86_64-little_endian-llp64" ascii wide
$drop_jar = "874643384254.jar" ascii wide
$task_name = "MicrosoftEdgeUpdateTaskMachineOA" ascii wide
$doh_url = "cloudflare-dns.com/dns-query" ascii wide
$doh_ct = "application/dns-message" ascii wide
$c2_domain = "download.launcher.mcleaks.de" ascii
$xor_key = "pt9T;c8" ascii
condition:
uint16(0) == 0x504B and filesize > 5MB and filesize < 20MB and
($manifest_zkm and $class_rm) or
($source_patch and any of ($drop_*)) or
($doh_url and $doh_ct and $c2_domain) or
3 of ($drop_qt, $drop_jar, $task_name, $crack_leaks, $xor_key)
}
Sandbox Reports
What To Do
If you downloaded a cracked copy of ZKM: Assume compromise. Check for 874643384254.jar in the paths listed above. Check for a scheduled task named MicrosoftEdgeUpdateTaskMachineOA. Audit any software you built with the trojanized copy -- the RAT ran on your build machine during every obfuscation pass.
If you run a SOC: Hunt for HTTPS POST traffic to cloudflare-dns.com with application/dns-message content type originating from non-browser processes, especially java or javaw.exe. This is the DoH C2 channel. Monitor for scheduled tasks matching the Edge update masquerade pattern. Deploy the YARA rules above against your file scanning infrastructure.
If you're a developer on a budget: The free tier of ProGuard exists. Open-source obfuscation isn't as aggressive as ZKM, but it doesn't come with a RAT. The $2,000 you saved on a pirated license could cost you everything on your machine.
This investigation was conducted by Breakglass Intelligence's autonomous GHOST system. The trojanized ZKM sample was originally submitted to MalwareBazaar by @smica83. String decryption, infrastructure analysis, and IOC extraction were performed through static analysis and Java reflection techniques on an isolated analysis environment.
Breakglass Intelligence | April 1, 2026