js-confuser-vm 0.0.1 → 0.0.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## `1.0.3` First update
2
+
3
+ - Created [Website Playground](https://development--confuser.netlify.app/vm)
4
+
5
+ - Added partial support for `try..catch` - The `finally` operator is not supported yet
6
+ - More ES5 coverage: getter/setters, debugger statement
7
+ - Improved compilation process:
8
+ - Parsing:
9
+ JS -> AST
10
+ Done by [@babel/parser](https://www.npmjs.com/package/@babel/parser)
11
+ - Compilation:
12
+ AST -> IR bytecode.
13
+ This bytecode contains pseudo instructions and symbolic values for things like jump labels and constants
14
+ - Transform passes (Assembler):
15
+ Transform passes obfuscate and finally prepare the pseudo bytecode to be runnable. Here, all jump labels get converted into absolute PCs
16
+ - Serializer:
17
+ The bytecode is printed into the array form or encoded string if you have `encodeBytecode` enabled
18
+ - Generating:
19
+ This includes two sub-stages:
20
+ - 1) Creating (another parsing->transforming->generating) the VM Runtime with the given options (randomized op codes, shuffled handlers)
21
+ - 2) Placing the final bytecode into this VM Runtime
22
+
23
+ - Typescript is now transpiled for NPM
24
+
25
+
26
+
27
+ ## `1.0.2` First release
28
+
package/README.MD ADDED
@@ -0,0 +1,197 @@
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 are beyond scope.
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 opcode values in OP mapping?
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
+ timingChecks: true, // add timing checks to detect debuggers?
43
+ minify: true // pass final output through Google Closure Compiler? (Renames VM class properties)
44
+ }).then(result => {
45
+ console.log(result.code)
46
+ })
47
+
48
+ /*
49
+ 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={}}
50
+ 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}
51
+ 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})}
52
+ 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,
53
+ 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);
54
+ 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===
55
+ 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;
56
+ 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<
57
+ 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);
58
+ 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=
59
+ 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;
60
+ G(new x(d("4AMAANUEAAAjAQAA1QUAAKEFAAAjBgAAGQAAAK0YAAChBwAAIwgAAEsAAAChBQAAoQQAAKEFAAAXAQAAngIAAEQAAAChBQAAIgAAACMBAABlAAAA1QUAAEQAAAAqBAAAlQAAACMKAACPCQAARPEAAOcLAACeRQAA54YAALZ9AAA8jAAAbdUAALFkAAArdAAAcysAAI+3AACxbwAAqMAAABPbAACO1wAA88sAAJ3DAACeGgAAsVIAAOCMAACOVwAA85sAANIEAABRwAAAUSIAANicAADVgAAAj1UAANjtAAC1DAAArJMAADy0AAAnBwAAregAAA=="),[0,1,void 0,{k:1,t:5,v:[],u:25},"fibonacci","i",25,"console","log","IwAAADQCAAAjAQAANAMAANIAAAA0BAAA0gAAACIAAAAjAQAA5wAAADQAAAAjAQAAqAAAAK04AADSAgAA0gMAAGUAAAA0BAAA0gQAAEQAAADSAwAANAIAANICAABEAAAA0gQAADQDAADSAwAARAAAACohAADSBAAAlQAAACMCAACVAAAAlQAAAA==",
61
+ 27],H));
62
+ */
63
+ ```
64
+
65
+ ### Features
66
+
67
+ - [x] functions: call, arguments, return
68
+ - [x] closures and nested functions
69
+ - [x] literals
70
+ - [x] binary expressions
71
+ - [x] unary expressions
72
+ - [x] update expressions
73
+ - [x] if statements
74
+ - [x] while, do-while, for loops
75
+ - [x] get property
76
+ - [x] logical expressions
77
+ - [x] array, object expression
78
+ - [x] function expression
79
+ - [x] default arguments in functions
80
+ - [x] sequence expression
81
+ - [x] conditional expression (ternary operator)
82
+ - [x] delete operator
83
+ - [x] in / instanceof
84
+ - [x] this, new expression
85
+ - [x] arguments
86
+ - [x] Infinity, NaN
87
+ - [x] break/continue
88
+ - [x] switch statement
89
+ - [x] throw statement
90
+ - [x] labeled statements
91
+ - [x] for..in loop
92
+ - [x] RegExp literals
93
+ - [x] try..catch
94
+ - [x] getter/setters
95
+ - [x] debugger;
96
+
97
+ ### Missing
98
+
99
+ - [ ] try..finally
100
+ - [ ] with statement
101
+ - [ ] arguments.callee, argument parameter syncing
102
+
103
+ ### Hardening
104
+
105
+ - [x] opcode randomization per build
106
+ - [x] property name concealment of vm internals
107
+ - - Google Closure Compiler aggressively renames our class props
108
+ - [x] shuffled handler order
109
+ - [ ] dead handlers
110
+ - [ ] dead bytecode insertion
111
+ - [ ] macro opcodes
112
+ - [x] encoded bytecode array and words
113
+ - [x] self-modifying bytecode
114
+ - [x] timing checks
115
+ - [ ] low-level bytecode obfuscations
116
+ - [ ] stack protection
117
+ - [ ] control flow integrity
118
+
119
+ ### Minification
120
+
121
+ 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.
122
+
123
+ ### No Try Finally
124
+
125
+ While Try Catch is supported, Try..Finally is not. You can use Try..Finally by defining an outside helper function:
126
+
127
+ ```js
128
+ function TryFinally(cb, _finally) {
129
+ try {
130
+ return { value: cb() }
131
+ } catch (error) {
132
+ return { error };
133
+ } finally {
134
+ _finally()
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### ES5 Only
140
+
141
+ 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.
142
+
143
+ Please transpile your code down first using [Babel](https://github.com/babel/babel).
144
+
145
+ ### Project
146
+
147
+ - Stack based VM
148
+ - Lua-style closure and upvalue model
149
+ - CPython-style opcodes and codegen
150
+ - Compiler is in src/compiler.ts
151
+ - Runtime is in src/runtime.ts
152
+ - "Typescript"
153
+ - - This "Typescript" project uses Node's new flag `--experimental-strip-types`. This means we can run `node index.ts` directly!
154
+
155
+ ### Use with JS-Confuser
156
+
157
+ 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.
158
+
159
+ ```js
160
+ import JsConfuser from "js-confuser";
161
+ import JsConfuserVM from "js-confuser-vm";
162
+ import { readFile, writeFile } from "fs/promises";
163
+
164
+ async function main() {
165
+ // Read input code
166
+ const sourceCode = await readFile("input.js", "utf8");
167
+
168
+ const { code: virtualized } = await JsConfuserVM.obfuscate(sourceCode, {
169
+ target: "browser",
170
+ randomizeOpcodes: true
171
+ });
172
+ const { code: obfuscated } = await JsConfuser.obfuscate(virtualized, {
173
+ target: "browser",
174
+ preset: "medium",
175
+ pack: false,
176
+ globalConcealing: false,
177
+ });
178
+
179
+ // Write output file
180
+ await writeFile("output.js", obfuscated, "utf8");
181
+ }
182
+
183
+ main().catch(console.error);
184
+ ```
185
+
186
+ ### WIP
187
+
188
+ - 178 tests, 91.18% coverage
189
+ - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 66.67%
190
+
191
+ ### Made with AI
192
+
193
+ This project has been created with the help of AI. Expect issues.
194
+
195
+ ### License
196
+
197
+ 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
+ };
@@ -0,0 +1,23 @@
1
+ {
2
+ "presets": [
3
+ [
4
+ "@babel/preset-env",
5
+ {
6
+ "targets": {
7
+ "node": "18"
8
+ },
9
+ "modules": false
10
+ }
11
+ ],
12
+ ["@babel/preset-typescript"]
13
+ ],
14
+ "plugins": [
15
+ "./babel-plugin-inline-runtime.cjs",
16
+ [
17
+ "replace-import-extension",
18
+ {
19
+ "extMapping": { ".ts": ".js" }
20
+ }
21
+ ]
22
+ ]
23
+ }