@lxandr1/js-vm-guard
v1.0.16
Published
JavaScript VM-based Obfuscator — converts JS to custom VM bytecode to increase reverse engineering difficulty
Maintainers
Readme
js-vm-guard
JavaScript VM-based Obfuscator
Converts JavaScript code into a custom stack-based VM bytecode format to increase reverse engineering difficulty. No eval, no new Function() — all code runs through a switch-based interpreter with randomized opcodes.
Installation
npm install -g js-vm-guardOr use directly with npx:
npx js-vm-guard input.js -o output.jsCLI Usage
js-vm-guard <input.js> [options]
Options:
-o, --output <file> Output file path
--level <1-5> Obfuscation level (default: 3)
--seed <number> Random seed for deterministic output
-h, --help Show helpObfuscation Levels
| Level | Description | |-------|-------------| | 1 | Opcode randomization only | | 2 | Level 1 + basic transformations | | 3 | Level 2 + full function VM + top-level VM (default) | | 4 | Level 3 + XOR-encoded bytecode | | 5 | Reserved for future improvements |
Example
Input (input.js):
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(10));Output (level 3, simplified):
(function() {
function $(b,s,t,g){ /* VM interpreter with 55+ opcodes */ }
var F={};function g(n,v){ /* global lookup */ }
function fibonacci(){return $([[...]],{...},this,g);}
F["fibonacci"]=fibonacci;
$(mainBytecode,{...},this,g);
})();Programmatic API
const jvg = require('js-vm-guard');
// Parse source to AST
const ast = jvg.parseCode('var x = 1 + 2;');
// Transform AST to bytecode
const result = jvg.transform(ast, 3, 42);
// result.bytecode — main bytecode array
// result.opcodeMap — shuffled opcode mapping
// result.functions — function bytecode table
// result.mainScope — top-level variable names
// Generate VM runtime code
const vm = jvg.generateVM(result.opcodeMap, result.functions);
// vm.code — string: the $() interpreter function
// Full pipeline
jvg.main(); // runs CLI from APILow-level exports
jvg.createOpcodeMap(seed)— Create shuffled opcode mappingjvg.Transformer— Transformer class for custom usagejvg.BytecodeBuilder— Bytecode builder classjvg.HANDLER— VM handler definitionsjvg.getOpcodeNames()— List of all opcode namesjvg.seedRng(seed)/jvg.rngNext()— PRNG utilities
How It Works
- Parse — Source is parsed to AST via
@babel/parser - Transform — AST is compiled to stack-based bytecode (LOAD/STORE/JUMP/CALL etc.)
- Shuffle — Opcodes are assigned random numeric values via seeded Fisher-Yates shuffle
- Generate — A switch-based VM interpreter function is generated as a JavaScript string
- Assemble — Output wraps the VM, function wrappers, and encoded bytecode in an IIFE
Architecture
┌──────────┐ ┌──────────────┐ ┌─────────┐ ┌──────────┐
│ Parser │ → │ Transformer │ → │ VM Gen │ → │ CLI │
│ parser.js│ │transformer.js│ │ vm.js │ │ cli.js │
└──────────┘ └──────────────┘ └─────────┘ └──────────┘
│ │
↓ ↓
BytecodeBuilder OpcodeMap (shuffled)Supported constructs
- Function declarations, expressions, IIFEs
- Top-level code (all source goes through VM)
- Control flow:
if/else,for,while,do-while,switch,for-in,for-of - Expressions: arithmetic, logical, bitwise, ternary, assignment, member access, calls,
new, spreads - Template literals, regex, update expressions (
++/--) - Exceptions:
try/catch,throw,break/continue
Known Limitations
- No class/async/generator support (passed through as-is)
- No
argumentsobject inside VM-compiled functions - Function bytecodes in the
farray expose string constants (only main bytecode is XOR-encoded at level 4) - No control flow flattening or dead code injection (planned)
- The
scope: function(){...}definitions expose variable names newoperator usesReflect.constructwhich has limitations with VM wrappers
License
MIT
