Back to reports

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

PublishedApril 1, 2026
supply-chaintrojanized-toolszelix-klassmasterdns-over-httpsjavapiracymcleaksfud

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.java visible in bytecode attributes
  • Crack watermark: leaks[.]tf attribution ties the distribution chain to a known platform
  • Domain reuse: mcleaks[.]de connects this operation to the Minecraft underground ecosystem
  • Wildcard cert: certificate transparency logs expose all *.mcleaks.de subdomains

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

TypeValue
SHA256cb574adcec44a9b051269d23bd4567b876253c068c3b30835ff38aec85d49d55
MD570b4dc6765860e58793c85645c690491
SHA11c5ef7ee0afe774d35325a4133ac26c4f2c02ad2
Filenameout.jar
Size10,183,894 bytes
Drop filename874643384254.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

TypeValue
Scheduled TaskMicrosoftEdgeUpdateTaskMachineOA
Drop Directoryqtshadercache-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 Classcom.zelix.ResourceMonitor
Manifest Main-Classcom.zelix.ZKM
Processjavaw.exe or java with -jar and 874643384254.jar
DoH TrafficHTTPS POST to cloudflare-dns.com with application/dns-message content type
XOR Keypt9T;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

IDTechniqueEvidence
T1195.002Supply Chain CompromiseTrojanized pirated ZKM obfuscator
T1204.002User Execution: Malicious FileVictim runs JAR expecting ZKM
T1053.005Scheduled TaskMicrosoftEdgeUpdateTaskMachineOA persistence
T1027Obfuscated Files or InformationDual-layer: ZKM DES + custom XOR
T1036.005Masquerading: Match Legitimate NameTask name, class name, drop path
T1071.004Application Layer Protocol: DNSDNS-over-HTTPS via Cloudflare
T1090.002Proxy: External ProxyDoH through Cloudflare as resolution proxy
T1105Ingress Tool TransferDownloads PL.jar/csb.jar from C2
T1129Shared ModulesURLClassLoader 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

Share