js-confuser-vm 0.1.0 → 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.
Files changed (63) hide show
  1. package/README.md +281 -147
  2. package/dist/build-runtime.js +41 -15
  3. package/dist/compiler.js +714 -265
  4. package/dist/disassembler.js +367 -0
  5. package/dist/index.js +7 -2
  6. package/dist/runtime.js +160 -119
  7. package/dist/template.js +163 -42
  8. package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
  9. package/dist/transforms/bytecode/concealConstants.js +2 -2
  10. package/dist/transforms/bytecode/controlFlowFlattening.js +569 -0
  11. package/dist/transforms/bytecode/dispatcher.js +15 -111
  12. package/dist/transforms/bytecode/macroOpcodes.js +2 -2
  13. package/{src/transforms/bytecode/resolveContants.ts → dist/transforms/bytecode/resolveConstants.js} +30 -56
  14. package/dist/transforms/bytecode/resolveRegisters.js +23 -4
  15. package/dist/transforms/bytecode/selfModifying.js +88 -21
  16. package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
  17. package/dist/transforms/bytecode/specializedOpcodes.js +23 -12
  18. package/dist/transforms/bytecode/stringConcealing.js +288 -0
  19. package/dist/transforms/runtime/classObfuscation.js +43 -0
  20. package/dist/transforms/runtime/handlerTable.js +91 -0
  21. package/dist/transforms/runtime/semanticOpcodes.js +35 -0
  22. package/dist/transforms/runtime/specializedOpcodes.js +11 -5
  23. package/dist/types.js +1 -1
  24. package/dist/utils/ast-utils.js +75 -0
  25. package/dist/utils/op-utils.js +1 -2
  26. package/dist/utils/pass-utils.js +100 -0
  27. package/dist/utils/profile-utils.js +3 -0
  28. package/package.json +8 -1
  29. package/.gitmodules +0 -4
  30. package/.prettierignore +0 -1
  31. package/CHANGELOG.md +0 -335
  32. package/babel-plugin-inline-runtime.cjs +0 -34
  33. package/babel.config.json +0 -23
  34. package/index.ts +0 -38
  35. package/jest-strip-types.js +0 -10
  36. package/jest.config.js +0 -52
  37. package/src/build-runtime.ts +0 -78
  38. package/src/compiler.ts +0 -2593
  39. package/src/index.ts +0 -14
  40. package/src/minify.ts +0 -21
  41. package/src/options.ts +0 -18
  42. package/src/runtime.ts +0 -923
  43. package/src/template.ts +0 -141
  44. package/src/transforms/bytecode/aliasedOpcodes.ts +0 -148
  45. package/src/transforms/bytecode/concealConstants.ts +0 -52
  46. package/src/transforms/bytecode/dispatcher.ts +0 -398
  47. package/src/transforms/bytecode/macroOpcodes.ts +0 -193
  48. package/src/transforms/bytecode/microOpcodes.ts +0 -291
  49. package/src/transforms/bytecode/resolveLabels.ts +0 -112
  50. package/src/transforms/bytecode/resolveRegisters.ts +0 -221
  51. package/src/transforms/bytecode/selfModifying.ts +0 -121
  52. package/src/transforms/bytecode/specializedOpcodes.ts +0 -153
  53. package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
  54. package/src/transforms/runtime/internalVariables.ts +0 -270
  55. package/src/transforms/runtime/macroOpcodes.ts +0 -138
  56. package/src/transforms/runtime/microOpcodes.ts +0 -93
  57. package/src/transforms/runtime/minify.ts +0 -1
  58. package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
  59. package/src/transforms/runtime/specializedOpcodes.ts +0 -156
  60. package/src/types.ts +0 -93
  61. package/src/utils/op-utils.ts +0 -48
  62. package/src/utils/random-utils.ts +0 -31
  63. package/tsconfig.json +0 -12
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Simple bytecode disassembler for debugging.
3
+ *
4
+ * Takes the bytecode debug comment block (as generated by the compiler)
5
+ * and produces a flat pseudo-code listing with registers, gotos, and labels.
6
+ */
7
+
8
+ // Regex to match a single instruction line from the debug comment block.
9
+ // Groups: raw array, opcode name, annotation (rest of line after opcode name)
10
+ const INSTR_RE = /^\s*\/\/\s*\[([^\]]+)\],\s+(\w+)\s+(.*?)$/;
11
+
12
+ // Label line: // label_name:
13
+ const LABEL_RE = /^\s*\/\/\s*(\w+):$/;
14
+ function parseBlock(commentBlock) {
15
+ const lines = [];
16
+ for (const raw of commentBlock.split("\n")) {
17
+ const trimmed = raw.trim();
18
+ if (!trimmed || !trimmed.startsWith("//")) continue;
19
+ const instrMatch = trimmed.match(INSTR_RE);
20
+ if (instrMatch) {
21
+ const nums = instrMatch[1].split(",").map(s => parseInt(s.trim(), 10));
22
+ lines.push({
23
+ kind: "instr",
24
+ instr: {
25
+ raw: nums,
26
+ opName: instrMatch[2],
27
+ annotation: instrMatch[3].trim()
28
+ }
29
+ });
30
+ continue;
31
+ }
32
+ const labelMatch = trimmed.match(LABEL_RE);
33
+ if (labelMatch) {
34
+ lines.push({
35
+ kind: "label",
36
+ label: labelMatch[1]
37
+ });
38
+ }
39
+ }
40
+ return lines;
41
+ }
42
+
43
+ // Extract a label name from an annotation string like "[4, while_exit_8]" or "while_top_7"
44
+ // Also handles "PC=fn_1_1" for closures
45
+ function extractLabel(annotation) {
46
+ // Strip trailing source location info like "3:4-7:5"
47
+ const stripped = annotation.replace(/\s+\d+:\d+-\d+:\d+\s*$/, "").trim();
48
+ const bracketMatch = stripped.match(/\[\d+,\s*(\w+)\]/);
49
+ if (bracketMatch) return bracketMatch[1];
50
+ const pcMatch = stripped.match(/\bPC=(\w+)/);
51
+ if (pcMatch) return pcMatch[1];
52
+ const gotoMatch = stripped.match(/\bgoto\s+(\w+)/);
53
+ if (gotoMatch) return gotoMatch[1];
54
+
55
+ // Bare label at end: last whitespace-separated token
56
+ const lastToken = stripped.split(/\s+/).pop();
57
+ if (lastToken && /^[a-zA-Z_]\w*$/.test(lastToken)) return lastToken;
58
+ return null;
59
+ }
60
+ const ARITH_SYMBOLS = {
61
+ ADD: "+",
62
+ SUB: "-",
63
+ MUL: "*",
64
+ DIV: "/",
65
+ MOD: "%",
66
+ EXP: "**",
67
+ BAND: "&",
68
+ BOR: "|",
69
+ BXOR: "^",
70
+ SHL: "<<",
71
+ SHR: ">>",
72
+ USHR: ">>>"
73
+ };
74
+ const CMP_SYMBOLS = {
75
+ LT: "<",
76
+ GT: ">",
77
+ LTE: "<=",
78
+ GTE: ">=",
79
+ EQ: "===",
80
+ NEQ: "!==",
81
+ LOOSE_EQ: "==",
82
+ LOOSE_NEQ: "!=",
83
+ IN: "in",
84
+ INSTANCEOF: "instanceof"
85
+ };
86
+ const UNARY_SYMBOLS = {
87
+ UNARY_NEG: "-",
88
+ UNARY_POS: "+",
89
+ UNARY_NOT: "!",
90
+ UNARY_BITNOT: "~"
91
+ };
92
+
93
+ // Extract every identifier-like label inside the leading "[...]" of an
94
+ // annotation, in order, skipping pure numbers. Handles forms where the label
95
+ // comes first (e.g. "[catch_4, 0]" or "[finally_1, 1, 2, finally_throw_3]").
96
+ function extractBracketLabels(annotation) {
97
+ const stripped = annotation.replace(/\s+\d+:\d+-\d+:\d+\s*$/, "").trim();
98
+ const m = stripped.match(/\[([^\]]+)\]/);
99
+ if (!m) return [];
100
+ return m[1].split(",").map(s => s.trim()).filter(s => /^[a-zA-Z_]\w*$/.test(s));
101
+ }
102
+
103
+ // Extract trailing source location like "1:0-1:27" from an annotation
104
+ function extractSourceLoc(annotation) {
105
+ const m = annotation.match(/(\d+:\d+-\d+:\d+)\s*$/);
106
+ return m ? m[1] : null;
107
+ }
108
+
109
+ // Extract value from annotation like 'reg[1] = "Hello"' or 'reg[1] = 42'
110
+ function extractConstValue(annotation) {
111
+ const m = annotation.match(/=\s*(.+?)(?:\s+\d+:\d+-\d+:\d+)?$/);
112
+ return m ? m[1].trim() : null;
113
+ }
114
+ function disassembleInstr(instr) {
115
+ const {
116
+ raw,
117
+ opName,
118
+ annotation
119
+ } = instr;
120
+ const r = i => `r${raw[i]}`;
121
+ switch (opName) {
122
+ case "LOAD_CONST":
123
+ {
124
+ const val = extractConstValue(annotation);
125
+ return `${r(1)} = ${val ?? `const[${raw[2]}]`}`;
126
+ }
127
+ case "LOAD_INT":
128
+ return `${r(1)} = ${raw[2]}`;
129
+ case "LOAD_GLOBAL":
130
+ {
131
+ const val = extractConstValue(annotation);
132
+ return `${r(1)} = ${val ?? `global[${raw[2]}]`}`;
133
+ }
134
+ case "LOAD_UPVALUE":
135
+ return `${r(1)} = upvalue[${raw[2]}]`;
136
+ case "LOAD_THIS":
137
+ return `${r(1)} = this`;
138
+ case "MOVE":
139
+ return `${r(1)} = ${r(2)}`;
140
+ case "STORE_GLOBAL":
141
+ {
142
+ // raw: [constIdx, concealKey, srcReg]; annotation: name = reg[src]
143
+ const nameMatch = annotation.match(/^(.+?)\s*=\s*reg\[/);
144
+ const name = nameMatch ? `"${nameMatch[1].trim()}"` : `const[${raw[1]}]`;
145
+ return `global[${name}] = ${r(3)}`;
146
+ }
147
+ case "STORE_UPVALUE":
148
+ return `upvalue[${raw[1]}] = ${r(2)}`;
149
+ case "GET_PROP":
150
+ return `${r(1)} = ${r(2)}[${r(3)}]`;
151
+ case "SET_PROP":
152
+ return `${r(1)}[${r(2)}] = ${r(3)}`;
153
+ case "DELETE_PROP":
154
+ return `${r(1)} = delete ${r(2)}[${r(3)}]`;
155
+
156
+ // Arithmetic / bitwise
157
+ case "ADD":
158
+ case "SUB":
159
+ case "MUL":
160
+ case "DIV":
161
+ case "MOD":
162
+ case "EXP":
163
+ case "BAND":
164
+ case "BOR":
165
+ case "BXOR":
166
+ case "SHL":
167
+ case "SHR":
168
+ case "USHR":
169
+ return `${r(1)} = ${r(2)} ${ARITH_SYMBOLS[opName]} ${r(3)}`;
170
+
171
+ // Comparison
172
+ case "LT":
173
+ case "GT":
174
+ case "LTE":
175
+ case "GTE":
176
+ case "EQ":
177
+ case "NEQ":
178
+ case "LOOSE_EQ":
179
+ case "LOOSE_NEQ":
180
+ case "IN":
181
+ case "INSTANCEOF":
182
+ return `${r(1)} = ${r(2)} ${CMP_SYMBOLS[opName]} ${r(3)}`;
183
+
184
+ // Unary
185
+ case "UNARY_NEG":
186
+ case "UNARY_POS":
187
+ case "UNARY_NOT":
188
+ case "UNARY_BITNOT":
189
+ return `${r(1)} = ${UNARY_SYMBOLS[opName]}${r(2)}`;
190
+ case "TYPEOF":
191
+ return `${r(1)} = typeof ${r(2)}`;
192
+ case "VOID":
193
+ return `${r(1)} = void ${r(2)}`;
194
+ case "TYPEOF_SAFE":
195
+ {
196
+ const val = extractConstValue(annotation);
197
+ return `${r(1)} = typeof ${val ?? `safe[${raw[2]}]`}`;
198
+ }
199
+
200
+ // Control flow
201
+ case "JUMP":
202
+ {
203
+ const label = extractLabel(annotation);
204
+ return `goto: ${label ?? `pc:${raw[1]}`}`;
205
+ }
206
+ case "JUMP_IF_FALSE":
207
+ {
208
+ const label = extractLabel(annotation);
209
+ return `if (!${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
210
+ }
211
+ case "JUMP_IF_TRUE":
212
+ {
213
+ const label = extractLabel(annotation);
214
+ return `if (${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
215
+ }
216
+ case "JUMP_REG":
217
+ return `goto: *${r(1)}`;
218
+
219
+ // Calls
220
+ case "CALL":
221
+ {
222
+ const dst = r(1);
223
+ const callee = r(2);
224
+ const argc = raw[3];
225
+ const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
226
+ return `${dst} = ${callee}(${args.join(", ")})`;
227
+ }
228
+ case "CALL_METHOD":
229
+ {
230
+ const dst = r(1);
231
+ const recv = r(2);
232
+ const callee = r(3);
233
+ const argc = raw[4];
234
+ const args = raw.slice(5, 5 + argc).map((_, i) => `r${raw[5 + i]}`);
235
+ return `${dst} = ${callee}.call(${recv}, ${args.join(", ")})`;
236
+ }
237
+ case "NEW":
238
+ {
239
+ const dst = r(1);
240
+ const callee = r(2);
241
+ const argc = raw[3];
242
+ const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
243
+ return `${dst} = new ${callee}(${args.join(", ")})`;
244
+ }
245
+ case "RETURN":
246
+ return `return ${r(1)}`;
247
+ case "THROW":
248
+ return `throw ${r(1)}`;
249
+
250
+ // Closures
251
+ case "MAKE_CLOSURE":
252
+ {
253
+ const dst = r(1);
254
+ const startPc = raw[2];
255
+ const paramCount = raw[3];
256
+ const regCount = raw[4];
257
+ const uvCount = raw[5];
258
+ const label = extractLabel(annotation);
259
+ const uvParts = [];
260
+ for (let i = 0; i < uvCount; i++) {
261
+ const isLocal = raw[6 + i * 2];
262
+ const idx = raw[6 + i * 2 + 1];
263
+ uvParts.push(isLocal ? `local[${idx}]` : `uv[${idx}]`);
264
+ }
265
+ const uvStr = uvCount > 0 ? `, upvalues=[${uvParts.join(", ")}]` : "";
266
+ return `${dst} = MakeClosure(${label ?? `pc:${startPc}`}, params=${paramCount}, regs=${regCount}${uvStr})`;
267
+ }
268
+
269
+ // Collections
270
+ case "BUILD_ARRAY":
271
+ {
272
+ const dst = r(1);
273
+ const count = raw[2];
274
+ const elems = raw.slice(3, 3 + count).map((_, i) => `r${raw[3 + i]}`);
275
+ return `${dst} = [${elems.join(", ")}]`;
276
+ }
277
+ case "BUILD_OBJECT":
278
+ {
279
+ const dst = r(1);
280
+ const pairCount = raw[2];
281
+ const pairs = [];
282
+ for (let i = 0; i < pairCount; i++) {
283
+ pairs.push(`[r${raw[3 + i * 2]}]: r${raw[4 + i * 2]}`);
284
+ }
285
+ return `${dst} = {${pairs.join(", ")}}`;
286
+ }
287
+
288
+ // Property definitions
289
+ case "DEFINE_GETTER":
290
+ return `Object.defineGetter(${r(1)}, ${r(2)}, ${r(3)})`;
291
+ case "DEFINE_SETTER":
292
+ return `Object.defineSetter(${r(1)}, ${r(2)}, ${r(3)})`;
293
+
294
+ // For-in
295
+ case "FOR_IN_SETUP":
296
+ return `${r(1)} = ForInSetup(${r(2)})`;
297
+ case "FOR_IN_NEXT":
298
+ {
299
+ const label = extractLabel(annotation);
300
+ return `${r(1)} = ForInNext(${r(2)}) else goto ${label ?? `pc:${raw[3]}`}`;
301
+ }
302
+
303
+ // Exception handling.
304
+ // Emitted as plain call-expression statements (no braces) so the listing
305
+ // stays parseable as JavaScript for a flat, raw debugging view.
306
+ case "TRY_SETUP":
307
+ {
308
+ const catchLabel = extractBracketLabels(annotation)[0] ?? `pc_${raw[1]}`;
309
+ return `TrySetup(${catchLabel}, exReg=${r(2)})`;
310
+ }
311
+ case "TRY_END":
312
+ return `TryEnd()`;
313
+ case "FINALLY_SETUP":
314
+ {
315
+ const labels = extractBracketLabels(annotation);
316
+ const finallyLabel = labels[0] ?? `pc_${raw[1]}`;
317
+ const throwLabel = labels[1] ?? `pc_${raw[4]}`;
318
+ return `FinallySetup(${finallyLabel}, cont=${r(2)}, payload=${r(3)}, onThrow=${throwLabel})`;
319
+ }
320
+
321
+ // Self-modifying
322
+ case "PATCH":
323
+ return `patch(dest=pc:${raw[1]}, src=pc:${raw[2]}..${raw[3]})`;
324
+ case "DEBUGGER":
325
+ return `debugger`;
326
+ default:
327
+ return `??? ${opName} [${raw.join(", ")}]`;
328
+ }
329
+ }
330
+ export function disassembleCommentBlock(commentBlock) {
331
+ const lines = parseBlock(commentBlock);
332
+
333
+ // First pass: map function-entry labels to their param/reg counts from MAKE_CLOSURE
334
+ const funcMeta = new Map();
335
+ for (const line of lines) {
336
+ if (line.kind === "instr" && line.instr?.opName === "MAKE_CLOSURE") {
337
+ const label = extractLabel(line.instr.annotation);
338
+ if (label) {
339
+ funcMeta.set(label, {
340
+ params: line.instr.raw[3],
341
+ regs: line.instr.raw[4]
342
+ });
343
+ }
344
+ }
345
+ }
346
+ let bodies = [];
347
+ let currentBody = [];
348
+ for (const line of lines) {
349
+ if (line.kind === "label") {
350
+ const meta = funcMeta.get(line.label);
351
+ const paramList = meta ? `(${Array.from({
352
+ length: meta.params
353
+ }, (_, i) => `r${i}`).join(", ")})` : "";
354
+ let newBody = [];
355
+ newBody.push(`// ${line.label}${paramList}:`);
356
+ bodies.push(newBody);
357
+ currentBody = newBody;
358
+ } else if (line.instr) {
359
+ const instrText = disassembleInstr(line.instr);
360
+ const loc = extractSourceLoc(line.instr.annotation);
361
+ const outputLine = loc ? `${instrText.padEnd(50)} // ${loc}` : instrText;
362
+ currentBody.push(` ${outputLine}`);
363
+ }
364
+ }
365
+ const out = bodies.flatMap(body => body);
366
+ return out.join("\n");
367
+ }
package/dist/index.js CHANGED
@@ -1,10 +1,15 @@
1
1
  import { compileAndSerialize } from "./compiler.js";
2
2
  import { DEFAULT_OPTIONS } from "./options.js";
3
+ import { disassembleCommentBlock } from "./disassembler.js";
3
4
  async function obfuscate(source, options = DEFAULT_OPTIONS) {
4
- const result = compileAndSerialize(source, options);
5
+ const result = await compileAndSerialize(source, options);
5
6
  return result;
6
7
  }
8
+ async function disassemble(bytecodeComments) {
9
+ return disassembleCommentBlock(bytecodeComments);
10
+ }
7
11
  export const JsConfuserVM = {
8
- obfuscate
12
+ obfuscate,
13
+ disassemble
9
14
  };
10
15
  export default JsConfuserVM;