A Java obfuscation, virtualization, and Native packing toolchain
简体中文 · English
JavaShroud is a Java obfuscation and hardening toolchain: a Kotlin engine performs bytecode transformation, selected methods can be lowered into protected resources executed by the Native runtime, and a Wails + Vue desktop app handles configuration and task management.
The design is Kerckhoffs-oriented: protection strength comes from per-artifact keys, layouts, opcode dialects, and the Java / Native execution boundary, not from long-term secrecy of the implementation. The artifact ships with everything needed to run, so the goal is to raise the cost of analysis and cross-sample reuse rather than claim absolute irreversibility.
| Area | Pass / entry point |
|---|---|
| Renaming | rename-classes, rename-packages, rename-methods, rename-fields (stable) |
| Constants and strings | integer-constant-obfuscation, string-encryption, field-string-encryption |
| Control flow | control-flow-obfuscation, control-flow-flattening, reference-proxy, invoke-dynamic-indirection, condy-constant-indirection |
| Method virtualization | method-virtualization: JVM bytecode lowering to native VM bytecode, executed by the Native runtime |
| Resource and class protection | JSRP current-format authenticated resource envelopes and native typed-page routing |
| Runtime defenses | os-anti-debug, os-anti-vm, callsite-rotation-protection, exception-semantic-virtualization |
| Native runtime | jni-microkernel-loader: Qp Rust runtime, authenticated resources, and platform binding |
| Desktop workflow | Wails + Vue UI, configuration editing, engine task management |
26 passes are registered; the default pipeline contains only strip-compile-debug-info. Stable passes are enabled by default. Experimental passes must be enabled explicitly in the config, and opt-in passes additionally require allowOptInPasses = true.
JavaShroud has two control-flow passes. control-flow-obfuscation rewrites existing branch and exception-handler structure. control-flow-flattening adds dispatch blocks and noise inside a method. reference-proxy, invoke-dynamic-indirection, and condy-constant-indirection change call or constant-resolution paths and can be used alongside those passes when needed.
control-flow-obfuscation inserts opaque predicates at method entry and wraps selected GOTO edges with equivalent dispatch. Predicate families include quadratic residues, bitwise identities, and modular arithmetic; mixed chooses among them. Jumps can use if-chain, lookupswitch, or tableswitch-hybrid. density controls how many jumps are considered, frequency controls predicate insertion, and seed reproduces the same selection.
The pass also has two structural rewrites:
branchInjectionreplaces frame-verified empty-stackGOTOedges with conditional edges backed by a synthetic state field. Its levels arelight,normal, andaggressive.handlerSplitsplits eligible same-type pure-rethrow handlers into overlapping protected ranges and a relay. Its levels arelightandheavy.
control-flow-flattening adds another layer. It uses density to select insertion points and supports arithmetic-nop, dead-branch, unreachable-method, and field-noise dispatch-block patterns. Handler bodies can remain nop or use synthetic field writes and synthetic method calls.
| Goal | Suggested combination | Output effect |
|---|---|---|
| Low disruption | control-flow-obfuscation, density = 3..5, if-chain |
Adds entry predicates and a small number of synthetic edges, with lower size and debugging impact. |
| General protection | density = 6..8, algebraicFamily = "mixed", plus control-flow-flattening |
Mixes predicate and dispatch-block forms within a method, producing less direct decompiler output. |
| High disruption | density = 9..10, tableswitch-hybrid or lookupswitch, with compatible branchInjection / handlerSplit |
Changes more branch, exception-table, and local-dispatch structure; exception paths, hot methods, and startup time need focused testing. |
| High-value logic | Control-flow passes plus call indirection and string / constant protection; use method-virtualization where required |
Changes call resolution as well, or moves selected methods into Native runtime execution. |
“Strength” here means the amount of work required for static reading, CFG recovery, and pattern matching. It is not an irreversibility claim. JVM instructions remain in the artifact, and invariant conditions or dead paths can be simplified with enough analysis time. Start with a small set of important classes and expand only after testing.
The passes do not force coverage. Constructors, interfaces, abstract / native methods, and high-risk shapes involving monitors, switches, uninitialized objects, or complex exception tables are skipped. Changed methods have their StackMap frames recomputed and are analyzed again. Before release, run java -Xverify:all, start the application, and cover the business paths that matter.
JSRP is the project's protected resource envelope format (magic JSRP, current version 7). VM bytecode, Native libraries, manifests, and the bootstrap index are all sealed through QpResourceCodec:
- Layout: a 27-byte header + 96 bytes of encrypted metadata + an AES-CTR body + a 32-byte HMAC-SHA256 tag. Keys and IVs for metadata and body are derived from the partition key via HMAC domain separation.
- Keys come from a build-time CSPRNG-generated partition table (
RuntimeKeyPartitions) and are selected per resource partition; any change to header, metadata, or body fails tag verification. - The body is zstd-compressed by default (
QpCompressionCodec); metadata records the SHA-256 of both plaintext and compressed bytes, and decode re-checks lengths and hashes at each step.
Field layout and the decode flow are in QpResourceCodec.
method-virtualization lowers selected Java methods into native VM bytecode (QpSerializer) sealed as JSRP resources; the original method body is replaced by a dispatcher stub. At runtime the stub calls QpBridge.executeVmResource(entryToken, …) to enter the JNI microkernel, and the Native runtime behind js_vm_execute_resource authenticates, parses, executes, and wipes sensitive state.
flowchart LR
A["Method selection and compatibility checks"] --> B["native VM lowering"]
B --> C["JSRP sealed envelope"]
C --> D["dispatcher stub"]
D --> E["JNI microkernel"]
E --> F["Native runtime authenticated execution"]
A -.incompatible.-> X["build-time fail-closed"]
E -.authentication failure.-> Y["runtime fail-closed"]
Execution entry is bound to per-artifact entry tokens, opcode dialects, resource paths, layout digests, and the dispatcher profile (DispatcherProfile). Methods that are not selected or not compatible stay within the ordinary bytecode-obfuscation boundary.
Qp uses the Rust-only runtime boundary and the final authenticated resource locator:
- Production resources are selected only for Windows x64 and Linux x64, then bound to the final artifact digest and current runtime format.
- Resource, platform, length, image, and binding failures reject loading; Java does not fall back to an old C shell or system-path library.
- The former
NativeKernelShellPackerC shell, Mach-O loader, Zig entrypoints, and.dyliboutputs are retired and remain fail-closed only for stale source fixtures.
| Platform | Qp Native boundary |
|---|---|
| Windows x64 | Rust runtime; the only cargo target is x86_64-pc-windows-gnu, with a .dll resource; the old PE/C loader is not a production path |
| Linux x64 | Rust runtime; the only cargo target is x86_64-unknown-linux-gnu.2.17, with a .so resource; the old ELF/C loader is not a production path |
| Other platforms | macOS, Mach-O, and .dylib selection, build, resource, and load paths fail closed |
Qp no longer compiles, packages, or runs the retired C/Zig Native runtime. Retired build/cache/temp entrypoints remain only as fail-closed quarantine shims.
| Dimension | Typical JNIC / Native obfuscation | JavaShroud method virtualization |
|---|---|---|
| Conversion target | Java method to native function | Java method to a protected Native VM resource |
| Execution | JNI calls the corresponding native function | Native VM authenticates, parses, and dispatches virtual instructions |
| Main analysis surface | JNI bridge, exports, and machine code | Dispatcher, resource envelope, virtual ISA, VM state, and Native boundary |
| Diversification | Native compiler output | Per-artifact keys, layout, opcodes, tokens, and runtime profiles |
The two approaches are not mutually exclusive; in JavaShroud the Native layer is part of a virtual execution protocol, not only a place to move code.
- The JavaShroud engine itself builds and runs on JDK 21+.
- Renaming, metadata cleanup, and most basic passes can process Java 8 classfiles without raising the classfile version.
ConstantDynamicfeatures require Java 11+; method virtualization, the Rust Native runtime, and most runtime defense passes target Java 11+ runtimes.- The Native runtime accepts only the declared Qp Windows/Linux x64 targets; release acceptance should use the final artifact digest, locator, and runtime result.
# Build the core engine
.\gradlew.bat :core-engine:jar
# Inspect the CLI schema (pass list, parameters, default pipeline)
java -jar build\core-engine\libs\obfuscator-engine-0.12.jar -schema
# Process a JAR with a TOML configuration
java -jar build\core-engine\libs\obfuscator-engine-0.12.jar -config path\to\config.tomlMinimal configuration example:
inputJarPath = "app.jar"
outputJarPath = "app-obf.jar"
allowOptInPasses = true
[[passes]]
id = "rename-classes"
enabled = true
[[passes]]
id = "control-flow-flattening"
enabled = true
[ruleSet]
[[ruleSet.rules]]
target = "class **"
action = "obfuscate"
[[ruleSet.rules]]
target = "class com.example.api.**"
action = "exclude"Desktop development:
corepack yarn --cwd desktop-app\frontend install --immutable
corepack yarn --cwd desktop-app\frontend build
Set-Location desktop-app
go build ./...
go test ./...Full Windows release entrypoint:
.\build-release.batThe release script builds the core engine, the GraalVM native engine, frontend assets, and the Wails desktop application into build\release\javashroud-windows-amd64\. .github/workflows/release.yml builds and publishes a GitHub Release when a v* tag is pushed.
core-engine/ Kotlin / Java engine, method virtualization, and Native runtime
desktop-app/ Go / Wails desktop host and Vue frontend
annotations/ JavaShroud annotation module
assets/ README and release assets
build-release.bat Windows release entrypoint
JavaShroud is released under the GNU GPL v3. See THIRD_PARTY_NOTICES.md and NOTICE for third-party and vendored-source notices.
