| name | crypto-recon-deobf |
| description | Specialist agent for handling minified, obfuscated, and webpack-bundled JavaScript before crypto analysis. Runs between download and analysis phases. Part of crypto-recon pipeline. |
Crypto Recon — Deobfuscation & Minified JS Handler
Companion Files
sourcemap-recovery.md — How to find, download, decode, and extract original source files from JS source maps (.map files). After recovery: original variable names visible.
string-array-decode.md — Manual and automatic decoding of _0x string array obfuscation. Covers all variants: simple arrays, rotation ciphers, shufflers, XOR-encoded strings, obfuscator.io.
Purpose
Transform downloaded minified/obfuscated/bundled JS into readable form so crypto patterns can be found reliably. Output goes to output/[domain]/deobf/ — analysis specialists read from there, not the raw files.
When to Run
Always run this phase. Even "plain" minified JS benefits from beautification.
- Run AFTER crypto-recon-fetch completes
- Run BEFORE crypto-recon-hash, signing, symmetric, etc.
- If deobf produces no improved files, specialists fall back to raw files
The Four Techniques (apply all in order)
Technique 1: Source Map Recovery (Highest Value)
Source maps contain the original unminified source with real variable and function names.
Step 1 — Detect source map references
At the bottom of each minified JS file, look for:
Also check HTTP response header: X-SourceMap: /assets/js/index.js.map
Step 2 — Fetch the map file
Run:
python tools/deobf.py --sourcemap <domain_folder> <base_url>
This:
- Scans each JS for
sourceMappingURL
- Fetches the
.map file (relative URL resolved against base URL)
- Parses the JSON source map
- Reconstructs original source files from
sources + sourcesContent arrays
Step 3 — Save recovered sources
output/[domain]/deobf/sourcemap/
├── src/
│ ├── utils/crypto.js ← original filenames from source map
│ ├── api/request.js
│ └── store/auth.js
If source maps are found: This is the jackpot. Pass these files directly to all specialists — original variable names, comments, file structure all intact.
Source map JSON structure:
{
"version": 3,
"sources": ["src/utils/crypto.js", "src/api/request.js"],
"sourcesContent": ["// original source code...", "..."],
"mappings": "AAAA,..."
}
We only need sources and sourcesContent — ignore mappings.
Technique 2: JS Beautification
Restores indentation and formatting. Does NOT restore variable names, but makes context readable.
Run:
python tools/deobf.py --beautify <domain_folder>
Output: output/[domain]/deobf/beautified/[filename].js
Before beautification:
function t(e){var n=r.v4().hex;e.random=n;var i=["signature","track","appSpecificField"];var o={};Object.keys(e).sort().forEach(function(t){var n=e[t];if(i.includes(t)||null===n||""===n)return;o[t]=n});var a=JSON.stringify(o,null,0);var s=c.MD5(a).toString().toUpperCase().slice(0,32);var u=Math.floor(Date.now()/1e3);return[n,s,u]}
After beautification:
function t(e) {
var n = r.v4().hex;
e.random = n;
var i = ["signature", "track", "appSpecificField"];
var o = {};
Object.keys(e).sort().forEach(function(t) {
var n = e[t];
if (i.includes(t) || null === n || "" === n) return;
o[t] = n
});
var a = JSON.stringify(o, null, 0);
var s = c.MD5(a).toString().toUpperCase().slice(0, 32);
var u = Math.floor(Date.now() / 1e3);
return [n, s, u]
}
Even with mangled names (t, e, n, c), the PATTERN is now readable:
r.v4().hex → UUID
["signature","track","appSpecificField"] → exclude list (strings survive minification!)
Object.keys(e).sort() → sort pattern
c.MD5(a).toString().toUpperCase().slice(0,32) → MD5, uppercase, 32 chars
Key insight: String literals are NEVER minified. The exclude list ["signature", "track", "appSpecificField"] will appear verbatim even in heavily minified code.
Technique 3: String Array Extraction (Obfuscator Pattern)
Many obfuscators (obfuscator.io, javascript-obfuscator) move all strings into an array:
var _0x3f2a = ["signature", "track", "MD5", "random", "toString", "toUpperCase"];
function _0x1b3c(_0x4d2e, _0x5f1a) { return _0x3f2a[_0x4d2e - 0x1b0]; }
Run:
python tools/deobf.py --strings <domain_folder>
This:
- Finds string array patterns:
var _0x[a-f0-9]+ = \[(".*?",?\s*)*\]
- Finds the accessor function:
function _0x[a-f0-9]+\(_0x[a-f0-9]+,\s*_0x[a-f0-9]+\)
- Builds decode table:
{_0x1b3c(0x1b0): "signature", _0x1b3c(0x1b1): "track", ...}
- Replaces all calls in file with their string values
- Saves to
output/[domain]/deobf/strings-decoded/[filename].js
After string decoding, run beautifier again on the result.
What strings to look for in extracted list:
"signature", "sign", "random", "nonce", "timestamp", "token", "auth"
"md5", "sha256", "hmac", "encrypt", "decrypt", "base64"
"Authorization", "X-Sign", "X-Token"
"toUpperCase", "toLowerCase"
JSON.stringify, sort, filter
If ANY of these appear in the string array → crypto is in this file.
Technique 4: AST-Based Structural Analysis
Parse JS into Abstract Syntax Tree and find patterns by structure, not variable names.
Run:
python tools/deobf.py --ast <domain_folder>
What AST analysis finds that regex misses:
Pattern A: Sort + Stringify call chain
CallExpression (JSON.stringify)
└── CallExpression (.sort)
└── CallExpression (Object.keys / Object.entries)
→ Report: "Sort-then-stringify pattern at line X — likely signing"
Pattern B: Multi-step hash chain
CallExpression (.slice(0, N))
└── CallExpression (.toUpperCase / .toLowerCase)
└── CallExpression (.toString)
└── CallExpression (md5 / sha256 / anyHash)
→ Report: "Hash truncation pattern — likely signature generation"
Pattern C: UUID + Date.now combo
VariableDeclaration
└── CallExpression (uuid / v4 / randomBytes)
VariableDeclaration
└── CallExpression (Date.now / Math.floor)
→ Report: "Nonce + timestamp assignment — likely signing setup"
Pattern D: Axios interceptor
CallExpression (.use)
└── MemberExpression (.interceptors.request)
└── MemberExpression (axios / instance)
→ Report: "Request interceptor found — all API calls pass through here"
Pattern E: Object key exclusion filter
ArrayExpression containing StringLiterals
└── used in .includes() or .indexOf() call
└── inside .filter() or forEach
→ Report: "Exclusion list: ['signature', 'track', 'appSpecificField']"
AST output: output/[domain]/deobf/ast-findings.json
Webpack Module Unwrapping
Webpack bundles wrap each module:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
[chunkId],
{
"moduleId1": (function(module, __webpack_exports__, __webpack_require__) {
}),
"moduleId2": (function(module, __webpack_exports__, __webpack_require__) {
})
}
])
Run:
python tools/deobf.py --webpack <domain_folder>
This:
- Detects webpack bundle format (v4, v5, vite)
- Extracts each module as a separate file
- Saves to
output/[domain]/deobf/modules/module_[id].js
- Smaller files = faster analysis, more focused context
Vite/Rollup format:
const __vite__mapDeps = (indexes, deps) => {...}
Output Structure
output/[domain]/
├── [original JS files]
└── deobf/
├── sourcemap/ ← Technique 1 (best)
│ └── src/
│ ├── utils/crypto.js
│ └── api/request.js
├── beautified/ ← Technique 2 (always)
│ ├── index-5ef15b56.js
│ └── common.modules-xxx.js
├── strings-decoded/ ← Technique 3 (when obfuscated)
│ └── index-5ef15b56.js
├── modules/ ← Webpack unwrap
│ ├── module_001.js
│ └── module_002.js
└── ast-findings.json ← Technique 4
Analysis Priority Order
Tell specialists to use files in this priority:
deobf/sourcemap/src/**/*.js (if exists) ← best
deobf/strings-decoded/*.js (if obfuscated)
deobf/beautified/*.js (always available)
deobf/modules/*.js (webpack-split)
- Original
*.js files (fallback)
What Minification Cannot Hide
Even in the most minified code, these always survive:
- String literals:
"signature", "md5", "Authorization"
- Library calls:
CryptoJS.MD5(, btoa(, JSON.stringify(
- Number literals:
0x20 (32), 1e3 (1000 = seconds)
- Array literals:
["signature","track","appSpecificField"]
- Regex patterns:
/[a-f0-9]+/
These are the anchors. Find them → expand context → understand logic.