How to recover the cleartext payload from obfuscated JavaScript using eval interception, browser DevTools breakpoints, and CyberChef — with patterns for Base64, string concatenation, character codes, and nested eval.
Sign in to mark this article as read and track your progress.
JavaScript runs in the browser, and browsers download scripts in plaintext. To hide payloads from security scanners, attackers obfuscate their JavaScript using encoding, string manipulation, dead code insertion, and variable renaming.
The analyst''s key advantage: no matter how many layers of obfuscation are applied, the browser must eventually produce executable JavaScript to run it. The final step is almost always a call to eval(), Function(), setTimeout(), or document.write() with the decoded string as the argument.
The strategy: intercept the decoded string after the decryption routine runs but before eval() executes it.
| Pattern | Mechanism | Recognition |
|---|---|---|
| Base64 encoding | Payload decoded at runtime with atob() | Strings ending in = or ==, long alphanumeric sequences |
| String concatenation | Split into fragments: "htt"+"p:/"+"/evil" | Many + operators joining short quoted strings |
| Character codes | String.fromCharCode(104,116,116,112) produces http | Arrays of numbers 32–126 |
| Variable renaming | Names replaced with _0x4a3b, a1b2c3 | Hex-like variable names throughout |
| Dead code | Unused variables and unreachable blocks inserted | Large file size relative to apparent functionality |
| Nested eval | eval(eval(atob(atob("...")))) | Multiple eval/atob calls nested |
| Replace chains | ("hXXp://evil" .replace("XX","tt")) | -replace, -split patterns |
Open browser DevTools (F12) before loading the page. In the Console tab, override execution functions to print instead of execute:
// Override eval
var original_eval = eval;
eval = function(code) {
console.log("[INTERCEPTED eval]:", code);
// deliberately NOT calling original_eval(code)
};
// Override document.write
document.write = function(html) {
console.log("[INTERCEPTED document.write]:", html);
};
// Override Function constructor
var original_Function = Function;
Function = function() {
console.log("[INTERCEPTED Function]:", arguments);
return function(){};
};
After pasting these overrides, navigate to the malicious page. The decoded payload appears in the console log instead of executing.
1. Open DevTools (F12) → Sources tab
2. Locate the script containing eval() or atob()
3. Click the line number to set a breakpoint on the eval() call
4. Reload the page — browser pauses at the breakpoint
5. Hover over the argument to eval() — tooltip shows the decoded string
6. Check the Scope panel for all local variables and their current values
This is safer than the override technique for complex scripts — you can step through each decode layer individually.
For scripts that are purely static transformations (no runtime behaviour needed), CyberChef decodes offline:
gchq.github.io/CyberChef
Recipe example for a Base64 + URL-encoded payload:
1. From Base64
2. URL Decode
3. (repeat if nested)
Build a recipe in CyberChef by dragging operations from the left panel. The output updates live. If still obfuscated, add another decode step.
1. Open DevTools BEFORE loading the page
2. Paste eval/document.write overrides into Console
3. Load the malicious page (in your isolated analysis VM only)
4. Copy intercepted payload from Console output
5. If still obfuscated:
a. Identify the encoding type (Base64? char codes? hex?)
b. Apply the appropriate decode in CyberChef
c. Repeat until plaintext is recovered
6. Extract IOCs from the plaintext:
- C2 URLs and IP addresses
- File paths for dropped payloads
- Shellcode or further encoded stages
Obfuscated input:
var _0x3f2a = ['\x68\x74\x74\x70','\x3a\x2f\x2f'];
eval(String.fromCharCode(105,110,110,101,114,72,84,77,76) +
'="' + _0x3f2a[0] + _0x3f2a[1] + 'evil.com/gate.php"');
Step 1: Resolve hex escapes in _0x3f2a:
\x68\x74\x74\x70 = http\x3a\x2f\x2f = ://Step 2: Decode String.fromCharCode(105,110,110,101,114,72,84,77,76):
innerHTM L → innerHTMLStep 3: Reassemble:
innerHTML = "http://evil.com/gate.php"
IOC extracted: http://evil.com/gate.php
Malicious JavaScript sometimes checks the environment before executing the payload:
if (navigator.language !== "en-US") { return; }
if (screen.width < 1024) { return; }
if (document.referrer === "") { return; }
// payload only executes if all checks pass
Environmental keying causes the script to exit silently if a specific domain, locale, or browser property is not present. This is why automated sandbox analysis sometimes misses the payload.
Bypass: manually set the required properties in the console before the script runs:
Object.defineProperty(navigator, 'language', {value: 'en-US'});