jvmtp: what it does

jvmtp takes selected methods out of a JAR and turns them into native C++ that follows HotSpot's zero-interpreter semantics, then links the result into a JNI shared library. The output JAR still runs on a stock JVM: only the methods you picked become native, and an injected NativeLoader binds them at startup. On top of plain transpilation it can virtualize methods into a bundled VM, obfuscate the generated native code, and refuse to run under a debugger or attached agent.

transpile
Bytecode → native

Chosen methods lower to C++/JNI with JVM-accurate semantics. The rest of the JAR is untouched.

virtualize
VM backend

Turn a method into an encrypted program for a bundled interpreter, so its logic never appears as readable instructions.

protect
Obfuscation + anti-debug

Opt-in native obfuscation modules, plus runtime checks that detect a debugger, agent, or JNI hook.

ship
Cross-compile anywhere

A bundled Zig toolchain builds every supported platform from one host, with no Xcode, MSVC, or NDK.

Transpilation

The core pipeline: pick methods, lower them to native code that behaves exactly like the JVM would, and repackage the JAR so nothing changes for the end user.

Method-level selection

Pick what becomes native by annotation simple-name (any annotation works, no API jar required), in include or exclude mode, narrowed further by path-glob filters.

JNI shared library output

Output is a fat JAR plus one JAR per target triple, each carrying a standard JNI library (.so / .dll / .dylib). The injected NativeLoader picks the matching binary at runtime.

JVM-accurate semantics

Lowering follows HotSpot's own zero-interpreter and JIT internals. Integer overflow, NaN propagation, exception ordering, and stack effects match the JVM specification.

Constructor extraction

<init> and <clinit> bodies are split into synthetic methods that can be transpiled too. Synthetic names follow a configurable scheme (hash, alphabetic, random).

Built-in intrinsics

Calls into Math, StrictMath, Integer, Long, Float, Double, Unsafe, and others become direct native equivalents instead of a JNI round-trip.

Custom intrinsics

Map any owner | name | desc to a C++ function in one of your headers, with options for JNIEnv*, exception checking, return casts, and skipping the receiver argument.

Virtualization (VM backend)

Virtualization replaces a method's bytecode with an encrypted program for a bundled interpreter, so the original logic never appears as native instructions a disassembler can read. It is the strongest protection jvmtp applies and the most expensive at run time, so it is off by default and selected per method. A method the backend cannot virtualize yet falls back to a normal native method, so enabling it never fails a build.

Stack interpreter

The default virtualizing machine. Selected methods run as encrypted programs on a bundled stack-based interpreter with full ISA coverage, including invokedynamic.

Register machine vm.register

A second machine alongside the stack interpreter. A method lowers to a register form covering the same constructs. One binary can mix both, by filter or a per-build ratio split.

Dual form vm.dual

Emit a method on both machines in one wrapper and choose which runs per call at run time, so no single execution trace observes both forms and the two share no opcode space.

Interpreter hardening

vm.harden-interpreter obfuscates the dispatch loop itself; vm.lazy-decrypt decrypts one chunk at a time as control flow reaches it, so untaken branches stay ciphertext in memory.

Per-build diversity

Opcode permutation, operand encoding, handler mutation (with genuine MBA), instruction fragmentation, fusion, and junk insertion make one build's VM differ from the next, so analysis does not transfer.

Runtime dispatch mutation

Multiple independent dispatch tables, reshuffled periodically at run time, so reversing one instance's mapping does not reveal the others and no single trace observes them all.

Native obfuscation

Independent, opt-in modules that make the generated native code harder to read. Enable the master switch, then turn on the modules you want. They apply to transpiled methods and to the native intrinsics support code, and can be scoped globally or per module with include/exclude lists.

Constant hiding

Integer and long literals no longer appear directly in the binary. obfuscation.constants.mba conceals them through mixed-boolean-arithmetic an optimizer cannot fold back.

String encryption

Java string literals, and the identifier strings in the intrinsics support code, are kept out of the binary as readable text and reconstructed only on first use.

Call concealment

Call targets resolve indirectly at runtime instead of as plain calls, with variants to inline the cipher per site, cover plain native calls, or route through a shared per-signature dispatcher.

Control-flow obfuscation

Opaque predicates hide conditional branches, and flattening restructures control flow into a form much harder to follow than the original structured code.

Arithmetic rewriting

Integer arithmetic and bitwise operations are rewritten into more complex equivalent forms that resist analysis.

Computed jumps

obfuscation.jumps turns comparisons into branchless arithmetic and transfers control through a register, and can emulate a call with a jump where the toolchain allows it.

Runtime protection

Checks and hardening that run inside the shipped binary, woven into load-time init and re-run from the VM interpreter's own hot path. Each family is conditionally compiled, so a build with it off carries none of the code.

Anti-debug antidebug.enable

OS-level checks (attached debugger, disabled ASLR, LD_PRELOAD, instrumentation modules, hooked libc, int3 self-scan) and Java agent detection (JVMTI / JDWP / instrumentation).

Decoupled detection and response

A positive check marks the process rather than exiting on the spot; the process terminates a moment later at a shared point, so no single check site maps 1:1 to the exit.

Anti-hook safe-env compiler.safe-env

Restores the original JNIEnv* function table on method entry to defeat agent-style JNI pointer hooks. The aggressive mode re-checks periodically, catching a hook installed after load.

Periodic re-checks

Anti-debug and environment checks re-run from the interpreter hot path, so a debugger or agent that attaches after startup is caught, the same as one already present when the process loaded.

Batch member resolution compiler.batch-resolve

Every field and method ID a class owns resolves together on first use, off the per-instruction path, so no single lookup is tied to a specific instruction a debugger is watching.

Compressed packaging resources.compress

Packs the per-platform libraries into one compressed blob, streamed back out at startup by the injected loader, for a smaller and less discoverable output JAR.

Build & toolchain

One host builds every platform. jvmtp drives the C++ build itself and hooks into external protectors where you want stronger native protection than it provides on its own.

Bundled Zig cross-compiler

Zig is downloaded automatically on first run and brings its own linker and headers. One config.conf produces libraries for every configured triple from any host, no host-side SDK required.

Parallel, resilient builds

Per-file compiles run in parallel with a configurable job cap, a per-invocation timeout that kills a hung compiler, and automatic retries that ride out a transient stall without retrying a real error.

VMProtect integration

compiler.vmp-support wraps each transpiled function in a marker labeled with its qualified name, links the VMProtect SDK, and jvmtp repack merges the protected binaries back in.

Pairs with other protectors

jvmtp sits in front of bytecode obfuscators (ZKM and the like) and native protectors such as Themida. It moves methods into native code rather than replacing a protector.

Supported platforms

triple os / environment status
x86_64-linuxLinux (glibc)stable
x86_64-windowsWindows 10+stable
aarch64-macosmacOS (Apple Silicon)stable
aarch64-windowsWindows on ARMstable
aarch64-linuxLinuxexperimental
x86_64-macosmacOS (Intel)experimental

Triples are configured via target.platforms in config.conf. Any JVM implementing the JNI specification runs the output (HotSpot, OpenJ9, GraalVM, Android ART).

Next

Every key behind these features is documented in the configuration reference. To try jvmtp on your own JAR, start with getting started, or request an evaluation sample with sample transpiled output. Licensing terms are on the license page.