js-confuser-vm 0.0.1
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/.claude/settings.local.json +8 -0
- package/.prettierignore +1 -0
- package/LICENSE +21 -0
- package/ReadME.MD +164 -0
- package/index.ts +17 -0
- package/input.js +15 -0
- package/jest-strip-types.js +10 -0
- package/jest.config.js +7 -0
- package/minify.js +17 -0
- package/minify_empty_externs.js +4 -0
- package/obfuscate.js +12 -0
- package/package.json +41 -0
- package/src/compiler.ts +1617 -0
- package/src/index.js +5 -0
- package/src/random.js +3 -0
- package/src/runtime.ts +631 -0
- package/tsconfig.json +12 -0
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Brasington
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/ReadME.MD
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
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/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { virtualize } from "./src/index.js";
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
|
|
4
|
+
// Compile and write the output to a file
|
|
5
|
+
const sourceCode = readFileSync("input.js", "utf-8");
|
|
6
|
+
const { code: output } = virtualize(sourceCode);
|
|
7
|
+
|
|
8
|
+
writeFileSync("output.js", output, "utf-8");
|
|
9
|
+
console.log(output);
|
|
10
|
+
|
|
11
|
+
// Eval the code like our test suite does
|
|
12
|
+
var window = { TEST_OUTPUT: null };
|
|
13
|
+
eval(output);
|
|
14
|
+
console.log(window.TEST_OUTPUT);
|
|
15
|
+
|
|
16
|
+
// Minify using Google Closure Compiler (optional)
|
|
17
|
+
import("./minify.js");
|
package/input.js
ADDED
package/jest.config.js
ADDED
package/minify.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "js-confuser-vm",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"index": "NODE_OPTIONS=\"--disable-warning=ExperimentalWarning\" node index.ts",
|
|
7
|
+
"test": "NODE_OPTIONS=\"--experimental-vm-modules --experimental-strip-types --disable-warning=ExperimentalWarning\" jest --coverage --coverageReporters=html",
|
|
8
|
+
"test262": "NODE_OPTIONS=\"--experimental-vm-modules --experimental-strip-types --disable-warning=ExperimentalWarning\" node test262-scripts/run-test262.ts",
|
|
9
|
+
"prepublishOnly": "npm run test"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"vm",
|
|
14
|
+
"virtual machine",
|
|
15
|
+
"obfuscator",
|
|
16
|
+
"obfuscation",
|
|
17
|
+
"uglify",
|
|
18
|
+
"code protection",
|
|
19
|
+
"javascript obfuscator",
|
|
20
|
+
"js obfuscator"
|
|
21
|
+
],
|
|
22
|
+
"author": "MichaelXF",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"description": "",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@babel/generator": "^7.29.1",
|
|
27
|
+
"@babel/parser": "^7.29.0",
|
|
28
|
+
"@babel/traverse": "^7.29.0",
|
|
29
|
+
"@babel/types": "^7.29.0",
|
|
30
|
+
"google-closure-compiler": "^20260216.0.0",
|
|
31
|
+
"js-confuser": "^2.0.0",
|
|
32
|
+
"json5": "^2.2.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.3.0",
|
|
36
|
+
"jest": "^30.2.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=24.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|