js-confuser-vm 0.0.6 → 0.0.8
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 +55 -0
- package/README.md +514 -0
- package/dist/build-runtime.js +15 -2
- package/dist/compiler.js +98 -51
- package/dist/runtime.js +5 -1
- package/dist/transforms/bytecode/aliasedOpcodes.js +2 -8
- package/dist/transforms/bytecode/macroOpcodes.js +21 -19
- package/dist/transforms/bytecode/microOpcodes.js +236 -0
- package/dist/transforms/bytecode/resolveContants.js +5 -11
- package/dist/transforms/bytecode/resolveLabels.js +5 -3
- package/dist/transforms/bytecode/specializedOpcodes.js +21 -16
- package/dist/transforms/runtime/internalVariables.js +202 -0
- package/dist/transforms/runtime/macroOpcodes.js +30 -18
- package/dist/transforms/runtime/microOpcodes.js +76 -0
- package/dist/transforms/runtime/specializedOpcodes.js +20 -18
- package/dist/utils/op-utils.js +15 -8
- package/index.ts +3 -2
- package/jest.config.js +2 -0
- package/package.json +1 -1
- package/src/build-runtime.ts +18 -3
- package/src/compiler.ts +152 -65
- package/src/options.ts +1 -0
- package/src/runtime.ts +5 -1
- package/src/transforms/bytecode/aliasedOpcodes.ts +2 -12
- package/src/transforms/bytecode/macroOpcodes.ts +28 -29
- package/src/transforms/bytecode/microOpcodes.ts +291 -0
- package/src/transforms/bytecode/resolveContants.ts +6 -13
- package/src/transforms/bytecode/resolveLabels.ts +5 -4
- package/src/transforms/bytecode/specializedOpcodes.ts +38 -28
- package/src/transforms/runtime/internalVariables.ts +270 -0
- package/src/transforms/runtime/macroOpcodes.ts +47 -20
- package/src/transforms/runtime/microOpcodes.ts +93 -0
- package/src/transforms/runtime/specializedOpcodes.ts +27 -32
- package/src/types.ts +1 -1
- package/src/utils/op-utils.ts +21 -8
- package/README.MD +0 -450
- package/src/utilts.ts +0 -3
package/README.MD
DELETED
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
# JS Confuser VM
|
|
2
|
-
|
|
3
|
-
[](https://npmjs.com/package/js-confuser-vm) [](https://github.com/MichaelXF/js-confuser-vm) [](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 the bytecode array?
|
|
41
|
-
concealConstants: true, // conceal strings and integers in the constant pool?
|
|
42
|
-
selfModifying: true, // do self-modifying bytecode for function bodies?
|
|
43
|
-
macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
|
|
44
|
-
specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
|
|
45
|
-
aliasedOpcodes: true, // create duplicate opcodes for commonly used opcodes?
|
|
46
|
-
timingChecks: true, // add timing checks to detect debuggers?
|
|
47
|
-
minify: true // pass final output through Google Closure Compiler? (Renames VM class properties)
|
|
48
|
-
}).then(result => {
|
|
49
|
-
console.log(result.code)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
/*
|
|
53
|
-
var c=Symbol();function ha(b,k){this.A=b;this.H=k;this.I=!1;this.J=void 0}function ia(b){return b.I?b.J:b.A.g[b.H]}function ja(b,k){b.I?b.J=k:b.A.g[b.H]=k}function y(b){this.v=b;this.D=[];this.prototype={}}function B(b,k,a){this.K=b;this.g=Array(b.v.L).fill(void 0);this.i=b.v.P;this.V=k!==void 0?k:void 0;this.G=a!==void 0?a:0;this.m=null;this.F=[]}function C(b,k,a,g,t){this.o=b;this.l=g;this.u=t;this.j=[];this.B=[];this.h=new B(new y({C:0,L:a,P:k}),void 0,0)}function D(b){return b.o[b.h.i++]}
|
|
54
|
-
function ka(b,k,a){for(var g=0;g<b.B.length;g++){var t=b.B[g];if(t.A===k&&t.H===a)return t}t=new ha(k,a);b.B.push(t);return t}function R(b,k){b.B=b.B.filter(function(a){return a.A===k?(a.J=a.A.g[a.H],a.I=!0,!1):!0})}
|
|
55
|
-
function S(b){for(var k=performance.now();;){var a=b.h;if(a.i>=b.o.length)break;var g=b.o[a.i++],t=performance.now(),la=t-k>1E3;k=t;if(la){for(var e=0;e<b.o.length;e++)b.o[e]=0;a.g.fill(void 0);g=50848;a.i=b.o.length}try{switch(g){case 6517:var d=D(b),f=a.g[D(b)];a.g[d]=f===a.g[D(b)];break;case 50054:d=D(b);var p=a.g[D(b)],x=D(b),m=Array(x);for(e=0;e<x;e++)m[e]=a.g[D(b)];if(p&&p[c]){var v=p[c],r=new B(v,b.u,d);for(e=0;e<m.length;e++)r.g[e]=m[e];r.g[v.v.C]=m;b.j.push(b.h);b.h=r}else a.g[d]=p.apply(null,
|
|
56
|
-
m);break;case 5504:var l=a.g[D(b)],n=a.g[D(b)],ma=a.g[D(b)],z=Object.getOwnPropertyDescriptor(l,n);a={set:ma,configurable:!0,enumerable:!0};z&&typeof z.get==="function"&&(a.get=z.get);Object.defineProperty(l,n,a);break;case 36488:d=6;a.g[d]=b.l[1];break;case 29667:d=D(b);f=a.g[D(b)];a.g[d]=f|a.g[D(b)];break;case 29500:d=D(b);a.g[d]=+a.g[D(b)];break;case 16142:d=D(b);var V=a.g[D(b)];p=a.g[D(b)];x=D(b);m=Array(x);for(e=0;e<x;e++)m[e]=a.g[D(b)];if(p&&p[c]){v=p[c];r=new B(v,V,d);for(e=0;e<m.length;e++)r.g[e]=
|
|
57
|
-
m[e];r.g[v.v.C]=m;b.j.push(b.h);b.h=r}else a.g[d]=p.apply(V,m);break;case 2036:d=D(b);l=a.g[D(b)];n=a.g[D(b)];a.g[d]=l[n];break;case 50848:a.i=D(b);break;case 46964:d=3;a.g[d]=b.l[3];break;case 17567:d=D(b);f=a.g[D(b)];a.g[d]=f/a.g[D(b)];break;case 46176:var h=a.g[3];R(b,a);if(b.j.length===0)return h;a.m===null||typeof h==="object"&&h!==null||(h=a.m);var u=b.j.pop();u.g[a.G]=h;b.h=u;break;case 40235:d=D(b);f=a.g[D(b)];a.g[d]=f!=a.g[D(b)];break;case 2835:d=5;a.g[d]=b.l[0];break;case 53433:var na=D(b),
|
|
58
|
-
W=D(b),oa=D(b);for(a=W;a<oa;a++)b.o[na+(a-W)]=b.o[a];break;case 42352:l=a.g[D(b)];n=a.g[D(b)];var E=a.g[D(b)];Reflect.set(l,n,E);break;case 23743:d=4;a.g[d]=a.g[5];break;case 50800:d=1;a.g[d]=a.g[3];break;case 36349:throw a.g[D(b)];case 53101:d=4;a.g[d]=b.l[1];break;case 545:d=D(b);f=a.g[D(b)];a.g[d]=f>a.g[D(b)];break;case 55196:var pa=D(b);b.u[b.l[pa]]=a.g[D(b)];break;case 22757:var w=4,q=76;a.g[w]||(a.i=q);break;case 58785:d=3;a.g[d]=b.l[1];break;case 25598:d=D(b);a.g[d]=a.V;break;default:throw Error("Unknown opcode: "+
|
|
59
|
-
g+" at pc "+(a.i-1));case 53665:d=5;l=a.g[3];n=a.g[4];a.g[d]=l[n];break;case 9914:d=3;a.g[d]=a.g[4];break;case 14527:d=D(b);f=a.g[D(b)];a.g[d]=f^a.g[D(b)];break;case 4831:d=D(b);a.g[d]=-a.g[D(b)];break;case 41053:d=D(b);a.g[d]=typeof a.g[D(b)];break;case 23985:d=D(b);D(b);a.g[d]=void 0;break;case 9348:d=3;a.g[d]=b.u[b.l[4]];break;case 1904:a.F.push({T:D(b),R:D(b),S:b.j.length});break;case 14261:w=9;q=107;a.g[w]||(a.i=q);break;case 44576:d=D(b);l=a.g[D(b)];n=a.g[D(b)];a.g[d]=delete l[n];break;case 6789:w=
|
|
60
|
-
D(b);q=D(b);a.g[w]||(a.i=q);break;case 17207:w=D(b);q=D(b);a.g[w]&&(a.i=q);break;case 21753:d=D(b);var X=b.l[D(b)];E=Object.prototype.hasOwnProperty.call(b.u,X)?b.u[X]:void 0;a.g[d]=typeof E;break;case 9073:d=3;a.g[d]=a.g[6];break;case 26633:d=D(b);a.g[d]=b.l[D(b)];break;case 57348:d=D(b);f=a.g[D(b)];a.g[d]=f>=a.g[D(b)];break;case 43802:d=D(b);var Y=D(b),Z=Array(Y);for(e=0;e<Y;e++)Z[e]=a.g[D(b)];a.g[d]=Z;break;case 38611:d=4;a.g[d]=b.l[5];break;case 43097:d=5;a.g[d]=b.l[2];break;case 16365:d=D(b);
|
|
61
|
-
a.g[d]=b.u[b.l[D(b)]];break;case 63592:d=D(b);p=a.g[D(b)];x=D(b);m=Array(x);for(e=0;e<x;e++)m[e]=a.g[D(b)];if(p&&p[c]){v=p[c];var aa=Object.create(v.prototype||null);r=new B(v,aa,d);r.m=aa;for(e=0;e<m.length;e++)r.g[e]=m[e];r.g[v.v.C]=m;b.j.push(b.h);b.h=r}else a.g[d]=Reflect.construct(p,m);break;case 56057:d=3;a.g[d]=b.l[2];break;case 20064:d=3;a.g[d]=a.g[2];break;case 46958:d=D(b);f=a.g[D(b)];a.g[d]=f==a.g[D(b)];break;case 5066:d=D(b);f=a.g[D(b)];a.g[d]=f-a.g[D(b)];break;case 16426:h=a.g[4];R(b,
|
|
62
|
-
a);if(b.j.length===0)return h;a.m===null||typeof h==="object"&&h!==null||(h=a.m);u=b.j.pop();u.g[a.G]=h;b.h=u;break;case 47875:a.i=45;break;case 51418:d=2;a.g[d]=a.g[5];break;case 25520:d=D(b);f=a.g[D(b)];a.g[d]=f*a.g[D(b)];break;case 41854:d=D(b);l=a.g[D(b)];g=[];if(l!==null&&l!==void 0)for(var ba=Object.create(null),F=Object(l);F!==null;){var ca=Object.getOwnPropertyNames(F);for(e=0;e<ca.length;e++){var H=ca[e];if(!(H in ba)){ba[H]=!0;var da=Object.getOwnPropertyDescriptor(F,H);da&&da.enumerable&&
|
|
63
|
-
g.push(H)}}F=Object.getPrototypeOf(F)}a.g[d]={N:g,O:0};break;case 45737:d=D(b);var qa=D(b);g={};for(e=0;e<qa;e++)n=a.g[D(b)],E=a.g[D(b)],g[n]=E;a.g[d]=g;break;case 3520:d=D(b);f=a.g[D(b)];a.g[d]=f<<a.g[D(b)];break;case 34870:d=D(b);a.g[d]=!a.g[D(b)];break;case 11596:d=D(b);a.g[d]=~a.g[D(b)];break;case 16818:w=9;q=25;a.g[w]||(a.i=q);break;case 38378:d=D(b);a.g[d]=ia(a.K.D[D(b)]);break;case 50935:d=D(b);f=a.g[D(b)];a.g[d]=f>>>a.g[D(b)];break;case 7720:d=0;a.g[d]=a.g[7];break;case 61003:d=8;a.g[d]=b.l[1];
|
|
64
|
-
break;case 40007:d=D(b);f=a.g[D(b)];a.g[d]=f&a.g[D(b)];break;case 48619:debugger;break;case 21396:d=D(b);a.g[d]=a.g[D(b)];break;case 34467:d=D(b);a.g[d]=D(b);break;case 26361:h=a.g[D(b)];R(b,a);if(b.j.length===0)return h;a.m===null||typeof h==="object"&&h!==null||(h=a.m);u=b.j.pop();u.g[a.G]=h;b.h=u;break;case 7585:l=a.g[D(b)];n=a.g[D(b)];var ra=a.g[D(b)];z=Object.getOwnPropertyDescriptor(l,n);a={get:ra,configurable:!0,enumerable:!0};z&&typeof z.set==="function"&&(a.set=z.set);Object.defineProperty(l,
|
|
65
|
-
n,a);break;case 23121:var sa=D(b);ja(a.K.D[sa],a.g[D(b)]);break;case 51231:d=D(b);f=a.g[D(b)];a.g[d]=f%a.g[D(b)];break;case 22797:d=D(b);var I=a.g[D(b)],ta=D(b);I.O>=I.N.length?a.i=ta:a.g[d]=I.N[I.O++];break;case 37593:a.i=9;break;case 504:d=D(b);f=a.g[D(b)];a.g[d]=f>>a.g[D(b)];break;case 5174:h=a.g[5];R(b,a);if(b.j.length===0)return h;a.m===null||typeof h==="object"&&h!==null||(h=a.m);u=b.j.pop();u.g[a.G]=h;b.h=u;break;case 9939:d=5;a.g[d]=a.g[0];break;case 41972:d=5;f=a.g[2];a.g[d]=f+a.g[4];break;
|
|
66
|
-
case 49424:d=7;f=a.g[0];a.g[d]=f-a.g[6];break;case 26807:d=D(b);f=a.g[D(b)];a.g[d]=f<a.g[D(b)];break;case 21843:d=4;a.g[d]=a.g[0];break;case 19103:d=D(b);f=a.g[D(b)];a.g[d]=f in a.g[D(b)];break;case 40335:d=D(b);l=a.g[D(b)];var M=a.g[D(b)];if(typeof M==="function")a.g[d]=l instanceof M;else{var ua=M.prototype;q=Object.getPrototypeOf(l);for(g=!1;q!==null;){if(q===ua){g=!0;break}q=Object.getPrototypeOf(q)}a.g[d]=g}break;case 47743:d=D(b);f=a.g[D(b)];a.g[d]=f+a.g[D(b)];break;case 27740:d=4;f=a.g[2];
|
|
67
|
-
a.g[d]=f<=a.g[3];break;case 51318:d=D(b);f=a.g[D(b)];a.g[d]=f!==a.g[D(b)];break;case 64752:d=2;a.g[d]=a.g[3];break;case 1927:a.i=91;break;case 22484:d=5;f=a.g[2];a.g[d]=f+a.g[3];break;case 64028:d=D(b);var va=D(b),wa=D(b),xa=D(b),ea=D(b),J=Array(ea);for(e=0;e<ea;e++){var ya=D(b),za=D(b);J[e]={U:ya,M:za}}var G=new y({C:wa,L:xa,P:va,W:J});for(e=0;e<J.length;e++){var N=J[e];N.U?G.D.push(ka(b,a,N.M)):G.D.push(a.K.D[N.M])}var K=b,Q=function(A){return function(){for(var O=Array.prototype.slice.call(arguments),
|
|
68
|
-
fa=new C(K.o,0,A.v.L,K.l,K.u),P=new B(A,this==null?K.u:this,0),L=0;L<O.length;L++)P.g[L]=O[L];P.g[A.v.C]=O;fa.h=P;return S(fa)}}(G);Q[c]=G;Q.prototype=G.prototype;a.g[d]=Q;break;case 30313:d=9;f=a.g[5];a.g[d]=f>a.g[8];break;case 50428:a.F.pop();break;case 35999:d=D(b),f=a.g[D(b)],a.g[d]=f<=a.g[D(b)]}}catch(A){a=null;for(g=b.h;;){if(g.F.length>0){a=g;break}R(b,g);if(b.j.length===0)break;g=b.j.pop();b.h=g}if(!a)throw A;g=a.F.pop();b.j.length=g.S;a.g[g.R]=A;a.i=g.T;b.h=a}}}var T={},U;
|
|
69
|
-
for(U of Object.getOwnPropertyNames(globalThis))T[U]=globalThis[U];typeof window!=="undefined"&&(T.window=window);T.undefined=void 0;T.Infinity=Infinity;T.NaN=NaN;
|
|
70
|
-
S(new C(function(b){b=typeof Buffer!=="undefined"?Buffer.from(b,"base64"):Uint8Array.from(atob(b),function(g){return g.charCodeAt(0)});for(var k=new Uint16Array(b.length/2),a=0;a<k.length;a++)k[a]=b[a*2]|b[a*2+1]<<8;return k}("udAEAHIAdwAOP263K53rveu9udANAHcAgwCUUyud6pWUUx/IHPo3Q/2Nj50hArdobre50B0AgwCGAMoT+WYc+rnQJACGAI8AIQJo+Dxzo4afRO0/98YhAqDGudAxAI8AoQCwY7FdoR0NWX+6PHNRWuu9f7qUU7dot2ifRJ+MBOCAFYAVt2i50EcAoQCmAJ+M/Y1ut3ClDVm50FAApgCoAF2gHPq50FYAqACtAONzoMbfEvQHqbK50F8ArQC5AONzNoj4AXAH43P3xpRT/MSc14bDdsjADbnQbwC5ALwAPHM3Q+0/EwvayIiOcSNTVdMmiI4QwSgeS+5pdrJB1Fe/XPD8uibZkipAWag2FBz6AwBSAAEACgAAAHDGoeXw/HS3XGzlWIQk05ah0YbDBgABAAEAAgAOPwcAAwAFAAIAAgAGAGBObc/0o9rIA7v52mC0EwvayIiOcSNTVdMmiI4QwSgeS+5pdrU31Fe/XPD8uiaHBypAWag2FA=="),32,
|
|
71
|
-
0,[0,1,void 0,25,"console","log"],T));
|
|
72
|
-
*/
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Features
|
|
76
|
-
|
|
77
|
-
- [x] functions: call, arguments, return
|
|
78
|
-
- [x] closures and nested functions
|
|
79
|
-
- [x] literals
|
|
80
|
-
- [x] binary expressions
|
|
81
|
-
- [x] unary expressions
|
|
82
|
-
- [x] update expressions
|
|
83
|
-
- [x] if statements
|
|
84
|
-
- [x] while, do-while, for loops
|
|
85
|
-
- [x] get property
|
|
86
|
-
- [x] logical expressions
|
|
87
|
-
- [x] array, object expression
|
|
88
|
-
- [x] function expression
|
|
89
|
-
- [x] default arguments in functions
|
|
90
|
-
- [x] sequence expression
|
|
91
|
-
- [x] conditional expression (ternary operator)
|
|
92
|
-
- [x] delete operator
|
|
93
|
-
- [x] in / instanceof
|
|
94
|
-
- [x] this, new expression
|
|
95
|
-
- [x] arguments
|
|
96
|
-
- [x] Infinity, NaN
|
|
97
|
-
- [x] break/continue
|
|
98
|
-
- [x] switch statement
|
|
99
|
-
- [x] throw statement
|
|
100
|
-
- [x] labeled statements
|
|
101
|
-
- [x] for..in loop
|
|
102
|
-
- [x] RegExp literals
|
|
103
|
-
- [x] try..catch
|
|
104
|
-
- [x] getter/setters
|
|
105
|
-
- [x] debugger;
|
|
106
|
-
|
|
107
|
-
### Missing
|
|
108
|
-
|
|
109
|
-
- [ ] try..finally
|
|
110
|
-
- [ ] with statement
|
|
111
|
-
- [ ] arguments.callee, argument parameter syncing
|
|
112
|
-
|
|
113
|
-
### Hardening
|
|
114
|
-
|
|
115
|
-
- [x] opcode randomization per build
|
|
116
|
-
- [x] property name concealment of vm internals
|
|
117
|
-
- - Google Closure Compiler aggressively renames our class props
|
|
118
|
-
- [x] shuffled handler order
|
|
119
|
-
- [ ] dead handlers
|
|
120
|
-
- [ ] dead bytecode insertion
|
|
121
|
-
- [x] macro opcodes (Combine multiple opcodes into a "macro opcode")
|
|
122
|
-
- [x] specialized opcodes (Create specific opcodes for opcode+operand pairs)
|
|
123
|
-
- [x] aliased opcodes (create duplicate opcodes, including variants with shuffled operand order)
|
|
124
|
-
- [x] encoded bytecode array
|
|
125
|
-
- [x] self-modifying bytecode
|
|
126
|
-
- [x] timing checks
|
|
127
|
-
- [ ] low-level bytecode obfuscations
|
|
128
|
-
- [ ] stack protection
|
|
129
|
-
- [ ] control flow integrity
|
|
130
|
-
|
|
131
|
-
### Options
|
|
132
|
-
|
|
133
|
-
#### `target` ("node"/"browser")
|
|
134
|
-
|
|
135
|
-
Currently has no effect.
|
|
136
|
-
|
|
137
|
-
#### `randomizeOpcodes` (true/false)
|
|
138
|
-
|
|
139
|
-
Randomizes the opcode numbers.
|
|
140
|
-
|
|
141
|
-
#### `shuffleOpcodes` (true/false)
|
|
142
|
-
|
|
143
|
-
Shuffles the order of opcode handlers in the VM runtime.
|
|
144
|
-
|
|
145
|
-
#### `encodeBytecode` (true/false)
|
|
146
|
-
|
|
147
|
-
Encodes the bytecode array.
|
|
148
|
-
|
|
149
|
-
```js
|
|
150
|
-
// Before
|
|
151
|
-
var BYTECODE = [2, 1, 0, 0, 2, 1, 8, 3, 1, 2, 0, 4, 2, 43, 5, 1, 3, 1, 4, 0, 1, 3, 45, 1];
|
|
152
|
-
|
|
153
|
-
// After
|
|
154
|
-
var BYTECODE = "AgABAAAAAAACAAEACAADAAEAAgAAAAQAAgArAAUAAQADAAEABAAAAAEAAwAtAAEA";
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### `concealConstants` (true/false)
|
|
158
|
-
|
|
159
|
-
Conceals strings and integers in the constant pool.
|
|
160
|
-
|
|
161
|
-
```js
|
|
162
|
-
// Before
|
|
163
|
-
var CONSTANTS = [/* 0 */"console", /* 1 */"log", /* 2 */"Hello world!", /* 3 */undefined];
|
|
164
|
-
|
|
165
|
-
// After
|
|
166
|
-
var CONSTANTS = [/* 0 */"DaQApB6kAqQdpB+kEaQ=", /* 1 */"TCFOIUUh", /* 2 */"kKK8orait6Kzov2iqaKwopKijaKGosKi", /* 3 */undefined];
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
#### `macroOpcodes` (true/false)
|
|
170
|
-
|
|
171
|
-
Combines multiple opcodes commonly used from your bytecode.
|
|
172
|
-
|
|
173
|
-
```js
|
|
174
|
-
// Input Code
|
|
175
|
-
console.log("Hello world!");
|
|
176
|
-
console.log("Hello world!");
|
|
177
|
-
|
|
178
|
-
// Before
|
|
179
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 2:0-2:7
|
|
180
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 2:0-2:27
|
|
181
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 2:0-2:27
|
|
182
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello world!" 2:12-2:26
|
|
183
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)2:0-2:27
|
|
184
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 3:0-3:7
|
|
185
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 3:0-3:27
|
|
186
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 3:0-3:27
|
|
187
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello world!" 3:12-3:26
|
|
188
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)3:0-3:27
|
|
189
|
-
|
|
190
|
-
// After
|
|
191
|
-
// [5074, 1, 0, 2, 1, 3, 1, 2, 4, 2, 5, 1, 3, 1, 4], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST,CALL_METHOD [1, 0, 2, 1, 3, 1, 2, 4, 2, 5, 1, 3, 1, 4]2:0-2:7
|
|
192
|
-
// [5074, 1, 0, 2, 1, 3, 1, 2, 4, 2, 5, 1, 3, 1, 4], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST,CALL_METHOD [1, 0, 2, 1, 3, 1, 2, 4, 2, 5, 1, 3, 1, 4]3:0-3:7
|
|
193
|
-
|
|
194
|
-
// What the opcode "LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST,CALL_METHOD" (5074) looks like:
|
|
195
|
-
case 5074:
|
|
196
|
-
// LOAD_GLOBAL
|
|
197
|
-
var dst = this._operand();
|
|
198
|
-
frame.regs[dst] = this.globals[this.constants[this._operand()]];
|
|
199
|
-
// LOAD_CONST
|
|
200
|
-
var dst = this._operand();
|
|
201
|
-
frame.regs[dst] = this.constants[this._operand()];
|
|
202
|
-
// GET_PROP
|
|
203
|
-
// dst = regs[obj][regs[key]]
|
|
204
|
-
var dst = this._operand();
|
|
205
|
-
var obj = frame.regs[this._operand()];
|
|
206
|
-
var key = frame.regs[this._operand()];
|
|
207
|
-
frame.regs[dst] = obj[key];
|
|
208
|
-
// LOAD_CONST
|
|
209
|
-
var dst = this._operand();
|
|
210
|
-
frame.regs[dst] = this.constants[this._operand()];
|
|
211
|
-
// CALL_METHOD
|
|
212
|
-
// dst, receiverReg, calleeReg, argc, [argReg...]
|
|
213
|
-
var dst = this._operand();
|
|
214
|
-
var receiver = frame.regs[this._operand()];
|
|
215
|
-
var callee = frame.regs[this._operand()];
|
|
216
|
-
var argc = this._operand();
|
|
217
|
-
var args = new Array(argc);
|
|
218
|
-
for (var i = 0; i < argc; i++) args[i] = frame.regs[this._operand()];
|
|
219
|
-
if (callee && callee[CLOSURE_SYM]) {
|
|
220
|
-
var c = callee[CLOSURE_SYM];
|
|
221
|
-
var f = new Frame(c, frame._pc, frame, receiver, dst);
|
|
222
|
-
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
223
|
-
f.regs[c.fn.paramCount] = args;
|
|
224
|
-
this._frameStack.push(this._currentFrame);
|
|
225
|
-
this._currentFrame = f;
|
|
226
|
-
} else {
|
|
227
|
-
frame.regs[dst] = callee.apply(receiver, args);
|
|
228
|
-
}
|
|
229
|
-
break;
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
#### `specializedOpcodes` (true/false)
|
|
233
|
-
|
|
234
|
-
Creates specialized opcodes for commonly used opcode+operand pairs.
|
|
235
|
-
|
|
236
|
-
```js
|
|
237
|
-
// Input Code
|
|
238
|
-
console.log("Hello world!");
|
|
239
|
-
|
|
240
|
-
// Before
|
|
241
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
|
|
242
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 1:0-1:27
|
|
243
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 1:0-1:27
|
|
244
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello world!" 1:12-1:26
|
|
245
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:27
|
|
246
|
-
|
|
247
|
-
// What the opcode "LOAD_GLOBAL" looks like:
|
|
248
|
-
case OP.LOAD_CONST:
|
|
249
|
-
var dst = this._operand();
|
|
250
|
-
frame.regs[dst] = this.constants[this._operand()];
|
|
251
|
-
break;
|
|
252
|
-
|
|
253
|
-
// After
|
|
254
|
-
// [16316], LOAD_GLOBAL_1_0 1:0-1:7
|
|
255
|
-
// [43765], LOAD_CONST_2_1 1:0-1:27
|
|
256
|
-
// [58568], GET_PROP_3_1_2 1:0-1:27
|
|
257
|
-
// [35110], LOAD_CONST_4_2 1:12-1:26
|
|
258
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:27
|
|
259
|
-
|
|
260
|
-
// What the opcode "LOAD_GLOBAL_1_0" (16316) looks like:
|
|
261
|
-
case 16316:
|
|
262
|
-
// LOAD_GLOBAL_1_0 (specialized)
|
|
263
|
-
var dst = 1;
|
|
264
|
-
frame.regs[dst] = this.globals[this.constants[0]];
|
|
265
|
-
break;
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
#### `aliasedOpcodes` (true/false)
|
|
269
|
-
|
|
270
|
-
Creates duplicate opcodes, including variants with shuffled operand order.
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
// Input Code
|
|
274
|
-
console.log("Hello, world!");
|
|
275
|
-
|
|
276
|
-
// Before
|
|
277
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
|
|
278
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 1:0-1:28
|
|
279
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 1:0-1:28
|
|
280
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello, world!" 1:12-1:27
|
|
281
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
282
|
-
// [0, 1, 3], LOAD_CONST reg[1] = undefined
|
|
283
|
-
// [45, 1], RETURN reg[1]
|
|
284
|
-
|
|
285
|
-
// What the opcode "LOAD_GLOBAL" looks like:
|
|
286
|
-
case OP.LOAD_GLOBAL:
|
|
287
|
-
var dst = this._operand();
|
|
288
|
-
frame.regs[dst] = this.globals[this.constants[this._operand()]];
|
|
289
|
-
break;
|
|
290
|
-
|
|
291
|
-
// After
|
|
292
|
-
// [52040, 0, 1], ALIAS_LOAD_GLOBAL_1_0 [0, 1] 1:0-1:7
|
|
293
|
-
// [24862, 1, 2], ALIAS_LOAD_CONST_1_0 [1, 2] 1:0-1:28
|
|
294
|
-
// [25202, 1, 2, 3], ALIAS_GET_PROP_1_2_0 [1, 2, 3] 1:0-1:28
|
|
295
|
-
// [24862, 2, 4], ALIAS_LOAD_CONST_1_0 [2, 4] 1:12-1:27
|
|
296
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
297
|
-
// [24862, 3, 1], ALIAS_LOAD_CONST_1_0 [3, 1]
|
|
298
|
-
// [51807, 1], ALIAS_RETURN_0 1
|
|
299
|
-
|
|
300
|
-
// What the opcode "ALIAS_LOAD_GLOBAL_1_0" (52040) looks like:
|
|
301
|
-
case 52040:
|
|
302
|
-
// ALIAS_LOAD_GLOBAL_1_0 (order: [1,0])
|
|
303
|
-
let _unsortedOperands = [this._operand(), this._operand()];
|
|
304
|
-
let _operands = [_unsortedOperands[1], _unsortedOperands[0]];
|
|
305
|
-
var dst = _operands[0];
|
|
306
|
-
frame.regs[dst] = this.globals[this.constants[_operands[1]]];
|
|
307
|
-
break;
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
#### `selfModifying` (true/false)
|
|
311
|
-
|
|
312
|
-
Function bodies are replaced upon runtime entry to the real bytecode.
|
|
313
|
-
|
|
314
|
-
```diff
|
|
315
|
-
// Input Code
|
|
316
|
-
console.log("Hello, world!");
|
|
317
|
-
|
|
318
|
-
// Before
|
|
319
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
|
|
320
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 1:0-1:28
|
|
321
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 1:0-1:28
|
|
322
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello, world!" 1:12-1:27
|
|
323
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
324
|
-
// [0, 1, 3], LOAD_CONST reg[1] = undefined
|
|
325
|
-
// [45, 1], RETURN reg[1]
|
|
326
|
-
|
|
327
|
-
// After
|
|
328
|
-
// [56, 4, 28, 50], PATCH [4, 28, 50]
|
|
329
|
-
-// [52], FOR_IN_SETUP <-- 22 ints of garbage code
|
|
330
|
-
-// [39], JUMP
|
|
331
|
-
-// [12], SUB
|
|
332
|
-
-// [12], SUB
|
|
333
|
-
-// [2], LOAD_GLOBAL
|
|
334
|
-
-// [28], LOOSE_EQ
|
|
335
|
-
-// [12], SUB
|
|
336
|
-
-// [46], THROW
|
|
337
|
-
-// [18], BXOR
|
|
338
|
-
-// [8], GET_PROP
|
|
339
|
-
-// [5], MOVE
|
|
340
|
-
-// [55], TRY_END
|
|
341
|
-
-// [57], DEBUGGER
|
|
342
|
-
-// [46], THROW
|
|
343
|
-
-// [23], GT
|
|
344
|
-
-// [48], BUILD_ARRAY
|
|
345
|
-
-// [28], LOOSE_EQ
|
|
346
|
-
-// [18], BXOR
|
|
347
|
-
-// [2], LOAD_GLOBAL
|
|
348
|
-
-// [50], DEFINE_GETTER
|
|
349
|
-
-// [4], LOAD_THIS
|
|
350
|
-
-// [24], LTE
|
|
351
|
-
// [45, 1], RETURN reg[1]
|
|
352
|
-
+// [2, 1, 0], LOAD_GLOBAL reg[1] = console <-- 22 ints of real code
|
|
353
|
-
+// [0, 2, 1], LOAD_CONST reg[2] = "log" 1:0-1:28
|
|
354
|
-
+// [8, 3, 1, 2], GET_PROP [3, 1, 2] 1:0-1:28
|
|
355
|
-
+// [0, 4, 2], LOAD_CONST reg[4] = "Hello, world!" 1:12-1:27
|
|
356
|
-
+// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
357
|
-
+// [0, 1, 3], LOAD_CONST reg[1] = undefined
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
#### `timingChecks` (true/false)
|
|
361
|
-
|
|
362
|
-
Detects the use of debuggers by checking for >1second pauses. May break code with slow sync tasks.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
#### `minify` (true/false)
|
|
366
|
-
|
|
367
|
-
Minifies the final code with Google Closure Compiler. Renames the VM class properties.
|
|
368
|
-
|
|
369
|
-
### No Try Finally
|
|
370
|
-
|
|
371
|
-
While Try Catch is supported, Try..Finally is not. You can use Try..Finally by defining an outside helper function:
|
|
372
|
-
|
|
373
|
-
```js
|
|
374
|
-
function TryFinally(cb, _finally) {
|
|
375
|
-
try {
|
|
376
|
-
return { value: cb() }
|
|
377
|
-
} catch (error) {
|
|
378
|
-
return { error };
|
|
379
|
-
} finally {
|
|
380
|
-
_finally()
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### ES5 Only
|
|
386
|
-
|
|
387
|
-
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.
|
|
388
|
-
|
|
389
|
-
Please transpile your code down first using [Babel](https://github.com/babel/babel).
|
|
390
|
-
|
|
391
|
-
### Project
|
|
392
|
-
|
|
393
|
-
- Register base VM
|
|
394
|
-
- Lua-style closure and upvalue model
|
|
395
|
-
- CPython-style opcodes and codegen
|
|
396
|
-
- Compiler is in src/compiler.ts
|
|
397
|
-
- Runtime is in src/runtime.ts
|
|
398
|
-
- "Typescript"
|
|
399
|
-
- - This "Typescript" project uses Node's new flag `--experimental-strip-types`. This means we can run `node index.ts` directly!
|
|
400
|
-
|
|
401
|
-
### Best practices
|
|
402
|
-
|
|
403
|
-
- **Don't rely on "function.length"**
|
|
404
|
-
- Avoid undeclared variables
|
|
405
|
-
- Don't rely on "function.name"
|
|
406
|
-
- Don't use eval() to reference or modify local variables
|
|
407
|
-
|
|
408
|
-
### Use with JS-Confuser
|
|
409
|
-
|
|
410
|
-
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.
|
|
411
|
-
|
|
412
|
-
```js
|
|
413
|
-
import JsConfuser from "js-confuser";
|
|
414
|
-
import JsConfuserVM from "js-confuser-vm";
|
|
415
|
-
import { readFile, writeFile } from "fs/promises";
|
|
416
|
-
|
|
417
|
-
async function main() {
|
|
418
|
-
// Read input code
|
|
419
|
-
const sourceCode = await readFile("input.js", "utf8");
|
|
420
|
-
|
|
421
|
-
const { code: virtualized } = await JsConfuserVM.obfuscate(sourceCode, {
|
|
422
|
-
target: "browser",
|
|
423
|
-
randomizeOpcodes: true
|
|
424
|
-
});
|
|
425
|
-
const { code: obfuscated } = await JsConfuser.obfuscate(virtualized, {
|
|
426
|
-
target: "browser",
|
|
427
|
-
preset: "medium",
|
|
428
|
-
pack: false,
|
|
429
|
-
globalConcealing: false,
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Write output file
|
|
433
|
-
await writeFile("output.js", obfuscated, "utf8");
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
main().catch(console.error);
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### WIP
|
|
440
|
-
|
|
441
|
-
- 178 tests, 91.18% coverage
|
|
442
|
-
- [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 66.67%
|
|
443
|
-
|
|
444
|
-
### Made with AI
|
|
445
|
-
|
|
446
|
-
This project has been created with the help of AI. Expect issues.
|
|
447
|
-
|
|
448
|
-
### License
|
|
449
|
-
|
|
450
|
-
MIT License
|
package/src/utilts.ts
DELETED