js-confuser-vm 0.0.5 → 0.0.6

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 (34) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/README.MD +186 -107
  3. package/dist/build-runtime.js +7 -1
  4. package/dist/compiler.js +801 -785
  5. package/dist/runtime.js +409 -332
  6. package/dist/transforms/bytecode/aliasedOpcodes.js +140 -0
  7. package/dist/transforms/bytecode/concealConstants.js +31 -0
  8. package/dist/transforms/bytecode/macroOpcodes.js +22 -10
  9. package/dist/transforms/bytecode/resolveContants.js +73 -10
  10. package/dist/transforms/bytecode/selfModifying.js +3 -2
  11. package/dist/transforms/bytecode/specializedOpcodes.js +38 -28
  12. package/dist/transforms/runtime/aliasedOpcodes.js +134 -0
  13. package/dist/transforms/runtime/shuffleOpcodes.js +1 -1
  14. package/dist/transforms/runtime/specializedOpcodes.js +21 -16
  15. package/dist/utils/op-utils.js +29 -0
  16. package/dist/utils/random-utils.js +27 -0
  17. package/index.ts +10 -8
  18. package/jest.config.js +10 -0
  19. package/package.json +1 -1
  20. package/src/build-runtime.ts +7 -1
  21. package/src/compiler.ts +2395 -2069
  22. package/src/options.ts +2 -0
  23. package/src/runtime.ts +838 -771
  24. package/src/transforms/bytecode/aliasedOpcodes.ts +158 -0
  25. package/src/transforms/bytecode/concealConstants.ts +52 -0
  26. package/src/transforms/bytecode/macroOpcodes.ts +32 -15
  27. package/src/transforms/bytecode/resolveContants.ts +87 -16
  28. package/src/transforms/bytecode/selfModifying.ts +3 -3
  29. package/src/transforms/bytecode/specializedOpcodes.ts +58 -29
  30. package/src/transforms/runtime/aliasedOpcodes.ts +191 -0
  31. package/src/transforms/runtime/shuffleOpcodes.ts +1 -1
  32. package/src/transforms/runtime/specializedOpcodes.ts +39 -24
  33. package/src/{transforms/utils → utils}/op-utils.ts +7 -0
  34. /package/src/{transforms/utils → utils}/random-utils.ts +0 -0
@@ -19,20 +19,28 @@ function extractCaseBody(switchCase) {
19
19
  // Because specialized opcodes are only created for instructions that have
20
20
  // *exactly one* numeric operand, every `_operand()` call inside the original
21
21
  // handler is replaced by the constant value that was baked into the opcode.
22
- function inlineFixedOperand(bodyStmts, resolvedValue) {
22
+ function inlineFixedOperands(bodyStmts, resolvedValues) {
23
23
  // Wrap the statements in a temporary BlockStatement so traverse has a root.
24
24
  // The replacement mutates the original statement objects in place.
25
+ var replaced = 0;
25
26
  traverse(t.blockStatement(bodyStmts), {
26
27
  noScope: true,
27
28
  CallExpression(path) {
28
29
  const callee = path.node.callee;
29
- if (t.isMemberExpression(callee) && t.isThisExpression(callee.object) && t.isIdentifier(callee.property, {
30
- name: "_operand"
31
- }) && path.node.arguments.length === 0) {
32
- path.replaceWith(t.numericLiteral(resolvedValue));
30
+ const isMethodCall = methodName => {
31
+ return t.isMemberExpression(callee) && t.isThisExpression(callee.object) && t.isIdentifier(callee.property, {
32
+ name: methodName
33
+ }) && path.node.arguments.length === 0;
34
+ };
35
+ if (isMethodCall("_operand")) {
36
+ path.replaceWith(t.numericLiteral(resolvedValues[replaced++]));
37
+ }
38
+ if (isMethodCall("_constant")) {
39
+ path.node.arguments = [t.numericLiteral(resolvedValues[replaced++]), t.numericLiteral(resolvedValues[replaced++])];
33
40
  }
34
41
  }
35
42
  });
43
+ ok(replaced === resolvedValues.length, `Expected to replace ${resolvedValues.length} operands, but replaced ${replaced}`);
36
44
  }
37
45
 
38
46
  // Append a generated switch case for every entry in compiler.SPECIALIZED_OPS.
@@ -67,7 +75,7 @@ export function applySpecializedOpcodes(ast, bytecode, compiler) {
67
75
  const specialOpCode = Number(specialOpStr);
68
76
  const {
69
77
  originalOp,
70
- operand
78
+ operands
71
79
  } = info;
72
80
  const newName = compiler.OP_NAME[specialOpCode];
73
81
  const originalName = compiler.OP_NAME[originalOp];
@@ -77,18 +85,15 @@ export function applySpecializedOpcodes(ast, bytecode, compiler) {
77
85
 
78
86
  // Clone the original handler body
79
87
  const bodyStmts = extractCaseBody(originalCase).map(s => t.cloneNode(s, true));
80
- const placedOperand = info.resolvedOperand || {
81
- resolvedValue: 1337
82
- };
83
- ok(placedOperand !== undefined, `Could not find operand for original opcode ${newName}`);
84
- const resolvedValue = placedOperand?.resolvedValue ?? placedOperand;
85
- if (typeof resolvedValue !== "number") {
86
- console.error(resolvedValue);
87
- }
88
- ok(typeof resolvedValue === "number", "Expected a numeric operand value");
88
+ const placedOperands = info.resolvedOperands;
89
+ ok(placedOperands, `Could not find operand for original opcode ${newName}`);
90
+ const resolvedValues = placedOperands.map(placedOperand => {
91
+ return placedOperand?.resolvedValue ?? placedOperand;
92
+ });
93
+ ok(!resolvedValues.find(v => typeof v !== "number"), "Expected a numeric operand value");
89
94
 
90
95
  // Replace this._operand() with the baked-in constant
91
- inlineFixedOperand(bodyStmts, resolvedValue);
96
+ inlineFixedOperands(bodyStmts, resolvedValues);
92
97
 
93
98
  // Add a leading comment so the generated source stays readable
94
99
  if (bodyStmts.length > 0) {
@@ -0,0 +1,29 @@
1
+ import { getRandomInt } from "./random-utils.js";
2
+ export const U16_MAX = 0xffff; // bytecode operands are u16
3
+
4
+ /** Returns the next free opcode slot, or -1 when the space is exhausted. */
5
+ export function nextFreeSlot(usedOpcodes) {
6
+ if (usedOpcodes.size > U16_MAX) return -1;
7
+ let attempts = 0;
8
+ while (attempts++ < 512) {
9
+ const candidate = getRandomInt(0, U16_MAX);
10
+ if (!usedOpcodes.has(candidate)) {
11
+ usedOpcodes.add(candidate);
12
+ return candidate;
13
+ }
14
+ }
15
+ // Fallback: linear scan from a random start
16
+ const start = getRandomInt(0, U16_MAX);
17
+ for (let i = 0; i <= U16_MAX; i++) {
18
+ const v = start + i & U16_MAX;
19
+ if (!usedOpcodes.has(v)) {
20
+ usedOpcodes.add(v);
21
+ return v;
22
+ }
23
+ }
24
+ return -1;
25
+ }
26
+ export function getInstructionSize(instr) {
27
+ const size = instr.filter(op => op?.placeholder !== true).length;
28
+ return size;
29
+ }
@@ -0,0 +1,27 @@
1
+ import { ok } from "assert";
2
+ export function getPlaceholder() {
3
+ return Math.random().toString(36).substring(2, 15);
4
+ }
5
+ export function choice(elements) {
6
+ ok(elements.length > 0, "choice() called on empty sequence");
7
+ return elements[Math.floor(Math.random() * elements.length)];
8
+ }
9
+ export function getRandom() {
10
+ return Math.random();
11
+ }
12
+ export function getRandomInt(min, max) {
13
+ ok(min <= max, "min must be <= max");
14
+ return Math.floor(Math.random() * (max - min + 1)) + min;
15
+ }
16
+
17
+ /**
18
+ * Shuffles an array in-place using the Fisher-Yates algorithm.
19
+ * @param array - The array to shuffle (mutated)
20
+ */
21
+ export function shuffle(array) {
22
+ for (let i = array.length - 1; i > 0; i--) {
23
+ const j = Math.floor(Math.random() * (i + 1));
24
+ [array[i], array[j]] = [array[j], array[i]];
25
+ }
26
+ return array;
27
+ }
package/index.ts CHANGED
@@ -9,14 +9,16 @@ async function main() {
9
9
 
10
10
  const { code: output } = await JsConfuserVM.obfuscate(sourceCode, {
11
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 bytecode? when off, comments for instructions are added
15
- // selfModifying: true, // do self-modifying bytecode for function bodies?
16
- // macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
17
- // specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
18
- // timingChecks: true, // add timing checks to detect debuggers?
19
- // minify: true, // pass final output through Google Closure Compiler? (
12
+ randomizeOpcodes: true, // randomize the opcode numbers?
13
+ shuffleOpcodes: true, // shuffle order of opcode handlers in the runtime?
14
+ encodeBytecode: true, // encode bytecode? when off, comments for instructions are added
15
+ selfModifying: true, // do self-modifying bytecode for function bodies?
16
+ macroOpcodes: true, // create combined opcodes for repeated instruction sequences?
17
+ specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
18
+ aliasedOpcodes: true, // create duplicate opcodes for commonly used opcodes?
19
+ timingChecks: true, // add timing checks to detect debuggers?
20
+ minify: true, // pass final output through Google Closure Compiler? (Renames VM class properties)
21
+ concealConstants: true,
20
22
  });
21
23
 
22
24
  writeFileSync("output.original.js", orginalOutput, "utf-8");
package/jest.config.js CHANGED
@@ -10,6 +10,14 @@ const OPTIONS_MATRIX = [
10
10
  displayName: "specializedOpcodes",
11
11
  VM_OPTIONS: { specializedOpcodes: true },
12
12
  },
13
+ {
14
+ displayName: "aliasedOpcodes",
15
+ VM_OPTIONS: { aliasedOpcodes: true },
16
+ },
17
+ {
18
+ displayName: "concealConstants",
19
+ VM_OPTIONS: { concealConstants: true },
20
+ },
13
21
  {
14
22
  displayName: "all",
15
23
  VM_OPTIONS: {
@@ -20,6 +28,8 @@ const OPTIONS_MATRIX = [
20
28
  timingChecks: true,
21
29
  macroOpcodes: true,
22
30
  specializedOpcodes: true,
31
+ aliasedOpcodes: true,
32
+ concealConstants: true,
23
33
  },
24
34
  },
25
35
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-confuser-vm",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "babel src --out-dir dist --extensions \".ts,.js\"",
@@ -7,6 +7,7 @@ import { applyShuffleOpcodes } from "./transforms/runtime/shuffleOpcodes.ts";
7
7
  import { applyMinify } from "./transforms/runtime/minify.ts";
8
8
  import { Compiler } from "./compiler.ts";
9
9
  import { applySpecializedOpcodes } from "./transforms/runtime/specializedOpcodes.ts";
10
+ import { applyAliasedOpcodes } from "./transforms/runtime/aliasedOpcodes.ts";
10
11
  import type * as b from "./types.ts";
11
12
 
12
13
  export async function obfuscateRuntime(
@@ -22,7 +23,7 @@ export async function obfuscateRuntime(
22
23
  throw new Error("VM-Runtime final parsing failed", { cause: error });
23
24
  }
24
25
 
25
- // Macro opcode cases must be applied BEFORE shuffleOpcodes
26
+ // Specialized opcode cases must be applied BEFORE shuffleOpcodes
26
27
  if (options.specializedOpcodes) {
27
28
  applySpecializedOpcodes(ast, bytecode, compiler);
28
29
  }
@@ -32,6 +33,11 @@ export async function obfuscateRuntime(
32
33
  applyMacroOpcodes(ast, compiler);
33
34
  }
34
35
 
36
+ // Aliased opcode cases must be applied BEFORE shuffleOpcodes
37
+ if (options.aliasedOpcodes) {
38
+ applyAliasedOpcodes(ast, compiler);
39
+ }
40
+
35
41
  // Shuffle opcode handle order
36
42
  if (options.shuffleOpcodes) {
37
43
  applyShuffleOpcodes(ast);