js-confuser-vm 0.1.0 → 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 (58) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +75 -94
  3. package/bench.ts +146 -0
  4. package/disassemble.ts +12 -0
  5. package/dist/build-runtime.js +41 -15
  6. package/dist/compiler.js +134 -60
  7. package/dist/disassembler.js +317 -0
  8. package/dist/index.js +7 -2
  9. package/dist/runtime.js +68 -46
  10. package/dist/template.js +116 -0
  11. package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
  12. package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
  13. package/dist/transforms/bytecode/dispatcher.js +13 -109
  14. package/dist/transforms/bytecode/macroOpcodes.js +2 -2
  15. package/dist/transforms/bytecode/resolveConstants.js +100 -0
  16. package/dist/transforms/bytecode/resolveRegisters.js +4 -0
  17. package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
  18. package/dist/transforms/bytecode/specializedOpcodes.js +18 -10
  19. package/dist/transforms/bytecode/stringConcealing.js +110 -0
  20. package/dist/transforms/runtime/classObfuscation.js +43 -0
  21. package/dist/transforms/runtime/handlerTable.js +91 -0
  22. package/dist/transforms/runtime/semanticOpcodes.js +35 -0
  23. package/dist/transforms/runtime/specializedOpcodes.js +11 -5
  24. package/dist/types.js +1 -1
  25. package/dist/utils/ast-utils.js +14 -0
  26. package/dist/utils/op-utils.js +0 -2
  27. package/dist/utils/pass-utils.js +100 -0
  28. package/dist/utils/profile-utils.js +3 -0
  29. package/index.ts +22 -17
  30. package/jest.config.js +14 -2
  31. package/output.disassembled.js +41 -0
  32. package/package.json +2 -1
  33. package/src/build-runtime.ts +113 -78
  34. package/src/compiler.ts +2703 -2593
  35. package/src/disassembler.ts +329 -0
  36. package/src/index.ts +12 -2
  37. package/src/options.ts +7 -1
  38. package/src/runtime.ts +84 -51
  39. package/src/template.ts +125 -1
  40. package/src/transforms/bytecode/aliasedOpcodes.ts +4 -1
  41. package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
  42. package/src/transforms/bytecode/dispatcher.ts +19 -125
  43. package/src/transforms/bytecode/macroOpcodes.ts +2 -2
  44. package/src/transforms/bytecode/resolveRegisters.ts +5 -0
  45. package/src/transforms/bytecode/specializedOpcodes.ts +22 -11
  46. package/src/transforms/bytecode/stringConcealing.ts +130 -0
  47. package/src/transforms/runtime/classObfuscation.ts +59 -0
  48. package/src/transforms/runtime/specializedOpcodes.ts +14 -9
  49. package/src/types.ts +42 -1
  50. package/src/utils/ast-utils.ts +19 -0
  51. package/src/utils/op-utils.ts +0 -2
  52. package/src/utils/pass-utils.ts +126 -0
  53. package/src/utils/profile-utils.ts +3 -0
  54. package/tsconfig.json +1 -1
  55. package/src/transforms/bytecode/microOpcodes.ts +0 -291
  56. package/src/transforms/runtime/internalVariables.ts +0 -270
  57. package/src/transforms/runtime/microOpcodes.ts +0 -93
  58. /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
@@ -0,0 +1,43 @@
1
+ import * as t from "@babel/types";
2
+ import { shuffle } from "../../utils/random-utils.js";
3
+ function hasComment(node, text) {
4
+ const all = [...(node.leadingComments ?? []), ...(node.innerComments ?? []), ...(node.trailingComments ?? [])];
5
+ return all.some(c => c.value.includes(text));
6
+ }
7
+ function isPrototypeAssignment(stmt) {
8
+ if (!t.isExpressionStatement(stmt)) return false;
9
+ const expr = stmt.expression;
10
+ if (!t.isAssignmentExpression(expr)) return false;
11
+ const left = expr.left;
12
+ return t.isMemberExpression(left) && t.isMemberExpression(left.object) && t.isIdentifier(left.object.property, {
13
+ name: "prototype"
14
+ });
15
+ }
16
+ export function applyClassObfuscation(ast, _compiler) {
17
+ const body = ast.program.body;
18
+
19
+ // Split at the first statement that carries the @BOOT comment.
20
+ // Everything from that statement onward is the boot section and must stay last.
21
+ let bootIdx = body.findIndex(stmt => hasComment(stmt, "@BOOT"));
22
+ if (bootIdx === -1) bootIdx = body.length;
23
+ const shufflable = body.slice(0, bootIdx);
24
+ const boot = body.slice(bootIdx);
25
+
26
+ // Partition the shufflable section into two independent groups.
27
+ // Group A: variable/function declarations (constructors, standalone vars).
28
+ // Group B: prototype method assignments (X.prototype.Y = ...).
29
+ // Both groups are shuffled independently; A always precedes B so that
30
+ // constructors are defined before methods reference them.
31
+ const varDecls = [];
32
+ const methodDefs = [];
33
+ for (const stmt of shufflable) {
34
+ if (isPrototypeAssignment(stmt)) {
35
+ methodDefs.push(stmt);
36
+ } else {
37
+ varDecls.push(stmt);
38
+ }
39
+ }
40
+ shuffle(varDecls);
41
+ shuffle(methodDefs);
42
+ ast.program.body = [...varDecls, ...methodDefs, ...boot];
43
+ }
@@ -0,0 +1,91 @@
1
+ import * as t from "@babel/types";
2
+ import traverseImport from "@babel/traverse";
3
+ import { ok } from "assert";
4
+ import { parse } from "@babel/parser";
5
+ const traverse = traverseImport.default || traverseImport;
6
+
7
+ // Converts the switch-case dispatch into a handler table:
8
+ //
9
+ // Before (in .run):
10
+ // switch(op) { case OP.ADD: { ... break; } default: { ... } }
11
+ //
12
+ // After (in .init):
13
+ // this[OP.ADD] = function(){ ... }
14
+ // this["default"] = function(){ ... }
15
+ //
16
+ // After (in .run, replacing the switch):
17
+ // if(!this[op]) this["default"]();
18
+ // else this[op]();
19
+ //
20
+ export function applyHandlerTable(ast) {
21
+ // 1. Find the @SWITCH statement
22
+ let switchPath = null;
23
+ traverse(ast, {
24
+ SwitchStatement(path) {
25
+ if (path.node.leadingComments?.some(c => c.value.includes("@SWITCH"))) {
26
+ switchPath = path;
27
+ path.stop();
28
+ }
29
+ }
30
+ });
31
+ ok(switchPath, "Could not find opcode handlers switch statement");
32
+ const switchNode = switchPath.node;
33
+ const discriminant = switchNode.discriminant; // `op`
34
+
35
+ // 2. Find the @INIT method
36
+ let initPath = null;
37
+ traverse(ast, {
38
+ BlockStatement(path) {
39
+ if (path.node.innerComments?.some(c => c.value.includes("@INIT"))) {
40
+ initPath = path;
41
+ path.stop();
42
+ }
43
+ }
44
+ });
45
+ ok(initPath, "Could not find @INIT method");
46
+ const initFn = initPath.parentPath;
47
+
48
+ // 3. Build handler assignments for each case
49
+ const handlerAssignments = [];
50
+ for (const switchCase of switchNode.cases) {
51
+ // Strip trailing `break` from body
52
+ let body = [...switchCase.consequent];
53
+ if (body.length === 1 && t.isBlockStatement(body[0])) {
54
+ body = body[0].body;
55
+ }
56
+ if (body.length > 0 && t.isBreakStatement(body[body.length - 1])) {
57
+ body.pop();
58
+ }
59
+ body.unshift(...parse("var frame = this._currentFrame; var base = frame._base; var pc = frame._pc - 1; var regs = this._regs; ").program.body);
60
+ const block = t.blockStatement(body);
61
+ traverse(block, {
62
+ noScope: true,
63
+ ThisExpression(path) {
64
+ path.replaceWith(t.identifier("_this"));
65
+ },
66
+ Function(path) {
67
+ path.skip();
68
+ }
69
+ });
70
+
71
+ // Key: the case test, or "default" for the default case
72
+ const key = switchCase.test ? switchCase.test : t.stringLiteral("default");
73
+
74
+ // this[key] = function(){ ...body }
75
+ const handlerFn = t.functionExpression(null, [], block);
76
+ const assignment = t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.thisExpression(), key, true), handlerFn));
77
+ handlerAssignments.push(assignment);
78
+ }
79
+
80
+ // 4. Inject handler assignments into the @INIT body
81
+ initPath.node.body = handlerAssignments;
82
+
83
+ // 5. Replace the switch statement with handler dispatch:
84
+ // if(!this[op]) this["default"]();
85
+ // else this[op]();
86
+ const thisLookup = t.memberExpression(t.thisExpression(), discriminant, true);
87
+ const defaultCall = t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.stringLiteral("default"), true), []));
88
+ const handlerCall = t.expressionStatement(t.callExpression(thisLookup, []));
89
+ const dispatch = t.ifStatement(t.unaryExpression("!", thisLookup), t.blockStatement([defaultCall]), t.blockStatement([handlerCall]));
90
+ switchPath.replaceWith(dispatch);
91
+ }
@@ -0,0 +1,35 @@
1
+ import { parse } from "@babel/parser";
2
+ import * as t from "@babel/types";
3
+ import traverseImport from "@babel/traverse";
4
+ import { ok } from "assert";
5
+ const traverse = traverseImport.default || traverseImport;
6
+ function parseStatements(code) {
7
+ const parsed = parse(code, {
8
+ sourceType: "unambiguous"
9
+ });
10
+ const [statement] = parsed.program.body;
11
+ ok(statement && t.isBlockStatement(statement), "Expected semantic opcode block");
12
+ return statement.body;
13
+ }
14
+ export function applySemanticOpcodes(ast, compiler) {
15
+ let switchStatement = null;
16
+ traverse(ast, {
17
+ SwitchStatement(path) {
18
+ if (path.node.leadingComments?.some(c => c.value.includes("@SWITCH"))) {
19
+ switchStatement = path.node;
20
+ path.stop();
21
+ }
22
+ }
23
+ });
24
+ ok(switchStatement, "Could not find @SWITCH statement for semantic opcodes");
25
+ for (const [semanticOpStr, info] of Object.entries(compiler.SEMANTIC_OPS)) {
26
+ const semanticOpCode = Number(semanticOpStr);
27
+ const originalName = compiler.OP_NAME[info.originalOp] ?? `OP_${info.originalOp}`;
28
+ const bodyStmts = parseStatements(info.code).map(statement => t.cloneNode(statement, true));
29
+ if (bodyStmts.length > 0) {
30
+ t.addComment(bodyStmts[0], "leading", ` ${compiler.OP_NAME[semanticOpCode]} -> ${originalName}`, true);
31
+ }
32
+ bodyStmts.push(t.breakStatement());
33
+ switchStatement.cases.push(t.switchCase(t.numericLiteral(semanticOpCode), [t.blockStatement(bodyStmts)]));
34
+ }
35
+ }
@@ -20,7 +20,9 @@ function extractCaseBody(switchCase) {
20
20
  // Because specialized opcodes are only created for instructions that have
21
21
  // *exactly one* numeric operand, every `_operand()` call inside the original
22
22
  // handler is replaced by the constant value that was baked into the opcode.
23
- function inlineFixedOperands(bodyStmts, resolvedValues) {
23
+ function inlineFixedOperands(newName,
24
+ // for debugging
25
+ info, bodyStmts, resolvedValues) {
24
26
  // Wrap the statements in a temporary BlockStatement so traverse has a root.
25
27
  // The replacement mutates the original statement objects in place.
26
28
  var replaced = 0;
@@ -46,7 +48,10 @@ function inlineFixedOperands(bodyStmts, resolvedValues) {
46
48
  }
47
49
  }
48
50
  });
49
- ok(replaced === resolvedValues.length, `Expected to replace ${resolvedValues.length} operands, but replaced ${replaced}`);
51
+ if (replaced !== resolvedValues.length) {
52
+ console.error(resolvedValues, info);
53
+ throw new Error(`Specialized Opcode Inline Error: Given ${resolvedValues.length} operands to replace, but only found ${replaced} for ${newName}`);
54
+ }
50
55
  }
51
56
 
52
57
  // Append a generated switch case for every entry in compiler.SPECIALIZED_OPS.
@@ -84,18 +89,19 @@ export function applySpecializedOpcodes(ast, compiler) {
84
89
  const bodyStmts = extractCaseBody(originalCase).map(s => t.cloneNode(s, true));
85
90
  const placedOperands = info.operands;
86
91
  ok(placedOperands, `Could not find operand for original opcode ${newName}`);
87
- const resolvedValues = placedOperands.map(placedOperand => {
92
+ const resolvedValues = placedOperands
93
+ // .filter((x) => !(x as any)?.placeholder)
94
+ .map(placedOperand => {
88
95
  return placedOperand?.resolvedValue ?? placedOperand;
89
96
  });
90
97
  if (resolvedValues.find(v => typeof v !== "number")) {
91
- console.error(info);
92
98
  throw new Error("Expected all resolved operand values to be numbers");
93
99
  }
94
100
  newName = `${originalName}_${resolvedValues.join("_")}`;
95
101
  compiler.OP_NAME[specialOpCode] = newName;
96
102
 
97
103
  // Replace this._operand() with the baked-in constant
98
- inlineFixedOperands(bodyStmts, resolvedValues);
104
+ inlineFixedOperands(newName, info, bodyStmts, resolvedValues);
99
105
 
100
106
  // Add a leading comment so the generated source stays readable
101
107
  if (bodyStmts.length > 0) {
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Bytecode supports both real instructions and IR pseudo-instructions
2
- // Real instruction: [OP.ADD, 5] or multi-operand: [OP.MAKE_CLOSURE, labelRef, 2, 3, 0]
2
+ // Real instruction: [OP.ADD, 5] or multi-operand: [OP.MAKE_CLOSURE, labelRef, 2, 3, 0, 0]
3
3
  // IR instruction: [null, { type: "defineLabel", label: "FN_ENTRY_1" }]
4
4
 
5
5
  // IR instructions are used to hold symbolic information during compilation
@@ -0,0 +1,14 @@
1
+ import traverseImport from "@babel/traverse";
2
+ const traverse = traverseImport.default || traverseImport;
3
+ export function getSwitchStatement(ast) {
4
+ let switchStatement = null;
5
+ traverse(ast, {
6
+ SwitchStatement(path) {
7
+ if (path.node.leadingComments?.some(c => c.value.includes("@SWITCH"))) {
8
+ switchStatement = path.node;
9
+ path.stop();
10
+ }
11
+ }
12
+ });
13
+ return switchStatement;
14
+ }
@@ -13,7 +13,6 @@ export function nextFreeSlot(compiler) {
13
13
  while (attempts++ < 512) {
14
14
  const candidate = getRandomInt(0, U16_MAX);
15
15
  if (!usedOpcodes.has(candidate)) {
16
- usedOpcodes.add(candidate);
17
16
  return candidate;
18
17
  }
19
18
  }
@@ -24,7 +23,6 @@ export function nextFreeSlot(compiler) {
24
23
  for (let i = 0; i <= U16_MAX; i++) {
25
24
  const v = start + i & U16_MAX;
26
25
  if (!usedOpcodes.has(v)) {
27
- usedOpcodes.add(v);
28
26
  return v;
29
27
  }
30
28
  }
@@ -0,0 +1,100 @@
1
+ // Shared utilities for bytecode transformation passes.
2
+ //
3
+ // All three patterns below are identical across dispatcher, controlFlowFlattening,
4
+ // and stringConcealing. Centralising them here keeps each pass focused on its
5
+ // own logic and makes the shared contract explicit.
6
+
7
+ import * as b from "../types.js";
8
+ // Return a fresh RegisterOperand object with the same (id, fnId).
9
+ // IMPORTANT: operand objects must be unique throughout compilation —
10
+ // other passes (e.g. specializedOpcodes) mutate operands in-place and a
11
+ // shared reference would corrupt both sites.
12
+ export function ref(r) {
13
+ return b.registerOperand(r.id, r.fnId);
14
+ }
15
+
16
+ // Scan bc and return the highest virtual register id seen for each fnId.
17
+ // Used by passes that allocate new registers after the compiler has finished.
18
+ export function buildMaxIdMap(bc) {
19
+ const maxId = new Map();
20
+ for (const instr of bc) {
21
+ for (let j = 1; j < instr.length; j++) {
22
+ const op = instr[j];
23
+ if (op && op.type === "register") {
24
+ const cur = maxId.get(op.fnId) ?? -1;
25
+ if (op.id > cur) maxId.set(op.fnId, op.id);
26
+ }
27
+ }
28
+ }
29
+ return maxId;
30
+ }
31
+
32
+ // Allocate the next virtual register id for fnId, updating maxId in-place.
33
+ export function allocReg(fnId, maxId) {
34
+ const next = (maxId.get(fnId) ?? -1) + 1;
35
+ maxId.set(fnId, next);
36
+ return b.registerOperand(next, fnId);
37
+ }
38
+
39
+ // Return the label string if the operand is a { type:"label" } object,
40
+ // otherwise return null. Used by passes that need to identify jump targets.
41
+ export function extractLabel(op) {
42
+ if (op && typeof op === "object" && op.type === "label") return op.label;
43
+ return null;
44
+ }
45
+
46
+ // Walk bc, call transform() for every function body, and reassemble the output.
47
+ //
48
+ // For each function entry label the scanner collects all instructions up to the
49
+ // next entry label (or end-of-bytecode) into fnInstrs and passes them to
50
+ // transform() along with the function's fnId.
51
+ //
52
+ // The transform callback returns:
53
+ // instrs — the (possibly rewritten) function body to emit in place of fnInstrs
54
+ // tail — optional bytecode to append AFTER all function bodies
55
+ // (e.g. template-compiled decode closures)
56
+ //
57
+ // Instructions that appear before any entry label (the top-level preamble) are
58
+ // passed through unchanged.
59
+ export function forEachFunction(bc, compiler, transform) {
60
+ const entryLabels = new Set(compiler.fnDescriptors.map(d => d.entryLabel));
61
+ const entryLabelToFnId = new Map(compiler.fnDescriptors.map(d => [d.entryLabel, d._fnIdx]));
62
+ const result = [];
63
+ const tails = [];
64
+ let i = 0;
65
+ while (i < bc.length) {
66
+ const instr = bc[i];
67
+ const [op, operand0] = instr;
68
+ const isEntryLabel = op === null && operand0?.type === "defineLabel" && entryLabels.has(operand0.label);
69
+ if (!isEntryLabel) {
70
+ result.push(instr);
71
+ i++;
72
+ continue;
73
+ }
74
+ const entryLabel = operand0.label;
75
+ const fnId = entryLabelToFnId.get(entryLabel);
76
+ i++; // step past the defineLabel itself
77
+
78
+ const fnInstrs = [];
79
+ while (i < bc.length) {
80
+ const next = bc[i];
81
+ const [nextOp, nextOp0] = next;
82
+ if (nextOp === null && nextOp0?.type === "defineLabel" && entryLabels.has(nextOp0.label)) break;
83
+ fnInstrs.push(next);
84
+ i++;
85
+ }
86
+ result.push(instr); // emit the entry defineLabel
87
+ const {
88
+ instrs,
89
+ tail
90
+ } = transform(fnInstrs, fnId);
91
+ result.push(...instrs);
92
+ if (tail && tail.length > 0) tails.push(tail);
93
+ }
94
+ for (const tail of tails) {
95
+ result.push(...tail);
96
+ }
97
+ return {
98
+ bytecode: result
99
+ };
100
+ }
@@ -0,0 +1,3 @@
1
+ export function now() {
2
+ return performance?.now() || Date.now();
3
+ }
package/index.ts CHANGED
@@ -7,32 +7,37 @@ async function main() {
7
7
 
8
8
  const { code: orginalOutput } = await JsConfuserVM.obfuscate(sourceCode, {});
9
9
 
10
- const { code: output } = await JsConfuserVM.obfuscate(sourceCode, {
10
+ const result = 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 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
- microOpcodes: true, // break opcodes into sub-opcodes?
20
- specializedOpcodes: true, // create specialized opcodes for commonly used opcode+operand pairs?
21
- aliasedOpcodes: true, // create duplicate opcodes for commonly used opcodes?
22
- timingChecks: true, // add timing checks to detect debuggers?
23
- minify: true, // pass final output through Google Closure Compiler? (Renames VM class properties)
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,
24
27
  });
25
28
 
26
29
  writeFileSync("output.original.js", orginalOutput, "utf-8");
27
- writeFileSync("output.js", output, "utf-8");
30
+ writeFileSync("output.js", result.code, "utf-8");
31
+
32
+ console.log("Saved");
28
33
 
29
34
  // Eval the code like our test suite does
30
35
  var window = { TEST_OUTPUT: null };
31
- eval(output);
36
+ eval(result.code);
32
37
  console.log(window.TEST_OUTPUT);
33
38
 
34
- // Minify using Google Closure Compiler (optional)
35
- // import("./minify.js");
39
+ delete result.code;
40
+ console.log(JSON.stringify(result));
36
41
  }
37
42
 
38
43
  main();
package/jest.config.js CHANGED
@@ -6,7 +6,6 @@ const OPTIONS_MATRIX = [
6
6
  { displayName: "selfModifying", VM_OPTIONS: { selfModifying: true } },
7
7
  { displayName: "timingChecks", VM_OPTIONS: { timingChecks: true } },
8
8
  { displayName: "macroOpcodes", VM_OPTIONS: { macroOpcodes: true } },
9
- { displayName: "microOpcodes", VM_OPTIONS: { microOpcodes: true } },
10
9
  {
11
10
  displayName: "specializedOpcodes",
12
11
  VM_OPTIONS: { specializedOpcodes: true },
@@ -23,6 +22,18 @@ const OPTIONS_MATRIX = [
23
22
  displayName: "dispatcher",
24
23
  VM_OPTIONS: { dispatcher: true },
25
24
  },
25
+ {
26
+ displayName: "controlFlowFlattening",
27
+ VM_OPTIONS: { controlFlowFlattening: true },
28
+ },
29
+ {
30
+ displayName: "stringConcealing",
31
+ VM_OPTIONS: { stringConcealing: true },
32
+ },
33
+ {
34
+ displayName: "classObfuscation",
35
+ VM_OPTIONS: { classObfuscation: true },
36
+ },
26
37
  {
27
38
  displayName: "all",
28
39
  VM_OPTIONS: {
@@ -32,11 +43,12 @@ const OPTIONS_MATRIX = [
32
43
  selfModifying: true,
33
44
  timingChecks: true,
34
45
  macroOpcodes: true,
35
- microOpcodes: true,
36
46
  specializedOpcodes: true,
37
47
  aliasedOpcodes: true,
38
48
  concealConstants: true,
39
49
  dispatcher: true,
50
+ stringConcealing: true,
51
+ classObfuscation: true,
40
52
  },
41
53
  },
42
54
  ];
@@ -0,0 +1,41 @@
1
+ // fn_0_0:
2
+ r1 = 969
3
+ r2 = r1
4
+ // while_top_5:
5
+ r3 = 4439
6
+ r4 = r2 !== r3
7
+ if (!r4) goto: while_exit_6
8
+ r5 = 969
9
+ r6 = r2 === r5
10
+ if (!r6) goto: if_else_7
11
+ goto: cff_block_2
12
+ // if_else_7:
13
+ r7 = 1317
14
+ r8 = r2 === r7
15
+ if (!r8) goto: if_else_8
16
+ goto: cff_block_3
17
+ // if_else_8:
18
+ r9 = 58894
19
+ r10 = r2 === r9
20
+ if (!r10) goto: if_else_9
21
+ goto: if_else_1
22
+ // if_else_9:
23
+ goto: while_top_5
24
+ // while_exit_6:
25
+ // cff_block_3:
26
+ r11 = "Hello World"
27
+ r0 = r11
28
+ r2 = 58894
29
+ goto: while_top_5
30
+ // if_else_1:
31
+ r11 = undefined
32
+ return r11
33
+ // cff_block_2:
34
+ r0 = undefined
35
+ r11 = true
36
+ if (r11) goto: cff_skip_10
37
+ r2 = 58894
38
+ goto: while_top_5
39
+ // cff_skip_10:
40
+ r2 = 1317
41
+ goto: while_top_5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-confuser-vm",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "babel src --out-dir dist --extensions \".ts,.js\"",
@@ -36,6 +36,7 @@
36
36
  "@babel/core": "^7.29.0",
37
37
  "@babel/preset-env": "^7.29.0",
38
38
  "@babel/preset-typescript": "^7.28.5",
39
+ "@types/jest": "^30.0.0",
39
40
  "@types/node": "^25.3.0",
40
41
  "babel-plugin-module-resolver": "^5.0.2",
41
42
  "babel-plugin-replace-import-extension": "^1.1.5",