Recover Java source from JNI-native binaries.
Decompile native code, trace JNI, rebuild Java-like methods, and explore results in a desktop RE workspace.
Quick Start • Features • CLI • GUI • Output • Architecture • Development
Detranspiler is a reverse-engineering pipeline for Java applications that ship logic inside native libraries (Windows DLL, Linux SO, macOS dylib). Typical targets include:
- JNI wrappers produced by bytecode-to-native transpilers
- JNIC-protected jars with
RegisterNativestables - Radioegor / native-obfuscator style loaders (
native0.Loader,Hidden0, etc.) - Mixed binaries where Java method names survive in exports, strings, or registration metadata
The tool does not promise perfect decompilation of every method. It combines multiple recovery sources, scores confidence honestly, and produces reviewable Java-like source, HTML reports, and interactive maps so you can finish recovery manually with full context.
| Deliverable | Purpose |
|---|---|
pseudocode/sources/ |
Layer-merged Java files (best available body per method) |
analysis/report.html |
Human-readable analysis summary |
analysis/re_map.html |
Interactive graph: Java methods, native functions, JNI calls |
native_map/ |
Per-method C files linked to Java native declarations |
recovered_project/ |
Exportable project tree with manifest and confidence metadata |
analysis/source_provenance.json |
Line and method evidence linking Java to CFR, native functions, pseudo-C, and JNI calls |
job.json |
Single machine-readable record of the entire analysis |
| Component | Required | Notes |
|---|---|---|
| Python | 3.10+ | Tested on 3.10 through 3.14 |
| Java runtime | Recommended | Needed for CFR jar decompilation |
| Ghidra | Optional | Headless decompilation of native code |
| pywebview | Optional | Desktop GUI only |
git clone https://github.com/0WhiteDev/Detranspiler.git
cd detranspiler
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate
pip install -e .
pip install -e ".[gui]" # optional desktop GUIOn Linux, .[gui] installs pywebview only. You still need a webview backend.
For Qt (recommended inside a virtual environment):
pip install PyQt6 PyQt6-WebEngine qtpyWithout PyQt6-WebEngine, the GUI can fail with ModuleNotFoundError: No module named 'PyQt6.QtWebEngineCore'.
python -m detranspiler doctorSet Ghidra when you want headless decompilation:
# Windows PowerShell
$env:GHIDRA_INSTALL_DIR = "C:\ghidra\ghidra_11.0_PUBLIC"
# Linux / macOS
export GHIDRA_INSTALL_DIR=/opt/ghidra
# Linux Flatpak (system install)
export GHIDRA_INSTALL_DIR=/var/lib/flatpak/app/org.ghidra_sre.Ghidra/current/active/files/lib/ghidra
# Linux Flatpak (user install)
export GHIDRA_INSTALL_DIR=$HOME/.local/share/flatpak/app/org.ghidra_sre.Ghidra/current/active/files/lib/ghidraIf your shell is Fish, for example on some CachyOS setups:
set -x GHIDRA_INSTALL_DIR /var/lib/flatpak/app/org.ghidra_sre.Ghidra/current/active/files/lib/ghidraRun this before detranspilation when the native library is packaged inside a JAR:
# One directly embedded DLL
python -m detranspiler extract --jar application.jar --out ./native --mode standard
# JNIC .dat bundle, Windows x64 payload
python -m detranspiler extract --jar application.jar --out ./native --mode jnicThe extractor never executes JAR classes or loads the resulting DLL. It validates ZIP limits and paths, derives the JNIC Windows x64 range from loader bytecode, decodes a recognized stream transform, validates the result as an AMD64 PE32+ DLL, and writes extraction.json with the source entry, range, transform, PE metadata, and SHA-256.
Ambiguous or unsupported layouts fail with an explicit error instead of selecting a candidate heuristically.
Minimal example (native library only):
python -m detranspiler analyze native.dll --out ./out --forceRecommended example (native + jar for better Java recovery):
python -m detranspiler analyze native.dll \
--out ./out \
--jar application.jar \
--ghidra-install-dir "$GHIDRA_INSTALL_DIR" \
--forceReuse existing Ghidra exports (skip headless run):
python -m detranspiler analyze native.dll \
--out ./out \
--pseudo-c ./existing/decompiled.c \
--functions-json ./existing/functions.json \
--strings-json ./existing/strings.json \
--jar application.jar \
--no-ghidra \
--forceCompare two versions of a native library:
python -m detranspiler diff old.dll new.dll --out ./version-diffThe command accepts native binaries, completed analysis directories, or their
job.json files. Existing analyses are reused without running the pipeline again.
python -m detranspiler guiThe GUI supports fresh analysis, loading an existing output folder (job.json), browsing recovered sources with line-level evidence, opening reports, and exploring the native map.
The desktop GUI provides the complete workflow in one workspace: configure or load an analysis, extract native libraries from JAR files, inspect reports and the RE map, and browse recovered Java and native code. Its Tools area separates binary preparation, differential analysis with Version Diff, and environment diagnostics. Selecting a Java line shows its source layers, semantic and mapping confidence, native function address, relevant JNI calls, and the corresponding pseudo-C fragment. It uses pywebview and keeps analysis local on your machine.
| Analysis setup | Native extractor | Recovery workspace |
![]() |
![]() |
![]() |
Core pipeline
- Binary introspection with LIEF: format detection (PE / ELF / Mach-O), exports, imports, ASCII strings
- Ghidra headless integration with bundled export scripts (
decompiled.c,functions.json,strings.json) - JNI auto-detection and mode resolution (
AUTOpicksJNIwhen indicators are present) - Pattern scanning across exports, imports, strings, and pseudo-C (crypto, anti-debug, network, compression, JNI APIs)
- CFG analysis and control-flow flattening detection with recovery hints
- Call graph construction linking native functions, JNI registration, and Java exports
- Anti-analysis profiling to flag low-trust symbols before recovery
JNI and deobfuscation
- RegisterNatives recovery (
jni_register.json) with class/method/signature mapping - JNI call extraction from decompiled C (
jni_calls.json) - JNIC detection (loader exports, encrypted names, flattened dispatch)
- JNIC body reconstruction from JNI vtable traces in decompiled code
- OnLoad keystream and string decryption passes for obfuscated string pools
- Deobfuscation risk scoring with strategy selection for fallback recovery order
Java recovery layers
Recovery merges multiple layers into pseudocode/sources/ (last layer wins):
| Layer | Source | Role |
|---|---|---|
jar_sources |
CFR decompilation of input JAR | Ground-truth structure, modifiers, non-native methods |
jni / jni_exports |
Generated from native index | JNI-linked class stubs and recovered bodies |
jnic |
JNIC-specific overlay | Loader/register classes and traced methods |
radioegor_sources |
Radioegor overlay | Native-obfuscator jar repair, record canonicalization, accessor recovery |
Additional recovery mechanisms:
- Bytecode-to-Java inference from Ghidra pseudo-C
- Pseudocode-driven return/void/control-flow synthesis
- Inter-procedural helper inlining
- Flattening-aware switch recovery
- JAR-guided CFR fallback and jar repair pass for stubborn stubs
- Field accessor and constant-string recovery for transpiled JNI bodies
- Java
recordcanonicalization when CFR emitsextends Record
Reporting and exploration
- HTML report with recovery metrics, obfuscation summary, artifact links
- RE Map (
re_map.html): pan/zoom graph of Java classes, native functions, JNI APIs, registration edges - Native Map (
native_map/): README plus one.cfile per Java native method with decompiled body - Source provenance (
source_provenance.json): compressed line ranges with method evidence, independent semantic/mapping confidence, native addresses, JNI traces, and pseudo-C references - Recovery metrics with honest per-class native recovery rate (recovered vs still-
nativein final sources) - Method confidence levels (
MINIMALthroughHIGH) and export filtering - Job summarizer CLI for terminal-friendly status
Desktop GUI
Built with pywebview (Edge WebView2 on Windows):
| Tab | Function |
|---|---|
| Setup | Configure DLL, JAR, Ghidra path, output folder; run or load session |
| Native Extractor | Safely recover an embedded DLL or a JNIC Windows x64 payload before analysis |
| Report | Embedded analysis report |
| RE Map | Interactive relationship graph |
| Native Map | Tree of Java packages/classes/methods with C viewer and syntax highlighting |
| Sources | Inspect recovered .java line by line with source layers, confidence, JNI calls, and linked pseudo-C |
| Doctor | Environment diagnostics inside the app |
python -m detranspiler <command> [options]
Run the full pipeline on a native binary.
python -m detranspiler analyze <input> --out <dir> [options]
| Option | Description |
|---|---|
--out |
Output directory (required). Use --force to overwrite. |
--mode |
AUTO, JNI, MANAGED, AOT, GENERIC_NATIVE (default: AUTO) |
--jar |
Companion JAR for CFR decompilation and guided recovery |
--no-jar-decompile |
Skip CFR when a jar is provided |
--no-java-validation |
Skip Java AST validation and safe source repairs |
--javac-validation |
Run optional isolated javac validation after repairs |
--no-ghidra |
Skip Ghidra headless decompilation |
--ghidra-install-dir |
Ghidra root (or set GHIDRA_INSTALL_DIR) |
--pseudo-c |
Existing decompiled.c instead of running Ghidra |
--functions-json |
Existing Ghidra functions export |
--strings-json |
Existing Ghidra strings export |
--force |
Delete output directory if it already exists |
Exit code is non-zero when a critical stage throws (ghidra, java_like, jni_register, jni_calls, or report).
Safely extract the native input before analysis.
python -m detranspiler extract --jar <file.jar> --out <dir> --mode standard|jnic
| Option | Description |
|---|---|
--jar |
Input JAR (required) |
--out |
Output directory (required); existing output files are never overwritten |
--mode standard |
Require exactly one embedded .dll and validate it as a PE DLL |
--mode jnic |
Recover the Windows x64 range from a JNIC loader and write win-x64.dll |
JNIC mode supports direct concatenated data and the raw LZMA2 stream wrapper used by the recognized JNIC loader. Unknown transforms, ambiguous loaders, missing resources, invalid ranges, and non-AMD64 payloads return a non-zero exit code with a stable error identifier.
Compare successive versions of native code and recovered Java evidence.
python -m detranspiler diff <old> <new> [options]
Each input can be a DLL, SO, dylib, completed analysis directory, or job.json.
Raw binaries are analyzed automatically. The report covers added and removed JNI
methods, changed RegisterNatives mappings, strings, stable call-graph edges,
confidence changes, and recovered pseudocode differences.
| Option | Description |
|---|---|
--out |
Output directory; defaults to diff-<old>-to-<new> |
--old-jar, --new-jar |
Optional companion JARs used only during fresh analysis |
--mode |
Analysis mode for raw native inputs |
--no-ghidra |
Analyze raw inputs without Ghidra |
--ghidra-install-dir |
Ghidra root (or set GHIDRA_INSTALL_DIR) |
--no-jar-decompile |
Skip CFR for companion JARs |
--no-java-validation |
Skip Java validation for fresh analyses |
--force |
Replace the diff output directory |
The command never loads a native library or executes JAR classes. Missing evidence is marked unavailable rather than inferred. Function addresses are normalized with instruction fingerprints, and ambiguous graph nodes are excluded and counted.
Print dependency and toolchain diagnostics (Python, lief, pefile, Java, Ghidra).
python -m detranspiler doctor
python -m detranspiler doctor --jsonCompact text summary of a completed job.
python -m detranspiler summarize path/to/job.json
python -m detranspiler summarize --out summary.txtBuild or regenerate the interactive RE map.
python -m detranspiler re-map path/to/job.json
python -m detranspiler re-map --demo --out examples/re_map_demo.htmlLaunch the desktop application.
python -m detranspiler gui
python -m detranspiler gui --width 1440 --height 900After analyze, the output directory contains:
out/
job.json # master job record and artifact pointers
metadata/ # binary metadata, exports, imports, strings
preprocess/ # copied input binary
ghidra/ # Ghidra project artifacts and status
pseudo_c/
decompiled.c # Ghidra pseudo-C export
pseudocode/
jar_sources/ # CFR output from --jar
jni/ # JNI recovery classes
jni_exports/ # Java_* export oriented sources
jnic/ # JNIC overlay
radioegor_sources/ # Radioegor overlay when detected
sources/ # merged final Java tree
sources_manifest.json # layer provenance per file
analysis/
report.html
re_map.html
re_map.json
recovery.json # recovery metrics
native_index.json
jni_register.json
jni_calls.json
method_confidence.json
java_validation.json # AST repairs and optional javac diagnostics
source_provenance.json # line/method evidence and native links
... # stage JSON artifacts
native_map/
README.md
c/*.c # per-method decompiled C
recovered_project/ # validated exportable IDE-friendly tree
VALIDATION.json # validation manifest and remaining errors
PROVENANCE.json # export copy of source provenance metadata
src/ # repaired final Java sources
logs/ # Ghidra and tool logs
After diff, the output directory contains diff.json, diff.txt, and a standalone
diff.html. When raw binaries are supplied it also contains old_analysis/ and
new_analysis/.
flowchart LR
subgraph inputs [Inputs]
BIN[Native binary]
JAR[Optional JAR]
GH[Ghidra exports]
end
subgraph native [Native analysis]
LIEF[LIEF metadata]
GHD[Ghidra headless]
PAT[Pattern scan]
CFG[CFG and flattening]
CG[Call graph]
end
subgraph jni [JNI layer]
REG[RegisterNatives]
CALLS[JNI call trace]
DEC[strings decrypt]
end
subgraph java [Java recovery]
GEN[Java-like generator]
JNIC[JNIC overlay]
RAD[Radioegor overlay]
REP[Jar repair]
FIN[Final sources merge]
PROV[Line provenance]
end
subgraph out [Outputs]
SRC[sources/]
RPT[report.html]
MAP[re_map.html]
NMAP[native_map/]
SIDE[source_provenance.json]
end
BIN --> LIEF
BIN --> GHD
GH --> GHD
GHD --> PAT
PAT --> CFG
CFG --> CG
GHD --> REG
GHD --> CALLS
CALLS --> DEC
REG --> GEN
CALLS --> GEN
JAR --> GEN
GEN --> JNIC
JNIC --> RAD
RAD --> REP
REP --> FIN
FIN --> SRC
FIN --> PROV
REG --> PROV
CALLS --> PROV
GHD --> PROV
PROV --> RPT
PROV --> SIDE
CG --> MAP
GHD --> NMAP
| Path | Responsibility |
|---|---|
detranspiler/pipeline/ |
Orchestration, binary probe utilities |
detranspiler/ghidra/ |
Headless runner and export scripts |
detranspiler/binary/ |
PE/ELF helpers, patterns, call graph, CFG |
detranspiler/jni/ |
RegisterNatives parsing, vtable names, JNI synthesis |
detranspiler/deobfuscation/ |
JNIC, anti-analysis, keystream, string decrypt |
detranspiler/java/ |
Java-like generation, pseudocode inference, bytecode helpers |
detranspiler/jar/ |
CFR decompilation, repair, radioegor overlay, similarity |
detranspiler/native/ |
Native index, flattening recovery, interprocedural |
detranspiler/recovery/ |
Metrics, confidence, strategy, project export |
detranspiler/reporting/ |
HTML report, RE map, native map, summarizer |
detranspiler/validation/ |
Java structure parser, safe repairs, isolated javac |
detranspiler/provenance/ |
Line/method evidence model, pseudo-C lookup, export sidecar |
detranspiler/diffing/ |
Version snapshots, stable matching, differential reports |
detranspiler/gui/ |
Desktop shell, API bridge, asset bundle |
Detranspiler reports recovery against application classes in final merged sources:
- A method counts as recovered when the
nativekeyword no longer appears on that method inpseudocode/sources/ - A method counts as remaining when it is still declared
native - Per-class and global rates are computed from
native_index.jsoncross-checked with final Java output
This avoids inflated percentages from duplicate layers or stub files. Low-confidence bodies may still appear in intermediate layers but are flagged in method_confidence.json and filtered on export when configured.
Every final Java line is assigned to a compressed evidence range in analysis/source_provenance.json. Evidence records keep source layers such as CFR, RegisterNatives, JNIC reconstruction, Ghidra pseudo-C, JNI traces, and Java validation separate from their confidence values.
For linked native methods, the sidecar records the JVM descriptor, function symbol and address, C signature, pseudo-C line span, JNI call summary, and method body/declaration ranges. Semantic confidence describes how strongly the Java body is supported; mapping confidence describes how strongly the Java method is linked to the native function.
The GUI loads summaries with each source file and resolves the heavier pseudo-C fragment only after a line is selected. Paths are restricted to the active analysis directory, and browsing evidence never executes the input JAR or native binary.
The same sidecar is copied to recovered_project/PROVENANCE.json and referenced from MANIFEST.json.
Final Java sources pass through a validation stage before recovered_project is exported:
- Parse source structure and method bodies.
- Apply conservative repairs for unambiguous missing semicolons, inferable temporary variables, literal return mismatches, known JDK imports, and identical duplicate methods.
- Optionally run javac with annotation processing, implicit compilation, external classpaths, and external source paths disabled.
- Record unresolved structural and compiler errors without guessing unsafe fixes.
Results are written to analysis/java_validation.json, included in the HTML report, and copied to recovered_project/VALIDATION.json. javac validation is disabled by default and can be enabled from the GUI or with --javac-validation.
| Variable | Purpose |
|---|---|
GHIDRA_INSTALL_DIR |
Root directory of Ghidra installation |
DETRANSPILER_SMOKE_SESSION |
Path to analyzed output folder for integration smoke tests |
GUI settings are stored in the user config directory via detranspiler/gui/settings.py.
python -m compileall detranspiler -q
python -m detranspiler doctor
python -m detranspiler re-map --demoOptional integration smoke test against a real analyzed session:
# Windows PowerShell
$env:DETRANSPILER_SMOKE_SESSION = "C:\path\to\analysis\out"
python tools/smoke_test.py| Script | Purpose |
|---|---|
tools/smoke_test.py |
End-to-end checks on an existing output folder |
GitHub Actions workflow .github/workflows/ci.yml runs on push and pull request:
compileallon Python 3.10, 3.12, 3.14doctorhealth check- Demo RE map generation
- Optional Windows smoke test when repository variable
DETRANSPILER_SMOKE_SESSIONis set
The release workflow builds a source distribution and wheel, validates both packages, and attaches them to a GitHub Release. Its release notes contain a Changelog generated from commit messages added after the previous release tag and up to the new tag.
- Update
project.versioninpyproject.toml. - Commit the release changes.
- Create and push the matching tag, for example
v1.1.0.
git tag v1.1.0
git push origin v1.1.0For example, the v1.0.1 changelog contains commits after v1.0.0 through v1.0.1. The changelog is written directly into the GitHub Release notes; it is not committed or uploaded as a separate file.
The workflow rejects a tag when it does not exactly match the package version. Manual runs build downloadable workflow artifacts without publishing a GitHub Release.
- Output is pseudocode / recovered source, not guaranteed compilable Java
- Heavy obfuscation, missing JAR, or incomplete Ghidra decompilation reduce recovery rate
- Some stages require Java (CFR) and Ghidra for best results; the pipeline degrades gracefully when they are absent
- angr is listed in doctor diagnostics but is not required for the main pipeline
Contributions are welcome. Please open an issue before large architectural changes. For bug reports, attach job.json, relevant analysis/*.json snippets, and a minimal binary sample when possible.
- Fork the repository
- Create a feature branch
- Run
python -m compileall detranspiler -qandpython -m detranspiler doctor - Open a pull request with a clear description and test notes


