js-confuser-vm 0.0.9 → 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.
- package/.gitmodules +4 -0
- package/CHANGELOG.md +125 -2
- package/README.md +128 -53
- package/bench.ts +146 -0
- package/disassemble.ts +12 -0
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +328 -181
- package/dist/disassembler.js +317 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +255 -176
- package/dist/template.js +258 -0
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
- package/dist/transforms/bytecode/dispatcher.js +266 -0
- package/dist/transforms/bytecode/macroOpcodes.js +3 -3
- package/dist/transforms/bytecode/resolveConstants.js +100 -0
- package/dist/transforms/bytecode/resolveLabels.js +21 -18
- package/dist/transforms/bytecode/resolveRegisters.js +216 -0
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +22 -12
- package/dist/transforms/bytecode/stringConcealing.js +110 -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 +42 -1
- package/dist/utils/ast-utils.js +14 -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/index.ts +22 -16
- package/jest.config.js +19 -2
- package/output.disassembled.js +41 -0
- package/package.json +2 -1
- package/src/build-runtime.ts +113 -78
- package/src/compiler.ts +2703 -2482
- package/src/disassembler.ts +329 -0
- package/src/index.ts +12 -2
- package/src/options.ts +8 -1
- package/src/runtime.ts +294 -180
- package/src/template.ts +265 -0
- package/src/transforms/bytecode/aliasedOpcodes.ts +5 -2
- package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
- package/src/transforms/bytecode/dispatcher.ts +292 -0
- package/src/transforms/bytecode/macroOpcodes.ts +4 -4
- package/src/transforms/bytecode/resolveLabels.ts +31 -27
- package/src/transforms/bytecode/resolveRegisters.ts +226 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +27 -20
- package/src/transforms/bytecode/stringConcealing.ts +130 -0
- package/src/transforms/runtime/classObfuscation.ts +59 -0
- package/src/transforms/runtime/specializedOpcodes.ts +14 -9
- package/src/types.ts +106 -5
- package/src/utils/ast-utils.ts +19 -0
- package/src/utils/op-utils.ts +2 -2
- package/src/utils/pass-utils.ts +126 -0
- package/src/utils/profile-utils.ts +3 -0
- package/tsconfig.json +1 -1
- package/dist/transforms/utils/op-utils.js +0 -25
- package/dist/transforms/utils/random-utils.js +0 -27
- package/dist/utilts.js +0 -3
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
BAND: "&",
|
|
67
|
+
BOR: "|",
|
|
68
|
+
BXOR: "^",
|
|
69
|
+
SHL: "<<",
|
|
70
|
+
SHR: ">>",
|
|
71
|
+
USHR: ">>>"
|
|
72
|
+
};
|
|
73
|
+
const CMP_SYMBOLS = {
|
|
74
|
+
LT: "<",
|
|
75
|
+
GT: ">",
|
|
76
|
+
LTE: "<=",
|
|
77
|
+
GTE: ">=",
|
|
78
|
+
EQ: "===",
|
|
79
|
+
NEQ: "!==",
|
|
80
|
+
LOOSE_EQ: "==",
|
|
81
|
+
LOOSE_NEQ: "!=",
|
|
82
|
+
IN: "in",
|
|
83
|
+
INSTANCEOF: "instanceof"
|
|
84
|
+
};
|
|
85
|
+
const UNARY_SYMBOLS = {
|
|
86
|
+
UNARY_NEG: "-",
|
|
87
|
+
UNARY_POS: "+",
|
|
88
|
+
UNARY_NOT: "!",
|
|
89
|
+
UNARY_BITNOT: "~"
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Extract value from annotation like 'reg[1] = "Hello"' or 'reg[1] = 42'
|
|
93
|
+
function extractConstValue(annotation) {
|
|
94
|
+
const m = annotation.match(/=\s*(.+?)(?:\s+\d+:\d+-\d+:\d+)?$/);
|
|
95
|
+
return m ? m[1].trim() : null;
|
|
96
|
+
}
|
|
97
|
+
function disassembleInstr(instr) {
|
|
98
|
+
const {
|
|
99
|
+
raw,
|
|
100
|
+
opName,
|
|
101
|
+
annotation
|
|
102
|
+
} = instr;
|
|
103
|
+
const r = i => `r${raw[i]}`;
|
|
104
|
+
switch (opName) {
|
|
105
|
+
case "LOAD_CONST":
|
|
106
|
+
{
|
|
107
|
+
const val = extractConstValue(annotation);
|
|
108
|
+
return `${r(1)} = ${val ?? `const[${raw[2]}]`}`;
|
|
109
|
+
}
|
|
110
|
+
case "LOAD_INT":
|
|
111
|
+
return `${r(1)} = ${raw[2]}`;
|
|
112
|
+
case "LOAD_GLOBAL":
|
|
113
|
+
{
|
|
114
|
+
const val = extractConstValue(annotation);
|
|
115
|
+
return `${r(1)} = ${val ?? `global[${raw[2]}]`}`;
|
|
116
|
+
}
|
|
117
|
+
case "LOAD_UPVALUE":
|
|
118
|
+
return `${r(1)} = upvalue[${raw[2]}]`;
|
|
119
|
+
case "LOAD_THIS":
|
|
120
|
+
return `${r(1)} = this`;
|
|
121
|
+
case "MOVE":
|
|
122
|
+
return `${r(1)} = ${r(2)}`;
|
|
123
|
+
case "STORE_GLOBAL":
|
|
124
|
+
{
|
|
125
|
+
// annotation: globals[name] = reg[src]
|
|
126
|
+
const val = extractConstValue(annotation);
|
|
127
|
+
return `global[${val ?? raw[1]}] = ${r(2)}`;
|
|
128
|
+
}
|
|
129
|
+
case "STORE_UPVALUE":
|
|
130
|
+
return `upvalue[${raw[1]}] = ${r(2)}`;
|
|
131
|
+
case "GET_PROP":
|
|
132
|
+
return `${r(1)} = ${r(2)}[${r(3)}]`;
|
|
133
|
+
case "SET_PROP":
|
|
134
|
+
return `${r(1)}[${r(2)}] = ${r(3)}`;
|
|
135
|
+
case "DELETE_PROP":
|
|
136
|
+
return `${r(1)} = delete ${r(2)}[${r(3)}]`;
|
|
137
|
+
|
|
138
|
+
// Arithmetic / bitwise
|
|
139
|
+
case "ADD":
|
|
140
|
+
case "SUB":
|
|
141
|
+
case "MUL":
|
|
142
|
+
case "DIV":
|
|
143
|
+
case "MOD":
|
|
144
|
+
case "BAND":
|
|
145
|
+
case "BOR":
|
|
146
|
+
case "BXOR":
|
|
147
|
+
case "SHL":
|
|
148
|
+
case "SHR":
|
|
149
|
+
case "USHR":
|
|
150
|
+
return `${r(1)} = ${r(2)} ${ARITH_SYMBOLS[opName]} ${r(3)}`;
|
|
151
|
+
|
|
152
|
+
// Comparison
|
|
153
|
+
case "LT":
|
|
154
|
+
case "GT":
|
|
155
|
+
case "LTE":
|
|
156
|
+
case "GTE":
|
|
157
|
+
case "EQ":
|
|
158
|
+
case "NEQ":
|
|
159
|
+
case "LOOSE_EQ":
|
|
160
|
+
case "LOOSE_NEQ":
|
|
161
|
+
case "IN":
|
|
162
|
+
case "INSTANCEOF":
|
|
163
|
+
return `${r(1)} = ${r(2)} ${CMP_SYMBOLS[opName]} ${r(3)}`;
|
|
164
|
+
|
|
165
|
+
// Unary
|
|
166
|
+
case "UNARY_NEG":
|
|
167
|
+
case "UNARY_POS":
|
|
168
|
+
case "UNARY_NOT":
|
|
169
|
+
case "UNARY_BITNOT":
|
|
170
|
+
return `${r(1)} = ${UNARY_SYMBOLS[opName]}${r(2)}`;
|
|
171
|
+
case "TYPEOF":
|
|
172
|
+
return `${r(1)} = typeof ${r(2)}`;
|
|
173
|
+
case "VOID":
|
|
174
|
+
return `${r(1)} = void ${r(2)}`;
|
|
175
|
+
case "TYPEOF_SAFE":
|
|
176
|
+
{
|
|
177
|
+
const val = extractConstValue(annotation);
|
|
178
|
+
return `${r(1)} = typeof ${val ?? `safe[${raw[2]}]`}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Control flow
|
|
182
|
+
case "JUMP":
|
|
183
|
+
{
|
|
184
|
+
const label = extractLabel(annotation);
|
|
185
|
+
return `goto: ${label ?? `pc:${raw[1]}`}`;
|
|
186
|
+
}
|
|
187
|
+
case "JUMP_IF_FALSE":
|
|
188
|
+
{
|
|
189
|
+
const label = extractLabel(annotation);
|
|
190
|
+
return `if (!${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
|
|
191
|
+
}
|
|
192
|
+
case "JUMP_IF_TRUE":
|
|
193
|
+
{
|
|
194
|
+
const label = extractLabel(annotation);
|
|
195
|
+
return `if (${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
|
|
196
|
+
}
|
|
197
|
+
case "JUMP_REG":
|
|
198
|
+
return `goto: *${r(1)}`;
|
|
199
|
+
|
|
200
|
+
// Calls
|
|
201
|
+
case "CALL":
|
|
202
|
+
{
|
|
203
|
+
const dst = r(1);
|
|
204
|
+
const callee = r(2);
|
|
205
|
+
const argc = raw[3];
|
|
206
|
+
const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
|
|
207
|
+
return `${dst} = ${callee}(${args.join(", ")})`;
|
|
208
|
+
}
|
|
209
|
+
case "CALL_METHOD":
|
|
210
|
+
{
|
|
211
|
+
const dst = r(1);
|
|
212
|
+
const recv = r(2);
|
|
213
|
+
const callee = r(3);
|
|
214
|
+
const argc = raw[4];
|
|
215
|
+
const args = raw.slice(5, 5 + argc).map((_, i) => `r${raw[5 + i]}`);
|
|
216
|
+
return `${dst} = ${callee}.call(${recv}, ${args.join(", ")})`;
|
|
217
|
+
}
|
|
218
|
+
case "NEW":
|
|
219
|
+
{
|
|
220
|
+
const dst = r(1);
|
|
221
|
+
const callee = r(2);
|
|
222
|
+
const argc = raw[3];
|
|
223
|
+
const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
|
|
224
|
+
return `${dst} = new ${callee}(${args.join(", ")})`;
|
|
225
|
+
}
|
|
226
|
+
case "RETURN":
|
|
227
|
+
return `return ${r(1)}`;
|
|
228
|
+
case "THROW":
|
|
229
|
+
return `throw ${r(1)}`;
|
|
230
|
+
|
|
231
|
+
// Closures
|
|
232
|
+
case "MAKE_CLOSURE":
|
|
233
|
+
{
|
|
234
|
+
const dst = r(1);
|
|
235
|
+
const startPc = raw[2];
|
|
236
|
+
const paramCount = raw[3];
|
|
237
|
+
const regCount = raw[4];
|
|
238
|
+
const uvCount = raw[5];
|
|
239
|
+
const label = extractLabel(annotation);
|
|
240
|
+
const uvParts = [];
|
|
241
|
+
for (let i = 0; i < uvCount; i++) {
|
|
242
|
+
const isLocal = raw[6 + i * 2];
|
|
243
|
+
const idx = raw[6 + i * 2 + 1];
|
|
244
|
+
uvParts.push(isLocal ? `local[${idx}]` : `uv[${idx}]`);
|
|
245
|
+
}
|
|
246
|
+
const uvStr = uvCount > 0 ? `, upvalues=[${uvParts.join(", ")}]` : "";
|
|
247
|
+
return `${dst} = MakeClosure(${label ?? `pc:${startPc}`}, params=${paramCount}, regs=${regCount}${uvStr})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Collections
|
|
251
|
+
case "BUILD_ARRAY":
|
|
252
|
+
{
|
|
253
|
+
const dst = r(1);
|
|
254
|
+
const count = raw[2];
|
|
255
|
+
const elems = raw.slice(3, 3 + count).map((_, i) => `r${raw[3 + i]}`);
|
|
256
|
+
return `${dst} = [${elems.join(", ")}]`;
|
|
257
|
+
}
|
|
258
|
+
case "BUILD_OBJECT":
|
|
259
|
+
{
|
|
260
|
+
const dst = r(1);
|
|
261
|
+
const pairCount = raw[2];
|
|
262
|
+
const pairs = [];
|
|
263
|
+
for (let i = 0; i < pairCount; i++) {
|
|
264
|
+
pairs.push(`[r${raw[3 + i * 2]}]: r${raw[4 + i * 2]}`);
|
|
265
|
+
}
|
|
266
|
+
return `${dst} = {${pairs.join(", ")}}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Property definitions
|
|
270
|
+
case "DEFINE_GETTER":
|
|
271
|
+
return `Object.defineGetter(${r(1)}, ${r(2)}, ${r(3)})`;
|
|
272
|
+
case "DEFINE_SETTER":
|
|
273
|
+
return `Object.defineSetter(${r(1)}, ${r(2)}, ${r(3)})`;
|
|
274
|
+
|
|
275
|
+
// For-in
|
|
276
|
+
case "FOR_IN_SETUP":
|
|
277
|
+
return `${r(1)} = ForInSetup(${r(2)})`;
|
|
278
|
+
case "FOR_IN_NEXT":
|
|
279
|
+
{
|
|
280
|
+
const label = extractLabel(annotation);
|
|
281
|
+
return `${r(1)} = ForInNext(${r(2)}) else goto ${label ?? `pc:${raw[3]}`}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Exception handling
|
|
285
|
+
case "TRY_SETUP":
|
|
286
|
+
{
|
|
287
|
+
return `try { catch -> pc:${raw[1]}, exReg=${r(2)}`;
|
|
288
|
+
}
|
|
289
|
+
case "TRY_END":
|
|
290
|
+
return `} // end try`;
|
|
291
|
+
|
|
292
|
+
// Self-modifying
|
|
293
|
+
case "PATCH":
|
|
294
|
+
return `patch(dest=pc:${raw[1]}, src=pc:${raw[2]}..${raw[3]})`;
|
|
295
|
+
case "DEBUGGER":
|
|
296
|
+
return `debugger`;
|
|
297
|
+
default:
|
|
298
|
+
return `??? ${opName} [${raw.join(", ")}]`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
export function disassembleCommentBlock(commentBlock) {
|
|
302
|
+
const lines = parseBlock(commentBlock);
|
|
303
|
+
let bodies = [];
|
|
304
|
+
let currentBody = [];
|
|
305
|
+
for (const line of lines) {
|
|
306
|
+
if (line.kind === "label") {
|
|
307
|
+
let newBody = [];
|
|
308
|
+
newBody.push(`// ${line.label}:`);
|
|
309
|
+
bodies.push(newBody);
|
|
310
|
+
currentBody = newBody;
|
|
311
|
+
} else if (line.instr) {
|
|
312
|
+
currentBody.push(` ${disassembleInstr(line.instr)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const out = bodies.flatMap(body => body);
|
|
316
|
+
return out.join("\n");
|
|
317
|
+
}
|
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;
|