Skip to content

pog5/weedhack-analysis

Repository files navigation

Minecraft Fabric Mod Malware - Reverse Engineering Analysis

WARNING: This repository contains decompiled malware source code. DO NOT compile or execute any of the code. It is preserved here strictly for research and documentation purposes.

Background

This is a reverse engineering analysis of a malicious Minecraft Fabric mod that was distributed as a trojanized version of a legitimate mod. The original JAR (Hide_Item_Frame-1.0.32.jar) was received from a friend after their PC was compromised by it. The file is preserved in the source-jar/stage1/ directory.

The malware is a multi-stage loader disguised as "Hide Item Frame" - a Minecraft Fabric client mod. Stage 1 (this analysis) acts as a credential stealer and downloader for a Stage 2 payload, which is fetched at runtime from an attacker-controlled server resolved via the Ethereum blockchain.

Tooling & Methodology

  1. Decompilation: The original JAR (source-jar/stage1/Hide_Item_Frame-1.0.32.jar) was put through Recaf using the Vineflower decompiler to obtain the raw Java source files.
  2. Raw decompiled output: Placed in stage1-fabricmc-mod/ - class/method/field names remain as found in the JAR (obfuscated variable names, obfuscated string constants via Helper.load()).
  3. Manual deobfuscation: Placed in stage1-fabricmc-mod-deobfuscated/ - classes and methods have been renamed to reflect their true purpose:
    • Entrypoint -> Main
    • ExampleMixin -> ExtraClassLoader
    • ExampleMod -> ModMain
    • FabricAdapter -> RPCHelper
    • Helper -> StageTwoLoader
  4. String decoder: The decode/ directory contains a standalone Java tool (Decode.java) written to offline-decode the individual obfuscated string constants that are decoded at runtime via Helper.load(). The malware uses a custom cipher (S-box substitution + XOR + bit rotation with chained state) to hide all sensitive strings. To decode a string, the Helper.load(...) call parameters are copied into the Decode.java main method, compiled, and run via decode.bat.
  5. Stage 2 decompilation: The Stage 2 JAR (Module.jar) was also put through Recaf/Vineflower. The Vineflower option to hide synthetic fields (--decompile-inner) had to be disabled because the JNIC obfuscator marks certain real fields as synthetic (the ACC_SYNTHETIC flag), causing Vineflower to omit them from the output. The decompiled source is placed in stage2-weedhack/.

Disguise

The malware masquerades as a legitimate Fabric mod:

Field Value
Mod ID grshiftcart
Display Name "Hide Item Frame"
Version 1.0.22
Description "Hide the frame if it contains an item"
Target Minecraft 1.21.4
Target Fabric Loader >=0.17.3
Java Requirement >=21
Environment client
Legitimate entrypoint cc.rshift.RShiftClient (the real mod's client class)
Malicious entrypoint com.example.ExampleMod (registered under main)

The fabric.mod.json lists both a real-looking client entrypoint (cc.rshift.RShiftClient) and the malicious com.example.ExampleMod under the main entrypoints. It also bundles seemingly legitimate LWJGL NanoVG native JARs in META-INF/jars/ to appear authentic.

A secondary fabric.api.json resource is bundled alongside, containing:

{"api_version": "2fbff058-173d-4ba3-adaf-95a757636fc5"}

Presumably generated for each campaign/target as a UUID, it is read at runtime and sent to the C2 as a userId / campaign identifier - it is not a real Fabric API version field, the real Fabric loader does not even check for the existence of a fabric.api.json file.

Dual Execution Paths

The malware has two entrypoints that can invoke the Stage 2 loader:

1. Fabric Mod Entrypoint (ModMain / ExampleMod)

When loaded as a Fabric mod inside Minecraft, onInitialize() executes and:

  1. Builds a JSON context object with "executionEnvironment": "Fabric".
  2. Harvests the player's Minecraft session: username, UUID, and access token.
  3. Resolves the C2 domain via Ethereum RPC (see below).
  4. Reads the campaign UUID from the bundled fabric.api.json.
  5. Immediately exfiltrates credentials via HTTP POST to <C2_DOMAIN>/api/delivery/handler - this happens before Stage 2 is loaded.
  6. Spawns a new thread that calls StageTwoLoader.stageWithContext(context) to download and load the Stage 2 payload.

2. Standalone JAR Entrypoint (Main / Entrypoint)

The JAR can also be run directly (outside Minecraft). When executed:

  1. Checks for the --jw argument. If absent, relaunches itself via javaw.exe (the windowless Java runtime) with --jw, then exits - this hides the console window on Windows.
  2. Builds a context with "executionEnvironment": "Fabric" (same string regardless of execution path).
  3. Spawns a thread to invoke StageTwoLoader.stageWithContext(context).

Note: In standalone mode, no Minecraft session data is available, so only the Stage 2 download occurs (no credential exfiltration).

String Obfuscation - Helper.load()

All sensitive strings (URLs, JSON keys, field names, the RSA public key, etc.) are obfuscated using a custom runtime decoding function Helper.load(int[] d1, int[] d2, int k1, int k2). The algorithm:

  1. Interleaves two integer arrays (d1, d2) into a single array - even indices from d1, odd indices from d2.
  2. Generates a substitution box (S-box) via sbox[i] = (i * 53 + 97) % 256 and its inverse.
  3. For each byte in the interleaved data, applies:
    • Chained XOR: each value is XORed with the previous value (or k2 for the first).
    • Bit rotation: rotated right by a position-dependent shift (idx * 5 + k1) % 8.
    • Inverse S-box substitution.
    • Mask XOR: final XOR with a rolling mask derived from k2, a stateful counter, and the position.
  4. The result is cast to char and appended to build the final plaintext string.

All calls in this sample use constants k1=187, k2=67.

A standalone decoder is provided in decode/Decode.java to offline-decode any Helper.load() call without executing the malware. A complete mapping of all 57 call sites to their decoded plaintext strings is documented in MAPPINGS.md.

C2 Domain Resolution via Ethereum RPC

The malware uses an Ethereum smart contract to dynamically resolve its C2 domain, making it resilient to simple domain takedowns - the attacker can update the on-chain value to point to a new domain at any time.

Mechanism

  1. The RPCHelper (obfuscated as FabricAdapter) contains a hardcoded list of 32 public Ethereum JSON-RPC endpoints (Llamarpc, Publicnode, Tenderly, Flashbots, DRPC, etc.).
  2. It iterates through these endpoints and sends an eth_call to contract address 0x1280a841Fbc1F883365d3C83122260E0b2995B74 (here it is on Etherscan.io) with function selector 0xce6d41de.
  3. The returned hex-encoded data is decoded (ABI string decoding: offset at bytes 0–32, length at bytes 32–64, UTF-8 string data at bytes 64+).
  4. The decoded string is in the format <C2_URL>|<RSA_SIGNATURE>.
  5. The RSA signature is verified against a hardcoded RSA-2048 public key (algorithm: SHA256withRSA) before the URL is trusted. This prevents third parties from hijacking the C2 domain by writing to the contract - only the attacker's private key can produce a valid signature.

Resolved Domain

As of 2026-03-06T19:31:40Z (March 6th, 2026), the Ethereum contract resolves to:

https://whnewreceive.ru

RPC Endpoints Used

The full list of Ethereum RPC endpoints the malware cycles through:

  • https://eth.llamarpc.com
  • https://eth.api.onfinality.io/public
  • https://rpc.eth.gateway.fm
  • https://ethereum-rpc.publicnode.com
  • https://eth.rpc.blxrbdn.com
  • https://ethereum.rpc.subquery.network/public
  • https://ethereum-json-rpc.stakely.io
  • https://ethereum-public.nodies.app
  • https://core.gashawk.io/rpc
  • https://mainnet.gateway.tenderly.co
  • https://ethereum-mainnet.gateway.tatum.io
  • https://eth1.lava.build
  • https://eth.meowrpc.com
  • https://public-eth.nownodes.io
  • https://rpc.mevblocker.io/fast
  • https://rpc.mevblocker.io/noreverts
  • https://rpc.mevblocker.io/fullprivacy
  • https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7
  • https://eth-mainnet.public.blastapi.io
  • https://ethereum.public.blockpi.network/v1/rpc/public
  • https://eth-mainnet.rpcfast.com?api_key=xbhWBI1Wkguk8SNMu1bvvLurPGLXmgwYeC4S6g2H7WdwFigZSmPWVZRxrskEQwIf
  • https://eth.drpc.org
  • https://eth.blockrazor.xyz
  • https://rpc.flashbots.net/fast
  • https://gateway.tenderly.co/public/mainnet
  • https://rpc.flashbots.net
  • https://rpc.fullsend.to
  • https://eth.merkle.io
  • https://api.zan.top/eth-mainnet
  • https://rpc.mevblocker.io
  • https://endpoints.omniatech.io/v1/eth/mainnet/public
  • https://1rpc.io/eth

Stage 2 Loader

Once the C2 domain is resolved, the malware downloads and loads a Stage 2 payload:

  1. Downloads a JAR file from <C2_DOMAIN>/files/jar/module via HTTP GET. The served file is named Module.jar.
  2. Parses the downloaded JAR in-memory using JarInputStream - it is never written to disk.
  3. Separates entries into a classMap (.class files) and a resourceMap (everything else).
  4. Instantiates a custom ClassLoader (ExtraClassLoader / ExampleMixin) that can load classes and resources from these in-memory maps.
  5. Loads the class dev.majanito.Main from the downloaded JAR.
  6. Reflectively invokes the method initializeWeedhack(String contextJson) on a new instance, passing the collected context (execution environment, campaign UUID, and Minecraft credentials if available).

The Stage 2 payload is loaded entirely in-memory via a custom ClassLoader, leaving no artifacts on disk beyond the original JAR.


C2 Endpoint Behavior

The following endpoint behaviors were observed through manual probing of the C2 server:

POST /api/delivery/handler

This is the credential exfiltration endpoint. Its behavior varies based on the request:

Request Response
Any method other than POST 405 Method Not Allowed
POST without Content-Type: application/json 400 Bad Request
POST with Content-Type: application/json and valid body 200 OK with body {}

A response of {} indicates the server successfully received and processed the exfiltrated data.

GET /files/jar/module

Returns the Stage 2 payload as a file download named Module.jar. This is the endpoint the malware calls to fetch the Stage 2 JAR for in-memory loading.

Stage 2 Analysis — Module.jar ("Weedhack")

The Stage 2 payload has been obtained and decompiled. Its source is in stage2-weedhack/. The JAR is built with JNIC — a Java-to-native compiler that compiles Java bytecode into native code (C/C++ compiled to platform-specific DLLs). As a result, nearly every method body in the decompiled output is native — the actual logic lives in an embedded native library extracted at runtime.

Platform Support

JNIC requires the developer to select target platforms at compile time. This sample supports two platforms only:

Platform Architecture .dat Byte Range
Windows x64 x86_64 / amd64 0516608 (≈504 KB)
Windows ARM64 aarch64 516608986112 (≈458 KB)

If the platform is not one of these two, the loader throws UnsatisfiedLinkError("Platform not supported"). Linux and macOS are not supported.

JNIC Loader Mechanism

The dev.jnic.kWGlIS package contains the JNIC runtime loader:

  1. JNICLoader (which also doubles as a custom LZMA InputStream decompressor) reads the embedded resource /dev/jnic/lib/43072760-0388-4d20-83c3-edcc17b3391a.dat.
  2. Based on os.name and os.arch, it selects the appropriate byte range from the .dat file and decompresses the native DLL.
  3. The DLL is written to a temp file (File.createTempFile("lib", null)) marked with deleteOnExit().
  4. System.load() loads the extracted DLL, making all native methods available.
  5. Each class's static {} initializer calls JNICLoader.init() followed by $jnicLoader() (and $jnicClinit() where applicable) to register its native methods.

The remaining single-letter classes in dev.jnic.kWGlIS (B, d, F, H, i, K, L, P, S, T, V, Y) implement the LZMA decompression algorithm used to decompress the native DLL from the .dat file.

Malware Classes (Stage 2)

Class Description
dev.majanito.Main Entry point — initializeWeedhack(String) receives context JSON from Stage 1. Also has addDefenderExclusions() (Windows Defender evasion) and extractJnaNative().
dev.majanito.Elevator UAC bypass via CMSTP (Connection Manager Profile Installer) — a known Windows privilege escalation technique. Uses JNA to call SendMessage to simulate pressing Enter on the CMSTP elevation prompt. Creates .xdmf config files, can doSystem() to run arbitrary commands, and downloads an "elevator" payload to disk.
dev.majanito.RPCHelper Same Ethereum RPC C2 resolution as Stage 1 — identical contract address, function selector, RSA public key, and signature verification. Redundant copy for standalone Stage 2 operation.
dev.majanito.TelemetryHelper Data exfiltration — initTelemetry(String) likely sends stolen data to the C2. Has readAllAsString() and readAllBytes() helpers.
dev.majanito.IMCL In-memory ClassLoader — identical pattern to Stage 1's ExtraClassLoader. Takes classDefinitions and resourceDefinitions maps for loading classes/resources without touching disk. Suggests Stage 2 may load a Stage 3.

Key Observations

  • All real logic is native — the Java source only shows method signatures. Full behavioral analysis requires reverse engineering the extracted DLLs (IDA Pro / Ghidra on the decompressed .dat contents).
  • UAC bypass via CMSTPElevator uses cmstp.exe to escalate privileges without a UAC prompt, a well-documented technique (T1218.003).
  • Windows Defender exclusionsMain.addDefenderExclusions() suggests the malware modifies Defender settings (likely via Set-MpPreference -ExclusionPath) after gaining elevated privileges.
  • JNA dependency — Stage 2 bundles JNA (net.java.dev.jna) for Windows API interop (WinDef.HWND, WinDef.WPARAM, SendMessage).
  • Another ClassLoaderIMCL mirrors Stage 1's in-memory class loading pattern, suggesting possible Stage 3 payload delivery.
  • Campaign UUID — The .dat resource UUID (43072760-0388-4d20-83c3-edcc17b3391a) may serve as a build/campaign identifier.

TODO — The native DLLs within the .dat file have not yet been extracted or reverse-engineered. Full behavioral analysis of the JNIC-compiled methods requires disassembly of the platform-specific native code.


Indicators of Compromise (IOCs)

Network IOCs

Type Value Description
Ethereum Contract 0x1280a841Fbc1F883365d3C83122260E0b2995B74 Smart contract storing the signed C2 domain
Ethereum Function Selector 0xce6d41de Function called to retrieve the C2 URL
C2 Domain whnewreceive.ru Resolved C2 domain (as of 2026-03-06, subject to change via on-chain update)
Credential Exfil Endpoint https://whnewreceive.ru/api/delivery/handler POST endpoint for stolen Minecraft session data
Stage 2 Download Endpoint https://whnewreceive.ru/files/jar/module GET endpoint serving the Stage 2 JAR payload

Host IOCs

Type Value Description
Malicious JAR Hide_Item_Frame-1.0.32.jar Original trojanized mod filename
Stage 2 JAR Module.jar Stage 2 payload filename
Fabric Mod ID grshiftcart Mod ID in fabric.mod.json
Mod Version 1.0.22 Version string in fabric.mod.json
Malicious Entrypoint com.example.ExampleMod Main entrypoint class in the JAR
Stage 2 Class dev.majanito.Main Class loaded from the downloaded Stage 2 JAR
Stage 2 Method initializeWeedhack(String) Reflectively invoked method in Stage 2
Campaign UUID 2fbff058-173d-4ba3-adaf-95a757636fc5 Hardcoded in fabric.api.json, sent to C2
JNIC Resource UUID 43072760-0388-4d20-83c3-edcc17b3391a .dat resource containing compressed native DLLs
CMSTP Config Extension .xdmf Extension used for CMSTP UAC bypass configs
Temp DLL Pattern lib*.tmp Temp file created by JNICLoader for native DLL

Crypto IOCs

Type Value Description
RSA Public Key (Base64) MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmNzDf4737/iYWvscWg6vQg9dHa/yUchfQY9r5htNTLZ3ZDAbqrzN93I0ctZHa27oRnkpB7XpowI4NH8eIRmaMThggpTYRXzHzLvUjhyrFFPkIOo/HI1gZF5IV7/XmvYWqgEsSpxl0iesOUlaWO5A8QlTu0QLsZAzZtzZyLj/v1XbPT02rTvZkuRhE6nzpUR4GN3Jp4Bn8zQAWdFDe17PWZxOi19uUTMPzgFj9n3h7DprwBmE3fR7IMsbiFacAoSHfqkTpEwY7A8ArK1DQ1yJXPog/PQ4aTU9gU38WC20wtct796ImZiuRYdNWcSzHda5ZbvZdvpw6RHh0zQqGVhRQIDAQAB Used to verify the signed C2 domain from the Ethereum contract
Signing Algorithm SHA256withRSA Signature verification algorithm

File Hashes

File SHA-256
Hide_Item_Frame-1.0.32.jar (Stage 1) 2c35516cc63322142b89ec6dd3feab357005f4390b276880d5a61d4fd8e76242
Module.jar (Stage 2) 91b150fa49060acb1efd67cfe5e952580a36953d53c75491781cd8f0556e5508

Behavioral IOCs

  • Fabric mod registering an entrypoint under the com.example package
  • HTTP POST to /api/delivery/handler containing JSON with fields: username, uuid, accessToken
  • eth_call JSON-RPC requests to Ethereum mainnet with data: "0xce6d41de" and to: "0x1280a841..." across many public RPC endpoints
  • In-memory JAR loading via custom ClassLoader (no Stage 2 artifacts on disk)
  • Relaunching via javaw.exe with --jw flag (standalone execution path to hide console window)
  • Extraction of a native DLL to %TEMP% from an embedded LZMA-compressed .dat resource (Stage 2 JNIC loader)
  • cmstp.exe execution with custom .xdmf config files (Stage 2 UAC bypass)
  • Modification of Windows Defender exclusion paths (Stage 2 privilege escalation)

Repository Structure

.
├── source-jar/
│   ├── stage1/
│   │   └── Hide_Item_Frame-1.0.32.jar    # Original malicious JAR (do NOT execute)
│   └── stage2/
│       └── Module.jar                     # Stage 2 payload JAR (do NOT execute)
├── stage1-fabricmc-mod/                   # Raw Recaf/Vineflower decompilation output (Stage 1)
│   └── src/main/java/com/example/
│       ├── ExampleMod.java                # Malicious Fabric mod entrypoint
│       ├── Helper.java                    # String decoder + Stage 2 loader
│       ├── FabricAdapter.java             # Ethereum RPC C2 resolver
│       ├── ExampleMixin.java              # Custom in-memory ClassLoader
│       └── Entrypoint.java                # Standalone JAR entrypoint
├── stage1-fabricmc-mod-deobfuscated/      # Manually deobfuscated & renamed version (Stage 1)
│   └── src/main/java/pog5/stage1/
│       ├── ModMain.java                   # → ExampleMod (deobfuscated)
│       ├── StageTwoLoader.java            # → Helper (deobfuscated)
│       ├── RPCHelper.java                 # → FabricAdapter (deobfuscated)
│       ├── ExtraClassLoader.java          # → ExampleMixin (deobfuscated)
│       └── Main.java                      # → Entrypoint (deobfuscated)
├── stage2-weedhack/                       # Recaf/Vineflower decompilation output (Stage 2)
│   └── src/main/java/
│       ├── dev/majanito/
│       │   ├── Main.java                  # Stage 2 entry point (JNIC native)
│       │   ├── Elevator.java              # UAC bypass via CMSTP
│       │   ├── RPCHelper.java             # Ethereum RPC C2 resolver (copy of Stage 1)
│       │   ├── TelemetryHelper.java       # Data exfiltration
│       │   └── IMCL.java                  # In-memory ClassLoader (possible Stage 3)
│       └── dev/jnic/kWGlIS/
│           ├── JNICLoader.java            # JNIC native DLL extractor + LZMA decompressor
│           ├── B.java, d.java, F.java ... # LZMA algorithm implementation classes
│           └── lib/43072760-...391a.dat   # Compressed native DLLs (win-x64 + win-aarch64)
├── decode/
│   ├── Decode.java                        # Standalone string decoder tool
│   └── decode.bat                         # Compile & run helper
├── MAPPINGS.md                            # Helper.load() string decode mappings
└── README.md                              # This file

Credits

About

Analysis of FabricMC mod based stealer malware named Weedhack.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors