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/README.MD +191 -0
- package/babel.config.json +24 -0
- package/dist/compiler.js +1505 -0
- package/dist/index.js +9 -0
- package/dist/minify.js +18 -0
- package/{minify_empty_externs.js → dist/minify_empty_externs.js} +1 -1
- package/dist/options.js +1 -0
- package/dist/random.js +27 -0
- package/dist/runtime.js +620 -0
- package/dist/runtimeObf.js +36 -0
- package/dist/utilts.js +3 -0
- package/index.ts +23 -12
- package/jest.config.js +16 -5
- package/package.json +13 -6
- package/src/compiler.ts +420 -360
- package/src/index.ts +13 -0
- package/src/minify.ts +21 -0
- package/src/options.ts +10 -0
- package/src/random.ts +31 -0
- package/src/runtime.ts +53 -39
- package/src/runtimeObf.ts +48 -0
- package/src/utilts.ts +3 -0
- package/.claude/settings.local.json +0 -8
- package/ReadME.MD +0 -164
- package/input.js +0 -15
- package/minify.js +0 -17
- package/obfuscate.js +0 -12
- package/src/index.js +0 -5
- package/src/random.js +0 -3
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
|
|
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 (!
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
55
|
+
// Closure & Frame
|
|
55
56
|
function Closure(fn) {
|
|
56
57
|
this.fn = fn;
|
|
57
58
|
this.upvalues = [];
|
|
58
|
-
this.prototype = {}; //
|
|
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; //
|
|
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; //
|
|
69
|
+
this._newObj = null; // <- set by NEW so RETURN can see it
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
//
|
|
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, //
|
|
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
|
|
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 (
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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]
|
|
179
|
-
// obj is PEEKED (not popped)
|
|
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)
|
|
297
|
+
// Native constructor (e.g. Array, Date) - native instanceof is fine
|
|
295
298
|
this._push(obj instanceof ctor);
|
|
296
299
|
} else {
|
|
297
|
-
// VM Closure
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 left
|
|
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
|
|
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
|
-
|
|
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]
|
|
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
|
|
475
|
+
// VM closure - run directly in this VM, no sub-VM overhead
|
|
463
476
|
var c = callee[CLOSURE_SYM];
|
|
464
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
//
|
|
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
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
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
package/src/random.js
DELETED