Getting Started

Wompfuscator protects your JVM applications with multiple layers of obfuscation and security. This guide will get you up and running in under 5 minutes.

1. Create a Configuration File

Create a config.yml in your project directory. This file will hold all of your obfuscation settings, targets, and exemptions.

2. Run Wompfuscator

terminal
java -jar wompfuscator.jar --config config.yml

That's it! Your protected JAR will be written to the output path.

Configuration

All settings are controlled through a single config.yml file.

Field Type Description
seed String Optional
Random seed for reproducible builds
input Path Required
Path to your compiled JAR file
output Path Required
Where to write the protected JAR
target-jdk Path Optional
Path to the JDK installation (Required only if using Native Loader)
libraries List Optional
Dependency JARs your application needs to resolve types
exempt List Optional
Packages/classes to exclude from obfuscation
targets List Optional
Native compilation targets (e.g. x86_64-windows). Defaults to your current OS if Native Loader is enabled.

Security Tips

Follow these best practices to get the most out of Wompfuscator's protection.

Keep Secrets on the Stack

Never store sensitive keys as class fields. Fields are easily visible in heap dumps and through reflection. Keep them as local variables inside protected methods.

AVOID THIS
public class APIConfig {
    public static String TOKEN = "sk-live-12345";

    public void init() {
        connect(TOKEN);
    }
}
DO THIS INSTEAD
public class APIConfig {
    public void init() {
        String token = "sk-live-12345";
        connect(token);
    }
}

Combine Multiple Layers

Use the Packer together with Anti-Dump and Anti-Agent for maximum effect. The Packer encrypts your classes, Anti-Dump prevents memory extraction and Anti-Agent blocks debugging tools.

Native Loader

The Native Loader compiles your Java bytecode into a native binary that is loaded at runtime. The logical instructions are completely removed from the JVM which prevents decompilers from seeing how the method works.

ORIGINAL SOURCE
public class LicenseManager {
    public boolean verifySignature(String key) {
        if (key == null) return false;
        return Crypto.checkRSA(key, "PUB_KEY");
    }
}
AFTER NATIVE COMPILATION
public class LicenseManager {
    // Logic compiled to native C++ binary
    // JVM has no knowledge of the implementation
    public native boolean verifySignature(String key);
    
    static {
        System.loadLibrary("core_x86_64");
    }
}

Supported Platforms

Target OS Architecture Output
x86_64-windows Windows x86_64 .dll
x86_64-linux Linux x86_64 .so
aarch64-linux Linux ARM64 .so
x86_64-macos macOS Intel .dylib
aarch64-macos macOS Apple Silicon .dylib

Packer

The Packer wraps your classes into an encrypted payload. Classes are loaded directly into memory at runtime to bypass the file system entirely.

For frameworks like Bukkit, Fabric, or Spigot, the Packer automatically generates specialized classes to ensure seamless integration:

  • Bootstrap Class: (e.g., Bootstrap) Initializes the native engine and decrypts the payload directly into JVM memory before the application actually starts.
  • EntryPoint Stub: A proxy class that mimics your original main class. It tricks the mod/plugin loader into loading the application normally, and immediately hands off execution to your real, decrypted code.
BEFORE: Standard JAR
📁 my-plugin-v1.0.jar
  ├── Main.class
  ├── ConfigHandler.class
  ├── EventListener.class
  ├── CommandManager.class
  └── UserSession.class
AFTER: Packed
📁 my-plugin-protected.jar
  ├── Bootstrap.class
  ├── Entrypoint.class
  ├── classes.vm              ← Encrypted classes
  └── win32.dll               ← Native Engine

String Encryption

All string literals in your code are encrypted in the bytecode and only decrypted when needed at runtime. This prevents anyone from searching the JAR for sensitive text.

ORIGINAL SOURCE
public void connectDatabase() {
    Logger.info("Connecting to db.local:5432");
    db.login("admin", "super_secret_password");
}
DECOMPILER VIEW (POLY2 CONDY)
public void O0OO0() {
    // Poly2 uses ConstantDynamic for decryption
    // Decompilers can't represent this in Java
    a.info((String) /* dynamic */ O00O0O(
        "x\u001A\u0099+", 0x7F2A91BC, 42
    ));
    
    b.login(
        (String) O00O0O("\u0011\"", 0x1A, 1),
        (String) O00O0O("3D", 0x2B, 2)
    );
}

Control Flow Obfuscation

Transforms the logical structure of your code into a complex maze. Your application runs exactly the same but decompilers produce unreadable output.

ORIGINAL SOURCE
public void processUser(User u) {
    if (u.isAdmin()) {
        grantAdminAccess();
    } else {
        showErrorMenu();
    }
}
DECOMPILER VIEW
public void a(User var1) {
    int var2 = 148923;
    while (var2 != 0) {
        switch (var2) {
            case 148923:
                var2 = var1.isAdmin() ? 892341 : 558129;
                break;
            case 892341:
                this.grantAdminAccess();
                var2 = 0;
                break;
            case 558129:
                this.showErrorMenu();
                var2 = 0;
                break;
        }
    }
}

Number Encryption

Numeric constants are replaced with complex mathematical expressions using Mixed Boolean Arithmetic (MBA). Simple integers become unreadable formulas extracted into hidden arrays and evaluated at runtime.

ORIGINAL SOURCE
public void healPlayer(Player p) {
    p.updateStatus(20);
    p.applyShield(100);
}
DECOMPILER VIEW
private static int[] O0OO0; // Hidden lookup table

public void O00O0(Player var1) {
    // Numbers extracted to arrays with MBA indexing
    var1.O0OOO(O0OO0[
        ((150 ^ 83) & ~(150 | 83)) + 4
    ]);

    var1.O0O0O(O0OO0[
        ~(~7 | 3) + (12 ^ 5)
    ]);
}

Name Obfuscation

All meaningful names are replaced with short randomized identifiers. This removes all context and makes the code unrecognizable.

ORIGINAL SOURCE
public class NetworkManager {
    private String serverIp;
    
    public void sendPacket(byte[] payload) {
        socket.write(payload);
    }
}
DECOMPILER VIEW
public class O0OO00 {
    private String o0o0o0;

    public void O0O0O(byte[] var1) {
        O000OO.O0O(var1);
    }
}

Array Obfuscation

Array allocations and length checks are rewritten to use the Java Reflection API. This breaks decompilers that attempt to reconstruct array initializations.

ORIGINAL SOURCE
public void allocateCache() {
    String[] cache = new String[10];
    int size = cache.length;
}
DECOMPILER VIEW
public void O0OO0() {
    // newarray and arraylength → reflection
    String[] var1 = (String[]) Array.newInstance(
        String.class, 10
    );
    int var2 = Array.getLength(var1);
}

Reference Proxying

Hides the relationship between your code and the APIs it calls. Method invocations and field accesses are routed through dynamically generated synthetic proxies and InvokeDynamic instructions.

ORIGINAL SOURCE
public void logEvent(String msg) {
    System.out.println(msg);
}
DECOMPILER VIEW
public void O00O0(String var1) {
    // Calls routed through synthetic bridges
    O0OO00.O00O(
        O000O.O0(), var1
    );
}

Decompiler Crashers

Specially crafted bytecode sequences that exploit known bugs in decompilers. When someone tries to decompile your JAR the decompiler will crash, freeze or produce broken output.

Targeted Decompilers

  • CFR — Stack overflows and infinite loops
  • Procyon — Invalid AST generation
  • Vineflower / Fernflower — Crashes on malformed attributes
  • JD-GUI — UI freeze on specially crafted code
DECOMPILER FAILURE LOG
// CFR crashes with StackOverflowError:
java.lang.StackOverflowError
    at org.benf.cfr.reader.bytecode.analysis...
    ... 1024 more

// Procyon output:
ERROR: Failed to decompile class 'a/b/c'
ERROR: Unexpected opcode at offset 0x2F

Integrity Checks

Runtime integrity verification detects if your code has been tampered with. Includes three protection layers:

Anti-Tamper

Injects checksum verification into critical classes. If the bytecode has been modified the application detects the change and takes protective action.

Anti-Dump

Prevents attackers from extracting decrypted classes from JVM memory. Works by monitoring class redefinition and detecting debugger attachment.

Anti-Agent

Blocks Java agents from attaching to your running application. This prevents tools from hooking into your code.

Virtualization

Converts your Java bytecode into custom opcodes that run on a built-in virtual machine. This is the strongest protection layer because the attacker needs to fully reverse-engineer a custom instruction set.

ORIGINAL SOURCE
public boolean hasPermission(int level) {
    return level >= 50;
}
DECOMPILER VIEW (VIRTUALIZED)
public boolean a(int var1) {
    // Translated to custom VM opcodes
    return (boolean) WompVM.execute(
        new int[]{
            0xFA, 0x11, 0x99, 0x2B, 
            0x4C, 0x88, 0x10, 0x00
        }, 
        new Object[]{var1}
    );
}

Exemptions

Exclude specific packages, classes, methods or fields from obfuscation. This is essential for API compatibility, reflection targets and framework entry points. You can also specify skip-heavy paths to bypass extreme transformations on performance-sensitive hotpaths.

Resource Encryption

Encrypt config files, JSON, XML and properties files embedded in your JAR. They are decrypted transparently at runtime so your code doesn't need any changes.

Platform Support

Wompfuscator has first-class support for Minecraft modding and plugin frameworks:

Platform Support Notes
Fabric Full Mixin-aware obfuscation via Fabric-Protect
Bukkit Full Preserves event handlers and command APIs
Paper Full Paper-specific APIs and async features preserved
Spigot Full Compatible with Spigot plugin conventions
Velocity Full Proxy plugin obfuscation with event preservation
Standalone Full Any JVM application