Skip to content

evex-dev/expose-kit

Repository files navigation

Expose Kit

release workflow

A universal toolkit for JavaScript deobfuscation


What is this?

JavaScript deobfuscation tools are everywhere.
But many of them are too aggressive, rewriting code until it breaks.

Expose Kit takes a different approach.

  • No brute force
  • Step-by-step, verifiable transforms
  • Designed to not break your code silently

Each transformation is meant to be checked and validated, so you always know when something goes wrong.

Alongside deobfuscation, Expose Kit also provides a set of practical utilities for working with obfuscated JavaScript.


Installation

Just one step:

npm i -g expose-kit
# or
bun i -g expose-kit
expose --help
expose parsable sample.js

Usage Notes

Default arguments

  • The first argument is the input file
    (--file / --input can also be used)
  • If required options are missing, Expose Kit will prompt you
  • A timeout is enabled by default to avoid hangs
    Use --unlimited for long-running execution

Recommended Workflow

First, an important premise:

It is impossible to create a static deobfuscation tool that never breaks.

Reasons include:

  • Unpredictable execution (eval, dynamic code)
  • Bugs or edge cases in AST manipulation

Because of this, you should verify the code at every step.

1. Always verify with parsable

After each transformation, run:

expose parsable file.js

This ensures the syntax is still valid.


2. Make scopes safe first

One of the most common causes of breakage is variable name confusion.

If you try to write your own deobfuscation logic (e.g. in Python), you’ll quickly realize how painful it is to track scopes correctly.

That’s why you should always start with:

expose safe-scope input.js

This renames bindings per scope, producing code like:

Before: var x = 810;((x) => console.log(x))(114514);
After:  var x = 810;((_x) => console.log(_x))(114514);

With this alone:

  • The code becomes far more resistant to breakage
  • Writing custom deobfuscation logic becomes much easier
  • You no longer need to worry about scope collisions

3. Apply transforms step by step

After safe-scope, combine common techniques like:

  • expand-array, expand-object and more

After each step, run parsable and remove-unused again.
(even if it's not frequent, we might overlook something broken)

The more you do it, the clearer it becomes.

Expose Kit will also clearly indicate whether a diff exists, making inspection easy.

Repeat this process, and the original code will gradually reveal itself.


Commands

expose parsable

Check whether a file is syntactically valid.

parsable:     const x = 810;
not parsable: const ;x; == 810;
expose parsable path/to/file.js

Args:

  • Default args only

expose safe-scope

Rename bindings per scope for safer transformations.

expose safe-scope path/to/file.js --output path/to/file.safe-scope.js

Example is here.

Args:

  • --o, --output <file>
    Output file path
    • No extension → file.safe-scope.js
    • With extension → file.safe-scope.<ext>

expose pre-evaluate

Pre-evaluate const numeric/string expressions. (Safe evaluate)

const a = 1 + 2 * 3; // => 7
const c = "aaaa";
const b = "a" + c; // => "aaaaa"
expose pre-evaluate path/to/file.js --output path/to/file.pre-evaluate.js

Args:

  • --o, --output <file>
    Output file path
    • No extension → file.pre-evaluate.js
    • With extension → file.pre-evaluate.<ext>

Notes:

  • Inlines zero-arg functions that return safe array literals by hoisting them to a var.

expose expand-array

Expand array index access for primitive values.

var a = [1, 1, 4, 5, 1, 4];
// before
console.log(a[0], a[2], a[3]);
// after
console.log(1, 4, 5);

Example is here.

expose expand-array path/to/file.js --target arrayName --output path/to/file.expand-array.js

Args:

  • --target <name>
    Target array variable name
  • --o, --output <file>
    Output file path

Notes:

  • Each replacement is validated by reparsing; unsafe replacements are skipped. (This array is intended to be immutable, so caution is required)

expose expand-object

Expand object property access for primitive values.

const obj = { a: 1, b: 2 };
// before
console.log(obj.a, obj["b"]);
// after
console.log(1, 2);

Example is here.

expose expand-object path/to/file.js --target objectName --output path/to/file.expand-object.js

Args:

  • --target <name>
    Target object variable name
  • --o, --output <file>
    Output file path

Notes:

  • Each replacement is validated by reparsing; unsafe replacements are skipped. (This object is intended to be immutable, so caution is required)

expose object-packer

Pack consecutive object property assignments into literals.

const obj = {};
// before
obj.a = 0;
obj["b"] = 1;
// after
const obj = { a: 0, b: 1 };

Example is here.

expose object-packer path/to/file.js --output path/to/file.object-packer.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Packs only consecutive assignments following an empty object literal.
  • Stops when a property value references the object itself.

expose key-simplify

Replace safe string literal property accesses (obj["foo"]) with dot-style access (obj.foo).

const obj = { foo: 1, bar_baz: 2 };
obj["foo"] = obj['bar'];
console.log(obj["foo"], obj["bar_baz"]);

Example is here.

expose key-simplify path/to/file.js --output path/to/file.key-simplify.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only rewrites computed keys that are plain identifiers.
  • Leaves invalid identifier keys untouched so the output stays parseable.

expose remove-updater

Replace safe update expressions with += or -=.

// before
a++;
--b;
// after
a += 1;
b -= 1;

Example is here.

expose remove-updater path/to/file.js --output path/to/file.remove-updater.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only replaces update expressions whose value is not used.
  • Safe for expression statements and for-loop update clauses.

expose remove-reassign

Inline safe alias assignments and wrapper calls.

const a = 0;
const b = a;
const c = b;
console.log(c); // => console.log(a);
function a(arg) {
  return b(arg);
}
function c(arg) {
  return d[arg];
}
a(0); // => b(0)
c(0); // => d[0]

Example is here.

expose remove-reassign path/to/file.js --output path/to/file.remove-reassign.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only inlines const/immutable alias chains.
  • Skips object shorthand replacements and shadowed bindings.
  • Wrapper inlining is limited to single-return passthroughs.

expose remove-deadcode

Remove unreachable branches and simplify conditional expressions.

if (true) {
  a();
} else {
  b();
}
// after
a();

const x = cond ? true : false;
// after
const x = !!cond;

Example is here.

expose remove-deadcode path/to/file.js --output path/to/file.remove-deadcode.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only removes branches with literal/array/object tests.
  • Simplifies ternaries when both branches are booleans.

expose remove-anti-tamper

Drop the self-defending wrapper, debug guards, and console hijacks generated by anti-tamper scripts.

var wrapper = (function () {
  var guard = true;
  return function (ctx, fn) {
    var thunk = guard ? function () {
      if (fn) {
        fn.apply(ctx, arguments);
        fn = null;
      }
    } : function () {};
    guard = false;
    return thunk;
  };
})();

wrapper(this, function () {
  console.log("tamper");
});

console.log("safe");

Example is here.

expose remove-anti-tamper path/to/file.js --output path/to/file.remove-anti-tamper.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Targets wrappers that return guarded helper functions and removes their invocations.
  • Cleans up debug/console helper functions before removing the wrapper.

expose control-flow-packer

Inline control-flow flattening loops generated by string-based state machines.

const _0x1 = "a|b|c".split("|");
let _0x2 = 0;

while (true) {
  switch (_0x1[_0x2++]) {
    case "a":
      console.log("a");
      continue;
    case "b":
      console.log("b");
      continue;
    case "c":
      console.log("c");
      return;
  }
  break;
}

Example is here.

expose control-flow-packer path/to/file.js --output path/to/file.control-flow-packer.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only flattens loops whose state array comes from a literal split.
  • Stops when a branch returns or a state token is missing.

expose fn-inliner

Inline proxy function calls into expressions.

const add = (a, b) => a + b;
const sum = add(1, 2);
// after
const sum = 1 + 2;

Example is here.

expose fn-inliner path/to/file.js --output path/to/file.fn-inliner.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Only inlines functions that return a single expression.
  • Skips return expressions with assignments, functions, blocks, or sequences.

expose sequence-split

Split sequence expressions into statements.

// before
a, b, c;
// after
a;
b;
c;

Example is here.

expose sequence-split path/to/file.js --output path/to/file.sequence-split.js

Args:

  • --o, --output <file>
    Output file path

Notes:

  • Splits safe sequence expressions into standalone statements.
  • Also normalizes single-statement control flow blocks.

expose remove-unused

Remove unused variabless.

// before
var a = 0, b = 1;
console.log(a);
// after
var a = 0;
console.log(a);

Example is here.

expose remove-unused path/to/file.js --output path/to/file.remove-unused.js

Args:

  • --o, --output <file>
    Output file path

Community & Support


Author

Built for research, not abuse.
Want stronger obfuscation? Then build something this tool can’t reverse.