Scatter anti-decompilation bytecode patterns from the helios-decompiler/decompiler-vulnerabilities-and-bugs research catalogue (DVB) across a target JAR.
Every real class in the input JAR is infected with a randomised mix of techniques,
emitted as private synthetic methods with seeded-random names. Techniques whose
class-file-version requirements don't fit the host (DVB-0001/0002/0009 cap at v50/v49
because they rely on JSR or uninitialised-across-jump bytecode) are dropped into the
JAR as shadow classes, scattered into real packages with names that mimic
compiler-generated synthetics (com/example/$$3537d8d9.class). The original methods
are never touched, so the program runs identically — but every class now carries
multiple decompiler tripwires.
java -jar dcmpkiller-1.0.0.jar (or just double-click on Windows) opens the
workbench: pick an input JAR, an output JAR, dial in the scatter parameters, tick the
techniques you want, click Inject.
dcmpkiller [command] [options]
Commands:
inject (default) Inject techniques into a JAR.
list Print every registered transformer.
gui Launch the graphical interface.
help Print this message.
Inject options:
--in <path> Input JAR (required).
--out <path> Output JAR (required).
--only <ids> Comma-separated transformer IDs.
--all Apply every registered transformer.
--seed <hex|long> RNG seed (default 0xD0C04D11).
--per-class-min <int> Minimum techniques per real class (default 1).
--per-class-max <int> Maximum techniques per real class (default 3).
--shadow-multiplier <int>
Shadow-class copies per technique (default 3).
--verbose, -v Verbose logging.
# Aggressive default-style scatter
java -jar dcmpkiller-1.0.0.jar --in app.jar --out app-protected.jar --all
# Saturate every class (10–18 techniques each) and a deterministic seed
java -jar dcmpkiller-1.0.0.jar --in app.jar --out app-protected.jar --all \
--per-class-min 10 --per-class-max 18 --seed cafef00d
# A curated subset
java -jar dcmpkiller-1.0.0.jar --in app.jar --out app-protected.jar \
--only DVB-0007,DVB-0021,DVB-0023
# Inspect the catalogue
java -jar dcmpkiller-1.0.0.jar listTransformResult result = new DcmpKiller().run(
DcmpKillerOptions.builder()
.input(Path.of("app.jar"))
.output(Path.of("app-protected.jar"))
.useAll(true)
.perClassMin(3)
.perClassMax(6)
.seed(0xCAFEF00DL)
.build());
System.out.println(result.perTransformer());-
Per-host embed. For every real class, the engine picks
[perClassMin..perClassMax]compatible transformers in seeded-random order. "Compatible" means the host's class-file major version falls within the transformer's[minClassVersion..maxClassVersion]window. Each picked transformer adds aprivate static syntheticmethod (sometimes two — DVB-0023 needs a helper) with a randomised hex-string name. -
Try-then-rollback. After each embed, the host class is round-tripped through ASM with
COMPUTE_FRAMES. If frame computation or structural validation fails for a particular technique on a particular host, the method(s) are reverted and the engine moves on. The original code is never affected by failures. -
Shadow-class scatter. Transformers that can't embed anywhere (DVB-0001/0002/0009 on a v51+ jar) are written as shadow classes. Each shadow lands in a randomly-chosen real package with a randomised
$$xxxxxxxx-style name so it blends with compiler-generated synthetics. The engine also emits extra shadow copies of every other technique (controlled by--shadow-multiplier) for additional surface. -
Seeded RNG. All randomisation is seeded — a given input + seed produces a reproducible output. Change the seed and the output layout changes completely: different host counts, different class names, different technique placements. No static signature can identify the injections across builds.
- Subclass
AbstractTransformerundercom.dcmpkiller.transformer.impl. - Implement
emitInto(ClassNode host, String methodName, Random random)— emit the technique's bytecode as a method onhost. Usehost.namefor anyINVOKESTATICself-references so the same payload works in both real-class and shadow-class modes. - Override
getMinClassVersion/getMaxClassVersionif the technique requires a specific class-file-version window. - Register the new transformer in
TransformerRegistry.builtIn()(one line).
The CLI list command, the GUI checklist, the --only flag, and the scatter engine
pick it up automatically.
Each transformer's metadata declares the decompilers the technique is documented to break. Below is the status from May 2026 research — useful when picking a curated subset.
| Decompiler | Latest version (May 2026) | DVB status |
|---|---|---|
CFR (leibnitz27/cfr) |
0.152 (Dec 2021), master 0.153-SNAPSHOT actively maintained | 4/11 fixed (DVB-0013, 0019, 0021, 0023), 2 partial, 5 still broken |
| Fernflower (JetBrains) | Active, IDEA-tagged correctness fixes through Apr 2026 | Most DVBs not directly addressed; same error strings (parsing failure!, Statement cannot be decomposed) still emitted |
| Vineflower (active fork) | 1.12.0 (Apr 29 2026) | DVB-0003 fixed (PR #46, 1.8.0); DVB-0006 / 0014 partial (PR #494, 1.11.2); rest substantially the same as Fernflower |
Procyon (mstrobel/procyon) |
0.6.0 (Feb 2022) — effectively dormant | All 8 Procyon DVBs still broken; bundled vanilla in Recaf, Bytecode Viewer, Luyten |
| JD-Core / JD-GUI | 1.1.3 / 1.6.6 (Dec 2019) — dormant; nbauma109/jd-core 1.3.3 (Feb 2026) is the active community fork |
0 of 18 JD-GUI DVBs have a documented fix |
So even nine years after the original DVB report, the techniques still trip popular Java decompilers in 2026. DVB-0001 / 0002 / 0009 / 0023 are particularly stubborn across the board.
Java 25 + Maven. Lombok handles the @Getter/@Builder on the API and core-state
classes. ASM 9.9.1 is used for bytecode generation (it understands class file v69 /
Java 25).
mvn package
# -> target/dcmpkiller-1.0.0.jar (shaded, double-clickable)