js-confuser-vm 0.1.1 → 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 +242 -89
- package/dist/compiler.js +583 -208
- package/dist/disassembler.js +58 -8
- package/dist/runtime.js +93 -74
- package/dist/template.js +81 -76
- package/dist/transforms/bytecode/concealConstants.js +2 -2
- package/dist/transforms/bytecode/controlFlowFlattening.js +143 -25
- package/dist/transforms/bytecode/dispatcher.js +3 -3
- package/dist/transforms/bytecode/resolveRegisters.js +19 -4
- package/dist/transforms/bytecode/selfModifying.js +88 -21
- package/dist/transforms/bytecode/specializedOpcodes.js +6 -3
- package/dist/transforms/bytecode/stringConcealing.js +253 -75
- package/dist/utils/ast-utils.js +61 -0
- package/dist/utils/op-utils.js +1 -0
- package/package.json +7 -1
- package/.gitmodules +0 -4
- package/.prettierignore +0 -1
- package/CHANGELOG.md +0 -358
- package/babel-plugin-inline-runtime.cjs +0 -34
- package/babel.config.json +0 -23
- package/bench.ts +0 -146
- package/disassemble.ts +0 -12
- package/index.ts +0 -43
- package/jest-strip-types.js +0 -10
- package/jest.config.js +0 -64
- package/output.disassembled.js +0 -41
- package/src/build-runtime.ts +0 -113
- package/src/compiler.ts +0 -2703
- package/src/disassembler.ts +0 -329
- package/src/index.ts +0 -24
- package/src/minify.ts +0 -21
- package/src/options.ts +0 -24
- package/src/runtime.ts +0 -956
- package/src/template.ts +0 -265
- package/src/transforms/bytecode/aliasedOpcodes.ts +0 -151
- package/src/transforms/bytecode/concealConstants.ts +0 -52
- package/src/transforms/bytecode/controlFlowFlattening.ts +0 -566
- package/src/transforms/bytecode/dispatcher.ts +0 -292
- package/src/transforms/bytecode/macroOpcodes.ts +0 -193
- package/src/transforms/bytecode/resolveConstants.ts +0 -126
- package/src/transforms/bytecode/resolveLabels.ts +0 -112
- package/src/transforms/bytecode/resolveRegisters.ts +0 -226
- package/src/transforms/bytecode/selfModifying.ts +0 -121
- package/src/transforms/bytecode/specializedOpcodes.ts +0 -164
- package/src/transforms/bytecode/stringConcealing.ts +0 -130
- package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
- package/src/transforms/runtime/classObfuscation.ts +0 -59
- package/src/transforms/runtime/macroOpcodes.ts +0 -138
- package/src/transforms/runtime/minify.ts +0 -1
- package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
- package/src/transforms/runtime/specializedOpcodes.ts +0 -161
- package/src/types.ts +0 -134
- package/src/utils/ast-utils.ts +0 -19
- package/src/utils/op-utils.ts +0 -46
- package/src/utils/pass-utils.ts +0 -126
- package/src/utils/profile-utils.ts +0 -3
- package/src/utils/random-utils.ts +0 -31
- package/tsconfig.json +0 -12
|
@@ -1,110 +1,288 @@
|
|
|
1
1
|
// String Concealing
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// recover the original value at runtime.
|
|
3
|
+
// Replaces every string constant with a slice into a single shared "string
|
|
4
|
+
// bank" that is decoded at runtime with a position-dependent keystream cipher.
|
|
6
5
|
//
|
|
7
|
-
// ──
|
|
6
|
+
// ── Why a bank ───────────────────────────────────────────────────────────────
|
|
7
|
+
// Previously each string was its own encoded constant, so the encoded length
|
|
8
|
+
// leaked the plaintext length (an attacker could map by length + frequency).
|
|
9
|
+
// Here every string is concatenated into ONE opaque blob padded with random
|
|
10
|
+
// decoy bytes, so individual boundaries and lengths are no longer visible from
|
|
11
|
+
// static inspection of the constant pool.
|
|
8
12
|
//
|
|
9
|
-
//
|
|
13
|
+
// bank = [100‑250 decoys] str0 [0‑5 decoys] str1 [0‑5 decoys] … [100‑250 decoys]
|
|
10
14
|
//
|
|
11
|
-
//
|
|
12
|
-
// entry (hoisted). All decode calls within the function reuse it.
|
|
15
|
+
// Each original string is referenced by the triple (key, start, length).
|
|
13
16
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
17
|
+
// ── Cipher ───────────────────────────────────────────────────────────────────
|
|
18
|
+
// A Weyl-sequence keystream (golden-ratio increment + xorshift mix) produces a
|
|
19
|
+
// fresh 16-bit keyword per character, XOR'd against the char code:
|
|
16
20
|
//
|
|
17
|
-
//
|
|
21
|
+
// key = (key + 0x9e3779b9) | 0 // 32-bit Weyl step
|
|
22
|
+
// ks = (key ^ (key >>> 13)) & 0xffff // 16-bit keystream word
|
|
23
|
+
// enc = charCode ^ ks // XOR (self-inverse)
|
|
18
24
|
//
|
|
19
|
-
//
|
|
25
|
+
// XOR over the full 16-bit range means EVERY UTF-16 code unit round-trips,
|
|
26
|
+
// including control characters, newlines and non-ASCII / astral text. The
|
|
27
|
+
// per-string key is a full 32-bit seed (2^32 keyspace) so the encoding is not
|
|
28
|
+
// trivially enumerable.
|
|
20
29
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
30
|
+
// ── Transport / storage ──────────────────────────────────────────────────────
|
|
31
|
+
// The encoded bank is full-range u16, which would serialise as a wall of CJK /
|
|
32
|
+
// control glyphs. Instead it is packed as u16-LE bytes and base64-encoded, so
|
|
33
|
+
// the stored constant is pure ASCII (and smaller on disk than the raw glyphs).
|
|
34
|
+
//
|
|
35
|
+
// ── Runtime shape — PROGRAM-LEVEL bank ───────────────────────────────────────
|
|
36
|
+
// The bank is inflated EXACTLY ONCE, in the program's main scope, into a plain
|
|
37
|
+
// main-scope register (NOT a global — nothing is written to globalThis). That
|
|
38
|
+
// register is shared with the functions that need it through the VM's ordinary
|
|
39
|
+
// upvalue mechanism: an extra upvalue is threaded down the closure-creation tree
|
|
40
|
+
// to every string-using function and its ancestors. Each string-using function
|
|
41
|
+
// reads the already-inflated bank from that upvalue and passes it to a small
|
|
42
|
+
// per-function `decode` closure (decode itself is function-level — cheap):
|
|
43
|
+
//
|
|
44
|
+
// main: MAKE_CLOSURE rInflate
|
|
45
|
+
// LOAD_CONST rB64, <base64 bank>
|
|
46
|
+
// CALL rBankMain, rInflate, 1, rB64 (once)
|
|
47
|
+
// string-using fn: LOAD_UPVALUE rBank, <threaded idx>
|
|
48
|
+
// MAKE_CLOSURE rDecode
|
|
49
|
+
// per site: LOAD_INT rKey/rStart/rLen
|
|
50
|
+
// CALL rDst, rDecode, 4, rBank, rKey, rStart, rLen
|
|
24
51
|
//
|
|
25
52
|
// ── Pipeline position ─────────────────────────────────────────────────────────
|
|
26
|
-
// Runs BEFORE resolveRegisters and resolveLabels (same slot as Dispatcher/CFF)
|
|
53
|
+
// Runs BEFORE resolveRegisters and resolveLabels (same slot as Dispatcher/CFF),
|
|
54
|
+
// and FIRST among the bytecode passes so each FnDescriptor.upvalues count is
|
|
55
|
+
// still pristine (used to pick the threaded upvalue index).
|
|
27
56
|
|
|
28
57
|
import { Template } from "../../template.js";
|
|
29
58
|
import * as b from "../../types.js";
|
|
30
59
|
import { ref, buildMaxIdMap, allocReg, forEachFunction } from "../../utils/pass-utils.js";
|
|
60
|
+
import { getRandomInt } from "../../utils/random-utils.js";
|
|
61
|
+
import { U32_MAX } from "../../utils/op-utils.js";
|
|
31
62
|
|
|
32
|
-
// ──
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const rClosure = allocReg(fnId, maxId);
|
|
47
|
-
const out = [];
|
|
48
|
-
|
|
49
|
-
// Hoist: create the decode closure once at function entry.
|
|
50
|
-
out.push([OP.MAKE_CLOSURE, ref(rClosure), {
|
|
51
|
-
type: "label",
|
|
52
|
-
label: decodeDesc.entryLabel
|
|
53
|
-
}, decodeDesc.paramCount,
|
|
54
|
-
// 1 (encoded)
|
|
55
|
-
b.fnRegCountOperand(decodeDesc._fnIdx), 0,
|
|
56
|
-
// no upvalues
|
|
57
|
-
0 // hasRest = false
|
|
58
|
-
]);
|
|
59
|
-
|
|
60
|
-
// Transform each instruction.
|
|
61
|
-
for (const instr of instrs) {
|
|
62
|
-
if (instr[0] === OP.LOAD_CONST && instr.length === 3 && instr[2]?.type === "constant" && typeof instr[2].value === "string") {
|
|
63
|
-
const dst = instr[1];
|
|
64
|
-
const constOp = instr[2];
|
|
63
|
+
// ── Cipher ────────────────────────────────────────────────────────────────────
|
|
64
|
+
// Encode mirrors the runtime decode EXACTLY (see the decode template). XOR is
|
|
65
|
+
// self-inverse. `key` must be the raw (unmasked) seed emitted as the LOAD_INT
|
|
66
|
+
// operand, so both sides begin the Weyl sequence from the same integer.
|
|
67
|
+
function xorEncode(str, key) {
|
|
68
|
+
let k = key;
|
|
69
|
+
let out = "";
|
|
70
|
+
for (let i = 0; i < str.length; i++) {
|
|
71
|
+
k = k + 0x9e3779b9 | 0;
|
|
72
|
+
const ks = (k ^ k >>> 13) & 0xffff;
|
|
73
|
+
out += String.fromCharCode(str.charCodeAt(i) ^ ks);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
// Random decoy run, full 16-bit range so decoys look like encoded payload.
|
|
79
|
+
function decoyRun(count) {
|
|
80
|
+
let out = "";
|
|
81
|
+
for (let i = 0; i < count; i++) out += String.fromCharCode(getRandomInt(0, 0xffff));
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
]);
|
|
79
|
-
} else {
|
|
80
|
-
out.push(instr);
|
|
81
|
-
}
|
|
85
|
+
// Pack the u16 bank as little-endian bytes and base64-encode (ASCII, compact).
|
|
86
|
+
// Mirrored at runtime by the inflate template: byte[2i] = low, byte[2i+1] = high.
|
|
87
|
+
function bankToBase64(bank) {
|
|
88
|
+
const bytes = new Uint8Array(bank.length * 2);
|
|
89
|
+
for (let i = 0; i < bank.length; i++) {
|
|
90
|
+
const c = bank.charCodeAt(i);
|
|
91
|
+
bytes[i * 2] = c & 0xff;
|
|
92
|
+
bytes[i * 2 + 1] = c >> 8 & 0xff;
|
|
82
93
|
}
|
|
94
|
+
return Buffer.from(bytes).toString("base64");
|
|
95
|
+
}
|
|
96
|
+
function buildBank(strings) {
|
|
97
|
+
const parts = [];
|
|
98
|
+
const table = new Map();
|
|
99
|
+
let pos = 0;
|
|
100
|
+
const lead = decoyRun(getRandomInt(100, 250)); // leading decoys
|
|
101
|
+
parts.push(lead);
|
|
102
|
+
pos += lead.length;
|
|
103
|
+
for (const str of strings) {
|
|
104
|
+
const gap = decoyRun(getRandomInt(0, 5)); // 0‑5 decoys between strings
|
|
105
|
+
parts.push(gap);
|
|
106
|
+
pos += gap.length;
|
|
107
|
+
const key = getRandomInt(1, U32_MAX);
|
|
108
|
+
const encoded = xorEncode(str, key);
|
|
109
|
+
table.set(str, {
|
|
110
|
+
key,
|
|
111
|
+
start: pos,
|
|
112
|
+
length: str.length
|
|
113
|
+
});
|
|
114
|
+
parts.push(encoded);
|
|
115
|
+
pos += encoded.length;
|
|
116
|
+
}
|
|
117
|
+
parts.push(decoyRun(getRandomInt(100, 250))); // trailing decoys
|
|
83
118
|
return {
|
|
84
|
-
|
|
119
|
+
bank: parts.join(""),
|
|
120
|
+
table
|
|
85
121
|
};
|
|
86
122
|
}
|
|
123
|
+
function isStringLoadConst(instr, OP) {
|
|
124
|
+
return instr[0] === OP.LOAD_CONST && instr.length === 3 && instr[2]?.type === "constant" && typeof instr[2].value === "string";
|
|
125
|
+
}
|
|
87
126
|
|
|
88
127
|
// ── Pass entry point ──────────────────────────────────────────────────────────
|
|
89
128
|
export function stringConcealing(bc, compiler) {
|
|
129
|
+
const OP = compiler.OP;
|
|
130
|
+
const mainId = compiler.mainFn._fnIdx;
|
|
131
|
+
const entryLabelToFnId = new Map(compiler.fnDescriptors.map(d => [d.entryLabel, d._fnIdx]));
|
|
132
|
+
const entryLabels = new Set(entryLabelToFnId.keys());
|
|
133
|
+
|
|
134
|
+
// ── Prescan: collect strings + closure-creation graph ───────────────────────
|
|
135
|
+
// directUser — functions that contain a string LOAD_CONST.
|
|
136
|
+
// parentOf — childFnId → creating (lexical parent) fnId.
|
|
137
|
+
const strings = new Set();
|
|
138
|
+
const directUser = new Set();
|
|
139
|
+
const parentOf = new Map();
|
|
140
|
+
let curFn = -1;
|
|
141
|
+
for (const instr of bc) {
|
|
142
|
+
if (instr[0] === null && instr[1]?.type === "defineLabel" && entryLabels.has(instr[1].label)) {
|
|
143
|
+
curFn = entryLabelToFnId.get(instr[1].label);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (curFn < 0) continue;
|
|
147
|
+
if (isStringLoadConst(instr, OP)) {
|
|
148
|
+
strings.add(instr[2].value);
|
|
149
|
+
directUser.add(curFn);
|
|
150
|
+
} else if (instr[0] === OP.MAKE_CLOSURE) {
|
|
151
|
+
const childId = entryLabelToFnId.get(instr[2]?.label);
|
|
152
|
+
if (childId !== undefined) parentOf.set(childId, curFn);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (strings.size === 0) return {
|
|
156
|
+
bytecode: bc
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ── needSet = string users ∪ all their ancestors (so the upvalue can be
|
|
160
|
+
// threaded down to them). Walking each user to the root adds every ancestor. ──
|
|
161
|
+
const needSet = new Set();
|
|
162
|
+
for (const u of directUser) {
|
|
163
|
+
let p = u;
|
|
164
|
+
while (p !== undefined && !needSet.has(p)) {
|
|
165
|
+
needSet.add(p);
|
|
166
|
+
p = parentOf.get(p);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Threaded upvalue index per function = its ORIGINAL upvalue count (appended
|
|
171
|
+
// last). main holds the bank as a local, so it has no threaded index.
|
|
172
|
+
const bankUvIndex = new Map();
|
|
173
|
+
for (const f of needSet) {
|
|
174
|
+
if (f === mainId) continue;
|
|
175
|
+
bankUvIndex.set(f, compiler.fnDescriptors[f]?.upvalues?.length ?? 0);
|
|
176
|
+
}
|
|
90
177
|
const maxId = buildMaxIdMap(bc);
|
|
178
|
+
const rBankMain = allocReg(mainId, maxId); // program-level inflated bank
|
|
179
|
+
const {
|
|
180
|
+
bank,
|
|
181
|
+
table
|
|
182
|
+
} = buildBank(strings);
|
|
183
|
+
const bankB64 = bankToBase64(bank);
|
|
91
184
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
185
|
+
// Helper closures, compiled once and shared by reference.
|
|
186
|
+
// inflate(b64) → reconstruct the u16 bank from base64
|
|
187
|
+
// decode(bank, key, start, len) → slice + keystream-decrypt one string
|
|
188
|
+
const helpers = new Template(`
|
|
189
|
+
function inflate(s) {
|
|
190
|
+
var bytes = atob(s);
|
|
191
|
+
var out = "";
|
|
192
|
+
for (var i = 0; i < bytes["length"]; i += 2) {
|
|
193
|
+
out += String["fromCharCode"](
|
|
194
|
+
bytes["charCodeAt"](i) | (bytes["charCodeAt"](i + 1) << 8)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function decode(bank, key, start, length) {
|
|
200
|
+
var result = "";
|
|
201
|
+
for (var i = 0; i < length; i++) {
|
|
202
|
+
key = (key + 0x9e3779b9) | 0;
|
|
203
|
+
var ks = (key ^ (key >>> 13)) & 0xffff;
|
|
204
|
+
result += String["fromCharCode"](bank["charCodeAt"](start + i) ^ ks);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
96
207
|
}
|
|
97
208
|
`).compile({}, compiler);
|
|
98
|
-
const decodeDesc =
|
|
209
|
+
const [inflateDesc, decodeDesc] = helpers.functions;
|
|
210
|
+
const mkClosure = (dst, desc, params) => [OP.MAKE_CLOSURE, ref(dst), {
|
|
211
|
+
type: "label",
|
|
212
|
+
label: desc.entryLabel
|
|
213
|
+
}, params, b.fnRegCountOperand(desc._fnIdx), 0,
|
|
214
|
+
// upvalue count
|
|
215
|
+
0 // hasRest
|
|
216
|
+
];
|
|
99
217
|
const {
|
|
100
218
|
bytecode
|
|
101
|
-
} = forEachFunction(bc, compiler, (fnInstrs, fnId) =>
|
|
219
|
+
} = forEachFunction(bc, compiler, (fnInstrs, fnId) => {
|
|
220
|
+
if (!needSet.has(fnId)) return {
|
|
221
|
+
instrs: fnInstrs
|
|
222
|
+
};
|
|
223
|
+
const isMain = fnId === mainId;
|
|
224
|
+
const usesStrings = directUser.has(fnId);
|
|
225
|
+
|
|
226
|
+
// Bank source for closures created in THIS frame: main captures its local,
|
|
227
|
+
// every other frame inherits its own threaded upvalue.
|
|
228
|
+
const childUpvalue = isMain ? [1, ref(rBankMain)] : [0, bankUvIndex.get(fnId)];
|
|
229
|
+
const prologue = [];
|
|
230
|
+
let rBank = null;
|
|
231
|
+
let rDecode = null;
|
|
232
|
+
let rKey, rStart, rLen;
|
|
233
|
+
if (isMain) {
|
|
234
|
+
const rInflate = allocReg(fnId, maxId);
|
|
235
|
+
const rB64 = allocReg(fnId, maxId);
|
|
236
|
+
prologue.push(mkClosure(rInflate, inflateDesc, 1));
|
|
237
|
+
prologue.push([OP.LOAD_CONST, ref(rB64), b.constantOperand(bankB64)]);
|
|
238
|
+
prologue.push([OP.CALL, ref(rBankMain), ref(rInflate), 1, ref(rB64)]);
|
|
239
|
+
rBank = rBankMain;
|
|
240
|
+
} else if (usesStrings) {
|
|
241
|
+
rBank = allocReg(fnId, maxId);
|
|
242
|
+
prologue.push([OP.LOAD_UPVALUE, ref(rBank), bankUvIndex.get(fnId)]);
|
|
243
|
+
}
|
|
244
|
+
if (usesStrings) {
|
|
245
|
+
rDecode = allocReg(fnId, maxId);
|
|
246
|
+
prologue.push(mkClosure(rDecode, decodeDesc, 4));
|
|
247
|
+
rKey = allocReg(fnId, maxId);
|
|
248
|
+
rStart = allocReg(fnId, maxId);
|
|
249
|
+
rLen = allocReg(fnId, maxId);
|
|
250
|
+
}
|
|
251
|
+
const out = [...prologue];
|
|
252
|
+
for (const instr of fnInstrs) {
|
|
253
|
+
// Thread the bank upvalue into every closure this frame creates that
|
|
254
|
+
// needs it (string users + ancestors).
|
|
255
|
+
if (instr[0] === OP.MAKE_CLOSURE) {
|
|
256
|
+
const childId = entryLabelToFnId.get(instr[2]?.label);
|
|
257
|
+
if (childId !== undefined && needSet.has(childId)) {
|
|
258
|
+
instr[5] = instr[5] + 1; // bump uvCount
|
|
259
|
+
instr.push(childUpvalue[0], childUpvalue[1]);
|
|
260
|
+
}
|
|
261
|
+
out.push(instr);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (usesStrings && isStringLoadConst(instr, OP)) {
|
|
265
|
+
const dst = instr[1];
|
|
266
|
+
const entry = table.get(instr[2].value);
|
|
267
|
+
out.push([OP.LOAD_INT, ref(rKey), entry.key]);
|
|
268
|
+
out.push([OP.LOAD_INT, ref(rStart), entry.start]);
|
|
269
|
+
out.push([OP.LOAD_INT, ref(rLen), entry.length]);
|
|
270
|
+
out.push([OP.CALL, ref(dst), ref(rDecode), 4, ref(rBank), ref(rKey), ref(rStart), ref(rLen)]);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
out.push(instr);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
instrs: out
|
|
277
|
+
};
|
|
278
|
+
});
|
|
102
279
|
|
|
103
|
-
// Append the
|
|
104
|
-
|
|
105
|
-
// forEachFunction's tail mechanism.
|
|
106
|
-
bytecode.push(...decodeTemplate.bytecode);
|
|
280
|
+
// Append the helper functions' bytecode (defines their entryLabels).
|
|
281
|
+
bytecode.push(...helpers.bytecode);
|
|
107
282
|
return {
|
|
108
283
|
bytecode
|
|
109
284
|
};
|
|
110
|
-
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// [isLocal flag, upvalue source] — RegisterOperand when capturing a local,
|
|
288
|
+
// plain number when inheriting a parent upvalue.
|
package/dist/utils/ast-utils.js
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
import traverseImport from "@babel/traverse";
|
|
2
2
|
const traverse = traverseImport.default || traverseImport;
|
|
3
|
+
|
|
4
|
+
// Recursively visits every statement reachable from `stmts` within the current
|
|
5
|
+
// function scope — traversing into blocks, if branches, loop bodies, switch
|
|
6
|
+
// cases, try/catch/finally, and labeled statements — but never crossing into
|
|
7
|
+
// nested FunctionDeclaration/FunctionExpression bodies (those are separate scopes).
|
|
8
|
+
//
|
|
9
|
+
// `visit` is called for each statement before its children are traversed.
|
|
10
|
+
// ForStatement init and ForInStatement left VariableDeclarations are also
|
|
11
|
+
// passed to `visit` so callers don't need to special-case them.
|
|
12
|
+
export function walkHoistScope(stmts, visit) {
|
|
13
|
+
for (const stmt of stmts) {
|
|
14
|
+
visit(stmt);
|
|
15
|
+
switch (stmt.type) {
|
|
16
|
+
case "BlockStatement":
|
|
17
|
+
walkHoistScope(stmt.body, visit);
|
|
18
|
+
break;
|
|
19
|
+
case "IfStatement":
|
|
20
|
+
{
|
|
21
|
+
const cons = stmt.consequent.type === "BlockStatement" ? stmt.consequent.body : [stmt.consequent];
|
|
22
|
+
walkHoistScope(cons, visit);
|
|
23
|
+
if (stmt.alternate) {
|
|
24
|
+
const alt = stmt.alternate.type === "BlockStatement" ? stmt.alternate.body : [stmt.alternate];
|
|
25
|
+
walkHoistScope(alt, visit);
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case "WhileStatement":
|
|
30
|
+
case "DoWhileStatement":
|
|
31
|
+
{
|
|
32
|
+
const body = stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
33
|
+
walkHoistScope(body, visit);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "ForStatement":
|
|
37
|
+
{
|
|
38
|
+
if (stmt.init?.type === "VariableDeclaration") visit(stmt.init);
|
|
39
|
+
const body = stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
40
|
+
walkHoistScope(body, visit);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "ForInStatement":
|
|
44
|
+
{
|
|
45
|
+
if (stmt.left.type === "VariableDeclaration") visit(stmt.left);
|
|
46
|
+
const body = stmt.body.type === "BlockStatement" ? stmt.body.body : [stmt.body];
|
|
47
|
+
walkHoistScope(body, visit);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "SwitchStatement":
|
|
51
|
+
for (const c of stmt.cases) walkHoistScope(c.consequent, visit);
|
|
52
|
+
break;
|
|
53
|
+
case "TryStatement":
|
|
54
|
+
walkHoistScope(stmt.block.body, visit);
|
|
55
|
+
if (stmt.handler) walkHoistScope(stmt.handler.body.body, visit);
|
|
56
|
+
if (stmt.finalizer) walkHoistScope(stmt.finalizer.body, visit);
|
|
57
|
+
break;
|
|
58
|
+
case "LabeledStatement":
|
|
59
|
+
walkHoistScope([stmt.body], visit);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
3
64
|
export function getSwitchStatement(ast) {
|
|
4
65
|
let switchStatement = null;
|
|
5
66
|
traverse(ast, {
|
package/dist/utils/op-utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getRandomInt } from "./random-utils.js";
|
|
2
2
|
export const U16_MAX = 0xffff; // bytecode operands are u16
|
|
3
|
+
export const U32_MAX = 0xffffffff; // max sentinel / operand value
|
|
3
4
|
|
|
4
5
|
/** Returns the next free opcode slot, or -1 when the space is exhausted. */
|
|
5
6
|
export function nextFreeSlot(compiler) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js-confuser-vm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"LICENSE",
|
|
8
|
+
"README.md",
|
|
9
|
+
"package.json"
|
|
10
|
+
],
|
|
5
11
|
"scripts": {
|
|
6
12
|
"build": "babel src --out-dir dist --extensions \".ts,.js\"",
|
|
7
13
|
"index": "cross-env NODE_OPTIONS=\"--disable-warning=ExperimentalWarning\" node index.ts",
|
package/.gitmodules
DELETED
package/.prettierignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*.md
|