This document records the key mechanisms of the current JavaShroud implementation: the pass pipeline, the VMBC / NBVM protocol, the Native packing protocol, the security model, and the configuration reference. All symbol names and paths are taken from the repository; an evidence index is attached at the end.
简体中文 · English
The engine registers 26 passes (buildEngineSchemaPayload). The default pipeline contains only strip-compile-debug-info; every other pass must be enabled explicitly in the config, and opt-in passes additionally require allowOptInPasses = true.
| Pass ID | Category | Stability | Default | Opt-in | Dependencies / constraints |
|---|---|---|---|---|---|
strip-compile-debug-info |
Metadata | stable | yes | no | Default pipeline member |
member-shuffle |
Metadata | stable | yes | no | |
rename-classes |
Renaming | stable | yes | yes | |
rename-packages |
Renaming | stable | yes | yes | |
rename-methods |
Renaming | stable | yes | yes | |
rename-fields |
Renaming | stable | yes | yes | |
string-encryption |
Encryption | experimental | no | yes | Requires jni-microkernel-loader |
field-string-encryption |
Encryption | experimental | no | yes | |
integer-constant-obfuscation |
Obfuscation | experimental | no | yes | |
static-init-perturbation |
Obfuscation | experimental | no | yes | |
anti-decompiler-structure |
Obfuscation | experimental | no | yes | |
invoke-dynamic-indirection |
Obfuscation | experimental | no | yes | |
control-flow-obfuscation |
Obfuscation | experimental | no | yes | |
control-flow-flattening |
Obfuscation | experimental | no | yes | |
reference-proxy |
Obfuscation | experimental | no | yes | |
condy-constant-indirection |
Obfuscation | experimental | no | yes | |
member-hide |
Hiding | experimental | no | yes | |
callsite-rotation-protection |
RuntimeDefense | experimental | no | yes | |
anti-symbolic-execution |
RuntimeDefense | experimental | no | yes | |
exception-semantic-virtualization |
RuntimeDefense | experimental | no | yes | |
environment-bound-keys |
RuntimeDefense | experimental | no | no | Requires jni-microkernel-loader |
class-encryption-loader |
LoaderProtection | experimental | no | no | Requires jni-microkernel-loader |
method-body-delayed-decryption |
LoaderProtection | experimental | no | no | Requires jni-microkernel-loader |
anti-instrumentation |
NativeKernel | experimental | no | no | Requires jni-microkernel-loader |
anti-dump-protection |
NativeKernel | experimental | no | no | Requires jni-microkernel-loader; HotSpot JVM only |
jni-microkernel-loader |
NativeKernel | experimental | no | no | Windows x64 / Linux x64 / macOS x64 / arm64 |
method-virtualization |
VmProtection | experimental | no | no | Requires jni-microkernel-loader; Java 11+ target runtime |
Validation order at config load (loadValidatedConfig -> validateConfig):
- After dependency normalization,
requiredPassIdsandrequiresAnyPassIdsare checked; missing dependencies fail the load. - Hard conflict pairs are rejected unconditionally (
allowIncompleteis parsed and passed into compatibility validation, but the current implementation does not change hard-conflict rejection). - Soft (redundant) conflict pairs require
allowRedundantPasses = true. - Opt-in passes require
allowOptInPasses = true; withformat = "javashroud-workbench"this defaults to true. - Passes requested by annotation directives (such as
@ShroudEncrypt) requireallowAnnotationPasses = true.
Frequently used parameter keys (run -schema for the full parameter schema):
| Pass | Parameter keys |
|---|---|
method-virtualization |
seed, methodSelection (safe / critical-auto / critical-plus / all-compatible), strictVirtualization, maxInstructions, maxBroadVirtualizedMethods |
jni-microkernel-loader |
kernelComponents, targetPlatform, diversifiedVirtualization, nativeRecompilation, nativeProtectionLevel, nativePackingLevel (off / standard / max), seed |
string-encryption |
scope (all-strings / annotated / length-threshold), lengthThreshold, seed |
anti-instrumentation |
detectionLevel, response, seed |
anti-dump-protection |
protectionLevel |
VmBytecodeSerializer lowers JVM bytecode into VBC4. Each virtualized method produces one vbc4-meta-v2 metadata record (Vbc4EntryMetadata) with the fields, in order: entry token (u64 hex), return type tag, method-local profile, method identity (256-bit lowercase hex), owner identity (256-bit lowercase hex), argument tag vector, resource path, static flag, native VM profile id, and dispatch profile tag.
Fixed VBC4 invariants (cannot be disabled via parameters, see VmProtectionCapabilityBuilder.kt):
- State-bound encoding and handler morphing are always on; strength is fixed at max with no low-strength compatibility profile.
- Build-time interpreter diversity is always on; execution is native-only with no Java VM fallback.
- JNI call targets are resolved through per-artifact / per-method tokens; no plaintext symbols travel on the hot path.
- The native dispatcher executes register IR; stack opcodes are accepted only as compatibility input.
- The serializer folds super-operators with a per-method structural seed and includes them in the authenticated state.
- The session integrity digest participates in seed unwrap, block-key, and constant-pool key derivation.
- Native root material is derived on demand with a short lifetime and wiped after use.
Constant-pool strings are stored sealed (VBC4_CP_SEALED_STRING_TYPE = 0x06), with key / IV / tag derived from separate HMAC domain constants.
JSRP (magic 0x4A 0x53 0x52 0x50, version 7) is the uniform protected-resource envelope, encoded and decoded by RuntimeResourceCodec. Four resource kinds exist: VM bytecode (1), Native library (2), manifest (3), and Native index (4).
Binary layout:
| Section | Size | Description |
|---|---|---|
| magic + version | 5 bytes | JSRP + 0x07 |
| nonce | 16 bytes | Random per resource |
| metadata length / MAC length / partition id | 2 bytes LE each | 27-byte header in total |
| metadata ciphertext | 96 bytes | AES-CTR; plaintext holds kind, layer count (1..7), variant (0..127), compression flag, plain length, body length, key id, seed, plaintext / stored SHA-256, partition id |
| body ciphertext | variable | AES-CTR; content is zstd-compressed by default (stored raw when compression does not help) |
| tag | 32 bytes | HMAC-SHA256 over header + metadata + body with domain separator jsrp-auth-v3 + nonce |
| trailing byte | 1 byte | MAC length, re-checked on decode |
Key derivation: the AES key and IV are the first 16 bytes of HMAC-SHA256(partitionKey, "jsrp-aes-key" / "jsrp-aes-iv", nonce, kind, variant, layers); the key id in metadata is the first 4 bytes of HMAC(partitionKey, "jsrp-key-id-v3", nonce). The partition table RuntimeKeyPartitions is generated per build by a CSPRNG, selects a partition by resource identity, and includes one anchor slot.
Decode order: check magic / version -> length and partition-id bounds -> constant-time tag comparison -> decrypt metadata and verify its inner partition id and field ranges -> decrypt body and verify the stored hash -> zstd-decompress and verify the plaintext hash. Any failure returns null and the caller fails closed.
Dispatcher stubs call the executeVmResource overload family on JniMicrokernelHelper (Object / void / int / int-int / int-void shapes). When the Native kernel is not ready they throw SecurityException; there is no Java-side VM fallback. The JNI entry point is js_vm_execute_resource (js_vm_core.c / js_vm_resource.c), and virtual instruction dispatch lives in js_vm_dispatcher.c.
NativeKernelShellPacker / NativeKernelPacker produce the outer js_kernel_<platform> stub; the complete inner kernel is sealed inside the shell as an authenticated, encoded payload. The load chain (JniMicrokernelHelper):
- Resolve the platform suffix, read the bootstrap index
META-INF/.r/0.dat(version 1), and obtain the sealed Native library list. - Decode the sealed library resource, write it to a temporary directory (candidates include
~/.javashroud/native), andSystem.loadthe outer stub. nativeInitinitialization (retried once on return code 2).- Install the boot material (see below), publish the sealed bindings
META-INF/.r/bindings.dat, and preload runtime resources into native. - ABI self-check:
nativeGetBootTokenis XOR-compared against the local mirror value, catching wholesale library replacement (for example FridaInterceptor.replace); failure setsnativeSelfCheckFailedand all later execution is rejected.
JNI_OnLoad verifies the header, section digest, layout and dispatcher profile, payload binding, chunk tags, and payload MAC in sequence; any failure rejects execution.
The artifact only stores META-INF/.r/boot.dat (magic JSBM, version 2, BootMaterialEnvelope). Sealing uses AES-GCM (128-bit tag, 12-byte random nonce) with AAD javashroud-boot-material-v2; the key is the 256-bit Boot KEK.
Plaintext layout: version (1) + resource partition count (1) + total slots (1, range 2..17) + platform binding count (1) + master key (64) + JAR layout digest (32) + per-slot partition keys (slots x 32) + platform binding entries (1-byte platform id + 32-byte non-zero commitment each). Platform ids: windows-x64=1, linux-x64=2, macos-x64=3, macos-arm64=4.
The JVM delivers the material once during JNI_OnLoad via nativeInstallBootMaterial, confirms with nativeIsBootMaterialReady, and checks that the shell actually consumed the binding commitment. The KEK itself is read from JAVASHROUD_BOOT_SECRET_V1 (64 hex characters) or from the file named by JAVASHROUD_BOOT_SECRET_FILE_V1 (32 raw bytes or 64 hex characters); a missing or malformed KEK and GCM authentication failure all fail closed.
| Platform | Validation coverage |
|---|---|
| Windows x64 | PE64 in-memory mapping: sections, relocations, imports / exports, TLS, DllMain, JNI_OnLoad, ABI table |
| Linux x64 | Anonymous-memory ELF64 loader: PT_LOAD / PT_DYNAMIC, hash, symbols, RELA / PLT, initializers, entrypoint |
| macOS x64 / arm64 | Mach-O metadata, rebase / bind, export trie, initializers; unsupported anonymous execution mapping fails closed |
Native sources live under core-engine/src/main/native/: js_kernel.c (JNI entry and shell), js_vm_core.c / js_vm_core.h (VM core), js_vm_dispatcher.c (instruction dispatch), js_vm_resource.c (resource authentication and parsing).
- Kerckhoffs-oriented: strength comes from per-artifact CSPRNG material, structural diversity (layout, opcode dialects, dispatcher profiles), and the Java / Native execution boundary, not from implementation secrecy.
- Binding graph: VMBC resources <-> bootstrap index <-> resource paths <-> manifest <-> shell commitments; transplanting any link across artifacts breaks the authentication chain.
- Fail-closed list:
- Build time: under
strictVirtualization, a method with VBC4-unsupported bytecode or beyondmaxInstructionsfails the build; a missing or malformed KEK fails the build. - Load time: missing / malformed KEK or GCM authentication failure; boot-token ABI self-check failure; shell binding commitment not consumed; missing sealed index or bindings.
- Runtime: JSRP tag / hash mismatch; tampered resource paths or profiles.
- Build time: under
- Non-goals: the artifact is self-contained and carries all material needed to run; the project does not claim absolute irreversibility. The goal is to raise the cost of one-off analysis and cross-sample bulk reuse.
Configuration files are TOML (parsed with jackson-dataformat-toml), mapping to ObfuscationConfig:
| Field | Type | Description |
|---|---|---|
inputJarPath / outputJarPath |
string, required | May sit at the root or under an [input] table |
[[passes]] |
array, required | Each entry has id, enabled (boolean), and an optional params table |
[ruleSet] / rules |
array | Rules as [[ruleSet.rules]] inside [ruleSet], or top-level [[rules]] |
allowOptInPasses |
boolean | Allow opt-in passes; defaults to true with format = "javashroud-workbench" |
allowRedundantPasses |
boolean | Allow soft-conflict (redundant) pass pairs together |
allowAnnotationPasses |
boolean | Allow annotation directives to enable passes |
allowIncomplete |
boolean | Parsed and passed into compatibility validation; hard conflicts are still rejected unconditionally |
format |
string | javashroud-workbench marks a desktop-produced config |
Rule syntax: target = "class <pattern>", optionally followed by #member or #member:descriptor to narrow to members; the engine currently consumes the actions obfuscate (explicitly include) and exclude (skip; an explicit obfuscate match wins). Class patterns support * (single level) and ** (multi level) wildcards.
Complete example enabling VMBC and Native packing:
format = "javashroud-workbench"
inputJarPath = "app.jar"
outputJarPath = "app-obf.jar"
allowOptInPasses = true
[[passes]]
id = "rename-classes"
enabled = true
[[passes]]
id = "control-flow-flattening"
enabled = true
[[passes]]
id = "string-encryption"
enabled = true
params = { scope = "all-strings" }
[[passes]]
id = "jni-microkernel-loader"
enabled = true
params = { nativePackingLevel = "max" }
[[passes]]
id = "method-virtualization"
enabled = true
params = { methodSelection = "critical-plus", strictVirtualization = true }
[ruleSet]
[[ruleSet.rules]]
target = "class com.example.**"
action = "obfuscate"
[[ruleSet.rules]]
target = "class com.example.api.**"
action = "exclude"Set the same KEK before building and running:
$env:JAVASHROUD_BOOT_SECRET_V1 = "<64 hex characters>"| Symbol | Location |
|---|---|
| Pass registry and default pipeline | core-engine/src/main/kotlin/io/github/hht0rro/javashroud/capabilities/ (SchemaCapabilities.kt, the *CapabilityBuilder.kt files) |
| Config model and validation | .../model/config/ConfigModels.kt, .../config/ConfigDecodeSupport.kt, .../config/ConfigLoadSupport.kt, .../config/ConfigRedundantPassSupport.kt, .../config/PassConfigDecodeSupport.kt |
| Rule parsing | .../analysis/RuleMatching.kt, .../analysis/RuleTargeting.kt |
| VBC4 serialization | .../transforms/protection/VmBytecodeSerializer.kt |
| JSRP codec | .../transforms/protection/RuntimeResourceCodec.kt |
| Key partitions | .../transforms/protection/RuntimeKeyPartitions.kt |
| zstd codec | .../transforms/protection/Vbc4ZstdCodec.kt |
| Dispatcher profile | .../transforms/protection/DispatcherProfile.kt |
| VM resource catalog | .../transforms/protection/RuntimeVmCatalog.kt |
| Boot material envelope | .../transforms/protection/BootMaterialEnvelope.kt |
| Shell packing | .../transforms/protection/NativeKernelShellPacker.kt, .../transforms/protection/NativeKernelPacker.kt |
| Runtime JNI helper | core-engine/src/main/java/io/github/hht0rro/javashroud/transforms/protection/JniMicrokernelHelper.java |
| Native VM and shell | core-engine/src/main/native/js_kernel.c, js_vm_core.c, js_vm_core.h, js_vm_dispatcher.c, js_vm_resource.c |