webcrack
Deobfuscate, unminify and unpack bundled JavaScript into readable modules and a deobfuscated copy. Use when a webpack or browserify bundle must be read or fed to jsluice. Not for extracting endpoints
SKILL.mdskills/webcrack/SKILL.md
GitHubwebcrack
webcrack takes minified or obfuscated JavaScript and reverses the common transformations: string-array obfuscation, self-defending wrappers, debug protection, minified names and JSX. If the file is a bundle, it also extracts individual modules and records the bundler structure.
It is a static transformer. It parses and rewrites source; it never runs the input.
When to use
| Goal | Command shape |
|---|---|
| Readable copy of one script | webcrack app.js -o out/ |
| Unpack a webpack/browserify bundle | webcrack bundle.js -o out/ |
| Keep the bundle intact, only clean it | webcrack bundle.js -o out/ --no-unpack |
| Skip deobfuscation, just unminify | webcrack bundle.js -o out/ --no-deobfuscate |
| Keep original formatting, only deobfuscate | webcrack bundle.js -o out/ --no-unminify |
| Avoid Babel JSX output | webcrack bundle.js -o out/ --no-jsx |
| Overwrite a previous run | webcrack bundle.js -o out/ --force |
| Pipe source instead of a file | cat bundle.js | webcrack -o out/ |
| Prepare input for jsluice | webcrack bundle.js -o out/ && jsluice urls out/*.js |
Use jsluice when you only need endpoints or secrets; run webcrack when the code must be understood or when string-array obfuscation hides the URLs.
Prerequisites
- Binary at
/opt/node/bin/webcrack;webcrack --versionprints2.16.0. - Node.js 24.22.1 is installed alongside it.
- Input is a file argument or stdin. Output defaults to stdout; pass
-oto write a directory. - The output directory must not exist unless
-fis given. - No network access is used. No keys are required.
Option names matter on this build. There are no positive --unpack, --deobfuscate or --jsx flags. All four transforms are enabled by default and are controlled only through their --no- forms:
--no-jsx do not decompile JSX
--no-unpack do not extract modules from the bundle
--no-deobfuscate do not deobfuscate the code
--no-unminify do not unminify the code
Core invocations
# 1. full pipeline into a fresh directory
webcrack bundle.js -o out/
# 2. stdin input, same output layout
cat bundle.js | webcrack -o out/
# 3. stdout when no output directory is given
webcrack bundle.js > deobfuscated.js
# 4. single-file clean-up, no module extraction
webcrack bundle.js -o out/ --no-unpack
# 5. skip obfuscation reversal, keep unminify and unpack
webcrack bundle.js -o out/ --no-deobfuscate
# 6. preserve original formatting, only reverse obfuscation
webcrack bundle.js -o out/ --no-unminify
# 7. suppress JSX decompilation for non-React code
webcrack bundle.js -o out/ --no-jsx
# 8. overwrite an existing output directory
webcrack bundle.js -o out/ --force
# 9. inspect what the unpacker detected
cat out/bundle.json
# 10. list every produced artifact
find out -type f | sort
# 11. feed the deobfuscated tree to jsluice
find out -name '*.js' | jsluice urls -c 4
A full run on a two-module test bundle produced:
out/deobfuscated.js
out/bundle.json
out/0.js
out/2.js
bundle.json records the detected bundler and the module map:
{
"type": "webpack",
"entryId": "",
"modules": [
{ "id": "0", "path": "./0.js" },
{ "id": "2", "path": "./2.js" }
]
}
Module filenames come from the bundler's module IDs, not from original paths. entryId is empty when the entry cannot be determined.
| Artifact | When it appears | Contents |
|---|---|---|
deobfuscated.js |
always | transformed source as one file |
bundle.json |
bundle detected and unpack enabled | bundler type, entry, module list |
<id>.js |
one per extracted module | individual module source |
| stdout | no -o given |
the same transformed source, not a directory tree |
With --no-unpack, only deobfuscated.js is written.
Flags that matter
| Flag | Effect | Note |
|---|---|---|
-o, --output |
output directory | must not exist without -f |
-f, --force |
overwrite output directory | reuse a previous path |
-m, --mangle |
mangle variable names | smaller, less readable output |
--no-jsx |
disable JSX decompilation | default JSX handling is on |
--no-unpack |
keep the bundle as one file | default unpack is on |
--no-deobfuscate |
skip obfuscation transforms | default deobfuscate is on |
--no-unminify |
keep original whitespace | default unminify is on |
-V, --version |
print version | 2.16.0 |
-h, --help |
print options | authoritative for this build |
Transform log lines name the stages that ran: String Array, self-defending, debug-protection, jsx, merge-object-assignments and evaluate-globals. Absence of a stage in the log means it found nothing to change, not that it was skipped.
Limits
- Obfuscation is best-effort. String arrays protected by a runtime rotation or a decryption function may remain. Control-flow flattening is only partly reversed.
- Values that require execution (network responses,
evalof an encrypted blob, date-seeded decoding) are not recovered. - webcrack does not fetch source maps. Check for
//# sourceMappingURL=first; a real map beats any deobfuscator. - Bundler support is heuristic. Unusual or custom loaders may yield
deobfuscated.jswith nobundle.json. - React output depends on Babel parsing. If JSX transforms fail, retry with
--no-jsx --no-unminify. - Very large bundles can exhaust Node's heap; raise
--max-old-space-sizeon the node invocation if needed, or split the bundle. - When the result is still unreadable, fall back to
jsluice tree/jsluice queryfor the specific construct, Chrome DevTools pretty-print plus live breakpoints, or manual AST work.
Workflow
- Check for source maps. A
.mapfile or an inlinesourceMappingURLgives original sources with no guessing. - Fingerprint the file. Bundler banner,
webpackJsonp,__webpack_require__, browserify wrappers, or_0xstring arrays tell you what to expect. - Run the full pipeline into a new directory; keep the original untouched.
- Read
bundle.json. It tells you whether unpacking worked and which module is the entry. - Run jsluice over the output to recover endpoints before reading code line by line.
- Escalate selectively. Only the modules reachable from the entry matter; do not audit every vendor chunk.
OPSEC and telemetry
- webcrack is fully local and makes no network requests. Nothing about the target is transmitted.
- Never execute the input or the output. Obfuscated bundles are a common malware delivery format; run analysis in a disposable container with no credentials mounted.
- Output preserves embedded secrets, tokens and personal data from the original bundle. Store it like the original.
--forcedeletes the target directory. Point-oat a dedicated scratch path, never at a repository root.- Mangled output (
-m) is harder to compare across runs; keep the unmangled copy as evidence.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
output directory already exists |
previous run present | choose a new -o or add --force |
| No files written | no -o, so output went to stdout |
redirect or pass -o |
Cannot find module / stack trace |
input path wrong | check the path, or pipe via stdin |
| Parse error | syntax the parser rejects, often minified JSX | retry with --no-jsx or --no-unminify |
bundle.json missing |
no supported bundler detected | use deobfuscated.js; inspect manually |
| Output identical to input | already clean, or all transforms disabled | check flags; this is expected for clean code |
Still _0x... names after run |
runtime string-array protection | manual AST work or dynamic instrumentation |
| Process killed | heap exhaustion on a large bundle | raise Node heap, split the file |
| Module files are tiny wrappers | lazy chunk boundaries | locate the entry module and follow imports |
Verification
webcrack --version
# 2.16.0
webcrack --help | sed -n '1,20p'
# shows -o, -f, -m and the four --no-* transforms
webcrack bundle.js -o /tmp/wc-check
find /tmp/wc-check -type f | sort
# deobfuscated.js, bundle.json, and one file per module
Confirm the output parses and is non-empty before handing it on:
node --check /tmp/wc-check/deobfuscated.js
wc -l /tmp/wc-check/deobfuscated.js
If node --check fails, the input was probably not valid standalone JavaScript; try --no-unminify before concluding the bundle is hostile.
Related skills
- jsluice to extract endpoints and secrets from the output
- web-traffic-js-reversing for the capture-to-OpenAPI pipeline
- httpx to find the scripts worth downloading
- mitmproxy to capture scripts from a live session