js-confuser-vm 0.0.9 → 0.1.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.
Files changed (64) hide show
  1. package/.gitmodules +4 -0
  2. package/CHANGELOG.md +125 -2
  3. package/README.md +128 -53
  4. package/bench.ts +146 -0
  5. package/disassemble.ts +12 -0
  6. package/dist/build-runtime.js +41 -15
  7. package/dist/compiler.js +328 -181
  8. package/dist/disassembler.js +317 -0
  9. package/dist/index.js +7 -2
  10. package/dist/runtime.js +255 -176
  11. package/dist/template.js +258 -0
  12. package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
  13. package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
  14. package/dist/transforms/bytecode/dispatcher.js +266 -0
  15. package/dist/transforms/bytecode/macroOpcodes.js +3 -3
  16. package/dist/transforms/bytecode/resolveConstants.js +100 -0
  17. package/dist/transforms/bytecode/resolveLabels.js +21 -18
  18. package/dist/transforms/bytecode/resolveRegisters.js +216 -0
  19. package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
  20. package/dist/transforms/bytecode/specializedOpcodes.js +22 -12
  21. package/dist/transforms/bytecode/stringConcealing.js +110 -0
  22. package/dist/transforms/runtime/classObfuscation.js +43 -0
  23. package/dist/transforms/runtime/handlerTable.js +91 -0
  24. package/dist/transforms/runtime/semanticOpcodes.js +35 -0
  25. package/dist/transforms/runtime/specializedOpcodes.js +11 -5
  26. package/dist/types.js +42 -1
  27. package/dist/utils/ast-utils.js +14 -0
  28. package/dist/utils/op-utils.js +1 -2
  29. package/dist/utils/pass-utils.js +100 -0
  30. package/dist/utils/profile-utils.js +3 -0
  31. package/index.ts +22 -16
  32. package/jest.config.js +19 -2
  33. package/output.disassembled.js +41 -0
  34. package/package.json +2 -1
  35. package/src/build-runtime.ts +113 -78
  36. package/src/compiler.ts +2703 -2482
  37. package/src/disassembler.ts +329 -0
  38. package/src/index.ts +12 -2
  39. package/src/options.ts +8 -1
  40. package/src/runtime.ts +294 -180
  41. package/src/template.ts +265 -0
  42. package/src/transforms/bytecode/aliasedOpcodes.ts +5 -2
  43. package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
  44. package/src/transforms/bytecode/dispatcher.ts +292 -0
  45. package/src/transforms/bytecode/macroOpcodes.ts +4 -4
  46. package/src/transforms/bytecode/resolveLabels.ts +31 -27
  47. package/src/transforms/bytecode/resolveRegisters.ts +226 -0
  48. package/src/transforms/bytecode/specializedOpcodes.ts +27 -20
  49. package/src/transforms/bytecode/stringConcealing.ts +130 -0
  50. package/src/transforms/runtime/classObfuscation.ts +59 -0
  51. package/src/transforms/runtime/specializedOpcodes.ts +14 -9
  52. package/src/types.ts +106 -5
  53. package/src/utils/ast-utils.ts +19 -0
  54. package/src/utils/op-utils.ts +2 -2
  55. package/src/utils/pass-utils.ts +126 -0
  56. package/src/utils/profile-utils.ts +3 -0
  57. package/tsconfig.json +1 -1
  58. package/dist/transforms/utils/op-utils.js +0 -25
  59. package/dist/transforms/utils/random-utils.js +0 -27
  60. package/dist/utilts.js +0 -3
  61. package/src/transforms/bytecode/microOpcodes.ts +0 -291
  62. package/src/transforms/runtime/internalVariables.ts +0 -270
  63. package/src/transforms/runtime/microOpcodes.ts +0 -93
  64. /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
package/.gitmodules ADDED
@@ -0,0 +1,4 @@
1
+ [submodule "test262"]
2
+ path = test262
3
+ url = https://github.com/tc39/test262
4
+ branch = es5-tests
package/CHANGELOG.md CHANGED
@@ -1,4 +1,127 @@
1
- ## `0.0.7` Micro Opcodes
1
+ # Planned:
2
+
3
+ - - Planned: Shuffle the order of constructor and method parameters
4
+ - - Planned: Alias variables, and other function related obfuscations
5
+
6
+ ## `0.1.1` Control Flow Flattening, String Concealing, and more
7
+
8
+ - Added new option `controlFlowFlattening` which flattens the control flow of your program into a convoluted state machine
9
+ - Added new option `stringConcealing` which involves encoding strings to conceal plain-text values.
10
+
11
+ - Added new API method `JSConfuserVM.disassemble(sourceCode)` which returns a partial JS representation
12
+ - - This only works if the parameter `sourceCode` contains the original bytecode comment
13
+ - - This shouldn't be used with any options enabled, as the disassembler only supports default patterns
14
+
15
+ - Added new option `classObfuscation` which obfuscates the VM runtime classes:
16
+ - - Shuffles the order of declarations and methods
17
+
18
+ - Added new option `verbose` which `console.log`'s useful info for debugging purposes.
19
+
20
+ - Removed option `microOpcodes` - this option introduced scratch registers which left runtime values exposed, and were easily traceable by deobfuscators. The cons outweighed the pros and since it conflicts with the other obfuscations already present, it was removed.
21
+
22
+ - Added support for rest parameters (ES6 feature)
23
+
24
+ ## `0.1.0` Dispatcher, Virtual Registers, and more
25
+
26
+ - Added new option `dispatcher` which creates a middleman block to process jumps.
27
+
28
+ ```js
29
+ // Input Code
30
+ if (true) {
31
+ console.log("Hello world!");
32
+ }
33
+
34
+ // Before
35
+ // fn_0_0:
36
+ // [0, 0, 0, 0], LOAD_CONST reg[0] = true 1:4-1:8
37
+ // [40, 0, 29], JUMP_IF_FALSE [0, if_else_1] 1:0-3:1
38
+ // [2, 0, 1, 0], LOAD_GLOBAL reg[0] = console 2:2-2:9
39
+ // [0, 1, 2, 0], LOAD_CONST reg[1] = "log" 2:2-2:29
40
+ // [8, 2, 0, 1], GET_PROP reg[2] = reg[0][reg[1]] 2:2-2:29
41
+ // [0, 1, 3, 0], LOAD_CONST reg[1] = "Hello world!" 2:14-2:28
42
+ // [43, 3, 0, 2, 1, 1], CALL_METHOD reg[3] = reg[2](recv=reg[0], 1 args) 2:2-2:29
43
+ // if_else_1:
44
+ // [0, 0, 4, 0], LOAD_CONST reg[0] = undefined
45
+ // [45, 0], RETURN reg[0]
46
+
47
+ // What this looks like decompiled:
48
+ // fn_0_0:
49
+ r0 = true
50
+ if (!r0) goto if_else_1
51
+ r0 = console
52
+ r1 = "log"
53
+ r2 = r0[r1] // console.log
54
+ r1 = "Hello world!"
55
+ r3 = r2.call(r0, r1) // console.log("Hello world!")
56
+ // if_else_1:
57
+ r0 = undefined
58
+ return r0
59
+
60
+ // After
61
+ // fn_0_0:
62
+ // [47, 2, 57, 2, 5, 0], MAKE_CLOSURE reg[2] PC=fn_2_3 (params=2 regs=5 upvalues=0)
63
+ // [0, 3, 0, 0], LOAD_CONST reg[3] = true 1:4-1:8
64
+ // [41, 3, 21], JUMP_IF_TRUE [3, if_else_1_skip_5]
65
+ // [1, 0, 43020], LOAD_INT reg[0] = if_else_1
66
+ // [1, 1, 40151], LOAD_INT reg[1] = 40151
67
+ // [39, 49], JUMP dispatcher_4
68
+ // if_else_1_skip_5:
69
+ // [2, 3, 1, 0], LOAD_GLOBAL reg[3] = console 2:2-2:9
70
+ // [0, 4, 2, 0], LOAD_CONST reg[4] = "log" 2:2-2:29
71
+ // [8, 5, 3, 4], GET_PROP reg[5] = reg[3][reg[4]] 2:2-2:29
72
+ // [0, 4, 3, 0], LOAD_CONST reg[4] = "Hello world!" 2:14-2:28
73
+ // [43, 6, 3, 5, 1, 4], CALL_METHOD reg[6] = reg[5](recv=reg[3], 1 args) 2:2-2:29
74
+ // if_else_1:
75
+ // [0, 3, 4, 0], LOAD_CONST reg[3] = undefined
76
+ // [45, 3], RETURN reg[3]
77
+ // dispatcher_4:
78
+ // [42, 0, 2, 2, 0, 1], CALL reg[0] = reg[2](reg[0], reg[1])
79
+ // [58, 0], JUMP_REG PC = reg[0]
80
+ // fn_2_3:
81
+ // [18, 2, 0, 1], BXOR [2, 0, 1]
82
+ // [0, 3, 5, 0], LOAD_CONST reg[3] = 52048
83
+ // [11, 4, 2, 3], ADD [4, 2, 3]
84
+ // [0, 2, 6, 0], LOAD_CONST reg[2] = 65535
85
+ // [16, 3, 4, 2], BAND [3, 4, 2]
86
+ // [45, 3], RETURN reg[3]
87
+
88
+ // What this looks like decompiled:
89
+ // fn_0_0:
90
+ r2 = MakeClosure(fn_2_3, params=2)
91
+ r3 = true
92
+ if (r3) goto if_else_1_skip
93
+ r0 = 43020
94
+ r1 = 40151
95
+ goto dispatcher
96
+ // if_else_1_skip:
97
+ r3 = console
98
+ r4 = "log"
99
+ r5 = r3[r4] // console.log
100
+ r4 = "Hello world!"
101
+ r6 = r5.call(r3, r4) // console.log("Hello world!")
102
+ // if_else_1:
103
+ r3 = undefined
104
+ return r3
105
+
106
+ // dispatcher:
107
+ r0 = r2(r0, r1) // decode(encodedPC, siteKey)
108
+ goto *r0 // indirect jump
109
+
110
+ // fn_2_3:
111
+ function decode(x, k) {
112
+ return ((x ^ k) + 52048) & 65535;
113
+ }
114
+ ```
115
+
116
+ - Improved registers:
117
+ - - Now flattened into a single array with base/stack pointer mechanics
118
+ - - Improved compiler to support virtual registers, allowing passes to introduce scope-specific registers, with a final pass responsible for allocation and freeing
119
+
120
+ - Added a template system to insert synthetic JavaScript as native bytecode, similar to [JS-Confuser's template system](https://github.com/MichaelXF/js-confuser/blob/b6ef304f77b7fc27cb9cc8d7d841e50a4200d7bc/docs/Template.md)
121
+
122
+
123
+
124
+ ## `0.0.9` Micro Opcodes
2
125
 
3
126
  - Added new option `microOpcodes` which breaks opcodes into multiple sub-opcodes.
4
127
 
@@ -34,7 +157,7 @@ case OP.LOAD_CONST:
34
157
  // [59, 2, 0], MICRO_LOAD_CONST_1 [2, 0]
35
158
  // [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = reg[3](recv=reg[1], 1 args) 1:0-1:27
36
159
 
37
- // What the opcodes "MICRO_LOAD_CONST_0" (58) and "MICRO_LOAD_CONST_1" (59) looks like:
160
+ // What the opcodes "MICRO_LOAD_CONST_0" (58) and "MICRO_LOAD_CONST_1" (59) look like:
38
161
  case 58:
39
162
  // MICRO_LOAD_CONST_0
40
163
  this._internals[0] = this._operand();
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # JS Confuser VM
2
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)
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://js-confuser.com/vm)
4
4
 
5
5
 
6
6
  - **Requires Node v24.13.1 or higher**
7
7
  - ES5 support only. No complex features: async, generator, and even try..finally aren't supported.
8
8
  - Experimental. Expect issues.
9
- - [Try the web version.](https://development--confuser.netlify.app/vm)
9
+ - [Try the web version.](https://js-confuser.com/vm)
10
10
 
11
11
  ### Installation
12
12
 
@@ -39,12 +39,13 @@ JsConfuserVM.obfuscate(`
39
39
  shuffleOpcodes: true, // shuffle order of opcode handlers in the runtime?
40
40
  encodeBytecode: true, // encode the bytecode array?
41
41
  concealConstants: true, // conceal strings and integers in the constant pool?
42
+ dispatcher: true, // create middleman blocks to process jumps?
42
43
  selfModifying: true, // do self-modifying bytecode for function bodies?
43
44
  macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
44
- microOpcodes: true, // break opcodes into sub-opcodes?
45
45
  specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
46
46
  aliasedOpcodes: true, // create duplicate opcodes for commonly used opcodes?
47
47
  timingChecks: true, // add timing checks to detect debuggers?
48
+ classObfuscation: true, // obfuscate the VM runtime classes?
48
49
  minify: true // pass final output through Google Closure Compiler? (Renames VM class properties)
49
50
  }).then(result => {
50
51
  console.log(result.code)
@@ -115,6 +116,7 @@ A(new p(function(a){a=typeof Buffer!=="undefined"?Buffer.from(a,"base64"):Uint8A
115
116
  - [x] getter/setters
116
117
  - [x] debugger;
117
118
  - [x] template literals (**ES6**)
119
+ - [x] rest parameters (**ES6**)
118
120
 
119
121
  ### Missing
120
122
 
@@ -131,13 +133,13 @@ A(new p(function(a){a=typeof Buffer!=="undefined"?Buffer.from(a,"base64"):Uint8A
131
133
  - [ ] dead handlers
132
134
  - [ ] dead bytecode insertion
133
135
  - [x] macro opcodes (Combine multiple opcodes into a "macro opcode")
134
- - [x] micro opcodes (Break opcodes into sub-opcodes)
135
136
  - [x] specialized opcodes (Create specific opcodes for opcode+operand pairs)
136
137
  - [x] aliased opcodes (Create duplicate opcodes, including variants with shuffled operand order)
137
138
  - [x] encoded bytecode array
138
139
  - [x] self-modifying bytecode
139
140
  - [x] timing checks
140
141
  - [ ] low-level bytecode obfuscations
142
+ - [x] dispatcher (Encoded jumps)
141
143
  - [ ] stack protection
142
144
  - [ ] control flow integrity
143
145
 
@@ -179,6 +181,123 @@ var CONSTANTS = [/* 0 */"console", /* 1 */"log", /* 2 */"Hello world!", /* 3 */u
179
181
  var CONSTANTS = [/* 0 */"DaQApB6kAqQdpB+kEaQ=", /* 1 */"TCFOIUUh", /* 2 */"kKK8orait6Kzov2iqaKwopKijaKGosKi", /* 3 */undefined];
180
182
  ```
181
183
 
184
+ #### `controlFlowFlattening` (true/false)
185
+
186
+ Flattens the control flow of your program into a convoluted state machine.
187
+
188
+ ```js
189
+ // Input Code
190
+ var message;
191
+ if (true) {
192
+ message = "Hello World";
193
+ }
194
+
195
+ // Before
196
+ // fn_0_0:
197
+ r0 = undefined
198
+ r1 = true
199
+ if (!r1) goto: if_else_1
200
+ r1 = "Hello World"
201
+ r0 = r1
202
+ // if_else_1:
203
+ r1 = undefined
204
+ return r1
205
+
206
+ // After
207
+ // fn_0_0:
208
+ r1 = 969
209
+ r2 = r1
210
+ // while_top_5:
211
+ r3 = 4439
212
+ r4 = r2 !== r3
213
+ if (!r4) goto: while_exit_6
214
+ r5 = 969
215
+ r6 = r2 === r5
216
+ if (!r6) goto: if_else_7
217
+ goto: cff_block_2
218
+ // if_else_7:
219
+ r7 = 1317
220
+ r8 = r2 === r7
221
+ if (!r8) goto: if_else_8
222
+ goto: cff_block_3
223
+ // if_else_8:
224
+ r9 = 58894
225
+ r10 = r2 === r9
226
+ if (!r10) goto: if_else_9
227
+ goto: if_else_1
228
+ // if_else_9:
229
+ goto: while_top_5
230
+ // while_exit_6:
231
+ // cff_block_3:
232
+ r11 = "Hello World"
233
+ r0 = r11
234
+ r2 = 58894
235
+ goto: while_top_5
236
+ // if_else_1:
237
+ r11 = undefined
238
+ return r11
239
+ // cff_block_2:
240
+ r0 = undefined
241
+ r11 = true
242
+ if (r11) goto: cff_skip_10
243
+ r2 = 58894
244
+ goto: while_top_5
245
+ // cff_skip_10:
246
+ r2 = 1317
247
+ goto: while_top_5
248
+ ```
249
+
250
+ #### `dispatcher` (true/false)
251
+
252
+ Creates a middleman block to process jumps.
253
+
254
+ ```js
255
+ // Input Code
256
+ if (true) {
257
+ console.log("Hello world!");
258
+ }
259
+
260
+ // Before
261
+ // fn_0_0:
262
+ r0 = true
263
+ if (!r0) goto if_else_1
264
+ r0 = console
265
+ r1 = "log"
266
+ r2 = r0[r1] // console.log
267
+ r1 = "Hello world!"
268
+ r3 = r2.call(r0, r1) // console.log("Hello world!")
269
+ // if_else_1:
270
+ r0 = undefined
271
+ return r0
272
+
273
+ // After
274
+ // fn_0_0:
275
+ r2 = MakeClosure(fn_2_3, params=2)
276
+ r3 = true
277
+ if (r3) goto if_else_1_skip
278
+ r0 = 43020
279
+ r1 = 40151
280
+ goto dispatcher
281
+ // if_else_1_skip:
282
+ r3 = console
283
+ r4 = "log"
284
+ r5 = r3[r4] // console.log
285
+ r4 = "Hello world!"
286
+ r6 = r5.call(r3, r4) // console.log("Hello world!")
287
+ // if_else_1:
288
+ r3 = undefined
289
+ return r3
290
+
291
+ // dispatcher:
292
+ r0 = r2(r0, r1) // decode(encodedPC, siteKey)
293
+ goto *r0 // indirect jump
294
+
295
+ // fn_2_3:
296
+ function decode(x, k) {
297
+ return ((x ^ k) + 52048) & 65535;
298
+ }
299
+ ```
300
+
182
301
  #### `macroOpcodes` (true/false)
183
302
 
184
303
  Combines multiple opcodes commonly used from your bytecode.
@@ -246,53 +365,6 @@ case 5074:
246
365
  break;
247
366
  ```
248
367
 
249
- #### `microOpcodes` (true/false)
250
-
251
- Breaks opcodes into mulitple sub-opcodes.
252
-
253
- ```js
254
- // Input Code
255
- console.log("Hello world!");
256
-
257
- // Before
258
- // [2, 1, 0, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
259
- // [0, 2, 1, 0], LOAD_CONST reg[2] = "log" 1:0-1:27
260
- // [8, 3, 1, 2], GET_PROP reg[3] = reg[1][reg[2]] 1:0-1:27
261
- // [0, 4, 2, 0], LOAD_CONST reg[4] = "Hello world!" 1:12-1:26
262
- // [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = reg[3](recv=reg[1], 1 args) 1:0-1:27
263
-
264
- // What the opcode "LOAD_CONST" looks like:
265
- case OP.LOAD_CONST:
266
- var dst = this._operand();
267
- frame.regs[dst] = this._constant();
268
- break;
269
-
270
- // After
271
- // [60, 1], MICRO_LOAD_GLOBAL_0 1 1:0-1:7
272
- // [61, 0, 0], MICRO_LOAD_GLOBAL_1 [0, 0]
273
- // [62], MICRO_LOAD_GLOBAL_2
274
- // [63], MICRO_LOAD_GLOBAL_3
275
- // [58, 2], MICRO_LOAD_CONST_0 2 1:0-1:27
276
- // [59, 1, 0], MICRO_LOAD_CONST_1 [1, 0]
277
- // [64, 3], MICRO_GET_PROP_0 3 1:0-1:27
278
- // [65, 1], MICRO_GET_PROP_1 1
279
- // [66, 2], MICRO_GET_PROP_2 2
280
- // [67], MICRO_GET_PROP_3
281
- // [58, 4], MICRO_LOAD_CONST_0 4 1:12-1:26
282
- // [59, 2, 0], MICRO_LOAD_CONST_1 [2, 0]
283
- // [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = reg[3](recv=reg[1], 1 args) 1:0-1:27
284
-
285
- // What the opcodes "MICRO_LOAD_CONST_0" (58) and "MICRO_LOAD_CONST_1" (59) looks like:
286
- case 58:
287
- // MICRO_LOAD_CONST_0
288
- this._internals[0] = this._operand();
289
- break;
290
- case 59:
291
- // MICRO_LOAD_CONST_1
292
- frame.regs[this._internals[0]] = this._constant();
293
- break;
294
- ```
295
-
296
368
  #### `specializedOpcodes` (true/false)
297
369
 
298
370
  Creates specialized opcodes for commonly used opcode+operand pairs.
@@ -425,6 +497,9 @@ console.log("Hello, world!");
425
497
 
426
498
  Detects the use of debuggers by checking for >1second pauses. May break code with slow sync tasks.
427
499
 
500
+ ### `classObfuscation` (true/false)
501
+
502
+ Obfuscates the VM runtime classes by shuffling the order of declarations and methods.
428
503
 
429
504
  #### `minify` (true/false)
430
505
 
@@ -502,8 +577,8 @@ main().catch(console.error);
502
577
 
503
578
  ### WIP
504
579
 
505
- - 178 tests, 91.18% coverage
506
- - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 66.67%
580
+ - 202 tests, 91.18% coverage
581
+ - [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage: 78.58%
507
582
 
508
583
  ### Made with AI
509
584
 
package/bench.ts ADDED
@@ -0,0 +1,146 @@
1
+ import vm from "vm";
2
+ import fs from "fs";
3
+ import path from "path";
4
+
5
+ const args = process.argv.slice(2);
6
+ if (args.length === 0) {
7
+ console.log("Usage: node bench.js <file> [baseFile]");
8
+ console.log(" node bench.js output.js - benchmark a single file");
9
+ console.log(
10
+ " node bench.js output.js base.js - compare output.js against base.js",
11
+ );
12
+ process.exit(1);
13
+ }
14
+
15
+ const ITERATIONS = 5_000;
16
+
17
+ function getFileSize(filePath) {
18
+ return fs.statSync(filePath).size;
19
+ }
20
+
21
+ function formatBytes(bytes) {
22
+ if (bytes < 1024) return bytes + " B";
23
+ return (bytes / 1024).toFixed(2) + " KB";
24
+ }
25
+
26
+ function runBenchmark(filePath) {
27
+ const resolved = path.resolve(filePath);
28
+ if (!fs.existsSync(resolved)) {
29
+ console.error(`File not found: ${resolved}`);
30
+ process.exit(1);
31
+ }
32
+
33
+ const code = fs.readFileSync(resolved, "utf-8");
34
+ const script = new vm.Script(code, { filename: resolved });
35
+
36
+ // Verify the file runs successfully first
37
+ try {
38
+ const testCtx = vm.createContext({
39
+ window: {},
40
+ console,
41
+ process,
42
+ performance,
43
+ });
44
+ script.runInContext(testCtx);
45
+ } catch (e) {
46
+ console.error(`\nFile failed to execute: ${resolved}`);
47
+ console.error(e.message);
48
+ process.exit(1);
49
+ }
50
+
51
+ const times = [];
52
+
53
+ // Warmup
54
+ for (let i = 0; i < 50; i++) {
55
+ const ctx = vm.createContext({ window: {}, console, process, performance });
56
+ script.runInContext(ctx);
57
+ }
58
+
59
+ for (let i = 0; i < ITERATIONS; i++) {
60
+ const ctx = vm.createContext({ window: {}, console, process, performance });
61
+ const start = performance.now();
62
+ script.runInContext(ctx);
63
+ const end = performance.now();
64
+ times.push(end - start);
65
+
66
+ if ((i + 1) % 1000 === 0) {
67
+ process.stdout.write(`\r Running... ${i + 1}/${ITERATIONS}`);
68
+ }
69
+ }
70
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
71
+
72
+ times.sort((a, b) => a - b);
73
+
74
+ const median = times[Math.floor(times.length / 2)];
75
+ const q1 = times[Math.floor(times.length * 0.25)];
76
+ const q3 = times[Math.floor(times.length * 0.75)];
77
+ const min = times[0];
78
+ const max = times[times.length - 1];
79
+ const mean = times.reduce((a, b) => a + b, 0) / times.length;
80
+ const fileSize = getFileSize(resolved);
81
+
82
+ return { median, q1, q3, min, max, mean, fileSize, filePath: resolved };
83
+ }
84
+
85
+ function formatMs(ms) {
86
+ return ms.toFixed(3) + " ms";
87
+ }
88
+
89
+ function pctChange(base, compare) {
90
+ const pct = ((compare - base) / base) * 100;
91
+ const sign = pct >= 0 ? "+" : "";
92
+ return sign + pct.toFixed(1) + "%";
93
+ }
94
+
95
+ function printResult(label, result) {
96
+ console.log(` ${label}`);
97
+ console.log(` ${"=".repeat(label.length)}`);
98
+ console.log(` File: ${result.filePath}`);
99
+ console.log(` File Size: ${formatBytes(result.fileSize)}`);
100
+ console.log(` Iterations: ${ITERATIONS.toLocaleString()}`);
101
+ console.log();
102
+ console.log(` Median: ${formatMs(result.median)}`);
103
+ console.log(` Mean: ${formatMs(result.mean)}`);
104
+ console.log(` Q1: ${formatMs(result.q1)}`);
105
+ console.log(` Q3: ${formatMs(result.q3)}`);
106
+ console.log(` Min: ${formatMs(result.min)}`);
107
+ console.log(` Max: ${formatMs(result.max)}`);
108
+ console.log(` IQR: ${formatMs(result.q3 - result.q1)}`);
109
+ }
110
+
111
+ function printComparison(base, compare) {
112
+ console.log(" Comparison (vs Base)");
113
+ console.log(" " + "=".repeat(21));
114
+ console.log(` Median: ${pctChange(base.median, compare.median)}`);
115
+ console.log(` Mean: ${pctChange(base.mean, compare.mean)}`);
116
+ console.log(` Q1: ${pctChange(base.q1, compare.q1)}`);
117
+ console.log(` Q3: ${pctChange(base.q3, compare.q3)}`);
118
+ console.log(` File Size: ${pctChange(base.fileSize, compare.fileSize)}`);
119
+ }
120
+
121
+ console.log();
122
+ console.log(" JS-Confuser VM Benchmark");
123
+ console.log(" " + "-".repeat(26));
124
+ console.log();
125
+
126
+ if (args.length === 1) {
127
+ const result = runBenchmark(args[0]);
128
+ printResult(path.basename(args[0]), result);
129
+ console.log();
130
+ } else {
131
+ const baseFile = args[1];
132
+ const compareFile = args[0];
133
+
134
+ console.log(` Benchmarking base: ${path.basename(baseFile)}`);
135
+ const baseResult = runBenchmark(baseFile);
136
+ console.log(` Benchmarking compare: ${path.basename(compareFile)}`);
137
+ const compareResult = runBenchmark(compareFile);
138
+
139
+ console.log();
140
+ printResult("Base: " + path.basename(baseFile), baseResult);
141
+ console.log();
142
+ printResult("Compare: " + path.basename(compareFile), compareResult);
143
+ console.log();
144
+ printComparison(baseResult, compareResult);
145
+ console.log();
146
+ }
package/disassemble.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import JsConfuserVM from "./src/index.ts";
3
+
4
+ const fileContents = await readFile(process.argv[2], "utf-8");
5
+
6
+ const outputFile =
7
+ process.argv[3] || process.argv[2].replace(/\.js$/, ".disassembled.js");
8
+
9
+ const output = await JsConfuserVM.disassemble(fileContents);
10
+ console.log(output);
11
+
12
+ await writeFile(outputFile, output, "utf-8");
@@ -1,13 +1,14 @@
1
1
  import { generate } from "@babel/generator";
2
2
  import { parse } from "@babel/parser";
3
3
  import { applyMacroOpcodes } from "./transforms/runtime/macroOpcodes.js";
4
- import { applyMicroOpcodes } from "./transforms/runtime/microOpcodes.js";
5
- import { applyInteralVariablesToRuntime } from "./transforms/runtime/internalVariables.js";
6
4
  import { applyShuffleOpcodes } from "./transforms/runtime/shuffleOpcodes.js";
7
5
  import { applyMinify } from "./transforms/runtime/minify.js";
8
6
  import { applySpecializedOpcodes } from "./transforms/runtime/specializedOpcodes.js";
9
7
  import { applyAliasedOpcodes } from "./transforms/runtime/aliasedOpcodes.js";
10
- export async function obfuscateRuntime(runtime, bytecode, options, compiler, generateBytecodeComment) {
8
+ import { applyClassObfuscation } from "./transforms/runtime/classObfuscation.js";
9
+ import { getSwitchStatement } from "./utils/ast-utils.js";
10
+ import { now } from "./utils/profile-utils.js";
11
+ export async function buildRuntime(runtime, bytecode, options, compiler, generateBytecodeComment) {
11
12
  let ast;
12
13
  try {
13
14
  ast = parse(runtime, {
@@ -18,34 +19,52 @@ export async function obfuscateRuntime(runtime, bytecode, options, compiler, gen
18
19
  cause: error
19
20
  });
20
21
  }
22
+ const switchStatement = getSwitchStatement(ast);
23
+ const getHandlerCount = () => {
24
+ return switchStatement.cases.length;
25
+ };
26
+ const timings = {};
27
+ function runAndTime(pass, name) {
28
+ const startedAt = now();
29
+ compiler.log(`Running runtime pass ${name}...`);
30
+ pass(ast, compiler);
31
+ const endedAt = now();
32
+ const elapsedMs = endedAt - startedAt;
33
+ timings[name] = elapsedMs;
34
+ compiler.profileData.transforms[name] = {
35
+ fileSize: null,
36
+ // TODO: Add option as doing 'generate(ast).code.length' is slow
37
+ transformTime: elapsedMs,
38
+ handlerCount: getHandlerCount()
39
+ };
40
+ compiler.log(`Runtime pass ${name} completed in ${Math.floor(elapsedMs)}ms`);
41
+ }
21
42
 
22
43
  // Specialized opcode cases must be applied BEFORE shuffleOpcodes
23
44
  if (options.specializedOpcodes) {
24
- applySpecializedOpcodes(ast, compiler);
25
- }
26
- if (options.microOpcodes) {
27
- applyInteralVariablesToRuntime(ast, compiler);
28
- }
29
-
30
- // Micro opcode cases must be applied BEFORE shuffleOpcodes
31
- if (options.microOpcodes && Object.keys(compiler.MICRO_OPS).length > 0) {
32
- applyMicroOpcodes(ast, compiler);
45
+ runAndTime(applySpecializedOpcodes, "applySpecializedOpcodes");
33
46
  }
34
47
 
35
48
  // Macro opcode cases must be applied BEFORE shuffleOpcodes
36
49
  if (options.macroOpcodes && Object.keys(compiler.MACRO_OPS).length > 0) {
37
- applyMacroOpcodes(ast, compiler);
50
+ runAndTime(applyMacroOpcodes, "applyMacroOpcodes");
38
51
  }
39
52
 
40
53
  // Aliased opcode cases must be applied BEFORE shuffleOpcodes
41
54
  if (options.aliasedOpcodes) {
42
- applyAliasedOpcodes(ast, compiler);
55
+ runAndTime(applyAliasedOpcodes, "applyAliasedOpcodes");
43
56
  }
44
57
 
45
58
  // Shuffle opcode handle order
46
59
  if (options.shuffleOpcodes) {
47
- applyShuffleOpcodes(ast);
60
+ runAndTime(applyShuffleOpcodes, "applyShuffleOpcodes");
61
+ }
62
+
63
+ // Shuffle top-level var declarations and prototype method definitions
64
+ if (options.classObfuscation) {
65
+ runAndTime(applyClassObfuscation, "applyClassObfuscation");
48
66
  }
67
+ compiler.profileData.handlerCount = getHandlerCount();
49
68
  let generated;
50
69
  try {
51
70
  generated = generate(ast).code;
@@ -61,7 +80,14 @@ export async function obfuscateRuntime(runtime, bytecode, options, compiler, gen
61
80
  // Minify code?
62
81
  if (options.minify) {
63
82
  try {
83
+ let startedAt = now();
84
+ compiler.log("Running minify...");
64
85
  generated = await applyMinify(generated);
86
+ let elapsedMs = now() - startedAt;
87
+ compiler.log(`Minify completed in ${Math.floor(elapsedMs)}ms`);
88
+ compiler.profileData.transforms["minify"] = {
89
+ transformTime: elapsedMs
90
+ };
65
91
  } catch (error) {
66
92
  throw new Error("VM-Runtime final minification failed", {
67
93
  cause: error