simple-javascript-obf 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/.github/workflows/node.js.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/THIRD_PARTY_NOTICES.md +82 -0
- package/bin/js-obf +228 -0
- package/bin/obf.sh +257 -0
- package/package.json +26 -0
- package/src/index.js +106 -0
- package/src/options.js +128 -0
- package/src/pipeline.js +56 -0
- package/src/plugins/antiHook.js +123 -0
- package/src/plugins/controlFlowFlatten.js +203 -0
- package/src/plugins/deadCode.js +82 -0
- package/src/plugins/encodeMembers.js +44 -0
- package/src/plugins/entry.js +31 -0
- package/src/plugins/rename.js +100 -0
- package/src/plugins/stringEncode.js +494 -0
- package/src/plugins/vm/ast-utils.js +58 -0
- package/src/plugins/vm/compiler.js +113 -0
- package/src/plugins/vm/constants.js +72 -0
- package/src/plugins/vm/emit.js +916 -0
- package/src/plugins/vm/encoding.js +252 -0
- package/src/plugins/vm/index.js +366 -0
- package/src/plugins/vm/mapping.js +24 -0
- package/src/plugins/vm/normalize.js +692 -0
- package/src/plugins/vm/runtime.js +1145 -0
- package/src/plugins/vm.js +1 -0
- package/src/utils/names.js +55 -0
- package/src/utils/reserved.js +57 -0
- package/src/utils/rng.js +55 -0
- package/src/utils/stream.js +97 -0
- package/src/utils/string.js +13 -0
- package/test/bench-runner.js +78 -0
- package/test/benchmark-source.js +35 -0
- package/test/benchmark-vm.js +160 -0
- package/test/dist/bench.obf.js +1 -0
- package/test/dist/bench.original.js +35 -0
- package/test/dist/bench.vm.js +1 -0
- package/test/dist/sample-input.obf.js +1 -0
- package/test/dist/sample-input.vm.js +1 -0
- package/test/generate-obf.js +38 -0
- package/test/obf-smoke.js +129 -0
- package/test/sample-input.js +23 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
const parser = require("@babel/parser");
|
|
2
|
+
const { encodeString } = require("../utils/string");
|
|
3
|
+
|
|
4
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
5
|
+
|
|
6
|
+
function splitArray(data, rng, count) {
|
|
7
|
+
const total = data.length;
|
|
8
|
+
if (total === 0) {
|
|
9
|
+
return [[]];
|
|
10
|
+
}
|
|
11
|
+
const shards = [];
|
|
12
|
+
let offset = 0;
|
|
13
|
+
const shardCount = Math.max(1, Math.min(count, total));
|
|
14
|
+
for (let i = 0; i < shardCount; i += 1) {
|
|
15
|
+
const remaining = total - offset;
|
|
16
|
+
const remainingShards = shardCount - i;
|
|
17
|
+
const minSize = Math.max(1, remaining - (remainingShards - 1));
|
|
18
|
+
const size = i === shardCount - 1 ? remaining : rng.int(1, minSize);
|
|
19
|
+
shards.push(data.slice(offset, offset + size));
|
|
20
|
+
offset += size;
|
|
21
|
+
}
|
|
22
|
+
return shards;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildHiddenPools(groups, rng) {
|
|
26
|
+
return groups.map((group) => {
|
|
27
|
+
const mask = rng.int(1, 255);
|
|
28
|
+
const indexMask = rng.int(1, 255);
|
|
29
|
+
const indexPairs = [];
|
|
30
|
+
const data = [];
|
|
31
|
+
let offset = 0;
|
|
32
|
+
for (const value of group) {
|
|
33
|
+
const codes = [];
|
|
34
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
35
|
+
codes.push(value.charCodeAt(i) ^ mask);
|
|
36
|
+
}
|
|
37
|
+
indexPairs.push(offset ^ indexMask, codes.length ^ indexMask);
|
|
38
|
+
data.push(...codes);
|
|
39
|
+
offset += codes.length;
|
|
40
|
+
}
|
|
41
|
+
const shardCount = Math.min(Math.max(1, data.length), rng.int(2, 4));
|
|
42
|
+
const shards = splitArray(data, rng, shardCount);
|
|
43
|
+
const shardLens = shards.map((shard) => shard.length);
|
|
44
|
+
return {
|
|
45
|
+
shards,
|
|
46
|
+
shardLens,
|
|
47
|
+
indexPairs,
|
|
48
|
+
mask,
|
|
49
|
+
indexMask,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function shiftBase64(value, key, offset) {
|
|
55
|
+
const size = BASE64_ALPHABET.length;
|
|
56
|
+
let out = "";
|
|
57
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
58
|
+
const ch = value[i];
|
|
59
|
+
const idx = BASE64_ALPHABET.indexOf(ch);
|
|
60
|
+
if (idx < 0) {
|
|
61
|
+
out += ch;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const shift = key[(i + offset) % key.length];
|
|
65
|
+
out += BASE64_ALPHABET[(idx + shift) % size];
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildPoolGroups(pool, rng, groupCount) {
|
|
71
|
+
const indices = Array.from({ length: pool.length }, (_, i) => i);
|
|
72
|
+
rng.shuffle(indices);
|
|
73
|
+
const groups = Array.from({ length: groupCount }, () => []);
|
|
74
|
+
const groupMap = new Array(pool.length);
|
|
75
|
+
const indexMap = new Array(pool.length);
|
|
76
|
+
for (let i = 0; i < indices.length; i += 1) {
|
|
77
|
+
const original = indices[i];
|
|
78
|
+
const group = i % groupCount;
|
|
79
|
+
const position = groups[group].length;
|
|
80
|
+
groups[group].push(pool[original]);
|
|
81
|
+
groupMap[original] = group;
|
|
82
|
+
indexMap[original] = position;
|
|
83
|
+
}
|
|
84
|
+
return { groups, groupMap, indexMap };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isDirectiveLiteral(path) {
|
|
88
|
+
return (
|
|
89
|
+
path.parentPath &&
|
|
90
|
+
path.parentPath.isExpressionStatement() &&
|
|
91
|
+
Boolean(path.parentPath.node.directive)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isModuleString(path) {
|
|
96
|
+
return (
|
|
97
|
+
path.parentPath &&
|
|
98
|
+
(path.parentPath.isImportDeclaration() ||
|
|
99
|
+
path.parentPath.isExportAllDeclaration() ||
|
|
100
|
+
path.parentPath.isExportNamedDeclaration()) &&
|
|
101
|
+
path.parentPath.node.source === path.node
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function insertAtTop(programPath, nodes) {
|
|
106
|
+
const body = programPath.node.body;
|
|
107
|
+
let index = 0;
|
|
108
|
+
while (index < body.length) {
|
|
109
|
+
const stmt = body[index];
|
|
110
|
+
if (stmt.type === "ExpressionStatement" && stmt.directive) {
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
body.splice(index, 0, ...nodes);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildRuntime({
|
|
120
|
+
poolNames,
|
|
121
|
+
poolMeta,
|
|
122
|
+
orderName,
|
|
123
|
+
groupName,
|
|
124
|
+
indexName,
|
|
125
|
+
keyName,
|
|
126
|
+
cacheName,
|
|
127
|
+
decodeName,
|
|
128
|
+
getEncodedName,
|
|
129
|
+
shiftName,
|
|
130
|
+
rotKeyName,
|
|
131
|
+
alphabetName,
|
|
132
|
+
rotOffset,
|
|
133
|
+
b64Name,
|
|
134
|
+
rc4Name,
|
|
135
|
+
u8ToStrName,
|
|
136
|
+
unshiftName,
|
|
137
|
+
poolSelectName,
|
|
138
|
+
order,
|
|
139
|
+
groupMap,
|
|
140
|
+
indexMap,
|
|
141
|
+
key,
|
|
142
|
+
shift,
|
|
143
|
+
rotKey,
|
|
144
|
+
}) {
|
|
145
|
+
const valueName = "__obf_val";
|
|
146
|
+
const bytesName = "__obf_bytes";
|
|
147
|
+
const poolDecls = poolNames
|
|
148
|
+
.map((name, idx) => {
|
|
149
|
+
const meta = poolMeta[idx];
|
|
150
|
+
const shardDecls = meta.shards
|
|
151
|
+
.map((shard, shardIdx) => ` const d${shardIdx} = ${JSON.stringify(shard)};`)
|
|
152
|
+
.join("\n");
|
|
153
|
+
const shardRefs = meta.shards.map((_, shardIdx) => `d${shardIdx}`).join(", ");
|
|
154
|
+
const idxDecl = ` const i = ${JSON.stringify(meta.indexPairs)};`;
|
|
155
|
+
const lensDecl = ` const l = ${JSON.stringify(meta.shardLens)};`;
|
|
156
|
+
const maskDecl = ` const m = ${meta.mask};`;
|
|
157
|
+
const indexMaskDecl = ` const im = ${meta.indexMask};`;
|
|
158
|
+
return `const ${name} = (() => {\n${shardDecls}\n${idxDecl}\n${lensDecl}\n${maskDecl}\n${indexMaskDecl}\n return [[${shardRefs}], l, i, m, im];\n})();`;
|
|
159
|
+
})
|
|
160
|
+
.join("\n");
|
|
161
|
+
const poolSelectCases = poolNames
|
|
162
|
+
.map((name, idx) => ` case ${idx}: return ${name};`)
|
|
163
|
+
.join("\n");
|
|
164
|
+
const code = `
|
|
165
|
+
${poolDecls}
|
|
166
|
+
const ${orderName} = ${JSON.stringify(order)};
|
|
167
|
+
const ${groupName} = ${JSON.stringify(groupMap)};
|
|
168
|
+
const ${indexName} = ${JSON.stringify(indexMap)};
|
|
169
|
+
const ${keyName} = ${JSON.stringify(key)};
|
|
170
|
+
const ${shiftName} = ${shift};
|
|
171
|
+
const ${cacheName} = [];
|
|
172
|
+
const ${rotKeyName} = ${JSON.stringify(rotKey)};
|
|
173
|
+
const ${alphabetName} = ${JSON.stringify(BASE64_ALPHABET)};
|
|
174
|
+
const ${poolSelectName} = (group) => {
|
|
175
|
+
switch (group) {
|
|
176
|
+
${poolSelectCases}
|
|
177
|
+
default:
|
|
178
|
+
return ${poolNames[0]};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const ${getEncodedName} = (pool, pos) => {
|
|
182
|
+
const data = pool[0];
|
|
183
|
+
const lens = pool[1];
|
|
184
|
+
const idx = pool[2];
|
|
185
|
+
const mask = pool[3];
|
|
186
|
+
const idxMask = pool[4];
|
|
187
|
+
let offset = idx[pos * 2] ^ idxMask;
|
|
188
|
+
let length = idx[pos * 2 + 1] ^ idxMask;
|
|
189
|
+
let shard = 0;
|
|
190
|
+
let shardOffset = offset;
|
|
191
|
+
while (shard < lens.length && shardOffset >= lens[shard]) {
|
|
192
|
+
shardOffset -= lens[shard];
|
|
193
|
+
shard += 1;
|
|
194
|
+
}
|
|
195
|
+
let out = "";
|
|
196
|
+
for (let i = 0; i < length; i += 1) {
|
|
197
|
+
const piece = data[shard];
|
|
198
|
+
const code = piece[shardOffset] ^ mask;
|
|
199
|
+
out += String.fromCharCode(code);
|
|
200
|
+
shardOffset += 1;
|
|
201
|
+
if (shardOffset >= lens[shard]) {
|
|
202
|
+
shard += 1;
|
|
203
|
+
shardOffset = 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
};
|
|
208
|
+
const ${unshiftName} = (input) => {
|
|
209
|
+
let out = "";
|
|
210
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
211
|
+
const ch = input[i];
|
|
212
|
+
const idx = ${alphabetName}.indexOf(ch);
|
|
213
|
+
if (idx < 0) {
|
|
214
|
+
out += ch;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const shift = ${rotKeyName}[(i + ${rotOffset}) % ${rotKeyName}.length];
|
|
218
|
+
const next = (idx - shift + ${alphabetName}.length) % ${alphabetName}.length;
|
|
219
|
+
out += ${alphabetName}[next];
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
};
|
|
223
|
+
const ${b64Name} = (s) => {
|
|
224
|
+
if (typeof Buffer !== "undefined") {
|
|
225
|
+
return Buffer.from(s, "base64");
|
|
226
|
+
}
|
|
227
|
+
if (typeof atob !== "undefined") {
|
|
228
|
+
const bin = atob(s);
|
|
229
|
+
const out = new Uint8Array(bin.length);
|
|
230
|
+
for (let i = 0; i < bin.length; i += 1) {
|
|
231
|
+
out[i] = bin.charCodeAt(i);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
throw new Error("No base64 support");
|
|
236
|
+
};
|
|
237
|
+
const ${rc4Name} = (keyBytes) => {
|
|
238
|
+
const key = Uint8Array.from(keyBytes);
|
|
239
|
+
const len = key.length || 1;
|
|
240
|
+
const pick = (idx) => key[idx % len] & 255;
|
|
241
|
+
const variant = pick(0) & 1;
|
|
242
|
+
const r0 = (pick(1) % 7) + 3;
|
|
243
|
+
const r1 = (pick(2) % 7) + 5;
|
|
244
|
+
const r2 = (pick(3) % 7) + 3;
|
|
245
|
+
const r3 = (pick(4) % 7) + 5;
|
|
246
|
+
const rotl = (v, s) => ((v << s) | (v >>> (32 - s))) >>> 0;
|
|
247
|
+
const rotr = (v, s) => ((v >>> s) | (v << (32 - s))) >>> 0;
|
|
248
|
+
let x =
|
|
249
|
+
((pick(5) << 24) | (pick(6) << 16) | (pick(7) << 8) | pick(8)) >>> 0;
|
|
250
|
+
let y =
|
|
251
|
+
((pick(9) << 24) | (pick(10) << 16) | (pick(11) << 8) | pick(12)) >>> 0;
|
|
252
|
+
x ^= (len << 24) >>> 0;
|
|
253
|
+
y ^= (len << 16) >>> 0;
|
|
254
|
+
for (let i = 0; i < len; i += 1) {
|
|
255
|
+
const v = (pick(i) + i * 17) & 255;
|
|
256
|
+
const fold = ((v << (i & 7)) | (v >>> (8 - (i & 7)))) & 255;
|
|
257
|
+
x = (x + fold) >>> 0;
|
|
258
|
+
x = rotl(x, r0);
|
|
259
|
+
y = (y + (x ^ (v << r2))) >>> 0;
|
|
260
|
+
y = rotr(y, r1);
|
|
261
|
+
}
|
|
262
|
+
let idx = 0;
|
|
263
|
+
return (byte) => {
|
|
264
|
+
const k = key[idx % len];
|
|
265
|
+
let out;
|
|
266
|
+
if (variant === 0) {
|
|
267
|
+
x = (x + (y ^ (k + idx + 1))) >>> 0;
|
|
268
|
+
x = rotl(x, r0);
|
|
269
|
+
y = (y + (k ^ (x & 255))) >>> 0;
|
|
270
|
+
y = rotr(y, r1);
|
|
271
|
+
out = (x ^ y ^ (x >>> 7)) & 255;
|
|
272
|
+
} else {
|
|
273
|
+
y = (y + k + (x & 255)) >>> 0;
|
|
274
|
+
y = rotl(y, r2);
|
|
275
|
+
x = (x + (y ^ (k << 8))) >>> 0;
|
|
276
|
+
x = rotr(x, r3);
|
|
277
|
+
out = (x + y + (x >>> 11)) & 255;
|
|
278
|
+
}
|
|
279
|
+
idx += 1;
|
|
280
|
+
return byte ^ out;
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
const ${u8ToStrName} = (u8) => {
|
|
284
|
+
if (typeof TextDecoder !== "undefined") {
|
|
285
|
+
return new TextDecoder().decode(u8);
|
|
286
|
+
}
|
|
287
|
+
let s = "";
|
|
288
|
+
for (let i = 0; i < u8.length; i += 1) {
|
|
289
|
+
s += String.fromCharCode(u8[i]);
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return decodeURIComponent(escape(s));
|
|
293
|
+
} catch {
|
|
294
|
+
return s;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function ${decodeName}(i) {
|
|
298
|
+
let state = 0;
|
|
299
|
+
let mapped;
|
|
300
|
+
let group;
|
|
301
|
+
let pos;
|
|
302
|
+
let pool;
|
|
303
|
+
let ${valueName};
|
|
304
|
+
let ${bytesName};
|
|
305
|
+
while (true) {
|
|
306
|
+
switch (state) {
|
|
307
|
+
case 0:
|
|
308
|
+
i = i - ${shiftName};
|
|
309
|
+
${valueName} = ${cacheName}[i];
|
|
310
|
+
state = ${valueName} !== undefined ? 5 : 1;
|
|
311
|
+
break;
|
|
312
|
+
case 1:
|
|
313
|
+
mapped = ${orderName}[i];
|
|
314
|
+
group = ${groupName}[mapped];
|
|
315
|
+
pos = ${indexName}[mapped];
|
|
316
|
+
state = 2;
|
|
317
|
+
break;
|
|
318
|
+
case 2:
|
|
319
|
+
pool = ${poolSelectName}(group);
|
|
320
|
+
${bytesName} = ${b64Name}(${unshiftName}(${getEncodedName}(pool, pos)));
|
|
321
|
+
state = 3;
|
|
322
|
+
break;
|
|
323
|
+
case 3: {
|
|
324
|
+
const next = ${rc4Name}(${keyName});
|
|
325
|
+
for (let n = 0; n < ${bytesName}.length; n += 1) {
|
|
326
|
+
${bytesName}[n] = next(${bytesName}[n]);
|
|
327
|
+
}
|
|
328
|
+
${valueName} = ${u8ToStrName}(${bytesName});
|
|
329
|
+
${cacheName}[i] = ${valueName};
|
|
330
|
+
state = 5;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case 5:
|
|
334
|
+
return ${valueName};
|
|
335
|
+
default:
|
|
336
|
+
return ${valueName};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
return parser.parse(code, { sourceType: "script" }).program.body;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function stringEncode(ast, ctx) {
|
|
345
|
+
const { traverse, t, options, rng } = ctx;
|
|
346
|
+
const pool = [];
|
|
347
|
+
const indexMap = new Map();
|
|
348
|
+
const keyLength = rng.int(4, 10);
|
|
349
|
+
const key = Array.from({ length: keyLength }, () => rng.int(0, 255));
|
|
350
|
+
const indexShift = rng.int(40, 900);
|
|
351
|
+
const minLength = options.stringsOptions.minLength;
|
|
352
|
+
const maxCount = options.stringsOptions.maxCount;
|
|
353
|
+
|
|
354
|
+
let decoderId = null;
|
|
355
|
+
let keyId = null;
|
|
356
|
+
let cacheId = null;
|
|
357
|
+
let programPathRef = null;
|
|
358
|
+
|
|
359
|
+
traverse(ast, {
|
|
360
|
+
Program(path) {
|
|
361
|
+
programPathRef = path;
|
|
362
|
+
},
|
|
363
|
+
StringLiteral(path) {
|
|
364
|
+
if (!programPathRef) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (isDirectiveLiteral(path) || isModuleString(path)) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (path.parentPath && path.parentPath.isJSXAttribute()) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (path.parentPath && path.parentPath.isObjectProperty() && path.parentKey === "key") {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const value = path.node.value;
|
|
377
|
+
if (typeof value !== "string" || value.length < minLength) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (pool.length >= maxCount) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
let index = indexMap.get(value);
|
|
384
|
+
if (index === undefined) {
|
|
385
|
+
index = pool.length;
|
|
386
|
+
pool.push(encodeString(value, key));
|
|
387
|
+
indexMap.set(value, index);
|
|
388
|
+
}
|
|
389
|
+
if (!decoderId) {
|
|
390
|
+
decoderId = t.identifier(ctx.nameGen.next());
|
|
391
|
+
keyId = t.identifier(ctx.nameGen.next());
|
|
392
|
+
cacheId = t.identifier(ctx.nameGen.next());
|
|
393
|
+
}
|
|
394
|
+
path.replaceWith(
|
|
395
|
+
t.callExpression(decoderId, [t.numericLiteral(index + indexShift)])
|
|
396
|
+
);
|
|
397
|
+
},
|
|
398
|
+
TemplateLiteral(path) {
|
|
399
|
+
if (path.node.expressions.length > 0) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (!programPathRef) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (pool.length >= maxCount) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const value = path.node.quasis[0].value.cooked || "";
|
|
409
|
+
if (value.length < minLength) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let index = indexMap.get(value);
|
|
413
|
+
if (index === undefined) {
|
|
414
|
+
index = pool.length;
|
|
415
|
+
pool.push(encodeString(value, key));
|
|
416
|
+
indexMap.set(value, index);
|
|
417
|
+
}
|
|
418
|
+
if (!decoderId) {
|
|
419
|
+
decoderId = t.identifier(ctx.nameGen.next());
|
|
420
|
+
keyId = t.identifier(ctx.nameGen.next());
|
|
421
|
+
cacheId = t.identifier(ctx.nameGen.next());
|
|
422
|
+
}
|
|
423
|
+
path.replaceWith(
|
|
424
|
+
t.callExpression(decoderId, [t.numericLiteral(index + indexShift)])
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (pool.length && programPathRef && decoderId && keyId && cacheId) {
|
|
430
|
+
const shiftId = t.identifier(ctx.nameGen.next());
|
|
431
|
+
const orderId = t.identifier(ctx.nameGen.next());
|
|
432
|
+
const groupId = t.identifier(ctx.nameGen.next());
|
|
433
|
+
const indexId = t.identifier(ctx.nameGen.next());
|
|
434
|
+
const rotKeyId = t.identifier(ctx.nameGen.next());
|
|
435
|
+
const alphabetId = t.identifier(ctx.nameGen.next());
|
|
436
|
+
const b64Id = t.identifier(ctx.nameGen.next());
|
|
437
|
+
const rc4Id = t.identifier(ctx.nameGen.next());
|
|
438
|
+
const u8ToStrId = t.identifier(ctx.nameGen.next());
|
|
439
|
+
const unshiftId = t.identifier(ctx.nameGen.next());
|
|
440
|
+
const poolSelectId = t.identifier(ctx.nameGen.next());
|
|
441
|
+
const getEncodedId = t.identifier(ctx.nameGen.next());
|
|
442
|
+
const rotKeyLength = rng.int(3, 8);
|
|
443
|
+
const rotKey = Array.from(
|
|
444
|
+
{ length: rotKeyLength },
|
|
445
|
+
() => rng.int(1, BASE64_ALPHABET.length - 1)
|
|
446
|
+
);
|
|
447
|
+
const rotOffset = rng.int(0, 1000);
|
|
448
|
+
const shiftedPool = pool.map((value) => shiftBase64(value, rotKey, rotOffset));
|
|
449
|
+
const order = Array.from({ length: pool.length }, (_, i) => i);
|
|
450
|
+
rng.shuffle(order);
|
|
451
|
+
const orderMap = new Array(pool.length);
|
|
452
|
+
for (let i = 0; i < order.length; i += 1) {
|
|
453
|
+
orderMap[order[i]] = i;
|
|
454
|
+
}
|
|
455
|
+
const shuffledPool = order.map((idx) => shiftedPool[idx]);
|
|
456
|
+
const groupCount = Math.min(Math.max(1, pool.length), rng.int(2, 6));
|
|
457
|
+
const { groups, groupMap, indexMap } = buildPoolGroups(
|
|
458
|
+
shuffledPool,
|
|
459
|
+
rng,
|
|
460
|
+
groupCount
|
|
461
|
+
);
|
|
462
|
+
const hiddenPools = buildHiddenPools(groups, rng);
|
|
463
|
+
const poolNames = groups.map(() => t.identifier(ctx.nameGen.next()));
|
|
464
|
+
const runtime = buildRuntime({
|
|
465
|
+
poolNames: poolNames.map((id) => id.name),
|
|
466
|
+
poolMeta: hiddenPools,
|
|
467
|
+
orderName: orderId.name,
|
|
468
|
+
groupName: groupId.name,
|
|
469
|
+
indexName: indexId.name,
|
|
470
|
+
keyName: keyId.name,
|
|
471
|
+
cacheName: cacheId.name,
|
|
472
|
+
decodeName: decoderId.name,
|
|
473
|
+
getEncodedName: getEncodedId.name,
|
|
474
|
+
shiftName: shiftId.name,
|
|
475
|
+
rotKeyName: rotKeyId.name,
|
|
476
|
+
alphabetName: alphabetId.name,
|
|
477
|
+
rotOffset,
|
|
478
|
+
b64Name: b64Id.name,
|
|
479
|
+
rc4Name: rc4Id.name,
|
|
480
|
+
u8ToStrName: u8ToStrId.name,
|
|
481
|
+
unshiftName: unshiftId.name,
|
|
482
|
+
poolSelectName: poolSelectId.name,
|
|
483
|
+
order: orderMap,
|
|
484
|
+
groupMap,
|
|
485
|
+
indexMap,
|
|
486
|
+
key,
|
|
487
|
+
shift: indexShift,
|
|
488
|
+
rotKey,
|
|
489
|
+
});
|
|
490
|
+
insertAtTop(programPathRef, runtime);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = stringEncode;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function isSimpleLiteral(node) {
|
|
2
|
+
return (
|
|
3
|
+
node.type === "NumericLiteral" ||
|
|
4
|
+
node.type === "StringLiteral" ||
|
|
5
|
+
node.type === "BooleanLiteral" ||
|
|
6
|
+
node.type === "NullLiteral"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function literalValue(node) {
|
|
11
|
+
if (node.type === "NullLiteral") return null;
|
|
12
|
+
return node.value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeLiteral(t, value) {
|
|
16
|
+
if (value === undefined) return t.identifier("undefined");
|
|
17
|
+
if (value === null) return t.nullLiteral();
|
|
18
|
+
if (typeof value === "string") return t.stringLiteral(value);
|
|
19
|
+
if (typeof value === "number") return t.numericLiteral(value);
|
|
20
|
+
if (typeof value === "boolean") return t.booleanLiteral(value);
|
|
21
|
+
return t.nullLiteral();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function containsSuper(node) {
|
|
25
|
+
if (!node) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const stack = [node];
|
|
29
|
+
while (stack.length) {
|
|
30
|
+
const current = stack.pop();
|
|
31
|
+
if (!current) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (current.type === "Super") {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
for (const key of Object.keys(current)) {
|
|
38
|
+
const value = current[key];
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
for (const child of value) {
|
|
41
|
+
if (child && typeof child.type === "string") {
|
|
42
|
+
stack.push(child);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else if (value && typeof value.type === "string") {
|
|
46
|
+
stack.push(value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
containsSuper,
|
|
55
|
+
isSimpleLiteral,
|
|
56
|
+
literalValue,
|
|
57
|
+
makeLiteral,
|
|
58
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { OPCODES } = require("./constants");
|
|
2
|
+
|
|
3
|
+
function createLabel() {
|
|
4
|
+
return { position: null, patches: [] };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class VmCompiler {
|
|
8
|
+
constructor(ctx, locals, opcodeMapping) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
this.locals = locals;
|
|
11
|
+
this.code = [];
|
|
12
|
+
this.consts = [];
|
|
13
|
+
this.constMap = new Map();
|
|
14
|
+
this.tempIndex = 0;
|
|
15
|
+
this.rng = ctx.rng;
|
|
16
|
+
this.opcodeEncode = opcodeMapping.encode;
|
|
17
|
+
this.opcodeMask = opcodeMapping.mask || 0;
|
|
18
|
+
const fakeOpcodes = ctx.options.vm.fakeOpcodes;
|
|
19
|
+
this.fakeOpcodeProbability =
|
|
20
|
+
typeof fakeOpcodes === "number" ? fakeOpcodes : 0;
|
|
21
|
+
this.fakeOpcodeKinds = [
|
|
22
|
+
OPCODES.FAKE_ADD,
|
|
23
|
+
OPCODES.FAKE_POP_PUSH,
|
|
24
|
+
OPCODES.FAKE_JMP,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
addConst(value) {
|
|
29
|
+
const key = `${typeof value}:${String(value)}`;
|
|
30
|
+
if (this.constMap.has(key)) {
|
|
31
|
+
return this.constMap.get(key);
|
|
32
|
+
}
|
|
33
|
+
const index = this.consts.length;
|
|
34
|
+
this.consts.push(value);
|
|
35
|
+
this.constMap.set(key, index);
|
|
36
|
+
return index;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
emitRaw(op, ...args) {
|
|
40
|
+
this.code.push(this.opcodeEncode[op] ^ this.opcodeMask, ...args);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
emit(op, ...args) {
|
|
44
|
+
this.emitRaw(op, ...args);
|
|
45
|
+
this.maybeEmitFake();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
emitJump(op, label) {
|
|
49
|
+
this.emitRaw(op);
|
|
50
|
+
const idx = this.code.length;
|
|
51
|
+
this.code.push(0);
|
|
52
|
+
if (label.position !== null) {
|
|
53
|
+
this.code[idx] = label.position;
|
|
54
|
+
} else {
|
|
55
|
+
label.patches.push(idx);
|
|
56
|
+
}
|
|
57
|
+
this.maybeEmitFake();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
emitTry(catchLabel, finallyLabel, endLabel) {
|
|
61
|
+
this.emitRaw(OPCODES.TRY);
|
|
62
|
+
const catchIdx = this.code.length;
|
|
63
|
+
this.code.push(0);
|
|
64
|
+
const finallyIdx = this.code.length;
|
|
65
|
+
this.code.push(0);
|
|
66
|
+
const endIdx = this.code.length;
|
|
67
|
+
this.code.push(0);
|
|
68
|
+
if (catchLabel) catchLabel.patches.push(catchIdx);
|
|
69
|
+
else this.code[catchIdx] = -1;
|
|
70
|
+
if (finallyLabel) finallyLabel.patches.push(finallyIdx);
|
|
71
|
+
else this.code[finallyIdx] = -1;
|
|
72
|
+
if (endLabel) endLabel.patches.push(endIdx);
|
|
73
|
+
this.maybeEmitFake();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
maybeEmitFake() {
|
|
77
|
+
if (this.fakeOpcodeProbability <= 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!this.rng.bool(this.fakeOpcodeProbability)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.emitFakeOpcode();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
emitFakeOpcode() {
|
|
87
|
+
const opcode = this.rng.pick(this.fakeOpcodeKinds);
|
|
88
|
+
if (opcode === OPCODES.FAKE_JMP) {
|
|
89
|
+
const target = this.rng.int(0, this.code.length);
|
|
90
|
+
this.emitRaw(OPCODES.FAKE_JMP, target);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.emitRaw(opcode);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
mark(label) {
|
|
97
|
+
label.position = this.code.length;
|
|
98
|
+
for (const idx of label.patches) {
|
|
99
|
+
this.code[idx] = label.position;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createTempName() {
|
|
104
|
+
let name = `_vm$tmp${this.tempIndex++}`;
|
|
105
|
+
while (this.locals.has(name)) {
|
|
106
|
+
name = `_vm$tmp${this.tempIndex++}`;
|
|
107
|
+
}
|
|
108
|
+
this.locals.add(name);
|
|
109
|
+
return name;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { createLabel, VmCompiler };
|