js-confuser-vm 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { compileAndSerialize } from "./compiler.ts";
2
+ import type { Options } from "./options.ts";
3
+
4
+ async function obfuscate(source, options: Options = {}) {
5
+ const result = compileAndSerialize(source, options);
6
+
7
+ return result;
8
+ }
9
+
10
+ export const JsConfuserVM = {
11
+ obfuscate,
12
+ };
13
+ export default JsConfuserVM;
package/src/minify.ts ADDED
@@ -0,0 +1,21 @@
1
+ import ClosureCompiler from "google-closure-compiler";
2
+
3
+ export function minify(sourceCode: string): Promise<string> {
4
+ return new Promise((resolve, reject) => {
5
+ const compiler = new ClosureCompiler({
6
+ compilation_level: "ADVANCED",
7
+ warning_level: "QUIET",
8
+ });
9
+
10
+ const compilerProcess = compiler.run((exitCode: number, stdOut: string, stdErr: string) => {
11
+ if (exitCode !== 0) {
12
+ reject(new Error(stdErr || `Closure Compiler exited with code ${exitCode}`));
13
+ } else {
14
+ resolve(stdOut);
15
+ }
16
+ });
17
+
18
+ compilerProcess.stdin.write(sourceCode);
19
+ compilerProcess.stdin.end();
20
+ });
21
+ }
package/src/options.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface Options {
2
+ target?: "node" | "browser";
3
+
4
+ randomizeOpcodes?: boolean; // randomize opcode values in OP mapping?
5
+ shuffleOpcodes?: boolean; // shuffle order of opcode handlers in the runtime?
6
+ encodeBytecode?: boolean; // encode bytecode? when off, comments for instructions are added
7
+ selfModifying?: boolean; // do self-modifying bytecode for function bodies?
8
+ timingChecks?: boolean; // add timing checks to detect debuggers?
9
+ minify?: boolean; // pass final output through Google Closure Compiler? (Renames VM class properties)
10
+ }
package/src/random.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { ok } from "assert";
2
+
3
+ export function getPlaceholder() {
4
+ return Math.random().toString(36).substring(2, 15);
5
+ }
6
+
7
+ export function choice<T>(elements: T[]): T {
8
+ ok(elements.length > 0, "choice() called on empty sequence");
9
+ return elements[Math.floor(Math.random() * elements.length)];
10
+ }
11
+
12
+ export function getRandom(): number {
13
+ return Math.random();
14
+ }
15
+
16
+ export function getRandomInt(min: number, max: number): number {
17
+ ok(min <= max, "min must be <= max");
18
+ return Math.floor(Math.random() * (max - min + 1)) + min;
19
+ }
20
+
21
+ /**
22
+ * Shuffles an array in-place using the Fisher-Yates algorithm.
23
+ * @param array - The array to shuffle (mutated)
24
+ */
25
+ export function shuffle<T>(array: T[]): T[] {
26
+ for (let i = array.length - 1; i > 0; i--) {
27
+ const j = Math.floor(Math.random() * (i + 1));
28
+ [array[i], array[j]] = [array[j], array[i]];
29
+ }
30
+ return array;
31
+ }
package/src/runtime.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { OP } from "./compiler.ts";
1
+ import { OP_ORIGINAL as OP } from "./compiler.ts";
2
2
  const BYTECODE = [];
3
3
  const MAIN_START_PC = 0;
4
4
  const CONSTANTS = [];
5
- const PACK = false;
5
+ const ENCODE_BYTECODE = false;
6
+ const TIMING_CHECKS = false;
6
7
  // The text above is not included in the compiled output - for type intellisense only
7
8
  // @START
8
9
 
9
10
  function decodeBytecode(s) {
10
- if (!PACK) return s;
11
+ if (!ENCODE_BYTECODE) return s;
11
12
 
12
13
  var b =
13
14
  typeof Buffer !== "undefined"
@@ -25,12 +26,12 @@ function decodeBytecode(s) {
25
26
  return r;
26
27
  }
27
28
 
28
- // ── Closure symbol ────────────────────────────────────────────────
29
+ // Closure symbol
29
30
  // Used to tag shell functions so the VM can fast-path back to the
30
31
  // inner Closure instead of going through a sub-VM on internal calls.
31
32
  var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
32
33
 
33
- // ── Upvalue ───────────────────────────────────────────────────────
34
+ // Upvalue
34
35
  // While the outer frame is alive: reads/writes go to frame.locals[slot].
35
36
  // After the outer frame returns (closed): reads/writes hit this.value.
36
37
  function Upvalue(frame, slot) {
@@ -51,24 +52,24 @@ Upvalue.prototype.close = function () {
51
52
  this._closed = true;
52
53
  };
53
54
 
54
- // ── Closure & Frame ───────────────────────────────────────────────
55
+ // Closure & Frame
55
56
  function Closure(fn) {
56
57
  this.fn = fn;
57
58
  this.upvalues = [];
58
- this.prototype = {}; // default prototype object for \`new\`
59
+ this.prototype = {}; // <- default prototype object for \`new\`
59
60
  }
60
61
 
61
62
  function Frame(closure, returnPc, parent, thisVal?) {
62
63
  this.closure = closure;
63
64
  this.locals = new Array(closure.fn.localCount).fill(undefined);
64
- this.pc = closure.fn.startPc; // initialize from fn descriptor
65
+ this.pc = closure.fn.startPc; // <- initialize from fn descriptor
65
66
  this.returnPc = returnPc; // pc to resume in parent frame after RETURN
66
67
  this.parent = parent;
67
68
  this.thisVal = thisVal !== undefined ? thisVal : undefined;
68
- this._newObj = null; // set by NEW so RETURN can see it
69
+ this._newObj = null; // <- set by NEW so RETURN can see it
69
70
  }
70
71
 
71
- // ── VM ────────────────────────────────────────────────────────────
72
+ // VM
72
73
  function VM(bytecode, mainStartPc, constants, globals) {
73
74
  this.bytecode = bytecode;
74
75
  this.constants = constants;
@@ -80,7 +81,7 @@ function VM(bytecode, mainStartPc, constants, globals) {
80
81
  var mainFn = {
81
82
  paramCount: 0,
82
83
  localCount: 0,
83
- startPc: mainStartPc, // where main begins
84
+ startPc: mainStartPc, // <- where main begins
84
85
  };
85
86
  this.currentFrame = new Frame(new Closure(mainFn), null, null);
86
87
  }
@@ -108,7 +109,7 @@ VM.prototype.captureUpvalue = function (frame, slot) {
108
109
  };
109
110
 
110
111
  VM.prototype.closeUpvaluesFor = function (frame) {
111
- // Called on RETURN close every upvalue that was pointing into this frame.
112
+ // Called on RETURN - close every upvalue that was pointing into this frame.
112
113
  // After this, closures that captured from the frame read from upvalue.value.
113
114
  this.openUpvalues = this.openUpvalues.filter(function (uv) {
114
115
  if (uv.frame === frame) {
@@ -134,7 +135,7 @@ VM.prototype.run = function () {
134
135
  var op, operand;
135
136
  var word = bc[frame.pc++];
136
137
 
137
- if (PACK) {
138
+ if (ENCODE_BYTECODE) {
138
139
  op = word & 0xff;
139
140
  operand = word >>> 8;
140
141
  } else {
@@ -145,11 +146,13 @@ VM.prototype.run = function () {
145
146
  // console.log(frame.pc - 1, op, operand);
146
147
 
147
148
  // Debugging protection
148
- var t2 = now();
149
- var isTamper = t2 - t > 1000;
150
- t = t2;
151
- if (isTamper) {
152
- op = OP.RETURN;
149
+ if (TIMING_CHECKS) {
150
+ var t2 = now();
151
+ var isTamper = t2 - t > 1000;
152
+ t = t2;
153
+ if (isTamper) {
154
+ op = OP.POP;
155
+ }
153
156
  }
154
157
 
155
158
  /* @SWITCH */
@@ -175,8 +178,8 @@ VM.prototype.run = function () {
175
178
  break;
176
179
 
177
180
  case OP.GET_PROP: {
178
- // Stack: [..., obj, key] [..., obj, obj[key]]
179
- // obj is PEEKED (not popped) CALL_METHOD needs it as receiver
181
+ // Stack: [..., obj, key] -> [..., obj, obj[key]]
182
+ // obj is PEEKED (not popped) - CALL_METHOD needs it as receiver
180
183
  var key = this._pop();
181
184
  var obj = this.peek();
182
185
  this._push(obj[key]);
@@ -291,10 +294,10 @@ VM.prototype.run = function () {
291
294
  var ctor = this._pop();
292
295
  var obj = this._pop();
293
296
  if (typeof ctor === "function") {
294
- // Native constructor (e.g. Array, Date) native instanceof is fine
297
+ // Native constructor (e.g. Array, Date) - native instanceof is fine
295
298
  this._push(obj instanceof ctor);
296
299
  } else {
297
- // VM Closure ctor.prototype was set by MAKE_CLOSURE / user assignment.
300
+ // VM Closure - ctor.prototype was set by MAKE_CLOSURE / user assignment.
298
301
  // Walk obj's prototype chain looking for identity with ctor.prototype.
299
302
  var proto = ctor.prototype; // the .prototype property on the Closure
300
303
  var target = Object.getPrototypeOf(obj);
@@ -334,7 +337,7 @@ VM.prototype.run = function () {
334
337
  case OP.TYPEOF_SAFE: {
335
338
  // operand is a const index holding the variable name string.
336
339
  // Mimics JS semantics: typeof undeclaredVar === "undefined" (no throw).
337
- var name = this._pop(); // LOAD_CONST pushed the name consume it
340
+ var name = this._pop(); // LOAD_CONST pushed the name - consume it
338
341
  var val = Object.prototype.hasOwnProperty.call(this.globals, name)
339
342
  ? this.globals[name]
340
343
  : undefined;
@@ -351,7 +354,7 @@ VM.prototype.run = function () {
351
354
  break;
352
355
 
353
356
  case OP.JUMP_IF_TRUE_OR_POP:
354
- // || semantics: if truthy, we're done leave value, jump over RHS.
357
+ // || semantics: if truthy, we're done - leave value, jump over RHS.
355
358
  // If falsy, discard it and fall through to evaluate RHS.
356
359
  if (this.peek()) {
357
360
  frame.pc = operand;
@@ -361,7 +364,7 @@ VM.prototype.run = function () {
361
364
  break;
362
365
 
363
366
  case OP.JUMP_IF_FALSE_OR_POP:
364
- // && semantics: if falsy, we're done leave value, jump over RHS.
367
+ // && semantics: if falsy, we're done - leave value, jump over RHS.
365
368
  // If truthy, discard it and fall through to evaluate RHS.
366
369
  if (!this.peek()) {
367
370
  frame.pc = operand;
@@ -379,7 +382,7 @@ VM.prototype.run = function () {
379
382
  // Capture directly from current frame's local slot
380
383
  closure.upvalues.push(this.captureUpvalue(frame, desc._index));
381
384
  } else {
382
- // Relay take upvalue from the enclosing closure's list
385
+ // Relay - take upvalue from the enclosing closure's list
383
386
  closure.upvalues.push(frame.closure.upvalues[desc._index]);
384
387
  }
385
388
  }
@@ -391,7 +394,13 @@ VM.prototype.run = function () {
391
394
  return function () {
392
395
  var args = Array.prototype.slice.call(arguments);
393
396
  var sub = new VM(self.bytecode, 0, self.constants, self.globals);
394
- var f = new Frame(c, null, null, this);
397
+ // Sloppy-mode: null/undefined thisArg global object
398
+ var f = new Frame(
399
+ c,
400
+ null,
401
+ null,
402
+ this == null ? self.globals : this,
403
+ );
395
404
  for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
396
405
  f.locals[c.fn.paramCount] = args;
397
406
  sub.currentFrame = f;
@@ -420,7 +429,7 @@ VM.prototype.run = function () {
420
429
  }
421
430
 
422
431
  case OP.BUILD_OBJECT: {
423
- // Stack has: key0, val0, key1, val1 ... keyN, valN (pushed leftright)
432
+ // Stack has: key0, val0, key1, val1 ... keyN, valN (pushed left->right)
424
433
  // Pop all pairs and build the object.
425
434
  var pairs = this._stack.splice(this._stack.length - operand * 2);
426
435
  var o = {};
@@ -432,16 +441,20 @@ VM.prototype.run = function () {
432
441
  }
433
442
  case OP.SET_PROP: {
434
443
  // Stack: [..., obj, key, val]
435
- // Leaves val on stack assignment is an expression in JS.
444
+ // Leaves val on stack - assignment is an expression in JS.
436
445
  var val = this._pop();
437
446
  var key = this._pop();
438
447
  var obj = this._pop();
439
- obj[key] = val;
448
+ // Reflect.set performs [[Set]] without throwing on failure,
449
+ // correctly simulating sloppy-mode assignment from a strict-mode host
450
+ // (output.js is an ES module). This also properly invokes inherited
451
+ // or prototype-chain setter functions.
452
+ Reflect.set(obj, key, val);
440
453
  this._push(val); // assignment expression evaluates to the assigned value
441
454
  break;
442
455
  }
443
456
  case OP.GET_PROP_COMPUTED: {
444
- // Stack: [..., obj, key] key is a runtime value (nums[i])
457
+ // Stack: [..., obj, key] - key is a runtime value (nums[i])
445
458
  // Mirrors GET_PROP but pops the key that was pushed dynamically.
446
459
  var key = this._pop();
447
460
  var obj = this._pop();
@@ -459,9 +472,10 @@ VM.prototype.run = function () {
459
472
  var args = this._stack.splice(this._stack.length - operand);
460
473
  var callee = this._pop();
461
474
  if (callee && callee[CLOSURE_SYM]) {
462
- // VM closure run directly in this VM, no sub-VM overhead
475
+ // VM closure - run directly in this VM, no sub-VM overhead
463
476
  var c = callee[CLOSURE_SYM];
464
- var f = new Frame(c, frame.pc, frame, undefined);
477
+ // Sloppy-mode: plain function call global object as this
478
+ var f = new Frame(c, frame.pc, frame, this.globals);
465
479
  for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
466
480
  f.locals[c.fn.paramCount] = args;
467
481
  this.frameStack.push(this.currentFrame);
@@ -478,7 +492,7 @@ VM.prototype.run = function () {
478
492
  var callee = this._pop();
479
493
  var receiver = this._pop(); // left on stack by GET_PROP
480
494
  if (callee && callee[CLOSURE_SYM]) {
481
- // VM closure run directly in this VM with receiver as this
495
+ // VM closure - run directly in this VM with receiver as this
482
496
  var c = callee[CLOSURE_SYM];
483
497
  var f = new Frame(c, frame.pc, frame, receiver);
484
498
  for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
@@ -500,7 +514,7 @@ VM.prototype.run = function () {
500
514
  var args = this._stack.splice(this._stack.length - operand);
501
515
  var callee = this._pop();
502
516
  if (callee && callee[CLOSURE_SYM]) {
503
- // VM closure constructor prototype is unified via shell.prototype = closure.prototype
517
+ // VM closure constructor - prototype is unified via shell.prototype = closure.prototype
504
518
  var c = callee[CLOSURE_SYM];
505
519
  var newObj = Object.create(c.prototype || null);
506
520
  var f = new Frame(c, frame.pc, frame, newObj);
@@ -511,7 +525,7 @@ VM.prototype.run = function () {
511
525
  this.currentFrame = f;
512
526
  } else {
513
527
  // Native constructor (e.g. new Error(), new Date()).
514
- // Reflect.construct is required Object.create+apply does NOT set
528
+ // Reflect.construct is required - Object.create+apply does NOT set
515
529
  // internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
516
530
  this._push(Reflect.construct(callee, args));
517
531
  }
@@ -523,7 +537,7 @@ VM.prototype.run = function () {
523
537
  this.closeUpvaluesFor(frame); // must happen before frame is abandoned
524
538
  if (this.frameStack.length === 0) return retVal;
525
539
 
526
- // new-call rule: primitive return discard, use the constructed object instead
540
+ // new-call rule: primitive return -> discard, use the constructed object instead
527
541
  if (frame._newObj !== null) {
528
542
  if (typeof retVal !== "object" || retVal === null)
529
543
  retVal = frame._newObj;
@@ -592,7 +606,7 @@ VM.prototype.run = function () {
592
606
  var destPc = this._pop();
593
607
  var words = this.constants[operand];
594
608
 
595
- if (PACK) {
609
+ if (ENCODE_BYTECODE) {
596
610
  words = decodeBytecode(words);
597
611
  }
598
612
 
@@ -608,7 +622,7 @@ VM.prototype.run = function () {
608
622
  }
609
623
  };
610
624
 
611
- // ── Boot ─────────────────────────────────────────────────────────
625
+ // Boot
612
626
  var globals: any = {}; // global object for globals
613
627
 
614
628
  // Always pull built-ins from globalThis so eval() scoping can't shadow them
@@ -0,0 +1,48 @@
1
+ import * as t from "@babel/types";
2
+ import { generate } from "@babel/generator";
3
+ import { parse } from "@babel/parser";
4
+ import traverseImport from "@babel/traverse";
5
+ import { ok } from "assert";
6
+ import { choice, shuffle } from "./random.ts";
7
+ import type { Options } from "./options.ts";
8
+ import { escapeRegex } from "./utilts.ts";
9
+ import { minify } from "./minify.ts";
10
+ const traverse = traverseImport.default;
11
+
12
+ export async function obfuscateRuntime(runtime: string, options: Options) {
13
+ const ast = parse(runtime, {
14
+ sourceType: "unambiguous",
15
+ });
16
+
17
+ // shuffle order of opcode handlers
18
+
19
+ if (options.shuffleOpcodes) {
20
+ let switchStatement: t.SwitchStatement | null = null;
21
+ traverse(ast, {
22
+ SwitchStatement(path) {
23
+ if (
24
+ path.node.leadingComments?.some((comment) =>
25
+ comment.value.includes("@SWITCH"),
26
+ )
27
+ ) {
28
+ switchStatement = path.node;
29
+ path.stop();
30
+ }
31
+ },
32
+ });
33
+
34
+ ok(switchStatement, "Could not find opcode handlers switch statement");
35
+
36
+ // simply shuffle the order of the cases
37
+
38
+ switchStatement.cases = shuffle(switchStatement.cases);
39
+ }
40
+
41
+ let generated = generate(ast).code;
42
+
43
+ if (options.minify) {
44
+ generated = await minify(generated);
45
+ }
46
+
47
+ return generated;
48
+ }
package/src/utilts.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function escapeRegex(s: string): string {
2
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+ }
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(NODE_OPTIONS=--experimental-vm-modules npx jest:*)",
5
- "Bash(npm test:*)"
6
- ]
7
- }
8
- }
package/ReadME.MD DELETED
@@ -1,164 +0,0 @@
1
- # JS Confuser VM
2
-
3
- **Requires Node v24.13.1 or higher**
4
-
5
- - ES5 support only. No complex features: async, generator, and even try..catch are beyond scope.
6
- - Experimental. Expect issues.
7
-
8
- ### Usage
9
-
10
- ```shell
11
- $ git clone https://github.com/MichaelXF/js-confuser-vm
12
- ```
13
-
14
- - Example Script:
15
-
16
- ```js
17
- import { virtualize } from "./src/index.js";
18
- import { readFileSync, writeFileSync } from "fs";
19
-
20
- const sourceCode = readFileSync("input.js", "utf-8");
21
- const { code: output } = virtualize(sourceCode);
22
-
23
- writeFileSync("output.js", output, "utf-8");
24
- console.log(output);
25
- ```
26
-
27
- - Input/Output:
28
-
29
- ```js
30
- function fibonacci(num) {
31
- var a = 0,
32
- b = 1,
33
- c = num;
34
- while (num-- > 1) {
35
- c = a + b;
36
- a = b;
37
- b = c;
38
- }
39
- return c;
40
- }
41
-
42
- for (var i = 1; i <= 25; i++) {
43
- console.log(i, fibonacci(i));
44
- }
45
-
46
- /*
47
- function d(a){a=typeof Buffer!=="undefined"?Buffer.from(a,"base64"):Uint8Array.from(atob(a),function(b){return b.charCodeAt(0)});for(var h=new Int32Array(a.length/4),c=0;c<h.length;c++)h[c]=a[c*4]|a[c*4+1]<<8|a[c*4+2]<<16|a[c*4+3]<<24;return h}var f=Symbol();function l(a,h){this.g=a;this.m=h;this.n=!1;this.p=void 0}function m(a){return a.n?a.p:a.g.b[a.m]}function n(a,h){a.n?a.p=h:a.g.b[a.m]=h}function p(a){this.f=a;this.l=[];this.prototype={}}
48
- function w(a,h){this.r=a;this.b=Array(a.f.t).fill(void 0);this.d=a.f.u;this.x=h!==void 0?h:void 0;this.o=null}function x(a,h,c){this.q=a;this.e=h;this.i=c;this.a=[];this.h=[];this.j=[];this.c=new w(new p({k:0,t:0,u:0}))}function y(a,h){a.a.push(h)}function z(a){return a.a.pop()}function A(a){return a.a[a.a.length-1]}function E(a,h,c){for(var b=0;b<a.j.length;b++){var e=a.j[b];if(e.g===h&&e.m===c)return e}e=new l(h,c);a.j.push(e);return e}
49
- function F(a,h){a.j=a.j.filter(function(c){return c.g===h?(c.p=c.g.b[c.m],c.n=!0,!1):!0})}
50
- function G(a){for(var h=performance.now();;){var c=a.c,b=a.q;if(c.d>=b.length)break;b=b[c.d++];var e=b&255;b>>>=8;var g=performance.now(),k=g-h>1E3;h=g;k&&(e=13);switch(e){case 0:y(a,a.e[b]);break;case 1:y(a,c.b[b]);break;case 2:c.b[b]=z(a);break;case 3:y(a,a.i[a.e[b]]);break;case 4:a.i[a.e[b]]=z(a);break;case 5:c=z(a);b=A(a);y(a,b[c]);break;case 6:b=z(a);y(a,z(a)+b);break;case 7:b=z(a);y(a,z(a)-b);break;case 8:b=z(a);y(a,z(a)*b);break;case 9:b=z(a);y(a,z(a)/b);break;case 36:b=z(a);y(a,z(a)%b);break;
51
- case 37:b=z(a);y(a,z(a)&b);break;case 38:b=z(a);y(a,z(a)|b);break;case 39:b=z(a);y(a,z(a)^b);break;case 40:b=z(a);y(a,z(a)<<b);break;case 41:b=z(a);y(a,z(a)>>b);break;case 42:b=z(a);y(a,z(a)>>>b);break;case 15:b=z(a);y(a,z(a)<b);break;case 16:b=z(a);y(a,z(a)>b);break;case 17:b=z(a);y(a,z(a)===b);break;case 20:b=z(a);y(a,z(a)<=b);break;case 21:b=z(a);y(a,z(a)>=b);break;case 22:b=z(a);y(a,z(a)!==b);break;case 52:b=z(a);y(a,z(a)==b);break;case 53:b=z(a);y(a,z(a)!=b);break;case 46:b=z(a);y(a,z(a)in b);
52
- break;case 47:c=z(a);b=z(a);if(typeof c==="function")y(a,b instanceof c);else{c=c.prototype;b=Object.getPrototypeOf(b);for(e=!1;b!==null;){if(b===c){e=!0;break}b=Object.getPrototypeOf(b)}y(a,e)}break;case 25:y(a,-z(a));break;case 26:y(a,z(a));break;case 27:y(a,!z(a));break;case 28:y(a,~z(a));break;case 29:y(a,typeof z(a));break;case 30:z(a);y(a);break;case 31:b=z(a);e=Object.prototype.hasOwnProperty.call(a.i,b)?a.i[b]:void 0;y(a,typeof e);break;case 18:c.d=b;break;case 19:z(a)||(c.d=b);break;case 44:A(a)?
53
- c.d=b:z(a);break;case 43:A(a)?z(a):c.d=b;break;case 10:g=a.e[b];e=new p(g);for(b=0;b<g.v.length;b++)k=g.v[b],k.y?e.l.push(E(a,c,k.w)):e.l.push(c.r.l[k.w]);var t=a;b=function(B){return function(){for(var u=Array.prototype.slice.call(arguments),C=new x(t.q,t.e,t.i),v=new w(B,this),q=0;q<u.length;q++)v.b[q]=u[q];v.b[B.f.k]=u;C.c=v;return G(C)}}(e);b[f]=e;b.prototype=e.prototype;y(a,b);break;case 23:y(a,m(c.r.l[b]));break;case 24:n(c.r.l[b],z(a));break;case 32:b=a.a.splice(a.a.length-b);y(a,b);break;
54
- case 33:c=a.a.splice(a.a.length-b*2);e={};for(b=0;b<c.length;b+=2)e[c[b]]=c[b+1];y(a,e);break;case 34:e=z(a);c=z(a);b=z(a);b[c]=e;y(a,e);break;case 35:c=z(a);b=z(a);y(a,b[c]);break;case 45:c=z(a);b=z(a);y(a,delete b[c]);break;case 11:c=a.a.splice(a.a.length-b);if((e=z(a))&&e[f]){e=e[f];g=new w(e);for(b=0;b<c.length;b++)g.b[b]=c[b];g.b[e.f.k]=c;a.h.push(a.c);a.c=g}else y(a,e.apply(null,c));break;case 12:c=a.a.splice(a.a.length-b);e=z(a);b=z(a);if(e&&e[f]){e=e[f];g=new w(e,b);for(b=0;b<c.length;b++)g.b[b]=
55
- c[b];g.b[e.f.k]=c;a.h.push(a.c);a.c=g}else y(a,e.apply(b,c));break;case 48:y(a,c.x);break;case 49:c=a.a.splice(a.a.length-b);if((e=z(a))&&e[f]){e=e[f];b=Object.create(e.prototype||null);g=new w(e,b);g.o=b;for(b=0;b<c.length;b++)g.b[b]=c[b];g.b[e.f.k]=c;a.h.push(a.c);a.c=g}else y(a,Reflect.construct(e,c));break;case 13:b=z(a);F(a,c);if(a.h.length===0)return b;c.o===null||typeof b==="object"&&b!==null||(b=c.o);a.c=a.h.pop();y(a,b);break;case 14:z(a);break;case 50:y(a,A(a));break;case 51:throw z(a);
56
- case 54:b=z(a);c=[];if(b!==null&&b!==void 0)for(e=Object.create(null),g=Object(b);g!==null;){k=Object.getOwnPropertyNames(g);for(b=0;b<k.length;b++){var r=k[b];if(!(r in e)){e[r]=!0;var D=Object.getOwnPropertyDescriptor(g,r);D&&D.enumerable&&c.push(r)}}g=Object.getPrototypeOf(g)}y(a,{keys:c,s:0});break;case 55:e=z(a);e.s>=e.keys.length?c.d=b:y(a,e.keys[e.s++]);break;case 56:c=z(a);e=a.e[b];e=d(e);for(b=0;b<e.length;b++)a.q[c+b]=e[b];break;default:throw Error("Unknown opcode: "+e+" at pc "+(c.d-1));
57
- }}}var H={},I;for(I of Object.getOwnPropertyNames(globalThis))H[I]=globalThis[I];typeof window!=="undefined"&&(H.window=window);H.undefined=void 0;H.Infinity=Infinity;H.NaN=NaN;
58
- G(new x(d("CgMAAAQEAAAAAQAABAUAAAMFAAAABgAAFAAAABMYAAADBwAAAAgAAAUAAAADBQAAAwQAAAMFAAALAQAADAIAAA4AAAADBQAAMgAAAAABAAAGAAAABAUAAA4AAAASBAAADQAAAAAKAAA4CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="),[0,1,void 0,{k:1,t:5,v:[],u:25},"fibonacci","i",25,"console","log","AAAAAAICAAAAAQAAAgMAAAEAAAACBAAAAQAAADIAAAAAAQAABwAAAAIAAAAAAQAAEAAAABM4AAABAgAAAQMAAAYAAAACBAAAAQQAAA4AAAABAwAAAgIAAAECAAAOAAAAAQQAAAIDAAABAwAADgAAABIhAAABBAAADQAAAAACAAANAAAADQAAAA==",
59
- 27],H));
60
- */
61
- ```
62
-
63
- ### Features
64
-
65
- - [x] functions: call, arguments, return
66
- - [x] closures and nested functions
67
- - [x] literals
68
- - [x] binary expressions
69
- - [x] unary expressions
70
- - [x] update expressions
71
- - [x] if statements
72
- - [x] while, do-while, for loops
73
- - [x] get property
74
- - [x] logical expressions
75
- - [x] array, object expression
76
- - [x] function expression
77
- - [x] default arguments in functions
78
- - [x] sequence expression
79
- - [x] conditional expression (ternary operator)
80
- - [x] delete operator
81
- - [x] in / instanceof
82
- - [x] this, new expression
83
- - [x] arguments
84
- - [x] Infinity, NaN
85
- - [x] break/continue
86
- - [x] switch statement
87
- - [x] throw statement
88
- - [x] labeled statements
89
- - [x] for..in loop
90
-
91
- ### Missing
92
-
93
- - [ ] try..catch
94
- - [ ] RegExp literals
95
- - [ ] with statement
96
- - [ ] arguments.callee, argument parameter syncing
97
- - [ ] getter/setters
98
-
99
- ### Hardening
100
-
101
- - [x] opcode randomization per build
102
- - [x] property name concealment of vm internals
103
- - - Google Closure Compiler aggressively renames our class props
104
- - [ ] shuffled handler order
105
- - [ ] dead handlers
106
- - [ ] dead bytecode insertion
107
- - [ ] macro opcodes
108
- - [x] encoded bytecode array and words
109
- - [x] self-modifying bytecode
110
- - [x] timing checks
111
- - [ ] low-level bytecode obfuscations
112
- - [ ] stack protection
113
- - [ ] control flow integrity
114
-
115
- ### Minification
116
-
117
- `minify.js` uses Google Closure Compiler to minify the JS VM and renames (most) VM property names. This approach helps keep the VM Compiler simple and lets Google do the heavy lifting.
118
-
119
- ### No Try Catch
120
-
121
- Try..Catch is complex operator that may not get added. You can use Try..Catch by defining an outside helper function:
122
-
123
- ```js
124
- function TryCatch(cb) {
125
- try {
126
- return { value: cb() };
127
- } catch (error) {
128
- return { error };
129
- }
130
- }
131
- ```
132
-
133
- ## ES5 Only
134
-
135
- This VM Compiler only supports ES5 JavaScript. Most ES6+ features are syntax sugar that can be transpiled down relatively easily. This is a design decision to keep the VM wrapper simple and the bytecode more uniform. Having opcodes dedicated for classes and methods makes them standout more for attackers to debug easier. Keeping things simple enables easier hardening improvements.
136
-
137
- Please transpile your code down first using [Babel](https://github.com/babel/babel).
138
-
139
- ### Project:
140
-
141
- - Stack based VM
142
- - Lua-style Closure and Upvalue model
143
- - CPython-style opcodes and codegen
144
- - Compiler is in src/compiler.ts
145
- - Runtime is in src/runtime.ts
146
- - "Typescript"
147
- - - This "Typescript" projects uses Node's new flag `--experimental-strip-types`. This is means we can run `node index.ts` directly!
148
-
149
- ### Use with JS-Confuser
150
-
151
- JS-Confuser is recommended to be applied *after* virtualizing your source code. JS-Confuser's CFF can safeguard and obfuscate your VM internals - adding a layer of obscurity and preventing analysis of the opcodes.
152
-
153
- ### WIP
154
-
155
- - 120 tests, 90.16% coverage
156
- - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 43.26%
157
-
158
- ### Made with AI
159
-
160
- This project has been created with the help of AI. Expect issues.
161
-
162
- ### License
163
-
164
- MIT License
package/input.js DELETED
@@ -1,15 +0,0 @@
1
- function fibonacci(num) {
2
- var a = 0,
3
- b = 1,
4
- c = num;
5
- while (num-- > 1) {
6
- c = a + b;
7
- a = b;
8
- b = c;
9
- }
10
- return c;
11
- }
12
-
13
- for (var i = 1; i <= 25; i++) {
14
- console.log(i, fibonacci(i));
15
- }
package/minify.js DELETED
@@ -1,17 +0,0 @@
1
- import ClosureCompiler from "google-closure-compiler";
2
-
3
- const compiler = new ClosureCompiler({
4
- js: "output.js",
5
- js_output_file: "output.min.js",
6
- compilation_level: "ADVANCED",
7
-
8
- warning_level: "QUIET",
9
- env: "CUSTOM", // removes all default externs
10
- externs: "minify_empty_externs.js", // pass a blank file to satisfy the flag
11
- });
12
-
13
- compiler.run((exitCode, stdOut, stdErr) => {
14
- if (stdErr) console.error(stdErr);
15
- if (exitCode !== 0) process.exit(exitCode);
16
- console.log("Done -> output.min.js");
17
- });
package/obfuscate.js DELETED
@@ -1,12 +0,0 @@
1
- import JsConfuser from "../js-confuser/dist/index.js";
2
- import { readFileSync, writeFileSync } from "fs";
3
-
4
- const minified = readFileSync("output.min.js", "utf-8");
5
-
6
- JsConfuser.obfuscate(minified, {
7
- target: "browser",
8
- renameVariables: true,
9
- controlFlowFlattening: true,
10
- }).then((result) => {
11
- writeFileSync("output.obf.js", result.code, "utf-8");
12
- });
package/src/index.js DELETED
@@ -1,5 +0,0 @@
1
- import { compileAndSerialize } from "./compiler.ts";
2
-
3
- export function virtualize(source) {
4
- return compileAndSerialize(source);
5
- }
package/src/random.js DELETED
@@ -1,3 +0,0 @@
1
- export function getPlaceholder() {
2
- return Math.random().toString(36).substring(2, 15);
3
- }