js-confuser-vm 0.0.2 → 0.0.4

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/LICENSE +21 -21
  3. package/README.MD +370 -190
  4. package/babel-plugin-inline-runtime.cjs +34 -0
  5. package/babel.config.json +23 -24
  6. package/index.ts +34 -28
  7. package/jest-strip-types.js +10 -10
  8. package/jest.config.js +35 -18
  9. package/package.json +50 -48
  10. package/src/build-runtime.ts +57 -0
  11. package/src/compiler.ts +2069 -1677
  12. package/src/index.ts +14 -13
  13. package/src/minify.ts +21 -21
  14. package/src/options.ts +14 -10
  15. package/src/runtime.ts +771 -645
  16. package/src/transforms/bytecode/macroOpcodes.ts +177 -0
  17. package/src/transforms/bytecode/resolveContants.ts +62 -0
  18. package/src/transforms/bytecode/resolveLabels.ts +107 -0
  19. package/src/transforms/bytecode/selfModifying.ts +121 -0
  20. package/src/transforms/bytecode/specializedOpcodes.ts +118 -0
  21. package/src/transforms/runtime/macroOpcodes.ts +111 -0
  22. package/src/transforms/runtime/minify.ts +1 -0
  23. package/src/transforms/runtime/shuffleOpcodes.ts +24 -0
  24. package/src/transforms/runtime/specializedOpcodes.ts +146 -0
  25. package/src/transforms/utils/op-utils.ts +26 -0
  26. package/src/{random.ts → transforms/utils/random-utils.ts} +31 -31
  27. package/src/types.ts +33 -0
  28. package/src/utilts.ts +3 -3
  29. package/tsconfig.json +12 -12
  30. package/dist/compiler.js +0 -1505
  31. package/dist/index.js +0 -9
  32. package/dist/minify.js +0 -18
  33. package/dist/minify_empty_externs.js +0 -4
  34. package/dist/options.js +0 -1
  35. package/dist/random.js +0 -27
  36. package/dist/runtime.js +0 -620
  37. package/dist/runtimeObf.js +0 -36
  38. package/dist/utilts.js +0 -3
  39. package/src/runtimeObf.ts +0 -48
package/README.MD CHANGED
@@ -1,191 +1,371 @@
1
- # JS Confuser VM
2
-
3
- [![NPM](https://img.shields.io/badge/NPM-%23000000.svg?style=for-the-badge&logo=npm&logoColor=white)](https://npmjs.com/package/js-confuser-vm) [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/MichaelXF/js-confuser-vm)
4
-
5
-
6
- - **Requires Node v24.13.1 or higher**
7
- - ES5 support only. No complex features: async, generator, and even try..catch are beyond scope.
8
- - Experimental. Expect issues.
9
-
10
- ### Installation
11
-
12
- ```shell
13
- $ npm install js-confuser-vm
14
- ```
15
-
16
- ### Usage
17
-
18
- ```js
19
- import JsConfuserVM from "js-confuser-vm";
20
-
21
- JsConfuserVM.obfuscate(`
22
- function fibonacci(num){
23
- var a = 0, b = 1, c = num;
24
- while (num-- > 1) {
25
- c = a + b;
26
- a = b;
27
- b = c;
28
- }
29
- return c;
30
- }
31
-
32
- for ( var i = 1; i <= 25; i++ ) {
33
- console.log(i, fibonacci(i))
34
- }
35
- `, {
36
- target: "browser", // or "node"
37
- randomizeOpcodes: true, // randomize opcode values in OP mapping?
38
- shuffleOpcodes: true, // shuffle order of opcode handlers in the runtime?
39
- encodeBytecode: true, // encode bytecode? when off, comments for instructions are added
40
- selfModifying: true, // do self-modifying bytecode for function bodies?
41
- timingChecks: true, // add timing checks to detect debuggers?
42
- }).then(result => {
43
- console.log(result.code)
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=149);switch(e){case 142:b=z(a);y(a,z(a)in b);break;case 109: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 q=k[b];if(!(q in e)){e[q]=!0;var B=Object.getOwnPropertyDescriptor(g,q);B&&B.enumerable&&c.push(q)}}g=Object.getPrototypeOf(g)}y(a,{keys:c,
51
- s:0});break;default:throw Error("Unknown opcode: "+e+" at pc "+(c.d-1));case 243:z(a);y(a);break;case 114:b=z(a);y(a,z(a)^b);break;case 81:y(a,m(c.r.l[b]));break;case 6:A(a)?c.d=b:z(a);break;case 158: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]=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 77:y(a,!z(a));break;case 161:y(a,a.i[a.e[b]]);break;case 231:b=z(a);y(a,z(a)-b);break;case 35:y(a,a.e[b]);break;case 168:b=z(a);
52
- y(a,z(a)>b);break;case 224: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(C){return function(){for(var u=Array.prototype.slice.call(arguments),D=new x(t.q,t.e,t.i),v=new w(C,this),r=0;r<u.length;r++)v.b[r]=u[r];v.b[C.f.k]=u;D.c=v;return G(D)}}(e);b[f]=e;b.prototype=e.prototype;y(a,b);break;case 49: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===
53
- c){e=!0;break}b=Object.getPrototypeOf(b)}y(a,e)}break;case 115:b=z(a);y(a,z(a)|b);break;case 82:c=z(a);b=z(a);y(a,delete b[c]);break;case 60:n(c.r.l[b],z(a));break;case 182:y(a,z(a));break;case 177: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 72:y(a,c.x);break;case 53:b=z(a);y(a,z(a)===b);break;case 25:b=z(a);y(a,z(a)<=b);break;
54
- case 75:c=z(a);b=A(a);y(a,b[c]);break;case 225:e=z(a);e.s>=e.keys.length?c.d=b:y(a,e.keys[e.s++]);break;case 181:b=z(a);y(a,z(a)%b);break;case 42:c.d=b;break;case 134:e=z(a);c=z(a);b=z(a);b[c]=e;y(a,e);break;case 67:b=z(a);y(a,z(a)*b);break;case 43:b=z(a);y(a,z(a)<b);break;case 52:c.b[b]=z(a);break;case 68:z(a);break;case 143:c=z(a);e=a.e[b];e=d(e);for(b=0;b<e.length;b++)a.q[c+b]=e[b];break;case 244:y(a,~z(a));break;case 23: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<
55
- 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 123:b=z(a);y(a,z(a)!=b);break;case 14:b=z(a);y(a,z(a)<<b);break;case 238:A(a)?z(a):c.d=b;break;case 180: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 4:b=z(a);y(a,z(a)>>>b);break;case 19:b=z(a);y(a,z(a)&b);break;case 211:c=z(a);b=z(a);y(a,b[c]);break;case 97:b=z(a);y(a,z(a)!==b);break;case 255:b=z(a);y(a,z(a)==b);break;case 101:b=z(a);y(a,z(a)+b);break;case 122:throw z(a);
56
- case 157:y(a,-z(a));break;case 149: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 34:y(a,A(a));break;case 213:a.i[a.e[b]]=z(a);break;case 173:z(a)||(c.d=b);break;case 216:y(a,typeof z(a));break;case 39:b=z(a);y(a,z(a)>>b);break;case 210:y(a,c.b[b]);break;case 190:b=z(a);y(a,z(a)/b);break;case 150:b=z(a);y(a,z(a)>=b);break;case 172:b=z(a);e=Object.prototype.hasOwnProperty.call(a.i,b)?a.i[b]:void 0;y(a,typeof e);break;case 113:b=
57
- a.a.splice(a.a.length-b),y(a,b)}}}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("4AMAANUEAAAjAQAA1QUAAKEFAAAjBgAAGQAAAK0YAAChBwAAIwgAAEsAAAChBQAAoQQAAKEFAAAXAQAAngIAAEQAAAChBQAAIgAAACMBAABlAAAA1QUAAEQAAAAqBAAAlQAAACMKAACPCQAARPEAAOcLAACeRQAA54YAALZ9AAA8jAAAbdUAALFkAAArdAAAcysAAI+3AACxbwAAqMAAABPbAACO1wAA88sAAJ3DAACeGgAAsVIAAOCMAACOVwAA85sAANIEAABRwAAAUSIAANicAADVgAAAj1UAANjtAAC1DAAArJMAADy0AAAnBwAAregAAA=="),[0,1,void 0,{k:1,t:5,v:[],u:25},"fibonacci","i",25,"console","log","IwAAADQCAAAjAQAANAMAANIAAAA0BAAA0gAAACIAAAAjAQAA5wAAADQAAAAjAQAAqAAAAK04AADSAgAA0gMAAGUAAAA0BAAA0gQAAEQAAADSAwAANAIAANICAABEAAAA0gQAADQDAADSAwAARAAAACohAADSBAAAlQAAACMCAACVAAAAlQAAAA==",
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
- - [x] RegExp literals
91
-
92
- ### Missing
93
-
94
- - [ ] try..catch
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
- - [x] 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
- The `minify` option 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" project uses Node's new flag `--experimental-strip-types`. This 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
- ```js
154
- import JsConfuser from "js-confuser";
155
- import JsConfuserVM from "js-confuser-vm";
156
- import { readFile, writeFile } from "fs/promises";
157
-
158
- async function main() {
159
- // Read input code
160
- const sourceCode = await readFile("input.js", "utf8");
161
-
162
- const { code: virtualized } = await JsConfuserVM.obfuscate(sourceCode, {
163
- target: "browser",
164
- randomizeOpcodes: true
165
- });
166
- const { code: obfuscated } = await JsConfuser.obfuscate(virtualized, {
167
- target: "browser",
168
- preset: "medium",
169
- pack: false,
170
- globalConcealing: false,
171
- });
172
-
173
- // Write output file
174
- await writeFile("output.js", obfuscated, "utf8");
175
- }
176
-
177
- main().catch(console.error);
178
- ```
179
-
180
- ### WIP
181
-
182
- - 146 tests, 91.18% coverage
183
- - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 52.39%
184
-
185
- ### Made with AI
186
-
187
- This project has been created with the help of AI. Expect issues.
188
-
189
- ### License
190
-
1
+ # JS Confuser VM
2
+
3
+ [![NPM](https://img.shields.io/badge/NPM-%23000000.svg?style=for-the-badge&logo=npm&logoColor=white)](https://npmjs.com/package/js-confuser-vm) [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/MichaelXF/js-confuser-vm) [![Netlify](https://img.shields.io/badge/netlify-%23000000.svg?style=for-the-badge&logo=netlify&logoColor=#00C7B7)](https://development--confuser.netlify.app/vm)
4
+
5
+
6
+ - **Requires Node v24.13.1 or higher**
7
+ - ES5 support only. No complex features: async, generator, and even try..finally aren't supported.
8
+ - Experimental. Expect issues.
9
+ - [Try the web version.](https://development--confuser.netlify.app/vm)
10
+
11
+ ### Installation
12
+
13
+ ```shell
14
+ $ npm install js-confuser-vm
15
+ ```
16
+
17
+ ### Usage
18
+
19
+ ```js
20
+ import JsConfuserVM from "js-confuser-vm";
21
+
22
+ JsConfuserVM.obfuscate(`
23
+ function fibonacci(num){
24
+ var a = 0, b = 1, c = num;
25
+ while (num-- > 1) {
26
+ c = a + b;
27
+ a = b;
28
+ b = c;
29
+ }
30
+ return c;
31
+ }
32
+
33
+ for ( var i = 1; i <= 25; i++ ) {
34
+ console.log(i, fibonacci(i))
35
+ }
36
+ `, {
37
+ target: "browser", // or "node"
38
+ randomizeOpcodes: true, // randomize the opcode numbers?
39
+ shuffleOpcodes: true, // shuffle order of opcode handlers in the runtime?
40
+ encodeBytecode: true, // encode bytecode? when off, comments for instructions are added
41
+ selfModifying: true, // do self-modifying bytecode for function bodies?
42
+ macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
43
+ specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
44
+ timingChecks: true, // add timing checks to detect debuggers?
45
+ minify: true // pass final output through Google Closure Compiler? (Renames VM class properties)
46
+ }).then(result => {
47
+ console.log(result.code)
48
+ })
49
+
50
+ /*
51
+ var b=Symbol();function ea(a,k){this.B=a;this.G=k;this.H=!1;this.J=void 0}function fa(a){return a.H?a.J:a.B.g[a.G]}function ha(a,k){a.H?a.J=k:a.B.g[a.G]=k}function d(a){this.u=a;this.D=[];this.prototype={}}function g(a,k){this.K=a;this.g=Array(a.u.O).fill(void 0);this.j=a.u.P;this.V=k!==void 0?k:void 0;this.I=null;this.F=[]}function u(a,k,c){this.o=a;this.v=k;this.m=c;this.h=[];this.l=[];this.C=[];this.i=new g(new d({A:0,O:0,P:0}))}function w(a,k){a.h.push(k)}function y(a){return a.h.pop()}
52
+ function E(a){return a.h[a.h.length-1]}function R(a){return a.o[a.i.j++]}function ia(a,k,c){for(var p=0;p<a.C.length;p++){var v=a.C[p];if(v.B===k&&v.G===c)return v}v=new ea(k,c);a.C.push(v);return v}function S(a,k){a.C=a.C.filter(function(c){return c.B===k?(c.J=c.B.g[c.G],c.H=!0,!1):!0})}
53
+ function T(a){for(var k=performance.now();;){var c=a.i;if(c.j>=a.o.length)break;var p=a.o[c.j++],v=performance.now(),ja=v-k>1E3;k=v;if(ja){for(var e=0;e<a.o.length;e++)a.o[e]=0;p=18186;a.h=[]}try{switch(p){case 3078:var h=a.h.splice(a.h.length-R(a)),l=y(a);if(l&&l[b]){var q=l[b],W=Object.create(q.prototype||null),m=new g(q,W);m.I=W;for(e=0;e<h.length;e++)m.g[e]=h[e];m.g[q.u.A]=h;a.l.push(a.i);a.i=m}else w(a,Reflect.construct(l,h));break;case 60312:c.F.push({S:R(a),U:a.h.length,R:a.l.length});break;
54
+ case 39875:w(a,c.V);break;case 52854:w(a,fa(c.K.D[R(a)]));break;case 57982:var ka=y(a),t=y(a),n=y(a),x=Object.getOwnPropertyDescriptor(n,t);c={set:ka,configurable:!0,enumerable:!0};x&&typeof x.get==="function"&&(c.get=x.get);Object.defineProperty(n,t,c);break;case 24006:throw y(a);case 22876:w(a,a.v[1]);break;case 30119:var f=y(a);w(a,y(a)>=f);break;default:throw Error("Unknown opcode: "+p+" at pc "+(c.j-1));case 47627:f=y(a);w(a,y(a)===f);break;case 40726:h=a.h.splice(a.h.length-R(a));l=y(a);var z=
55
+ y(a);if(l&&l[b]){q=l[b];m=new g(q,z);for(e=0;e<h.length;e++)m.g[e]=h[e];m.g[q.u.A]=h;a.l.push(a.i);a.i=m}else w(a,l.apply(z,h));break;case 44825:var L=a.h.splice(a.h.length-R(a)*2);c={};for(e=0;e<L.length;e+=2)c[L[e]]=L[e+1];w(a,c);break;case 9764:f=y(a);w(a,y(a)<f);break;case 17188:w(a,a.m[a.v[R(a)]]);break;case 47335:var A=y(a);S(a,c);if(a.l.length===0)return A;c.I===null||typeof A==="object"&&A!==null||(A=c.I);a.i=a.l.pop();w(a,A);break;case 26437:c.g[2]=y(a);break;case 36615:c.F.pop();break;case 42717:h=
56
+ a.h.splice(a.h.length-1);if((l=y(a))&&l[b]){q=l[b];m=new g(q,a.m);for(e=0;e<h.length;e++)m.g[e]=h[e];m.g[q.u.A]=h;a.l.push(a.i);a.i=m}else w(a,l.apply(null,h));break;case 9310:w(a,c.g[0]);break;case 40942:t=y(a);n=E(a);w(a,n[t]);break;case 28991:a.m[a.v[R(a)]]=y(a);break;case 55626:c.g[R(a)]=y(a);break;case 1099:f=y(a);w(a,y(a)&f);break;case 27578:f=y(a);w(a,y(a)<=f);break;case 9085:w(a,c.g[2]);break;case 46808:var r=R(a);y(a)||(c.j=r);break;case 45706:f=y(a);w(a,y(a)^f);break;case 50889:h=a.h.splice(a.h.length-
57
+ 2);l=y(a);z=y(a);if(l&&l[b]){q=l[b];m=new g(q,z);for(e=0;e<h.length;e++)m.g[e]=h[e];m.g[q.u.A]=h;a.l.push(a.i);a.i=m}else w(a,l.apply(z,h));break;case 30510:r=R(a);E(a)?c.j=r:y(a);break;case 43517:w(a,c.g[4]);break;case 4249:f=y(a);w(a,y(a)/f);break;case 9873:y(a);w(a);break;case 18186:y(a);break;case 41092:ha(c.K.D[R(a)],y(a));break;case 11897:r=R(a);var F=y(a);F.N>=F.M.length?c.j=r:w(a,F.M[F.N++]);break;case 4731:w(a,E(a));break;case 3252:w(a,typeof y(a));break;case 45288:f=y(a);w(a,y(a)%f);break;
58
+ case 33898:w(a,a.m[a.v[2]]);break;case 47394:w(a,c.g[R(a)]);break;case 2573:w(a,a.v[R(a)]);break;case 21702:debugger;break;case 36429:f=y(a);w(a,y(a)!==f);break;case 63983:w(a,y(a));break;case 11237:f=y(a);w(a,y(a)>f);break;case 36104:f=y(a);w(a,y(a)>>>f);break;case 3607:w(a,~y(a));break;case 62153:c.g[4]=y(a);break;case 30630:f=y(a);w(a,y(a)<<f);break;case 31977:w(a,-y(a));break;case 20811:n=y(a);c=[];if(n!==null&&n!==void 0)for(var X=Object.create(null),B=Object(n);B!==null;){var Y=Object.getOwnPropertyNames(B);
59
+ for(e=0;e<Y.length;e++){var G=Y[e];if(!(G in X)){X[G]=!0;var Z=Object.getOwnPropertyDescriptor(B,G);Z&&Z.enumerable&&c.push(G)}}B=Object.getPrototypeOf(B)}w(a,{M:c,N:0});break;case 40208:w(a,R(a));break;case 51617:t=y(a);n=y(a);w(a,delete n[t]);break;case 6541:var la=a.h.splice(a.h.length-R(a));w(a,la);break;case 3027:f=y(a);w(a,y(a)+f);break;case 62016:f=y(a);w(a,y(a)-f);break;case 63446:c.g[0]=y(a);break;case 27060:f=y(a);w(a,y(a)==f);break;case 60562:r=R(a);E(a)?y(a):c.j=r;break;case 55362:c.j=
60
+ 9;break;case 7995:var H=y(a);t=y(a);n=y(a);Reflect.set(n,t,H);w(a,H);break;case 17850:var ma=y(a);t=y(a);n=y(a);x=Object.getOwnPropertyDescriptor(n,t);c={get:ma,configurable:!0,enumerable:!0};x&&typeof x.set==="function"&&(c.set=x.set);Object.defineProperty(n,t,c);break;case 54027:f=y(a);w(a,y(a)!=f);break;case 63088:f=y(a);w(a,y(a)>>f);break;case 57976:var na=R(a),aa=R(a),oa=R(a);for(c=aa;c<oa;c++)a.o[na+(c-aa)]=a.o[c];break;case 18659:h=a.h.splice(a.h.length-R(a));if((l=y(a))&&l[b]){q=l[b];m=new g(q,
61
+ a.m);for(e=0;e<h.length;e++)m.g[e]=h[e];m.g[q.u.A]=h;a.l.push(a.i);a.i=m}else w(a,l.apply(null,h));break;case 1407:r=42;y(a)||(c.j=r);break;case 26140:w(a,c.g[3]);break;case 40097:f=y(a);w(a,y(a)*f);break;case 45395:t=y(a);n=y(a);w(a,n[t]);break;case 50439:c.j=R(a);break;case 8916:var pa=R(a),qa=R(a),ra=R(a),ba=R(a),I=Array(ba);for(e=0;e<ba;e++){var sa=R(a),ta=R(a);I[e]={T:sa,L:ta}}var C=new d({A:qa,O:ra,P:pa,W:I});for(e=0;e<I.length;e++){var M=I[e];M.T?C.D.push(ia(a,c,M.L)):C.D.push(c.K.D[M.L])}var J=
62
+ a,P=function(D){return function(){for(var N=Array.prototype.slice.call(arguments),ca=new u(J.o,J.v,J.m),O=new g(D,this==null?J.m:this),K=0;K<N.length;K++)O.g[K]=N[K];O.g[D.u.A]=N;ca.i=O;return T(ca)}}(C);P[b]=C;P.prototype=C.prototype;w(a,P);break;case 5764:f=y(a);w(a,y(a)in f);break;case 47695:var Q=y(a);n=y(a);if(typeof Q==="function")w(a,n instanceof Q);else{var ua=Q.prototype;r=Object.getPrototypeOf(n);for(c=!1;r!==null;){if(r===ua){c=!0;break}r=Object.getPrototypeOf(r)}w(a,c)}break;case 39207:f=
63
+ y(a);w(a,y(a)|f);break;case 16865:a.m[a.v[0]]=y(a);break;case 10233:c.g[3]=y(a);break;case 11143:w(a,!y(a));break;case 17244:var da=y(a);H=Object.prototype.hasOwnProperty.call(a.m,da)?a.m[da]:void 0;w(a,typeof H)}}catch(D){c=null;for(p=a.i;;){if(p.F.length>0){c=p;break}S(a,p);if(a.l.length===0)break;p=a.l.pop();a.i=p}if(!c)throw D;p=c.F.pop();a.h.length=p.U;w(a,D);a.l.length=p.R;c.j=p.S;a.i=c}}}var U={},V;for(V of Object.getOwnPropertyNames(globalThis))U[V]=globalThis[V];
64
+ typeof window!=="undefined"&&(U.window=window);U.undefined=void 0;U.Infinity=Infinity;U.NaN=NaN;
65
+ T(new u(function(a){a=typeof Buffer!=="undefined"?Buffer.from(a,"base64"):Uint8Array.from(atob(a),function(p){return p.charCodeAt(0)});for(var k=new Uint16Array(a.length/2),c=0;c<k.length;c++)k[c]=a[c*2]|a[c*2+1]<<8;return k}("1CIrAAEABQAAAOFBXFk/cQIAeOINAFwAbQBK2XD2Ga8LugvTeOK6a8ZdC7qndT9xC7o7HyK5Fw4KR1OxeOIiAG0AdQChyZjrexK6azsfQPKEoOUr57h44i8AdQB8ABmvukXTC4SgJ5nGVNMLeOI6AHwAgwB2zoQWtGk/cSQmT7qHK9i2UwB9Ixxm0wvJ8v2pCkccZkVnfSMKR/2p+SccZgpHB8U2AHjiVwCDAIQAQPLnuA0KAwDnuGqEDQoEALprfwUkQwUADQoGAO6faoQkQwAAaoTdpsnGCkdqhHsSXFnTCz9xAgAKR0LYDQoHAEVnXFn5J14kyfJeJHsSXFlA8tb3XFnlK/2p"),["fibonacci",
66
+ 1,"i",void 0,25,"console","log",0],U));
67
+ */
68
+ ```
69
+
70
+ ### Features
71
+
72
+ - [x] functions: call, arguments, return
73
+ - [x] closures and nested functions
74
+ - [x] literals
75
+ - [x] binary expressions
76
+ - [x] unary expressions
77
+ - [x] update expressions
78
+ - [x] if statements
79
+ - [x] while, do-while, for loops
80
+ - [x] get property
81
+ - [x] logical expressions
82
+ - [x] array, object expression
83
+ - [x] function expression
84
+ - [x] default arguments in functions
85
+ - [x] sequence expression
86
+ - [x] conditional expression (ternary operator)
87
+ - [x] delete operator
88
+ - [x] in / instanceof
89
+ - [x] this, new expression
90
+ - [x] arguments
91
+ - [x] Infinity, NaN
92
+ - [x] break/continue
93
+ - [x] switch statement
94
+ - [x] throw statement
95
+ - [x] labeled statements
96
+ - [x] for..in loop
97
+ - [x] RegExp literals
98
+ - [x] try..catch
99
+ - [x] getter/setters
100
+ - [x] debugger;
101
+
102
+ ### Missing
103
+
104
+ - [ ] try..finally
105
+ - [ ] with statement
106
+ - [ ] arguments.callee, argument parameter syncing
107
+
108
+ ### Hardening
109
+
110
+ - [x] opcode randomization per build
111
+ - [x] property name concealment of vm internals
112
+ - - Google Closure Compiler aggressively renames our class props
113
+ - [x] shuffled handler order
114
+ - [ ] dead handlers
115
+ - [ ] dead bytecode insertion
116
+ - [x] macro opcodes (Combine multiple opcodes into a "macro opcode")
117
+ - [x] specialized opcodes (Create specific opcodes for opcode+operand pairs)
118
+ - [x] encoded bytecode array
119
+ - [x] self-modifying bytecode
120
+ - [x] timing checks
121
+ - [ ] low-level bytecode obfuscations
122
+ - [ ] stack protection
123
+ - [ ] control flow integrity
124
+
125
+ ### Options
126
+
127
+ #### `target` ("node"/"browser")
128
+
129
+ Currently has no effect.
130
+
131
+ #### `randomizeOpcodes` (true/false)
132
+
133
+ Randomizes the opcode numbers.
134
+
135
+ #### `shuffleOpcodes` (true/false)
136
+
137
+ Shuffles the order of opcode handlers in the VM runtime.
138
+
139
+ #### `encodeBytecode` (true/false)
140
+
141
+ Encodes the bytecode array.
142
+
143
+ ```js
144
+ // Before
145
+ var BYTECODE = [3, 0, 0, 1, 5, 0, 2, 12, 1, 14, 13];
146
+
147
+ // After
148
+ var BYTECODE = "AwAAAAAAAQAFAAAAAgAMAAEADgANAA==";
149
+ ```
150
+
151
+ #### `macroOpcodes` (true/false)
152
+
153
+ Combines multiple opcodes commonly used from your bytecode.
154
+
155
+ ```js
156
+ // Input Code
157
+ console.log("Hello world!");
158
+ console.log("Hello world!");
159
+
160
+ // Before
161
+ // [3, 0], LOAD_GLOBAL "console" 1:0-1:7
162
+ // [0, 1], LOAD_CONST "log" 1:0-1:27
163
+ // [5], GET_PROP 1:0-1:27
164
+ // [0, 2], LOAD_CONST "Hello world!" 1:12-1:26
165
+ // [12, 1], CALL_METHOD (1 args) 1:0-1:27
166
+ // [14], POP 1:0-1:28
167
+ // [3, 0], LOAD_GLOBAL "console" 2:0-2:7
168
+ // [0, 1], LOAD_CONST "log" 2:0-2:27
169
+ // [5], GET_PROP 2:0-2:27
170
+ // [0, 2], LOAD_CONST "Hello world!" 2:12-2:26
171
+ // [12, 1], CALL_METHOD (1 args) 2:0-2:27
172
+ // [14], POP 2:0-2:28
173
+
174
+ // After
175
+ // [64, 0, 1, 2], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST [0, 1, 2]
176
+ // [12, 1], CALL_METHOD (1 args) 1:0-1:27
177
+ // [14], POP 1:0-1:28
178
+ // [64, 0, 1, 2], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST [0, 1, 2]
179
+ // [12, 1], CALL_METHOD (1 args) 2:0-2:27
180
+ // [14], POP 2:0-2:28
181
+
182
+ // What the opcode "LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST" (64) looks like:
183
+ case 64:
184
+ {
185
+ // LOAD_GLOBAL
186
+ this._push(this.globals[this.constants[this._operand()]]);
187
+ // LOAD_CONST
188
+ this._push(this.constants[this._operand()]);
189
+ // GET_PROP
190
+ // Stack: [..., obj, key] -> [..., obj, obj[key]]
191
+ // obj is PEEKED (not popped) - CALL_METHOD needs it as receiver
192
+ var key = this._pop();
193
+ var obj = this.peek();
194
+ this._push(obj[key]);
195
+ // LOAD_CONST
196
+ this._push(this.constants[this._operand()]);
197
+ break;
198
+ }
199
+ ```
200
+
201
+ #### `specializedOpcodes` (true/false)
202
+
203
+ Creates specialized opcodes for commonly used opcode+operand pairs.
204
+
205
+ ```js
206
+ // Input Code
207
+ console.log("Hello world!");
208
+
209
+ // Before
210
+ // [3, 0], LOAD_GLOBAL "console" 1:0-1:7
211
+ // [0, 1], LOAD_CONST "log" 1:0-1:27
212
+ // [5], GET_PROP 1:0-1:27
213
+ // [0, 2], LOAD_CONST "Hello world!" 1:12-1:26
214
+ // [12, 1], CALL_METHOD (1 args) 1:0-1:27
215
+ // [14], POP 1:0-1:28
216
+
217
+ // What the opcode "LOAD_GLOBAL" looks like:
218
+ case OP.LOAD_CONST:
219
+ this._push(this.constants[this._operand()]);
220
+ break;
221
+
222
+ // After
223
+ // [64], LOAD_GLOBAL_0 1:0-1:7
224
+ // [65], LOAD_CONST_1 1:0-1:27
225
+ // [5], GET_PROP 1:0-1:27
226
+ // [66], LOAD_CONST_2 1:12-1:26
227
+ // [67], CALL_METHOD_1 1:0-1:27
228
+ // [14], POP 1:0-1:28
229
+
230
+ // What the opcode "LOAD_GLOBAL_0" (64) looks like:
231
+ case 64:
232
+ // LOAD_GLOBAL_0 (specialized)
233
+ this._push(this.globals[this.constants[0]]);
234
+ break;
235
+ ```
236
+
237
+ #### `selfModifying` (true/false)
238
+
239
+ Function bodies are replaced upon runtime entry to the real bytecode.
240
+
241
+ ```diff
242
+ // Input
243
+ function greet() {
244
+ console.log("Hello, world!");
245
+ }
246
+
247
+ // Before
248
+ // [3, 1], LOAD_GLOBAL "console" 2:2-2:9
249
+ // [0, 2], LOAD_CONST "log" 2:2-2:30
250
+ // [5], GET_PROP 2:2-2:30
251
+ // [0, 3], LOAD_CONST "Hello, world!" 2:14-2:29
252
+ // [12, 1], CALL_METHOD (1 args) 2:2-2:30
253
+ // [14], POP 2:2-2:31
254
+ // [0, 4], LOAD_CONST undefined 1:0-3:1
255
+ // [13], RETURN 1:0-3:1
256
+
257
+ // After
258
+ // [56, 17, 30, 42], PATCH [17, 30, 42]
259
+ -// [24], STORE_UPVALUE <-- 12 length of garbage code
260
+ -// [15], LT
261
+ -// [5], GET_PROP
262
+ -// [46], IN
263
+ -// [57], TRY_SETUP
264
+ -// [50], DUP
265
+ -// [55], FOR_IN_NEXT
266
+ -// [41], SHR
267
+ -// [53], LOOSE_NEQ
268
+ -// [7], SUB
269
+ -// [3], LOAD_GLOBAL
270
+ -// [8], MUL
271
+ // [13], RETURN 1:0-3:1
272
+ +// [3, 1], LOAD_GLOBAL "console" <-- 12 length of real code
273
+ +// [0, 2], LOAD_CONST "log" 2:2-2:30
274
+ +// [5], GET_PROP 2:2-2:30
275
+ +// [0, 3], LOAD_CONST "Hello, world!" 2:14-2:29
276
+ +// [12, 1], CALL_METHOD (1 args) 2:2-2:30
277
+ +// [14], POP 2:2-2:31
278
+ +// [0, 4], LOAD_CONST undefined 1:0-3:1
279
+ ```
280
+
281
+ #### `timingChecks` (true/false)
282
+
283
+ Detects the use of debuggers by checking for >1second pauses. May break code with slow sync tasks.
284
+
285
+
286
+ #### `minify` (true/false)
287
+
288
+ Minifies the final code with Google Closure Compiler. Renames the VM class properties.
289
+
290
+ ### No Try Finally
291
+
292
+ While Try Catch is supported, Try..Finally is not. You can use Try..Finally by defining an outside helper function:
293
+
294
+ ```js
295
+ function TryFinally(cb, _finally) {
296
+ try {
297
+ return { value: cb() }
298
+ } catch (error) {
299
+ return { error };
300
+ } finally {
301
+ _finally()
302
+ }
303
+ }
304
+ ```
305
+
306
+ ### ES5 Only
307
+
308
+ 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.
309
+
310
+ Please transpile your code down first using [Babel](https://github.com/babel/babel).
311
+
312
+ ### Project
313
+
314
+ - Stack based VM
315
+ - Lua-style closure and upvalue model
316
+ - CPython-style opcodes and codegen
317
+ - Compiler is in src/compiler.ts
318
+ - Runtime is in src/runtime.ts
319
+ - "Typescript"
320
+ - - This "Typescript" project uses Node's new flag `--experimental-strip-types`. This means we can run `node index.ts` directly!
321
+
322
+ ### Best practices
323
+
324
+ - **Don't rely on "function.length"**
325
+ - Avoid undeclared variables
326
+ - Don't rely on "function.name"
327
+ - Don't use eval() to reference or modify local variables
328
+
329
+ ### Use with JS-Confuser
330
+
331
+ 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.
332
+
333
+ ```js
334
+ import JsConfuser from "js-confuser";
335
+ import JsConfuserVM from "js-confuser-vm";
336
+ import { readFile, writeFile } from "fs/promises";
337
+
338
+ async function main() {
339
+ // Read input code
340
+ const sourceCode = await readFile("input.js", "utf8");
341
+
342
+ const { code: virtualized } = await JsConfuserVM.obfuscate(sourceCode, {
343
+ target: "browser",
344
+ randomizeOpcodes: true
345
+ });
346
+ const { code: obfuscated } = await JsConfuser.obfuscate(virtualized, {
347
+ target: "browser",
348
+ preset: "medium",
349
+ pack: false,
350
+ globalConcealing: false,
351
+ });
352
+
353
+ // Write output file
354
+ await writeFile("output.js", obfuscated, "utf8");
355
+ }
356
+
357
+ main().catch(console.error);
358
+ ```
359
+
360
+ ### WIP
361
+
362
+ - 178 tests, 91.18% coverage
363
+ - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 66.67%
364
+
365
+ ### Made with AI
366
+
367
+ This project has been created with the help of AI. Expect issues.
368
+
369
+ ### License
370
+
191
371
  MIT License
@@ -0,0 +1,34 @@
1
+ const { readFileSync } = require("fs");
2
+ const { join } = require("path");
3
+ const babel = require("@babel/core");
4
+ const { stripTypeScriptTypes } = require("node:module");
5
+
6
+ module.exports = function inlineRuntimePlugin({ types: t }) {
7
+ const rawContent = readFileSync(join(__dirname, "./src/runtime.ts"), "utf-8");
8
+
9
+ const runtimeContent = stripTypeScriptTypes(rawContent);
10
+
11
+ return {
12
+ name: "inline-runtime",
13
+ visitor: {
14
+ VariableDeclarator(path) {
15
+ if (
16
+ path.node.id?.name === "readVMRuntimeFile" &&
17
+ (path.node.init?.type === "ArrowFunctionExpression" ||
18
+ path.node.init?.type === "FunctionExpression")
19
+ ) {
20
+ path.node.init = t.arrowFunctionExpression(
21
+ [],
22
+ t.stringLiteral(runtimeContent),
23
+ );
24
+ }
25
+ },
26
+ ImportDeclaration(path) {
27
+ const src = path.node.source.value;
28
+ if (src === "fs" || src === "path") {
29
+ path.remove();
30
+ }
31
+ },
32
+ },
33
+ };
34
+ };