Techniques27-Jan-26|11 min read

Why JSFuck Decoders Produce Wrong Output

The edge cases that defeat most decoders

Definition:

JSFuck deobfuscation is the process of reversing JSFuck-encoded JavaScript to recover the original payload. Unlike simple encoding schemes, JSFuck deobfuscation requires simulating JavaScript's type coercion system with exact fidelity. Most decoders fail on real-world malware because they miss edge cases in HTML string generation, escape sequence handling, and multi-pass resolution that the original JSFuck encoder exploits.

On the surface, decoding JSFuck seems straightforward. The technique uses only six characters and relies on well-documented type coercion rules. Every JSFuck tutorial walks through deriving "a" from "false"[1] and suggests that reversing the process is simply a matter of applying those same rules in reverse. This intuition fails catastrophically on production malware. The JSFuck encoder makes assumptions about JavaScript runtime behaviour that go far beyond basic type coercion. It relies on the exact string output of deprecated HTML methods, the precise formatting of regular expression literals, and character indexing into strings whose length depends on obscure specification details. A decoder that gets any of these wrong produces garbage output or, worse, subtly corrupted code that looks plausible but behaves differently from the original. Building a decoder that handles the JSFuck samples found in CTF challenges is a weekend project. Building one that handles the payloads extracted from actual malware campaigns requires confronting edge cases that most documentation never mentions.

The fontcolor Problem

JSFuck obtains many characters by calling deprecated String prototype methods that generate HTML. The fontcolor() method wraps a string in a <font color="..."> tag, and the encoder extracts specific characters from this output by index.

Most developers assume "test".fontcolor("red") produces <font color="red">test</font>. But what happens when the colour argument contains special characters?

javascript
// What a naive decoder expects:
"".fontcolor('"')  →  <font color="""></font>     // 22 chars

// What JavaScript actually produces (per ECMA-262 Annex B):
"".fontcolor('"')  →  <font color="&quot;"></font>  // 28 chars
//                               ^^^^^^
//                        HTML attribute escaping!

The ECMA-262 specification mandates that fontcolor() must escape the colour argument for safe HTML attribute inclusion. Double quotes become &quot; and ampersands become &amp;. This changes the string length and shifts every character index after the escape.

JSFuck exploits this length difference. When it needs &, it indexes position 13. When it needs q, position 20. When it needs ;, position 21. A decoder without HTML escaping extracts wrong characters at every affected position.

This is not a theoretical concern. The characters &, q, and ; appear in common JavaScript constructs. The string "constructor" contains no problematic characters, but "hasOwnProperty" contains both. Payloads that access prototype methods or build URL query strings routinely trigger this edge case.

Regular Expression Literal Escaping

JSFuck constructs characters by converting objects to strings and indexing into the result. One source of characters is the RegExp constructor: RegExp("/") + "" converts a regex to its string literal form.

JavaScript's specification requires that forward slashes within a regex pattern must be escaped in the string representation. This is necessary to produce a valid regex literal rather than a syntax error.

javascript
// Converting RegExp("/") to its string form:
RegExp("/") + ""   →   "/\//"   // 4 characters (correct)
//                        ^ ^ ^
//                        / \ /

// A broken decoder might produce:
                         "///"    // 3 characters (WRONG)
// This is a syntax error: empty regex // followed by comment /

JSFuck uses this technique to obtain the backslash character — index 1 of /\// is \. A decoder that produces /// instead will return / at index 1, and any payload requiring backslash characters will fail.

Backslashes appear in escape sequences, Windows file paths (C:\\Windows\\), registry keys, and regex patterns. A single incorrect character derivation here silently corrupts a wide range of real-world malware payloads.

The Multi-Pass Resolution Requirement

JSFuck character mappings are not independent. Some characters are defined in terms of other characters, which are themselves defined in terms of yet others. The encoder handles this through what amounts to iterative refinement: it processes the character table multiple times until no further substitutions are possible.

Consider how JSFuck obtains certain uppercase letters. The character "E" might be derived from the string "RegExp", which is obtained from a constructor name. But the expression that produces "RegExp" might itself contain references to "Function", which is another constructor obtained through a different chain.

javascript
// Dependency chain for the letter "E":
"E" ← "RegExp"[1]
         ↑
"RegExp" ← RegExp.constructor.name
              ↑
  constructor ← requires "c","o","n","s","t","r","u","c","t","o","r"
                  ↑
     Each of these has its own derivation chain...

// Single-pass decoder output (BROKEN):
"[object RegExp]"  // unresolved reference left in output

// Multi-pass decoder output (CORRECT):
"E"                // fully resolved after 3+ passes

A decoder that processes each character definition exactly once will leave unresolved references. The resulting output contains literal constructor names like "Function" or "RegExp" embedded in places where the original encoder expected further expansion.

The decoder must either resolve all references iteratively (process the table multiple times until stable) or compute a dependency-ordered resolution sequence. Most open-source implementations take neither approach.

This problem compounds in malware samples that use custom JSFuck variants. The JSFireTruck campaign modified the standard JSFuck character set, introducing additional characters that have their own dependency chains. A decoder tuned for standard JSFuck will mishandle these variants.

Escape Sequence Ambiguity

After resolving all character references, a JSFuck decoder produces a string containing the original JavaScript payload. This string often contains escape sequences for non-printable characters: tabs, newlines, null bytes, and control characters that appear in shellcode or binary data embedded in the script.

JavaScript supports multiple escape sequence formats, and they interact in surprising ways. Octal escapes consume up to three digits greedily, which creates dangerous ambiguity.

javascript
// Intended output: form feed (\14) followed by the digit "4"
"\14" + "4"    →  "\x0C" + "4"    // two characters: FF, 4

// What a naive octal decoder produces:
"\144"         →  "d"              // one character: ASCII 100
//    ^^^
// Greedy parsing consumed ALL three digits as one escape!

A decoder that outputs octal escapes will produce correct results for isolated non-printable characters but corrupt any payload where such characters appear adjacent to digits. The decoded output will be shorter than expected, with characters mysteriously transformed or missing.

Hexadecimal escapes (\xNN) always consume exactly two digits, eliminating the greedy parsing problem. A robust decoder must either use hex escapes exclusively or implement complex lookahead to determine when octal escapes are safe.

This edge case appears frequently in malware. Encoded payloads often contain binary shellcode, encrypted strings, or structured data with length prefixes. These naturally juxtapose non-printable bytes with numeric values.

JSFuck Decoder Edge Cases
fontcolor escaping
Incorrect BehaviourMissing &quot; / &amp;
ConsequenceWrong characters at affected indices
RegExp toString
Incorrect Behaviour/ instead of \/
ConsequenceMissing backslash character
Multi-pass resolution
Incorrect BehaviourSingle-pass processing
ConsequenceUnresolved constructor references
Octal escape greed
Incorrect Behaviour\14 followed by digit
ConsequenceMerged escape sequences, wrong output length
Number formatting
Incorrect BehaviourPython-style output
ConsequenceWrong characters from NaN, Infinity

Number Formatting Divergence

JSFuck derives characters from number-to-string conversions. The digits 0–9 come from simple cases, but special values like NaN and Infinity provide additional characters. The strings "NaN" and "Infinity" supply "N", "I", and "y" for payloads that need them.

JavaScript has specific rules for formatting these special values that differ from other languages.

javascript
// JavaScript number formatting (what JSFuck expects):
String(NaN)        →  "NaN"       // capital N
String(Infinity)   →  "Infinity"  // capital I
String(-0)         →  "0"         // NOT "-0"
String(1e20)       →  "100000000000000000000"
String(1e21)       →  "1e+21"     // note the + sign

// Python equivalents (WRONG for JSFuck decoding):
str(float('nan'))  →  "nan"       // lowercase n!
str(float('inf'))  →  "inf"       // completely different!

A decoder implemented in Python or another language must explicitly match JavaScript's formatting rules. Using the host language's default number formatting will produce wrong strings, and indexing into those strings will extract wrong characters.

This problem is subtle because the wrong characters often look plausible. A lowercase "n" instead of uppercase "N" might not be immediately obvious in a large payload, but it will cause syntax errors or behavioural changes when the decoded script executes.

The Tooling Landscape Gap

The most widely used JSFuck decoder, de4js, was archived in December 2021 and receives no updates. The remaining tools each have fundamental limitations:

Execution-based decodersRun the JSFuck code and capture the output. Achieves perfect accuracy by using the real JavaScript engine, but executes the malicious payload in the process. Defeats the purpose of static analysis and creates security risks. Sandboxing helps but adds complexity and still risks escapes.
Pattern-matching decodersMaintain lookup tables mapping JSFuck sequences to characters. Works for standard encodings but fails on optimised or variant JSFuck that uses alternative derivation paths. The same character can be expressed many different ways, and no finite lookup table covers all possibilities.
Simulation-based decodersAttempt to replicate JavaScript's type coercion in another language. The most promising approach, but requires exact fidelity to JavaScript's specification. Any deviation in number formatting, HTML escaping, or prototype chain behaviour produces wrong output.

The fundamental problem: perfect static decoding requires a complete JavaScript runtime simulation. Type coercion, prototype chain traversal, string formatting, and escape sequence handling must all match browser behaviour exactly. The security community lacks a decoder that is both accurate and safe.

Implications for Malware Analysis

These edge cases are not academic curiosities. Malware authors, intentionally or not, produce JSFuck payloads that trigger them. The JSFireTruck campaign samples exhibited several of these problems, causing multiple open-source decoders to produce incorrect or incomplete output.

Analysts who rely on broken decoders may conclude that a JSFuck sample is benign when it actually contains functional malware. Or they may extract a corrupted payload that exhibits different behaviour than the original, leading to incorrect threat intelligence.

Manual analysis remains possible but time-consuming. An analyst can trace through JSFuck character-by-character, consulting the specification for each edge case. This takes hours for a single sample and does not scale to campaign-level analysis. Automated deobfuscation must account for these edge cases to produce reliable results. Quality scoring, assessing whether decoded output is valid JavaScript, becomes essential when decoder accuracy cannot be guaranteed.

JSFuck is one of two prevalent JavaScript obfuscation patterns in active malware. The other is string-array obfuscation, the dominant pattern by sample volume thanks to obfuscator.io's wide adoption. The two have very different decoder requirements: JSFuck demands runtime-coercion simulation with specification-level fidelity; string-array obfuscation demands structural array walking, accessor offset resolution, and (for tier 5 samples) RC4 key recovery. A JS-focused deobfuscation pipeline needs both, because samples in the wild use both, sometimes in the same campaign.

KlaroSkope handles JSFuck edge cases automatically, simulating JavaScript's type system with specification-level accuracy. Multi-layer payloads are unwrapped recursively with quality scoring at each stage. Try KlaroSkope Free →

Frequently Asked Questions

Q

Why can't I just run JSFuck code to decode it?

Executing JSFuck runs the hidden payload, which is dangerous for malware analysis. You would be executing the malicious code you are trying to analyse. Execution-based decoding also provides no insight into the payload's structure and prevents analysis of code paths that require specific conditions to trigger. Static deobfuscation is necessary for safe analysis.
Q

Do all JSFuck decoders have these problems?

Most publicly available decoders have at least some of these issues. Pattern-matching decoders fail on non-standard encodings. Execution-based decoders have security risks. Simulation-based decoders vary in their specification compliance. Testing a decoder against known samples with verified output is the only way to assess its reliability for your use case.
Q

How do I know if my decoder output is correct?

Check for syntactic validity first: does the output parse as JavaScript? Then look for semantic plausibility: do string literals make sense, are function names valid identifiers, does the control flow look reasonable? Finally, compare against execution in a sandbox if safety permits. Output that is syntactically valid but semantically nonsensical often indicates an edge case failure.
Q

Can JSFuck variants bypass decoders entirely?

Yes. The standard JSFuck character set is not the only possibility. Variants can use alternative derivation paths for characters, add or change the permitted character set, or combine JSFuck with other obfuscation layers. The JSFireTruck campaign used a variant that included additional characters beyond the standard six. Decoders must either handle variants explicitly or use general-purpose JavaScript simulation.
Q

Why haven't browser vendors fixed the behaviours JSFuck exploits?

The behaviours JSFuck exploits are not bugs. They are specified features of JavaScript, many dating to the language's earliest versions. Changing them would break backward compatibility with existing websites. The deprecated HTML methods like fontcolor() remain in browsers specifically to avoid breaking legacy code. JavaScript's type coercion rules are similarly entrenched in billions of lines of existing code.

Found this useful? Sharing is caring!

Ready to decode?

See KlaroSkope transform obfuscated scripts into actionable intelligence.

Try It Free