js-confuser-vm 0.1.1 → 0.1.2
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/README.md +242 -89
- package/dist/compiler.js +583 -208
- package/dist/disassembler.js +58 -8
- package/dist/runtime.js +93 -74
- package/dist/template.js +81 -76
- package/dist/transforms/bytecode/concealConstants.js +2 -2
- package/dist/transforms/bytecode/controlFlowFlattening.js +143 -25
- package/dist/transforms/bytecode/dispatcher.js +3 -3
- package/dist/transforms/bytecode/resolveRegisters.js +19 -4
- package/dist/transforms/bytecode/selfModifying.js +88 -21
- package/dist/transforms/bytecode/specializedOpcodes.js +6 -3
- package/dist/transforms/bytecode/stringConcealing.js +253 -75
- package/dist/utils/ast-utils.js +61 -0
- package/dist/utils/op-utils.js +1 -0
- package/package.json +7 -1
- package/.gitmodules +0 -4
- package/.prettierignore +0 -1
- package/CHANGELOG.md +0 -358
- package/babel-plugin-inline-runtime.cjs +0 -34
- package/babel.config.json +0 -23
- package/bench.ts +0 -146
- package/disassemble.ts +0 -12
- package/index.ts +0 -43
- package/jest-strip-types.js +0 -10
- package/jest.config.js +0 -64
- package/output.disassembled.js +0 -41
- package/src/build-runtime.ts +0 -113
- package/src/compiler.ts +0 -2703
- package/src/disassembler.ts +0 -329
- package/src/index.ts +0 -24
- package/src/minify.ts +0 -21
- package/src/options.ts +0 -24
- package/src/runtime.ts +0 -956
- package/src/template.ts +0 -265
- package/src/transforms/bytecode/aliasedOpcodes.ts +0 -151
- package/src/transforms/bytecode/concealConstants.ts +0 -52
- package/src/transforms/bytecode/controlFlowFlattening.ts +0 -566
- package/src/transforms/bytecode/dispatcher.ts +0 -292
- package/src/transforms/bytecode/macroOpcodes.ts +0 -193
- package/src/transforms/bytecode/resolveConstants.ts +0 -126
- package/src/transforms/bytecode/resolveLabels.ts +0 -112
- package/src/transforms/bytecode/resolveRegisters.ts +0 -226
- package/src/transforms/bytecode/selfModifying.ts +0 -121
- package/src/transforms/bytecode/specializedOpcodes.ts +0 -164
- package/src/transforms/bytecode/stringConcealing.ts +0 -130
- package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
- package/src/transforms/runtime/classObfuscation.ts +0 -59
- package/src/transforms/runtime/macroOpcodes.ts +0 -138
- package/src/transforms/runtime/minify.ts +0 -1
- package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
- package/src/transforms/runtime/specializedOpcodes.ts +0 -161
- package/src/types.ts +0 -134
- package/src/utils/ast-utils.ts +0 -19
- package/src/utils/op-utils.ts +0 -46
- package/src/utils/pass-utils.ts +0 -126
- package/src/utils/profile-utils.ts +0 -3
- package/src/utils/random-utils.ts +0 -31
- package/tsconfig.json +0 -12
package/CHANGELOG.md
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
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
|
|
125
|
-
|
|
126
|
-
- Added new option `microOpcodes` which breaks opcodes into multiple sub-opcodes.
|
|
127
|
-
|
|
128
|
-
```js
|
|
129
|
-
// Input Code
|
|
130
|
-
console.log("Hello world!");
|
|
131
|
-
|
|
132
|
-
// Before
|
|
133
|
-
// [2, 1, 0, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
|
|
134
|
-
// [0, 2, 1, 0], LOAD_CONST reg[2] = "log" 1:0-1:27
|
|
135
|
-
// [8, 3, 1, 2], GET_PROP reg[3] = reg[1][reg[2]] 1:0-1:27
|
|
136
|
-
// [0, 4, 2, 0], LOAD_CONST reg[4] = "Hello world!" 1:12-1:26
|
|
137
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = reg[3](recv=reg[1], 1 args) 1:0-1:27
|
|
138
|
-
|
|
139
|
-
// What the opcode "LOAD_CONST" looks like:
|
|
140
|
-
case OP.LOAD_CONST:
|
|
141
|
-
var dst = this._operand();
|
|
142
|
-
frame.regs[dst] = this._constant();
|
|
143
|
-
break;
|
|
144
|
-
|
|
145
|
-
// After
|
|
146
|
-
// [60, 1], MICRO_LOAD_GLOBAL_0 1 1:0-1:7
|
|
147
|
-
// [61, 0, 0], MICRO_LOAD_GLOBAL_1 [0, 0]
|
|
148
|
-
// [62], MICRO_LOAD_GLOBAL_2
|
|
149
|
-
// [63], MICRO_LOAD_GLOBAL_3
|
|
150
|
-
// [58, 2], MICRO_LOAD_CONST_0 2 1:0-1:27
|
|
151
|
-
// [59, 1, 0], MICRO_LOAD_CONST_1 [1, 0]
|
|
152
|
-
// [64, 3], MICRO_GET_PROP_0 3 1:0-1:27
|
|
153
|
-
// [65, 1], MICRO_GET_PROP_1 1
|
|
154
|
-
// [66, 2], MICRO_GET_PROP_2 2
|
|
155
|
-
// [67], MICRO_GET_PROP_3
|
|
156
|
-
// [58, 4], MICRO_LOAD_CONST_0 4 1:12-1:26
|
|
157
|
-
// [59, 2, 0], MICRO_LOAD_CONST_1 [2, 0]
|
|
158
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = reg[3](recv=reg[1], 1 args) 1:0-1:27
|
|
159
|
-
|
|
160
|
-
// What the opcodes "MICRO_LOAD_CONST_0" (58) and "MICRO_LOAD_CONST_1" (59) look like:
|
|
161
|
-
case 58:
|
|
162
|
-
// MICRO_LOAD_CONST_0
|
|
163
|
-
this._internals[0] = this._operand();
|
|
164
|
-
break;
|
|
165
|
-
case 59:
|
|
166
|
-
// MICRO_LOAD_CONST_1
|
|
167
|
-
frame.regs[this._internals[0]] = this._constant();
|
|
168
|
-
break;
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
- Fixed `Macro Opcodes` possibly clashing variables when merging opcode handlers.
|
|
172
|
-
|
|
173
|
-
- Added support for update expressions on member expressions (`object.prop++`, `object.prop--`)
|
|
174
|
-
|
|
175
|
-
- Added support for Template literals (ES6 feature added for convenience)
|
|
176
|
-
|
|
177
|
-
- Added programs [cash.min.js](https://github.com/fabiospampinato/cash/blob/master/dist/cash.min.js) and [sha256.js](https://gist.github.com/bryanchow/1649353) to the test suite
|
|
178
|
-
|
|
179
|
-
## `0.0.6` Register based
|
|
180
|
-
|
|
181
|
-
- Switched from stack-based to register-based VM.
|
|
182
|
-
|
|
183
|
-
- `Specialized Opcodes` now applies to any fixed-size instruction, instead of just singular operands.
|
|
184
|
-
- - Specialized Opcodes never applies to N-sized instructions, such as `MAKE_CLOSURE`, `BUILD_ARRAY`, `CALL`, etc.
|
|
185
|
-
|
|
186
|
-
- `Macro Opcodes` can now include jumping/terminating opcodes if it's the last instruction in the sequence.
|
|
187
|
-
|
|
188
|
-
- Added new option `aliasedOpcodes` which creates duplicate opcodes, including variants with shuffled operand order.
|
|
189
|
-
|
|
190
|
-
```js
|
|
191
|
-
// Input Code
|
|
192
|
-
console.log("Hello, world!");
|
|
193
|
-
|
|
194
|
-
// Before
|
|
195
|
-
// [2, 1, 0], LOAD_GLOBAL reg[1] = console 1:0-1:7
|
|
196
|
-
// [0, 2, 1], LOAD_CONST reg[2] = "log" 1:0-1:28
|
|
197
|
-
// [8, 3, 1, 2], GET_PROP [3, 1, 2] 1:0-1:28
|
|
198
|
-
// [0, 4, 2], LOAD_CONST reg[4] = "Hello, world!" 1:12-1:27
|
|
199
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
200
|
-
// [0, 1, 3], LOAD_CONST reg[1] = undefined
|
|
201
|
-
// [45, 1], RETURN reg[1]
|
|
202
|
-
|
|
203
|
-
// What the opcode "LOAD_GLOBAL" looks like:
|
|
204
|
-
case OP.LOAD_GLOBAL:
|
|
205
|
-
var dst = this._operand();
|
|
206
|
-
frame.regs[dst] = this.globals[this.constants[this._operand()]];
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
// After
|
|
210
|
-
// [52040, 0, 1], ALIAS_LOAD_GLOBAL_1_0 [0, 1] 1:0-1:7
|
|
211
|
-
// [24862, 1, 2], ALIAS_LOAD_CONST_1_0 [1, 2] 1:0-1:28
|
|
212
|
-
// [25202, 1, 2, 3], ALIAS_GET_PROP_1_2_0 [1, 2, 3] 1:0-1:28
|
|
213
|
-
// [24862, 2, 4], ALIAS_LOAD_CONST_1_0 [2, 4] 1:12-1:27
|
|
214
|
-
// [43, 5, 1, 3, 1, 4], CALL_METHOD reg[5] = method(recv=reg[1], fn=reg[3], 1 args)1:0-1:28
|
|
215
|
-
// [24862, 3, 1], ALIAS_LOAD_CONST_1_0 [3, 1]
|
|
216
|
-
// [51807, 1], ALIAS_RETURN_0 1
|
|
217
|
-
|
|
218
|
-
// What the opcode "ALIAS_LOAD_GLOBAL_1_0" (52040) looks like:
|
|
219
|
-
case 52040:
|
|
220
|
-
// ALIAS_LOAD_GLOBAL_1_0 (order: [1,0])
|
|
221
|
-
let _unsortedOperands = [this._operand(), this._operand()];
|
|
222
|
-
let _operands = [_unsortedOperands[1], _unsortedOperands[0]];
|
|
223
|
-
var dst = _operands[0];
|
|
224
|
-
frame.regs[dst] = this.globals[this.constants[_operands[1]]];
|
|
225
|
-
break;
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
- Added new option `concealConstants` which XOR decrypts numbers and strings at runtime.
|
|
229
|
-
|
|
230
|
-
- Top level variables are now renamed and not exposed globally. To export a global function, you can use `window.MyGlobalFunction = function(){...}`
|
|
231
|
-
|
|
232
|
-
- Accessing an undeclared global variable will throw a ReferenceError
|
|
233
|
-
|
|
234
|
-
## `0.0.5` Generated Opcodes
|
|
235
|
-
|
|
236
|
-
- Added new option `specializedOpcodes` which creates specialized opcodes for commonly used opcode+operand pairs.
|
|
237
|
-
|
|
238
|
-
```js
|
|
239
|
-
// Input Code
|
|
240
|
-
console.log("Hello world!");
|
|
241
|
-
|
|
242
|
-
// Before
|
|
243
|
-
// [3, 0], LOAD_GLOBAL "console" 1:0-1:7
|
|
244
|
-
// [0, 1], LOAD_CONST "log" 1:0-1:27
|
|
245
|
-
// [5], GET_PROP 1:0-1:27
|
|
246
|
-
// [0, 2], LOAD_CONST "Hello world!" 1:12-1:26
|
|
247
|
-
// [12, 1], CALL_METHOD (1 args) 1:0-1:27
|
|
248
|
-
// [14], POP 1:0-1:28
|
|
249
|
-
|
|
250
|
-
// What the opcode "LOAD_GLOBAL" looks like:
|
|
251
|
-
case OP.LOAD_GLOBAL:
|
|
252
|
-
this._push(this.globals[this.constants[this._operand()]]);
|
|
253
|
-
break;
|
|
254
|
-
|
|
255
|
-
// After
|
|
256
|
-
// [64], LOAD_GLOBAL_0 1:0-1:7
|
|
257
|
-
// [65], LOAD_CONST_1 1:0-1:27
|
|
258
|
-
// [5], GET_PROP 1:0-1:27
|
|
259
|
-
// [66], LOAD_CONST_2 1:12-1:26
|
|
260
|
-
// [67], CALL_METHOD_1 1:0-1:27
|
|
261
|
-
// [14], POP 1:0-1:28
|
|
262
|
-
|
|
263
|
-
// What the opcode "LOAD_GLOBAL_0" (64) looks like:
|
|
264
|
-
case 64:
|
|
265
|
-
// LOAD_GLOBAL_0 (specialized)
|
|
266
|
-
this._push(this.globals[this.constants[0]]);
|
|
267
|
-
break;
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
- Added new option `macroOpcodes` which combines multiple opcodes commonly used from your bytecode
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
// Input Code
|
|
274
|
-
console.log("Hello world!");
|
|
275
|
-
console.log("Hello world!");
|
|
276
|
-
|
|
277
|
-
// Before
|
|
278
|
-
// [3, 0], LOAD_GLOBAL "console" 1:0-1:7
|
|
279
|
-
// [0, 1], LOAD_CONST "log" 1:0-1:27
|
|
280
|
-
// [5], GET_PROP 1:0-1:27
|
|
281
|
-
// [0, 2], LOAD_CONST "Hello world!" 1:12-1:26
|
|
282
|
-
// [12, 1], CALL_METHOD (1 args) 1:0-1:27
|
|
283
|
-
// [14], POP 1:0-1:28
|
|
284
|
-
// [3, 0], LOAD_GLOBAL "console" 2:0-2:7
|
|
285
|
-
// [0, 1], LOAD_CONST "log" 2:0-2:27
|
|
286
|
-
// [5], GET_PROP 2:0-2:27
|
|
287
|
-
// [0, 2], LOAD_CONST "Hello world!" 2:12-2:26
|
|
288
|
-
// [12, 1], CALL_METHOD (1 args) 2:0-2:27
|
|
289
|
-
// [14], POP 2:0-2:28
|
|
290
|
-
|
|
291
|
-
// After
|
|
292
|
-
// [64, 0, 1, 2], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST [0, 1, 2]
|
|
293
|
-
// [12, 1], CALL_METHOD (1 args) 1:0-1:27
|
|
294
|
-
// [14], POP 1:0-1:28
|
|
295
|
-
// [64, 0, 1, 2], LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST [0, 1, 2]
|
|
296
|
-
// [12, 1], CALL_METHOD (1 args) 2:0-2:27
|
|
297
|
-
// [14], POP 2:0-2:28
|
|
298
|
-
|
|
299
|
-
// What the opcode "LOAD_GLOBAL,LOAD_CONST,GET_PROP,LOAD_CONST" (64) looks like:
|
|
300
|
-
case 64:
|
|
301
|
-
{
|
|
302
|
-
// LOAD_GLOBAL
|
|
303
|
-
this._push(this.globals[this.constants[this._operand()]]);
|
|
304
|
-
// LOAD_CONST
|
|
305
|
-
this._push(this.constants[this._operand()]);
|
|
306
|
-
// GET_PROP
|
|
307
|
-
// Stack: [..., obj, key] -> [..., obj, obj[key]]
|
|
308
|
-
// obj is PEEKED (not popped) - CALL_METHOD needs it as receiver
|
|
309
|
-
var key = this._pop();
|
|
310
|
-
var obj = this.peek();
|
|
311
|
-
this._push(obj[key]);
|
|
312
|
-
// LOAD_CONST
|
|
313
|
-
this._push(this.constants[this._operand()]);
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
- Flattened the bytecode. Now, instructions can read as many operands as needed, and it's unclear to distinguish between opcodes and operands:
|
|
319
|
-
|
|
320
|
-
```js
|
|
321
|
-
// Before (Operands clearly visible)
|
|
322
|
-
var BYTECODE = [[3, 0], [0, 1], [5, undefined], [0, 2], [12, 1], [14, undefined], [3, 0], [0, 1], [5, undefined], [0, 2], [12, 1], [14, undefined], [13, undefined]];
|
|
323
|
-
|
|
324
|
-
// After (Flattened with multi-operand instruction support)
|
|
325
|
-
var BYTECODE = [3, 0, 0, 1, 5, 0, 2, 12, 1, 14, 3, 0, 0, 1, 5, 0, 2, 12, 1, 14, 13];
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
- Changed the bytecode to use ushorts (16-bit ints) allowing a max value of 65,535 for opcodes and operands.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
## `0.0.3` First update
|
|
332
|
-
|
|
333
|
-
- Created [Website Playground](https://development--confuser.netlify.app/vm)
|
|
334
|
-
|
|
335
|
-
- Added partial support for `try..catch` - The `finally` operator is not supported yet
|
|
336
|
-
- More ES5 coverage: getter/setters, debugger statement
|
|
337
|
-
- Improved compilation process:
|
|
338
|
-
- Parsing:
|
|
339
|
-
JS -> AST
|
|
340
|
-
Done by [@babel/parser](https://www.npmjs.com/package/@babel/parser)
|
|
341
|
-
- Compilation:
|
|
342
|
-
AST -> IR bytecode.
|
|
343
|
-
This bytecode contains pseudo instructions and symbolic values for things like jump labels and constants
|
|
344
|
-
- Transform passes (Assembler):
|
|
345
|
-
Transform passes obfuscate and finally prepare the pseudo bytecode to be runnable. Here, all jump labels get converted into absolute PCs
|
|
346
|
-
- Serializer:
|
|
347
|
-
The bytecode is printed into the array form or encoded string if you have `encodeBytecode` enabled
|
|
348
|
-
- Generating:
|
|
349
|
-
This includes two sub-stages:
|
|
350
|
-
- 1) Creating (another parsing->transforming->generating) the VM Runtime with the given options (randomized op codes, shuffled handlers)
|
|
351
|
-
- 2) Placing the final bytecode into this VM Runtime
|
|
352
|
-
|
|
353
|
-
- Typescript is now transpiled for NPM
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
## `0.0.2` First release
|
|
358
|
-
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const { readFileSync } = require("fs");
|
|
2
|
-
const { join } = require("path");
|
|
3
|
-
const babel = require("@babel/core");
|
|
4
|
-
const { stripTypeScriptTypes } = require("node:module");
|
|
5
|
-
|
|
6
|
-
module.exports = function inlineRuntimePlugin({ types: t }) {
|
|
7
|
-
const rawContent = readFileSync(join(__dirname, "./src/runtime.ts"), "utf-8");
|
|
8
|
-
|
|
9
|
-
const runtimeContent = stripTypeScriptTypes(rawContent);
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
name: "inline-runtime",
|
|
13
|
-
visitor: {
|
|
14
|
-
VariableDeclarator(path) {
|
|
15
|
-
if (
|
|
16
|
-
path.node.id?.name === "readVMRuntimeFile" &&
|
|
17
|
-
(path.node.init?.type === "ArrowFunctionExpression" ||
|
|
18
|
-
path.node.init?.type === "FunctionExpression")
|
|
19
|
-
) {
|
|
20
|
-
path.node.init = t.arrowFunctionExpression(
|
|
21
|
-
[],
|
|
22
|
-
t.stringLiteral(runtimeContent),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
ImportDeclaration(path) {
|
|
27
|
-
const src = path.node.source.value;
|
|
28
|
-
if (src === "fs" || src === "path") {
|
|
29
|
-
path.remove();
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
};
|
package/babel.config.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"presets": [
|
|
3
|
-
[
|
|
4
|
-
"@babel/preset-env",
|
|
5
|
-
{
|
|
6
|
-
"targets": {
|
|
7
|
-
"node": "18"
|
|
8
|
-
},
|
|
9
|
-
"modules": false
|
|
10
|
-
}
|
|
11
|
-
],
|
|
12
|
-
["@babel/preset-typescript"]
|
|
13
|
-
],
|
|
14
|
-
"plugins": [
|
|
15
|
-
"./babel-plugin-inline-runtime.cjs",
|
|
16
|
-
[
|
|
17
|
-
"replace-import-extension",
|
|
18
|
-
{
|
|
19
|
-
"extMapping": { ".ts": ".js" }
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
]
|
|
23
|
-
}
|
package/bench.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
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/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import JsConfuserVM from "./src/index.ts";
|
|
2
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
-
|
|
4
|
-
async function main() {
|
|
5
|
-
// Compile and write the output to a file
|
|
6
|
-
const sourceCode = readFileSync("input.js", "utf-8");
|
|
7
|
-
|
|
8
|
-
const { code: orginalOutput } = await JsConfuserVM.obfuscate(sourceCode, {});
|
|
9
|
-
|
|
10
|
-
const result = await JsConfuserVM.obfuscate(sourceCode, {
|
|
11
|
-
target: "browser", // or "node"
|
|
12
|
-
// randomizeOpcodes: true, // randomize the opcode numbers?
|
|
13
|
-
// shuffleOpcodes: true, // shuffle order of opcode handlers in the runtime?
|
|
14
|
-
// encodeBytecode: true, // encode the bytecode array?
|
|
15
|
-
// concealConstants: true, // conceal strings and integers in the constant pool?
|
|
16
|
-
// dispatcher: true, // create middleman blocks to process jumps?
|
|
17
|
-
// selfModifying: true, // do self-modifying bytecode for function bodies?
|
|
18
|
-
// macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
|
|
19
|
-
// specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
|
|
20
|
-
// aliasedOpcodes: true, // create duplicate opcodes for commonly used opcodes?
|
|
21
|
-
// timingChecks: true, // add timing checks to detect debuggers?
|
|
22
|
-
// minify: true, // pass final output through Google Closure Compiler? (Renames VM class properties)
|
|
23
|
-
controlFlowFlattening: true,
|
|
24
|
-
// stringConcealing: true,
|
|
25
|
-
verbose: true,
|
|
26
|
-
// classObfuscation: true,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
writeFileSync("output.original.js", orginalOutput, "utf-8");
|
|
30
|
-
writeFileSync("output.js", result.code, "utf-8");
|
|
31
|
-
|
|
32
|
-
console.log("Saved");
|
|
33
|
-
|
|
34
|
-
// Eval the code like our test suite does
|
|
35
|
-
var window = { TEST_OUTPUT: null };
|
|
36
|
-
eval(result.code);
|
|
37
|
-
console.log(window.TEST_OUTPUT);
|
|
38
|
-
|
|
39
|
-
delete result.code;
|
|
40
|
-
console.log(JSON.stringify(result));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
main();
|