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.
- package/CHANGELOG.md +57 -2
- package/README.MD +186 -107
- package/dist/build-runtime.js +7 -1
- package/dist/compiler.js +801 -785
- package/dist/runtime.js +409 -332
- package/dist/transforms/bytecode/aliasedOpcodes.js +140 -0
- package/dist/transforms/bytecode/concealConstants.js +31 -0
- package/dist/transforms/bytecode/macroOpcodes.js +22 -10
- package/dist/transforms/bytecode/resolveContants.js +73 -10
- package/dist/transforms/bytecode/selfModifying.js +3 -2
- package/dist/transforms/bytecode/specializedOpcodes.js +38 -28
- package/dist/transforms/runtime/aliasedOpcodes.js +134 -0
- package/dist/transforms/runtime/shuffleOpcodes.js +1 -1
- package/dist/transforms/runtime/specializedOpcodes.js +21 -16
- package/dist/utils/op-utils.js +29 -0
- package/dist/utils/random-utils.js +27 -0
- package/index.ts +10 -8
- package/jest.config.js +10 -0
- package/package.json +1 -1
- package/src/build-runtime.ts +7 -1
- package/src/compiler.ts +2395 -2069
- package/src/options.ts +2 -0
- package/src/runtime.ts +838 -771
- package/src/transforms/bytecode/aliasedOpcodes.ts +158 -0
- package/src/transforms/bytecode/concealConstants.ts +52 -0
- package/src/transforms/bytecode/macroOpcodes.ts +32 -15
- package/src/transforms/bytecode/resolveContants.ts +87 -16
- package/src/transforms/bytecode/selfModifying.ts +3 -3
- package/src/transforms/bytecode/specializedOpcodes.ts +58 -29
- package/src/transforms/runtime/aliasedOpcodes.ts +191 -0
- package/src/transforms/runtime/shuffleOpcodes.ts +1 -1
- package/src/transforms/runtime/specializedOpcodes.ts +39 -24
- package/src/{transforms/utils → utils}/op-utils.ts +7 -0
- /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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
path.
|
|
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
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
package/src/build-runtime.ts
CHANGED
|
@@ -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
|
-
//
|
|
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);
|