Deobfuscating two JS obfuscators (VM bytecode + control-flow flattening) with Claude Code

I tried something I think is relevant to share here: handed Claude Code two obfuscated JS files (no hint about which obfuscator made which file) with a four-paragraph prompt asking it to recover the source.

Both came back with a clean source - about 10 minutes for an open-source obfuscator, about 20 minutes for a commercial one.

This is given literally 13-line programs were inflated like 200x to demonstrate obfuscation capability, and these are own’s vendor demos. In production, 200x is unrealistic (maybe, just for a small chunk of code), which makes this even simpler for Claude.

I have a longer write-up on this topic with the exact prompts, inputs and outputs - the technique below is a TL;DR version.

Target 1 - the open-source

The input was a 1,587-line, 68 KB blob from javascript-obfuscator landing page demo (the 13-line calculatePrice function inflated ~194×). Claude returned clean source in ~10 minutes.

What got me wasn’t the time. It was that there were nine distinct defense layers stacked in that file - and the agent identified and undid all nine. Not myself pointing at any of them. The prompt was four paragraphs that boiled down to “here’s a JS file, get me the source”.

It also chose the right strategy on its own. What’s interesting, the first attempt was an 883-line script statically reimplementing the pipeline (RC4 string decryption, base64, the binary deserializer); it recovered the ~500 encrypted string calls and the env-fingerprint value, then it died on the custom zigzag-varint bytecode format. Claude pivoted from reimplement to instrument - a ~48-line script that splices logging hooks into the VM and lets the obfuscator deserialize its own bytecode at runtime. Recovered from the runtime trace:

  • function name, 2 params, 3 locals, constants [0.15, 100, 1, "calculatePrice"]
  • blockKey=54, jumpKey=9643, seKey=4168320119
  • the 22-instruction bytecode stream

A small AI disassembler named the opcodes (PUSH_CONST, LOAD_ARG, STORE_LOCAL, LOAD_LOCAL, MUL, SUB, GT, JMP_FALSE, RETURN, …) and reconstructed the original code:

function calculatePrice(price, quantity) {
  const taxRate = 0.15;
  const threshold = 100;
  let total = price * quantity;
  if (total > threshold) total = total * (1 - taxRate);
  return total;
}
// calculatePrice(10, 20) === 170, matches the original

Names and argument-order aren’t recovered (inferred from behavior); literal numbers are (the VM needs them in the bytecode).

The nine layers Claude unwrapped, and why each came apart

  1. String array + RC4 - self-contained; eval U() once, statically decrypt every call.
  2. String array rotation - IIFE rotates until a checksum matches; runs on startup, just read the result.
  3. Env fingerprint h() - XORs 0x5f3759df with built-in .length values; identical on every JS engine, so the “binding” is cosmetic.
  4. RC4-encrypted bytecode - key derived from h(); once h() is known, one line.
  5. Custom binary serialization B() - zigzag-varint, flag-gated fields; instrument it, don’t reimplement.
  6. Opcode shuffling - b3 table + per-function seed (seeded PRNG); reconstructible with seed + algorithm.
  7. Operand XOR - blockKey/jumpKey per function; simple XOR, one known-plaintext recovers it.
  8. Stack-value XOR - seKey, integers only; floats/strings/objects in plaintext.
  9. Anti-debug timing + the ~1,500-line VM - timing checks corrupt the opcode table if you step through slowly; automated instrumentation runs full-speed and never trips them. The VM is the inverse - instrument dispatch once, get everything.

Nine layers. None of them survived a single agentic pass.

Target 2 - the commercial obfuscator

The input was a 24,620-byte js from Jscrambler’s public demo (available through their portal after registration) - the original is a 5-line BACKGROUND sprite-atlas object. Claude returned clean source in ~20 minutes, and this time even no instrumentation pivot was needed; static analysis alone was enough.

Different machinery, very similar outcome. Control-flow flattening into a switch state-machine, a URL-encoded XOR string blob with a cyclic key, a self-replacing decoder that primes for 8 calls and then degrades to a direct lookup, a 3D index table of opaque integer tags, no bytecode VM. Same six-step arc translated almost directly. Jscrambler has a public blog post arguing AI can’t reverse their obfuscation - accurate about a chatbot that can’t execute code, not about an agent that can.

What does this means for obfuscation?

Nine independent defenses on Target 1, six on Target 2 - and the agent walked through every one without me pointing at any of them. A year ago this was a week of focused human work; today it’s one prompt.

The honest question I’m sitting with: is client-side obfuscation finished as a serious defense (which I don’t believe in), or has it shifted from “secret” to “friction”? (defeating static analyzers, still) What’s left that an agent genuinely can’t unwrap - anti-tamper integrity, environment-bound execution, runtime self-checks? Something else?

And if you ever thrown an agent at obfuscated code, what didn’t it crack, especially in production apps? That’s the comparison I’d find useful.

3 Likes

Okay, this post was written by AI. Your claims and idiocy show basically nothing of grandeur. Basically JScrambler has already been de-obfuscated by Ciarands and guess what companies like Anthropic do on a regular basis? (hint: it’s scrapping the internet to train their models). What was the point of this thread? Are you trying to get us to think that somehow the, so-called, black box that some models are (since they are closed-sourced models) are the key to answering every question? Are you sharing your slop so we all visit your website to see some stupid AI generated website with stupid products you’re trying to sell?

This kind of crap actually pisses me off because what did you do? Clearly, nothing and you are posting on this forum like you actually contributed something.

…handed Claude Code two obfuscated JS files (no hint about which obfuscator made which file) with a four-paragraph prompt asking it to recover the source.

You literally did nothing. You also forwarded a blog post that explains literally nothing. There isn’t any technical write-ups on your site, just AI generated crap. You’re profile states that you are “A creative man who enjoys what he does”, it’s more like “A loser who steals what he doesn’t”.

I think this thread should stay up for all to witness how stupid you are.

1 Like

My first post on 0x00sec, decided to give it a try. Thanks for a warm welcome. Hope you’re not the one representing this community.

How much of this post isn’t pushing your own bottom line with the products that you’re trying to sell? I really hope you’re not naive enough to think that I’m going to feel sorry for you.

1 Like