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.
- package/.gitmodules +4 -0
- package/CHANGELOG.md +125 -2
- package/README.md +128 -53
- package/bench.ts +146 -0
- package/disassemble.ts +12 -0
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +328 -181
- package/dist/disassembler.js +317 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +255 -176
- package/dist/template.js +258 -0
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
- package/dist/transforms/bytecode/dispatcher.js +266 -0
- package/dist/transforms/bytecode/macroOpcodes.js +3 -3
- package/dist/transforms/bytecode/resolveConstants.js +100 -0
- package/dist/transforms/bytecode/resolveLabels.js +21 -18
- package/dist/transforms/bytecode/resolveRegisters.js +216 -0
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +22 -12
- package/dist/transforms/bytecode/stringConcealing.js +110 -0
- package/dist/transforms/runtime/classObfuscation.js +43 -0
- package/dist/transforms/runtime/handlerTable.js +91 -0
- package/dist/transforms/runtime/semanticOpcodes.js +35 -0
- package/dist/transforms/runtime/specializedOpcodes.js +11 -5
- package/dist/types.js +42 -1
- package/dist/utils/ast-utils.js +14 -0
- package/dist/utils/op-utils.js +1 -2
- package/dist/utils/pass-utils.js +100 -0
- package/dist/utils/profile-utils.js +3 -0
- package/index.ts +22 -16
- package/jest.config.js +19 -2
- package/output.disassembled.js +41 -0
- package/package.json +2 -1
- package/src/build-runtime.ts +113 -78
- package/src/compiler.ts +2703 -2482
- package/src/disassembler.ts +329 -0
- package/src/index.ts +12 -2
- package/src/options.ts +8 -1
- package/src/runtime.ts +294 -180
- package/src/template.ts +265 -0
- package/src/transforms/bytecode/aliasedOpcodes.ts +5 -2
- package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
- package/src/transforms/bytecode/dispatcher.ts +292 -0
- package/src/transforms/bytecode/macroOpcodes.ts +4 -4
- package/src/transforms/bytecode/resolveLabels.ts +31 -27
- package/src/transforms/bytecode/resolveRegisters.ts +226 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +27 -20
- package/src/transforms/bytecode/stringConcealing.ts +130 -0
- package/src/transforms/runtime/classObfuscation.ts +59 -0
- package/src/transforms/runtime/specializedOpcodes.ts +14 -9
- package/src/types.ts +106 -5
- package/src/utils/ast-utils.ts +19 -0
- package/src/utils/op-utils.ts +2 -2
- package/src/utils/pass-utils.ts +126 -0
- package/src/utils/profile-utils.ts +3 -0
- package/tsconfig.json +1 -1
- package/dist/transforms/utils/op-utils.js +0 -25
- package/dist/transforms/utils/random-utils.js +0 -27
- package/dist/utilts.js +0 -3
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
package/.gitmodules
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,127 @@
|
|
|
1
|
-
|
|
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)
|
|
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
|
-
[](https://npmjs.com/package/js-confuser-vm) [](https://github.com/MichaelXF/js-confuser-vm) [](https://
|
|
3
|
+
[](https://npmjs.com/package/js-confuser-vm) [](https://github.com/MichaelXF/js-confuser-vm) [](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://
|
|
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
|
-
-
|
|
506
|
-
- [Test262 (es5-tests)](https://github.com/tc39/test262/tree/es5-tests) percentage:
|
|
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");
|
package/dist/build-runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
55
|
+
runAndTime(applyAliasedOpcodes, "applyAliasedOpcodes");
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
// Shuffle opcode handle order
|
|
46
59
|
if (options.shuffleOpcodes) {
|
|
47
|
-
applyShuffleOpcodes
|
|
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
|