jsguardian 1.2.0
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/adversarial-tokens.js +176 -0
- package/ai-antipattern.js +235 -0
- package/ai-callgraph-poison.js +331 -0
- package/ai-confusion.js +644 -0
- package/ai-semantic-poison.js +276 -0
- package/canary.js +158 -0
- package/cne.js +686 -0
- package/index.js +248 -0
- package/integrity.js +47 -0
- package/jsobf-config.js +38 -0
- package/krak-compiler.js +1480 -0
- package/krak-vm-core.js +892 -0
- package/layers.js +136 -0
- package/opaque-pred.js +32 -0
- package/package.json +32 -0
- package/pipeline.js +327 -0
- package/prng.js +28 -0
- package/signature-break.js +101 -0
- package/temporal-keys.js +194 -0
- package/timing-oracle.js +129 -0
- package/transform-vm.js +266 -0
- package/transforms.js +371 -0
- package/vm-poison.js +247 -0
package/transforms.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Transformari custom aplicate PE AST inainte de javascript-obfuscator.
|
|
3
|
+
// Reguli de corectitudine (nenegociabile):
|
|
4
|
+
// - MBA pe constante: foloseste DOAR identitatea XOR (a^b = (a|b)-(a&b) sau (a&~b)|(~a&b)),
|
|
5
|
+
// care e int32-safe; fiecare expresie generata e RE-EVALUATA si, daca nu da exact valoarea,
|
|
6
|
+
// se face fallback la literalul original. Corectitudinea e garantata, nu presupusa.
|
|
7
|
+
// - Predicate opace: ramuri MOARTE gardate de un adevar din teoria numerelor peste o valoare
|
|
8
|
+
// runtime (Date.now), deci constant-folding / DCE nu le pot evalua sau elimina.
|
|
9
|
+
// - Cifrare stringuri: keystream propriu (NU stringArray-ul lui jsobf) => semnaturile
|
|
10
|
+
// deobfuscatoarelor pentru javascript-obfuscator nu se potrivesc pe acest strat.
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.applyConstMba = applyConstMba;
|
|
13
|
+
exports.applyOpaque = applyOpaque;
|
|
14
|
+
exports.applyStringCipher = applyStringCipher;
|
|
15
|
+
exports.buildCanaryDecoder = buildCanaryDecoder;
|
|
16
|
+
const prng_1 = require("./prng");
|
|
17
|
+
const opaque_pred_1 = require("./opaque-pred");
|
|
18
|
+
// ----------------------------------------------------------------------------
|
|
19
|
+
// Helpers AST
|
|
20
|
+
// ----------------------------------------------------------------------------
|
|
21
|
+
function intLit(t, n) {
|
|
22
|
+
n = n | 0;
|
|
23
|
+
const node = n < 0
|
|
24
|
+
? t.unaryExpression("-", t.numericLiteral(-n), true)
|
|
25
|
+
: t.numericLiteral(n);
|
|
26
|
+
mark(node);
|
|
27
|
+
if (n < 0)
|
|
28
|
+
mark(node.argument);
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
function mark(node) {
|
|
32
|
+
if (node)
|
|
33
|
+
node.__obf = true;
|
|
34
|
+
}
|
|
35
|
+
// Mini-evaluator pe subsetul de noduri pe care le PRODUCEM noi. Folosit ca plasa
|
|
36
|
+
// de siguranta: verifica fiecare expresie MBA inainte de a o pune in arbore.
|
|
37
|
+
function evalNode(node) {
|
|
38
|
+
switch (node.type) {
|
|
39
|
+
case "NumericLiteral":
|
|
40
|
+
return node.value;
|
|
41
|
+
case "UnaryExpression": {
|
|
42
|
+
const v = evalNode(node.argument);
|
|
43
|
+
if (node.operator === "-")
|
|
44
|
+
return -v;
|
|
45
|
+
if (node.operator === "~")
|
|
46
|
+
return ~v;
|
|
47
|
+
throw new Error("unsupported unary " + node.operator);
|
|
48
|
+
}
|
|
49
|
+
case "BinaryExpression": {
|
|
50
|
+
const a = evalNode(node.left);
|
|
51
|
+
const b = evalNode(node.right);
|
|
52
|
+
switch (node.operator) {
|
|
53
|
+
case "|": return a | b;
|
|
54
|
+
case "&": return a & b;
|
|
55
|
+
case "^": return a ^ b;
|
|
56
|
+
case "+": return a + b;
|
|
57
|
+
case "-": return a - b;
|
|
58
|
+
case "*": return a * b;
|
|
59
|
+
case "<<": return a << b;
|
|
60
|
+
case ">>>": return a >>> b;
|
|
61
|
+
default: throw new Error("unsupported bin " + node.operator);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
throw new Error("unsupported node " + node.type);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ----------------------------------------------------------------------------
|
|
69
|
+
// Stage A — MBA pe constante (Mixed Boolean-Arithmetic)
|
|
70
|
+
// ----------------------------------------------------------------------------
|
|
71
|
+
// Reprezinta v = a ^ b (b = v ^ a) si rescrie XOR-ul printr-o identitate echivalenta.
|
|
72
|
+
// Recursiv pe a si pe b. Toate operatiile raman in int32 => exact, fara overflow.
|
|
73
|
+
function buildMba(v, depth, rng, t) {
|
|
74
|
+
v = v | 0;
|
|
75
|
+
if (depth <= 0 || rng.next() < 0.15)
|
|
76
|
+
return intLit(t, v);
|
|
77
|
+
const a = rng.int32();
|
|
78
|
+
const b = (v ^ a) | 0;
|
|
79
|
+
// doua subarbore independente pentru fiecare operand (fara noduri partajate)
|
|
80
|
+
const A1 = buildMba(a, depth - 1, rng, t);
|
|
81
|
+
const A2 = buildMba(a, depth - 1, rng, t);
|
|
82
|
+
const B1 = buildMba(b, depth - 1, rng, t);
|
|
83
|
+
const B2 = buildMba(b, depth - 1, rng, t);
|
|
84
|
+
let node;
|
|
85
|
+
if (rng.next() < 0.5) {
|
|
86
|
+
// a ^ b = (a | b) - (a & b)
|
|
87
|
+
node = bin(t, "-", bin(t, "|", A1, B1), bin(t, "&", A2, B2));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// a ^ b = (a & ~b) | (~a & b)
|
|
91
|
+
node = bin(t, "|", bin(t, "&", A1, un(t, "~", B1)), bin(t, "&", un(t, "~", A2), B2));
|
|
92
|
+
}
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
function bin(t, op, l, r) {
|
|
96
|
+
const n = t.binaryExpression(op, l, r);
|
|
97
|
+
mark(n);
|
|
98
|
+
return n;
|
|
99
|
+
}
|
|
100
|
+
function un(t, op, a) {
|
|
101
|
+
const n = t.unaryExpression(op, a, true);
|
|
102
|
+
mark(n);
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
// Builds the anchor term: Math.imul(0, __hsh(__anc))
|
|
106
|
+
// Evaluates to 0 at runtime in every case, but the presence of identifiers
|
|
107
|
+
// __hsh and __anc blocks every static constant-folder (safeEval, webcrack,
|
|
108
|
+
// wakaru) because they test /[a-zA-Z_$]/ and bail out.
|
|
109
|
+
function buildAnchorZero(t) {
|
|
110
|
+
// Math.imul(0, __hsh(__anc)) === 0 always
|
|
111
|
+
const call = t.callExpression(t.memberExpression(t.identifier("Math"), t.identifier("imul")), [
|
|
112
|
+
t.numericLiteral(0),
|
|
113
|
+
t.callExpression(t.identifier("__hsh"), [t.identifier("__anc")]),
|
|
114
|
+
]);
|
|
115
|
+
markDeep(call);
|
|
116
|
+
return call;
|
|
117
|
+
}
|
|
118
|
+
function applyConstMba(ast, traverse, t, rng, opts) {
|
|
119
|
+
const depth = opts.mbaDepth ?? 2;
|
|
120
|
+
const thr = opts.mbaThreshold ?? 0.8;
|
|
121
|
+
// Anchor MBA expressions with Math.imul(0, __hsh(__anc)) ONLY when
|
|
122
|
+
// integrityAnchors is on — otherwise __hsh/__anc are not defined at runtime.
|
|
123
|
+
const anchorsOn = opts.integrityAnchors !== false;
|
|
124
|
+
const anchorRate = anchorsOn ? 0.30 : 0;
|
|
125
|
+
traverse(ast, {
|
|
126
|
+
NumericLiteral(path) {
|
|
127
|
+
if (path.node.__obf)
|
|
128
|
+
return;
|
|
129
|
+
// Skip object property keys — they must stay as NumericLiteral / Identifier,
|
|
130
|
+
// not become BinaryExpression (Babel validator would throw).
|
|
131
|
+
const parent = path.parent;
|
|
132
|
+
if ((t.isObjectProperty(parent) || t.isObjectMethod(parent)) &&
|
|
133
|
+
parent.key === path.node && !parent.computed) {
|
|
134
|
+
mark(path.node);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const v = path.node.value;
|
|
138
|
+
if (!Number.isInteger(v) || v < -2147483648 || v > 2147483647)
|
|
139
|
+
return;
|
|
140
|
+
if (rng.next() > thr) {
|
|
141
|
+
mark(path.node);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const inner = buildMba(v, depth, rng, t);
|
|
145
|
+
// verificare de corectitudine + wrap final in |0 (garanteaza int32)
|
|
146
|
+
let ok = false;
|
|
147
|
+
try {
|
|
148
|
+
ok = (evalNode(inner) | 0) === v;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
ok = false;
|
|
152
|
+
}
|
|
153
|
+
if (!ok) {
|
|
154
|
+
mark(path.node);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Base wrap: (inner | 0)
|
|
158
|
+
let wrapped = bin(t, "|", inner, intLit(t, 0));
|
|
159
|
+
// Anchor: (inner | 0) | Math.imul(0, __hsh(__anc)) === (inner | 0) always.
|
|
160
|
+
// The OR with zero is a no-op, but now the expression contains identifiers
|
|
161
|
+
// and cannot be folded by any static tool without executing __hsh/__anc.
|
|
162
|
+
if (rng.next() < anchorRate) {
|
|
163
|
+
wrapped = bin(t, "|", wrapped, buildAnchorZero(t));
|
|
164
|
+
}
|
|
165
|
+
path.replaceWith(wrapped);
|
|
166
|
+
path.skip();
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// ----------------------------------------------------------------------------
|
|
171
|
+
// Stage B — Predicate opace (cod mort negarbageabil)
|
|
172
|
+
// ----------------------------------------------------------------------------
|
|
173
|
+
// n = Date.now() & 0x7fffffff (valoare runtime, necunoscuta static).
|
|
174
|
+
// Adevar din teoria numerelor: n*(n+1) e mereu par (produs de intregi consecutivi).
|
|
175
|
+
// => ((n*(n+1)) % 2) === 1 e MEREU FALS, dar nu poate fi pliat fara a cunoaste n.
|
|
176
|
+
// Bloc mort gardat de un predicat opac DIVERSIFICAT (fara semnatura fixa).
|
|
177
|
+
function buildDeadBlock(parse, rng) {
|
|
178
|
+
const g = "_o" + ((rng.int32() >>> 0).toString(36));
|
|
179
|
+
const pred = (0, opaque_pred_1.falsePredicateSource)(rng);
|
|
180
|
+
const noise = rng.int32() & 0xffff;
|
|
181
|
+
// cod auto-continut, plauzibil, dar inaccesibil (predicatul e mereu fals)
|
|
182
|
+
const src = `if (${pred}) { var ${g} = (Date.now() & 1023) * 3; ${g} ^= ${noise}; return ${g}; }`;
|
|
183
|
+
const stmt = parse(src, {
|
|
184
|
+
sourceType: "module",
|
|
185
|
+
allowReturnOutsideFunction: true,
|
|
186
|
+
}).program.body[0];
|
|
187
|
+
markDeep(stmt);
|
|
188
|
+
return stmt;
|
|
189
|
+
}
|
|
190
|
+
function applyOpaque(ast, traverse, parse, t, rng, opts) {
|
|
191
|
+
const thr = opts.opaqueThreshold ?? 0.6;
|
|
192
|
+
traverse(ast, {
|
|
193
|
+
Function(path) {
|
|
194
|
+
if (path.node.__obf)
|
|
195
|
+
return;
|
|
196
|
+
const body = path.get("body");
|
|
197
|
+
if (!body.isBlockStatement())
|
|
198
|
+
return;
|
|
199
|
+
path.node.__obf = true;
|
|
200
|
+
if (rng.next() > thr)
|
|
201
|
+
return;
|
|
202
|
+
body.unshiftContainer("body", buildDeadBlock(parse, rng));
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// ----------------------------------------------------------------------------
|
|
207
|
+
// Stage C — Cifrare stringuri cu keystream propriu
|
|
208
|
+
// ----------------------------------------------------------------------------
|
|
209
|
+
function encryptString(str, seed) {
|
|
210
|
+
const bytes = Buffer.from(str, "utf8");
|
|
211
|
+
const gen = (0, prng_1.ksGen)(seed >>> 0);
|
|
212
|
+
const out = Buffer.alloc(bytes.length);
|
|
213
|
+
for (let k = 0; k < bytes.length; k++)
|
|
214
|
+
out[k] = bytes[k] ^ gen();
|
|
215
|
+
return out.toString("base64");
|
|
216
|
+
}
|
|
217
|
+
function applyStringCipher(ast, traverse, parse, t, rng, buildKey, Hbuild, opts) {
|
|
218
|
+
const thr = opts.stringThreshold ?? 0.9;
|
|
219
|
+
const targets = [];
|
|
220
|
+
traverse(ast, {
|
|
221
|
+
StringLiteral(path) {
|
|
222
|
+
if (path.node.__obf)
|
|
223
|
+
return;
|
|
224
|
+
const p = path.parent;
|
|
225
|
+
// nu atinge surse import/export, chei de proprietate ne-calculate, directive
|
|
226
|
+
if (t.isImportDeclaration(p) ||
|
|
227
|
+
t.isExportNamedDeclaration(p) ||
|
|
228
|
+
t.isExportAllDeclaration(p) ||
|
|
229
|
+
t.isImportExpression(p))
|
|
230
|
+
return;
|
|
231
|
+
if (t.isObjectProperty(p) && p.key === path.node && !p.computed)
|
|
232
|
+
return;
|
|
233
|
+
if (t.isObjectMethod(p) && p.key === path.node && !p.computed)
|
|
234
|
+
return;
|
|
235
|
+
if (t.isClassMethod(p) && p.key === path.node && !p.computed)
|
|
236
|
+
return;
|
|
237
|
+
if (path.parentPath && path.parentPath.isDirective && path.parentPath.isDirective())
|
|
238
|
+
return;
|
|
239
|
+
if (path.node.value.length === 0)
|
|
240
|
+
return;
|
|
241
|
+
if (rng.next() > thr) {
|
|
242
|
+
mark(path.node);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
targets.push(path);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
if (targets.length === 0)
|
|
249
|
+
return [];
|
|
250
|
+
const entries = [];
|
|
251
|
+
targets.forEach((path, i) => {
|
|
252
|
+
entries.push(encryptString(path.node.value, (buildKey ^ i) >>> 0));
|
|
253
|
+
// Use __dc (real decoder name) — __dec is the canary injected post-jsobf
|
|
254
|
+
const call = t.callExpression(t.identifier("__dc"), [t.numericLiteral(i)]);
|
|
255
|
+
call.__obf = true;
|
|
256
|
+
path.replaceWith(call);
|
|
257
|
+
});
|
|
258
|
+
// ── Real runtime decoder ─────────────────────────────────────────────────
|
|
259
|
+
// The real decoder function is named __dc (opaque after jsobf).
|
|
260
|
+
// The canary decoder (__dec, __ca, __bk with visible names) is injected
|
|
261
|
+
// POST-jsobf at global scope by buildCanaryDecoder() — see pipeline.ts.
|
|
262
|
+
const storedBk = (buildKey ^ Hbuild) >>> 0;
|
|
263
|
+
const bkLine = Hbuild === 0
|
|
264
|
+
? `var __bk = ${buildKey} >>> 0;`
|
|
265
|
+
: `var __bk = (${storedBk} ^ __hsh(__anc)) >>> 0;`;
|
|
266
|
+
const runtimeSrc = `
|
|
267
|
+
${bkLine}
|
|
268
|
+
var __ca = ${JSON.stringify(entries)};
|
|
269
|
+
function __dc(__i) {
|
|
270
|
+
var __raw = Buffer.from(__ca[__i], "base64");
|
|
271
|
+
var __s = (__bk ^ __i) >>> 0;
|
|
272
|
+
var __o = Buffer.alloc(__raw.length);
|
|
273
|
+
for (var __k = 0; __k < __raw.length; __k++) {
|
|
274
|
+
__s = (__s + 0x6d2b79f5) | 0;
|
|
275
|
+
var __t = Math.imul(__s ^ (__s >>> 15), 1 | __s);
|
|
276
|
+
__t = (__t + Math.imul(__t ^ (__t >>> 7), 61 | __t)) ^ __t;
|
|
277
|
+
__o[__k] = __raw[__k] ^ (((__t ^ (__t >>> 14)) >>> 0) & 0xff);
|
|
278
|
+
}
|
|
279
|
+
return __o.toString("utf8");
|
|
280
|
+
}`;
|
|
281
|
+
const runtimeAst = parse(runtimeSrc, { sourceType: "module" });
|
|
282
|
+
const nodes = runtimeAst.program.body;
|
|
283
|
+
// marcheaza tot ce injectam ca sa nu fie re-procesat
|
|
284
|
+
for (const nd of nodes)
|
|
285
|
+
markDeep(nd);
|
|
286
|
+
ast.program.body.unshift(...nodes);
|
|
287
|
+
return entries;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Builds a canary decoder source to be injected POST-jsobf at global scope.
|
|
291
|
+
* Uses recognisable names (__dec, __ca, __bk) but a WRONG key so any attacker
|
|
292
|
+
* who finds and uses this function gets plausible-looking garbage.
|
|
293
|
+
*
|
|
294
|
+
* The real decoder (__dc) lives inside the module IIFE, renamed by jsobf.
|
|
295
|
+
*/
|
|
296
|
+
// Plausible-looking strings the honeypot __dec will return (wrong but convincing).
|
|
297
|
+
// An attacker who calls __dec sees real-looking API strings — not mojibake.
|
|
298
|
+
// They follow the path, which leads nowhere useful.
|
|
299
|
+
const HONEYPOT_PLAUSIBLE_STRINGS = [
|
|
300
|
+
"validateLicense", "POST", "/api/v1/validate", "Authorization",
|
|
301
|
+
"application/json", "X-License-Key", "invalid_product", "checksum_mismatch",
|
|
302
|
+
"basic", "advanced", "enterprise", "expired", "Content-Type",
|
|
303
|
+
"api.license.io", "/v2/keys/verify", "HMAC-SHA256",
|
|
304
|
+
];
|
|
305
|
+
function buildCanaryDecoder(buildKey, entries, rng) {
|
|
306
|
+
if (entries.length === 0)
|
|
307
|
+
return "";
|
|
308
|
+
// Per-build "wrong" seed — decodes to plausible strings, not mojibake
|
|
309
|
+
const wrongSeed = (buildKey ^ rng.int32() ^ 0xdeadbeef) >>> 0;
|
|
310
|
+
// Build a fake string table that decodes correctly with wrongSeed.
|
|
311
|
+
// We pick from HONEYPOT_PLAUSIBLE_STRINGS so __dec returns clean-looking output.
|
|
312
|
+
// The attacker calls __dec(0), gets "validateLicense", trusts the path.
|
|
313
|
+
const count = Math.min(entries.length, HONEYPOT_PLAUSIBLE_STRINGS.length);
|
|
314
|
+
const picks = [];
|
|
315
|
+
for (let i = 0; i < count; i++) {
|
|
316
|
+
picks.push(HONEYPOT_PLAUSIBLE_STRINGS[i % HONEYPOT_PLAUSIBLE_STRINGS.length]);
|
|
317
|
+
}
|
|
318
|
+
// Pad to same length as real entries so indices look consistent
|
|
319
|
+
while (picks.length < entries.length) {
|
|
320
|
+
picks.push(HONEYPOT_PLAUSIBLE_STRINGS[(rng.int32() >>> 0) % HONEYPOT_PLAUSIBLE_STRINGS.length]);
|
|
321
|
+
}
|
|
322
|
+
// Encode each plausible string with wrongSeed so __dec decodes it correctly
|
|
323
|
+
function encodeWithSeed(str, idx) {
|
|
324
|
+
const raw = Buffer.from(str, "utf8");
|
|
325
|
+
let s = (wrongSeed ^ idx) >>> 0;
|
|
326
|
+
const out = Buffer.alloc(raw.length);
|
|
327
|
+
for (let k = 0; k < raw.length; k++) {
|
|
328
|
+
s = (s + 0x6d2b79f5) | 0;
|
|
329
|
+
let tt = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
330
|
+
tt = (tt + Math.imul(tt ^ (tt >>> 7), 61 | tt)) ^ tt;
|
|
331
|
+
out[k] = raw[k] ^ (((tt ^ (tt >>> 14)) >>> 0) & 0xff);
|
|
332
|
+
}
|
|
333
|
+
return out.toString("base64");
|
|
334
|
+
}
|
|
335
|
+
const fakeEntries = picks.map((s, i) => encodeWithSeed(s, i));
|
|
336
|
+
// _pk is runtime-derived: per-build constant XOR'd with a Date.now() fragment.
|
|
337
|
+
// Static analysis cannot read a fixed poison key. In a real process Date.now()
|
|
338
|
+
// changes every millisecond so _pk varies; the canary decoder always decodes
|
|
339
|
+
// correctly because it uses wrongSeed, not _pk at all.
|
|
340
|
+
const pkBase = (rng.int32() & 0xFF) | 1; // non-zero per-build byte
|
|
341
|
+
const pkHex = `0x${pkBase.toString(16)}`;
|
|
342
|
+
return `
|
|
343
|
+
var __bk = ${wrongSeed} >>> 0;
|
|
344
|
+
var __ca = ${JSON.stringify(fakeEntries)};
|
|
345
|
+
function __dec(__i) {
|
|
346
|
+
if (!__ca[__i]) return "";
|
|
347
|
+
var __raw = Buffer.from(__ca[__i], "base64");
|
|
348
|
+
var __s = (__bk ^ __i) >>> 0;
|
|
349
|
+
var __o = Buffer.alloc(__raw.length);
|
|
350
|
+
for (var __k = 0; __k < __raw.length; __k++) {
|
|
351
|
+
__s = (__s + 0x6d2b79f5) | 0;
|
|
352
|
+
var __t = Math.imul(__s ^ (__s >>> 15), 1 | __s);
|
|
353
|
+
__t = (__t + Math.imul(__t ^ (__t >>> 7), 61 | __t)) ^ __t;
|
|
354
|
+
__o[__k] = __raw[__k] ^ (((__t ^ (__t >>> 14)) >>> 0) & 0xff);
|
|
355
|
+
}
|
|
356
|
+
return __o.toString("utf8");
|
|
357
|
+
}`;
|
|
358
|
+
}
|
|
359
|
+
function markDeep(node) {
|
|
360
|
+
if (!node || typeof node !== "object")
|
|
361
|
+
return;
|
|
362
|
+
if (node.type)
|
|
363
|
+
node.__obf = true;
|
|
364
|
+
for (const k of Object.keys(node)) {
|
|
365
|
+
const v = node[k];
|
|
366
|
+
if (Array.isArray(v))
|
|
367
|
+
v.forEach(markDeep);
|
|
368
|
+
else if (v && typeof v === "object" && v.type)
|
|
369
|
+
markDeep(v);
|
|
370
|
+
}
|
|
371
|
+
}
|
package/vm-poison.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// vm-poison.ts — Environment isolation helpers.
|
|
3
|
+
//
|
|
4
|
+
// The old stack-VM sandbox detection layer (Layer 12, buildSandboxVmCallStmt,
|
|
5
|
+
// OP_SANDBOX_DETECT / OP_HRTIME_BYTE / OP_POISON_APPLY opcodes) has been
|
|
6
|
+
// removed along with the stack-based VM engine. KrakVM handles bytecode
|
|
7
|
+
// virtualization; environment isolation is handled by wrapInModuleIife below.
|
|
8
|
+
//
|
|
9
|
+
// Stubs are kept for any code that imports the old names so it still compiles.
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.VM_SANDBOX_POISON = void 0;
|
|
45
|
+
exports.buildSandboxPoison = buildSandboxPoison;
|
|
46
|
+
exports.wrapInModuleIife = wrapInModuleIife;
|
|
47
|
+
const parser_1 = require("@babel/parser");
|
|
48
|
+
const t = __importStar(require("@babel/types"));
|
|
49
|
+
/** @deprecated No-op stub — old sandbox VM detection removed. */
|
|
50
|
+
function buildSandboxPoison(_poisonKey) { return ""; }
|
|
51
|
+
/** @deprecated No-op — old sandbox VM detection removed. */
|
|
52
|
+
exports.VM_SANDBOX_POISON = "";
|
|
53
|
+
// The raw IIFE shell. `body` is dropped between the head and the tail. This is
|
|
54
|
+
// the CJS-safe wrapper: it isolates top-level declarations from the outer scope
|
|
55
|
+
// and injects safe fallbacks for module/exports/require/__dirname/__filename so
|
|
56
|
+
// the anti-tamper layers (which reference them) work in any host.
|
|
57
|
+
function iifeShell(body, async = false) {
|
|
58
|
+
return (`;(${async ? "async " : ""}function(module,exports,require,Buffer,process,global,__dirname,__filename){\n` +
|
|
59
|
+
body +
|
|
60
|
+
`\n}).call(` +
|
|
61
|
+
`typeof globalThis!=='undefined'?globalThis:this,` +
|
|
62
|
+
`typeof module!=='undefined'?module:{exports:{}},` +
|
|
63
|
+
`typeof exports!=='undefined'?exports:{},` +
|
|
64
|
+
`typeof require!=='undefined'?require:function(m){throw new Error('Cannot require '+m);},` +
|
|
65
|
+
`typeof Buffer!=='undefined'?Buffer:undefined,` +
|
|
66
|
+
`typeof process!=='undefined'?process:{},` +
|
|
67
|
+
`typeof global!=='undefined'?global:typeof globalThis!=='undefined'?globalThis:{},` +
|
|
68
|
+
`typeof __dirname!=='undefined'?__dirname:'/',` +
|
|
69
|
+
`typeof __filename!=='undefined'?__filename:'index.js'` +
|
|
70
|
+
`);`);
|
|
71
|
+
}
|
|
72
|
+
// ESM `import`/`export` statements are only legal at the top level of a module —
|
|
73
|
+
// they CANNOT live inside the IIFE function body. Wrapping a module that uses
|
|
74
|
+
// them produces invalid code: Node throws ("'import'/'export' may only appear at
|
|
75
|
+
// the top level") and the integrity anchor never matches → fake tamper errors.
|
|
76
|
+
//
|
|
77
|
+
// splitEsm performs *string surgery* (NOT re-generation) so the obfuscated body
|
|
78
|
+
// stays byte-for-byte intact — only the import/export statements are relocated
|
|
79
|
+
// outside the IIFE. Exported bindings are bridged through a module-scope object
|
|
80
|
+
// the IIFE writes into.
|
|
81
|
+
//
|
|
82
|
+
// Returns null when the code is NOT an ES module (no import/export) — the caller
|
|
83
|
+
// then uses the plain CJS wrapper unchanged, so CJS output is byte-identical.
|
|
84
|
+
const EXP = "__crxExp";
|
|
85
|
+
const DEFLOCAL = "__crxDefault";
|
|
86
|
+
// True if the program contains a top-level `await` (or `for await`) — i.e. one
|
|
87
|
+
// not nested inside a function/method. Such modules use ESM top-level await,
|
|
88
|
+
// which is illegal once the body is wrapped in a *non-async* IIFE. Walks the AST
|
|
89
|
+
// manually, stopping at function boundaries.
|
|
90
|
+
function hasTopLevelAwait(program) {
|
|
91
|
+
let found = false;
|
|
92
|
+
const FN = new Set([
|
|
93
|
+
"FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression",
|
|
94
|
+
"ObjectMethod", "ClassMethod", "ClassPrivateMethod",
|
|
95
|
+
]);
|
|
96
|
+
const walk = (node) => {
|
|
97
|
+
if (found || !node || typeof node !== "object")
|
|
98
|
+
return;
|
|
99
|
+
if (Array.isArray(node)) {
|
|
100
|
+
for (const n of node)
|
|
101
|
+
walk(n);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (typeof node.type !== "string")
|
|
105
|
+
return;
|
|
106
|
+
if (node.type === "AwaitExpression") {
|
|
107
|
+
found = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (node.type === "ForOfStatement" && node.await) {
|
|
111
|
+
found = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (FN.has(node.type))
|
|
115
|
+
return; // don't descend into nested functions
|
|
116
|
+
for (const k of Object.keys(node)) {
|
|
117
|
+
if (k === "loc" || k === "start" || k === "end" || k === "leadingComments" ||
|
|
118
|
+
k === "trailingComments" || k === "innerComments")
|
|
119
|
+
continue;
|
|
120
|
+
walk(node[k]);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
for (const stmt of program.body)
|
|
124
|
+
walk(stmt);
|
|
125
|
+
return found;
|
|
126
|
+
}
|
|
127
|
+
function splitEsm(code) {
|
|
128
|
+
let ast;
|
|
129
|
+
try {
|
|
130
|
+
ast = (0, parser_1.parse)(code, { sourceType: "module", allowReturnOutsideFunction: true });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null; // unparseable here → caller falls back to plain wrap
|
|
134
|
+
}
|
|
135
|
+
const program = ast.program;
|
|
136
|
+
const hasEsm = program.body.some((n) => t.isImportDeclaration(n) ||
|
|
137
|
+
t.isExportNamedDeclaration(n) ||
|
|
138
|
+
t.isExportDefaultDeclaration(n) ||
|
|
139
|
+
t.isExportAllDeclaration(n));
|
|
140
|
+
if (!hasEsm)
|
|
141
|
+
return null; // pure CJS → caller wraps as before (no change)
|
|
142
|
+
const edits = [];
|
|
143
|
+
const topParts = [];
|
|
144
|
+
const bridge = [];
|
|
145
|
+
let hasDefault = false;
|
|
146
|
+
let defaultLocal = DEFLOCAL;
|
|
147
|
+
const slice = (n) => code.slice(n.start, n.end);
|
|
148
|
+
const remove = (n) => edits.push({ start: n.start, end: n.end, replacement: "" });
|
|
149
|
+
for (const node of program.body) {
|
|
150
|
+
// import ... → keep verbatim at top (bindings live in module scope; the
|
|
151
|
+
// IIFE closes over them). Remove from the body.
|
|
152
|
+
if (t.isImportDeclaration(node)) {
|
|
153
|
+
topParts.push(slice(node));
|
|
154
|
+
remove(node);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// export ... from "mod" / export * from "mod" → re-export, no local
|
|
158
|
+
// binding; keep verbatim at top, remove from body.
|
|
159
|
+
if ((t.isExportNamedDeclaration(node) && node.source) ||
|
|
160
|
+
t.isExportAllDeclaration(node)) {
|
|
161
|
+
topParts.push(slice(node));
|
|
162
|
+
remove(node);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// export <decl> (export function/const/let/var/class) → strip ONLY the
|
|
166
|
+
// `export ` keyword, leave the declaration verbatim inside the IIFE.
|
|
167
|
+
if (t.isExportNamedDeclaration(node) && node.declaration) {
|
|
168
|
+
const decl = node.declaration;
|
|
169
|
+
edits.push({ start: node.start, end: decl.start, replacement: "" });
|
|
170
|
+
if (t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) {
|
|
171
|
+
if (decl.id)
|
|
172
|
+
bridge.push({ exportName: decl.id.name, localName: decl.id.name });
|
|
173
|
+
}
|
|
174
|
+
else if (t.isVariableDeclaration(decl)) {
|
|
175
|
+
for (const d of decl.declarations) {
|
|
176
|
+
if (t.isIdentifier(d.id))
|
|
177
|
+
bridge.push({ exportName: d.id.name, localName: d.id.name });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// export { a as b, c } (specifiers, no source) → remove statement, bridge.
|
|
183
|
+
if (t.isExportNamedDeclaration(node) && !node.declaration) {
|
|
184
|
+
for (const spec of node.specifiers) {
|
|
185
|
+
if (t.isExportSpecifier(spec)) {
|
|
186
|
+
const exportName = t.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;
|
|
187
|
+
bridge.push({ exportName, localName: spec.local.name });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
remove(node);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// export default <decl|expr>
|
|
194
|
+
if (t.isExportDefaultDeclaration(node)) {
|
|
195
|
+
hasDefault = true;
|
|
196
|
+
const d = node.declaration;
|
|
197
|
+
if ((t.isFunctionDeclaration(d) || t.isClassDeclaration(d)) && d.id) {
|
|
198
|
+
// named declaration: strip `export default `, keep decl, bridge by name
|
|
199
|
+
edits.push({ start: node.start, end: d.start, replacement: "" });
|
|
200
|
+
defaultLocal = d.id.name;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// anonymous decl or expression: `export default X` → `var __crxDefault=X`
|
|
204
|
+
edits.push({ start: node.start, end: d.start, replacement: `var ${DEFLOCAL}=` });
|
|
205
|
+
defaultLocal = DEFLOCAL;
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// apply edits right-to-left so offsets stay valid
|
|
211
|
+
edits.sort((a, b) => b.start - a.start);
|
|
212
|
+
let body = code;
|
|
213
|
+
for (const e of edits)
|
|
214
|
+
body = body.slice(0, e.start) + e.replacement + body.slice(e.end);
|
|
215
|
+
const bridgeAssign = bridge.map(({ exportName, localName }) => `${EXP}.${exportName}=${localName};`).join("") +
|
|
216
|
+
(hasDefault ? `${EXP}.default=${defaultLocal};` : "");
|
|
217
|
+
const postParts = bridge.map(({ exportName }) =>
|
|
218
|
+
// `default` is a reserved word — `export const default=…` is illegal; it must
|
|
219
|
+
// be re-emitted as a default export.
|
|
220
|
+
exportName === "default"
|
|
221
|
+
? `export default ${EXP}.default;`
|
|
222
|
+
: `export const ${exportName}=${EXP}.${exportName};`);
|
|
223
|
+
if (hasDefault)
|
|
224
|
+
postParts.push(`export default ${EXP}.default;`);
|
|
225
|
+
return {
|
|
226
|
+
top: topParts.join("\n"),
|
|
227
|
+
bridgeDecl: bridge.length || hasDefault ? `var ${EXP}={};` : "",
|
|
228
|
+
bridgeAssign,
|
|
229
|
+
postExports: postParts.join("\n"),
|
|
230
|
+
body,
|
|
231
|
+
needsAsync: hasTopLevelAwait(program),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// Module-style IIFE wrapper — hides globals from the outer scope.
|
|
235
|
+
//
|
|
236
|
+
// CJS code: wrapped exactly as before (byte-identical).
|
|
237
|
+
// ESM code: import/export are hoisted outside the IIFE so the output is a valid
|
|
238
|
+
// ES module; the wrapped body keeps its scope isolation + anti-tamper layers.
|
|
239
|
+
function wrapInModuleIife(code) {
|
|
240
|
+
const esm = splitEsm(code);
|
|
241
|
+
if (!esm)
|
|
242
|
+
return iifeShell(code); // pure CJS — unchanged behaviour
|
|
243
|
+
const innerBody = esm.bridgeAssign ? esm.body + "\n" + esm.bridgeAssign : esm.body;
|
|
244
|
+
return [esm.top, esm.bridgeDecl, iifeShell(innerBody, esm.needsAsync), esm.postExports]
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.join("\n");
|
|
247
|
+
}
|