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.
- package/README.md +281 -147
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +714 -265
- package/dist/disassembler.js +367 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +160 -119
- package/dist/template.js +163 -42
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/concealConstants.js +2 -2
- package/dist/transforms/bytecode/controlFlowFlattening.js +569 -0
- package/dist/transforms/bytecode/dispatcher.js +15 -111
- package/dist/transforms/bytecode/macroOpcodes.js +2 -2
- package/{src/transforms/bytecode/resolveContants.ts → dist/transforms/bytecode/resolveConstants.js} +30 -56
- package/dist/transforms/bytecode/resolveRegisters.js +23 -4
- package/dist/transforms/bytecode/selfModifying.js +88 -21
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +23 -12
- package/dist/transforms/bytecode/stringConcealing.js +288 -0
- package/dist/transforms/runtime/classObfuscation.js +43 -0
- package/dist/transforms/runtime/handlerTable.js +91 -0
- package/dist/transforms/runtime/semanticOpcodes.js +35 -0
- package/dist/transforms/runtime/specializedOpcodes.js +11 -5
- package/dist/types.js +1 -1
- package/dist/utils/ast-utils.js +75 -0
- package/dist/utils/op-utils.js +1 -2
- package/dist/utils/pass-utils.js +100 -0
- package/dist/utils/profile-utils.js +3 -0
- package/package.json +8 -1
- package/.gitmodules +0 -4
- package/.prettierignore +0 -1
- package/CHANGELOG.md +0 -335
- package/babel-plugin-inline-runtime.cjs +0 -34
- package/babel.config.json +0 -23
- package/index.ts +0 -38
- package/jest-strip-types.js +0 -10
- package/jest.config.js +0 -52
- package/src/build-runtime.ts +0 -78
- package/src/compiler.ts +0 -2593
- package/src/index.ts +0 -14
- package/src/minify.ts +0 -21
- package/src/options.ts +0 -18
- package/src/runtime.ts +0 -923
- package/src/template.ts +0 -141
- package/src/transforms/bytecode/aliasedOpcodes.ts +0 -148
- package/src/transforms/bytecode/concealConstants.ts +0 -52
- package/src/transforms/bytecode/dispatcher.ts +0 -398
- package/src/transforms/bytecode/macroOpcodes.ts +0 -193
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/bytecode/resolveLabels.ts +0 -112
- package/src/transforms/bytecode/resolveRegisters.ts +0 -221
- package/src/transforms/bytecode/selfModifying.ts +0 -121
- package/src/transforms/bytecode/specializedOpcodes.ts +0 -153
- package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/macroOpcodes.ts +0 -138
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- package/src/transforms/runtime/minify.ts +0 -1
- package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
- package/src/transforms/runtime/specializedOpcodes.ts +0 -156
- package/src/types.ts +0 -93
- package/src/utils/op-utils.ts +0 -48
- package/src/utils/random-utils.ts +0 -31
- package/tsconfig.json +0 -12
package/dist/template.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
//
|
|
6
6
|
// ── Usage ─────────────────────────────────────────────────────────────────────
|
|
7
7
|
//
|
|
8
|
-
// const
|
|
8
|
+
// const template = new Template(`
|
|
9
9
|
// function {name}(x, y) {
|
|
10
10
|
// return x + y;
|
|
11
11
|
// }
|
|
12
12
|
// `);
|
|
13
13
|
//
|
|
14
|
-
// const bc =
|
|
14
|
+
// const bc = template.compile({ name: "myHelper" }, parentCompiler);
|
|
15
15
|
// result.push(...bc);
|
|
16
16
|
//
|
|
17
17
|
// ── How it works ──────────────────────────────────────────────────────────────
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
// • Opcodes with no JS equivalent (JUMP_REG, BXOR used as decode, etc.) cannot
|
|
46
46
|
// be expressed in a template; write those instruction arrays manually.
|
|
47
47
|
|
|
48
|
-
import { Compiler } from "./compiler.js";
|
|
48
|
+
import { Compiler, SOURCE_NODE_SYM } from "./compiler.js";
|
|
49
49
|
import { DEFAULT_OPTIONS } from "./options.js";
|
|
50
50
|
export class Template {
|
|
51
51
|
constructor(source) {
|
|
@@ -62,6 +62,67 @@ export class Template {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// ── Shared child-compiler setup ───────────────────────────────────────────
|
|
66
|
+
// Creates a child Compiler that inherits the parent's OP table and label
|
|
67
|
+
// counter, shares fnDescriptors (so inner functions auto-register), then
|
|
68
|
+
// compiles `code` to raw IR. Returns startIdx so callers can slice out
|
|
69
|
+
// the descriptors that belong to this template invocation.
|
|
70
|
+
_setupChild(code, parentCompiler) {
|
|
71
|
+
const child = new Compiler({
|
|
72
|
+
...DEFAULT_OPTIONS,
|
|
73
|
+
randomizeOpcodes: false
|
|
74
|
+
});
|
|
75
|
+
child.OP = {
|
|
76
|
+
...parentCompiler.OP
|
|
77
|
+
};
|
|
78
|
+
child.OP_NAME = {
|
|
79
|
+
...parentCompiler.OP_NAME
|
|
80
|
+
};
|
|
81
|
+
child.JUMP_OPS = new Set(parentCompiler.JUMP_OPS);
|
|
82
|
+
child.SENTINELS = {
|
|
83
|
+
...parentCompiler.SENTINELS
|
|
84
|
+
};
|
|
85
|
+
child._makeLabel = parentCompiler._makeLabel.bind(parentCompiler);
|
|
86
|
+
const startIdx = parentCompiler.fnDescriptors.length;
|
|
87
|
+
child.fnDescriptors = parentCompiler.fnDescriptors;
|
|
88
|
+
child.compile(code);
|
|
89
|
+
return {
|
|
90
|
+
startIdx
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Inner-function bytecode collection ───────────────────────────────────
|
|
95
|
+
// Gathers the descriptors for functions declared inside the template (all
|
|
96
|
+
// entries after startIdx in fnDescriptors), assembles their defineLabel +
|
|
97
|
+
// body into a flat bytecode array, and strips template source locations so
|
|
98
|
+
// they never leak into the parent's debug output.
|
|
99
|
+
_collectInnerFunctions(parentCompiler, startIdx) {
|
|
100
|
+
const innerFns = parentCompiler.fnDescriptors.slice(startIdx + 1);
|
|
101
|
+
const innerBytecode = [];
|
|
102
|
+
for (const desc of innerFns) {
|
|
103
|
+
innerBytecode.push([null, {
|
|
104
|
+
type: "defineLabel",
|
|
105
|
+
label: desc.entryLabel
|
|
106
|
+
}]);
|
|
107
|
+
for (const instr of desc.bytecode) {
|
|
108
|
+
innerBytecode.push(instr);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this._stripSourceNodes(innerBytecode);
|
|
112
|
+
return {
|
|
113
|
+
innerFns,
|
|
114
|
+
innerBytecode
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Source-location stripping ─────────────────────────────────────────────
|
|
119
|
+
// Removes the SOURCE_NODE_SYM symbol from every instruction so that template-
|
|
120
|
+
// internal line numbers (which point into the template string, not the user's
|
|
121
|
+
// source) never appear in the bytecode comment block.
|
|
122
|
+
_stripSourceNodes(bytecode) {
|
|
123
|
+
for (const instr of bytecode) delete instr[SOURCE_NODE_SYM];
|
|
124
|
+
}
|
|
125
|
+
|
|
65
126
|
// ── Main entry point ───────────────────────────────────────────────────────
|
|
66
127
|
/**
|
|
67
128
|
* Compile the template and return the inner (non-main) function descriptors
|
|
@@ -90,53 +151,113 @@ export class Template {
|
|
|
90
151
|
* template's main-scope instructions.
|
|
91
152
|
*/
|
|
92
153
|
compile(variables, parentCompiler) {
|
|
93
|
-
// ── 1. Interpolate ────────────────────────────────────────────────────
|
|
94
154
|
const code = this._interpolate(variables);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
106
|
-
child.OP_NAME = {
|
|
107
|
-
...parentCompiler.OP_NAME
|
|
155
|
+
const {
|
|
156
|
+
startIdx
|
|
157
|
+
} = this._setupChild(code, parentCompiler);
|
|
158
|
+
const {
|
|
159
|
+
innerFns,
|
|
160
|
+
innerBytecode
|
|
161
|
+
} = this._collectInnerFunctions(parentCompiler, startIdx);
|
|
162
|
+
return {
|
|
163
|
+
functions: innerFns,
|
|
164
|
+
bytecode: innerBytecode
|
|
108
165
|
};
|
|
109
|
-
|
|
110
|
-
child._makeLabel = parentCompiler._makeLabel.bind(parentCompiler);
|
|
166
|
+
}
|
|
111
167
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
168
|
+
// ── Inline compilation ───────────────────────────────────────────────────
|
|
169
|
+
/**
|
|
170
|
+
* Compile the template and return the **main scope** bytecode, with all
|
|
171
|
+
* register operands remapped to belong to `targetFnId`. This allows
|
|
172
|
+
* bytecode transforms to express high-level JS control flow (while-loops,
|
|
173
|
+
* if-chains, variable declarations) via Template and splice the result
|
|
174
|
+
* directly into an existing function's instruction stream.
|
|
175
|
+
*
|
|
176
|
+
* The implicit trailing RETURN added by _compileFunctionDecl is stripped —
|
|
177
|
+
* inline code should flow into the surrounding bytecode, not return.
|
|
178
|
+
*
|
|
179
|
+
* @param variables Substitution map for {name} placeholders.
|
|
180
|
+
* @param parentCompiler The Compiler whose OP table, label counter, and
|
|
181
|
+
* fnDescriptors are shared.
|
|
182
|
+
* @param targetFnId The function whose register file the template's
|
|
183
|
+
* registers should be remapped into.
|
|
184
|
+
* @param maxId Live map of max register id per fnId — updated
|
|
185
|
+
* in-place as new registers are allocated.
|
|
186
|
+
*
|
|
187
|
+
* @returns
|
|
188
|
+
* bytecode — main-scope IR (no entry defineLabel, no trailing RETURN),
|
|
189
|
+
* ready to splice into the target function's instruction stream.
|
|
190
|
+
* registers — mapping of JS variable names → remapped RegisterOperands,
|
|
191
|
+
* so the caller can reference template-declared variables
|
|
192
|
+
* (e.g. the `state` variable in CFF).
|
|
193
|
+
* functions — inner function descriptors (same as compile()).
|
|
194
|
+
* innerBytecode — inner function bytecode blocks (same as compile()).
|
|
195
|
+
*/
|
|
196
|
+
compileInline(variables, parentCompiler, targetFnId, maxId) {
|
|
197
|
+
const code = this._interpolate(variables);
|
|
198
|
+
const {
|
|
199
|
+
startIdx
|
|
200
|
+
} = this._setupChild(code, parentCompiler);
|
|
201
|
+
const mainDesc = parentCompiler.fnDescriptors[startIdx];
|
|
202
|
+
const mainFnId = mainDesc._fnIdx;
|
|
203
|
+
const mainBc = mainDesc.bytecode;
|
|
116
204
|
|
|
117
|
-
// ──
|
|
118
|
-
|
|
205
|
+
// ── Remap registers from the template's main fnId → targetFnId ────────
|
|
206
|
+
// Build a mapping: old register id → new RegisterOperand in targetFnId.
|
|
207
|
+
const regRemap = new Map();
|
|
208
|
+
const remapReg = id => {
|
|
209
|
+
if (!regRemap.has(id)) {
|
|
210
|
+
const next = (maxId.get(targetFnId) ?? -1) + 1;
|
|
211
|
+
maxId.set(targetFnId, next);
|
|
212
|
+
regRemap.set(id, {
|
|
213
|
+
type: "register",
|
|
214
|
+
id: next,
|
|
215
|
+
fnId: targetFnId
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return regRemap.get(id);
|
|
219
|
+
};
|
|
220
|
+
for (const instr of mainBc) {
|
|
221
|
+
for (let j = 1; j < instr.length; j++) {
|
|
222
|
+
const op = instr[j];
|
|
223
|
+
if (op && typeof op === "object" && op.type === "register" && op.fnId === mainFnId) {
|
|
224
|
+
const mapped = remapReg(op.id);
|
|
225
|
+
op.id = mapped.id;
|
|
226
|
+
op.fnId = mapped.fnId;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
119
230
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
const
|
|
231
|
+
// ── Build variable name → remapped register mapping ───────────────────
|
|
232
|
+
const registers = new Map();
|
|
233
|
+
const locals = mainDesc.ctx.scope._locals;
|
|
234
|
+
for (const [name, reg] of locals) {
|
|
235
|
+
const mapped = regRemap.get(reg.id);
|
|
236
|
+
if (mapped) registers.set(name, mapped);
|
|
237
|
+
}
|
|
123
238
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
239
|
+
// ── Strip entry defineLabel and trailing implicit RETURN ───────────────
|
|
240
|
+
let bytecode = mainBc.filter(instr => {
|
|
241
|
+
const op0 = instr[1];
|
|
242
|
+
return !(instr[0] === null && op0?.type === "defineLabel" && op0.label === mainDesc.entryLabel);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Remove trailing LOAD_CONST undefined + RETURN (implicit return added
|
|
246
|
+
// by _compileFunctionDecl).
|
|
247
|
+
const OP = parentCompiler.OP;
|
|
248
|
+
if (bytecode.length >= 2 && bytecode[bytecode.length - 1][0] === OP.RETURN && bytecode[bytecode.length - 2][0] === OP.LOAD_CONST) {
|
|
249
|
+
bytecode = bytecode.slice(0, -2);
|
|
136
250
|
}
|
|
251
|
+
this._stripSourceNodes(bytecode);
|
|
252
|
+
const {
|
|
253
|
+
innerFns,
|
|
254
|
+
innerBytecode
|
|
255
|
+
} = this._collectInnerFunctions(parentCompiler, startIdx);
|
|
137
256
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
257
|
+
bytecode,
|
|
258
|
+
registers,
|
|
259
|
+
functions: innerFns,
|
|
260
|
+
innerBytecode
|
|
140
261
|
};
|
|
141
262
|
}
|
|
142
263
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SOURCE_NODE_SYM } from "../../compiler.js";
|
|
1
|
+
import { OP_ORIGINAL, SOURCE_NODE_SYM } from "../../compiler.js";
|
|
2
2
|
import { nextFreeSlot } from "../../utils/op-utils.js";
|
|
3
3
|
import { shuffle } from "../../utils/random-utils.js";
|
|
4
4
|
|
|
@@ -41,6 +41,9 @@ export function aliasedOpcodes(bc, compiler) {
|
|
|
41
41
|
const arity = instr.length - 1;
|
|
42
42
|
if (arity < 1) continue; // 0-operand opcodes have nothing to permute
|
|
43
43
|
|
|
44
|
+
const opName = compiler.OP_NAME[op];
|
|
45
|
+
if (!OP_ORIGINAL[opName]) continue; // only consider original ops, not already-specialized ones
|
|
46
|
+
|
|
44
47
|
const existing = opStats.get(op);
|
|
45
48
|
if (!existing) {
|
|
46
49
|
opStats.set(op, {
|
|
@@ -2,8 +2,8 @@ export function concealConstants(bytecode, compiler) {
|
|
|
2
2
|
const newBytecode = [];
|
|
3
3
|
for (const instr of bytecode) {
|
|
4
4
|
const [op, ...operands] = instr;
|
|
5
|
-
const
|
|
6
|
-
if (!
|
|
5
|
+
const hasConstant = operands.some(o => o !== undefined && o !== null && typeof o === "object" && o.type === "constant");
|
|
6
|
+
if (!hasConstant) {
|
|
7
7
|
newBytecode.push(instr);
|
|
8
8
|
continue;
|
|
9
9
|
}
|