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/cne.js ADDED
@@ -0,0 +1,686 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // CNE — Criptor Native Engine (replaces javascript-obfuscator)
4
+ //
5
+ // Design goals:
6
+ // - ZERO public deobfuscation tooling written against this output
7
+ // - No recognisable string-array/shuffler/RC4/wrapper-function pattern
8
+ // - Full scope-aware identifier rename (every name, including injected
9
+ // __hsh / __anc / __vmq / __ksq) → _0x<hex><salt> names
10
+ // - Inline XOR string encoding with per-string random key (no central table)
11
+ // - Hex-literal conversion for all numeric literals
12
+ // - Variable-length string splitting (2–12 chars, random, seed-driven)
13
+ // - Control-flow flattening on function bodies (state-machine dispatcher)
14
+ // - Opaque true/false constants guarding dead branches
15
+ // - Self-contained: one Babel AST pass, no external tool dependency
16
+ //
17
+ // What we intentionally do NOT do here (already handled by earlier pipeline stages):
18
+ // - MBA constant encoding (transforms.ts applyConstMba)
19
+ // - Opaque predicates (transforms.ts applyOpaque)
20
+ // - VM bytecode virtualization (transform-vm.ts)
21
+ // - String cipher (custom RC4) (transforms.ts applyStringCipher)
22
+ // - Integrity anchors __anc/__hsh (integrity.ts + pipeline.ts)
23
+ // ============================================================================
24
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ var desc = Object.getOwnPropertyDescriptor(m, k);
27
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
+ desc = { enumerable: true, get: function() { return m[k]; } };
29
+ }
30
+ Object.defineProperty(o, k2, desc);
31
+ }) : (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ o[k2] = m[k];
34
+ }));
35
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
37
+ }) : function(o, v) {
38
+ o["default"] = v;
39
+ });
40
+ var __importStar = (this && this.__importStar) || (function () {
41
+ var ownKeys = function(o) {
42
+ ownKeys = Object.getOwnPropertyNames || function (o) {
43
+ var ar = [];
44
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
+ return ar;
46
+ };
47
+ return ownKeys(o);
48
+ };
49
+ return function (mod) {
50
+ if (mod && mod.__esModule) return mod;
51
+ var result = {};
52
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
+ __setModuleDefault(result, mod);
54
+ return result;
55
+ };
56
+ })();
57
+ var __importDefault = (this && this.__importDefault) || function (mod) {
58
+ return (mod && mod.__esModule) ? mod : { "default": mod };
59
+ };
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.makeNameGen = makeNameGen;
62
+ exports.normalizeEsmExports = normalizeEsmExports;
63
+ exports.applyFullRename = applyFullRename;
64
+ exports.applyHexLiterals = applyHexLiterals;
65
+ exports.applyStringSplit = applyStringSplit;
66
+ exports.applyCneFlattening = applyCneFlattening;
67
+ exports.applyOpaqueConstants = applyOpaqueConstants;
68
+ exports.applyDeadCodeInjection = applyDeadCodeInjection;
69
+ exports.buildEncodedAnchorSource = buildEncodedAnchorSource;
70
+ exports.obfuscateExportNames = obfuscateExportNames;
71
+ exports.cneObfuscate = cneObfuscate;
72
+ const parser_1 = require("@babel/parser");
73
+ const traverse_1 = __importDefault(require("@babel/traverse"));
74
+ const generator_1 = __importDefault(require("@babel/generator"));
75
+ const t = __importStar(require("@babel/types"));
76
+ const traverse = traverse_1.default.default || traverse_1.default;
77
+ const generate = generator_1.default.default || generator_1.default;
78
+ // ----------------------------------------------------------------------------
79
+ // Helpers
80
+ // ----------------------------------------------------------------------------
81
+ function mark(node) { if (node)
82
+ node.__cne = true; }
83
+ function markDeep(node) {
84
+ if (!node || typeof node !== "object")
85
+ return;
86
+ node.__cne = true;
87
+ for (const k of Object.keys(node)) {
88
+ const v = node[k];
89
+ if (Array.isArray(v))
90
+ v.forEach(markDeep);
91
+ else if (v && typeof v === "object" && v.type)
92
+ markDeep(v);
93
+ }
94
+ }
95
+ // ----------------------------------------------------------------------------
96
+ // A — Identifier Name Generator
97
+ //
98
+ // All output names look like _0x<4 hex><salt suffix>
99
+ // Salt is per-build (derived from seed), suffix is per-identifier index.
100
+ // Format: _0x + 4 hex chars from (seed XOR idx rotated) + 1-2 salt chars
101
+ //
102
+ // Crucially: we rename ALL binding names in scope — including our own injected
103
+ // names like __hsh, __anc, __vmq, __ksq, __dc, __bk, __ca, __drift, __toc.
104
+ // ----------------------------------------------------------------------------
105
+ function makeNameGen(seed) {
106
+ // Two independent hash rounds to spread entropy
107
+ const a = (seed ^ 0xdeadbeef) >>> 0;
108
+ const b = (seed ^ 0xcafebabe) >>> 0;
109
+ return (idx) => {
110
+ let h = (a ^ Math.imul(idx + 1, 0x9e3779b9)) >>> 0;
111
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b);
112
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
113
+ h = (h ^ (h >>> 16)) >>> 0;
114
+ const part1 = (h & 0xffff).toString(16).padStart(4, "0");
115
+ const part2 = ((b ^ (idx * 0x6b43)) & 0xff).toString(16).padStart(2, "0");
116
+ return `_0x${part1}${part2}`;
117
+ };
118
+ }
119
+ // ----------------------------------------------------------------------------
120
+ // B — Full Scope Rename
121
+ //
122
+ // Walks every scope in the AST. For each scope, collects all local bindings
123
+ // (var, let, const, function, param) and renames them with the name generator.
124
+ // Uses Babel's scope.rename() so all references update atomically.
125
+ //
126
+ // Special handling:
127
+ // - Names already looking like _0x... are left alone (already renamed or
128
+ // externally injected from an earlier pass we don't want to double-rename).
129
+ // - Global references (no binding found) are left alone.
130
+ // - module.exports / exports.X property names are NOT renamed (they're
131
+ // external API surface).
132
+ // ----------------------------------------------------------------------------
133
+ // ----------------------------------------------------------------------------
134
+ // ESM export normalisation — MUST run before applyFullRename.
135
+ //
136
+ // Babel's scope.rename() is buggy for *declaration exports* (`export class Foo`,
137
+ // `export function Foo`, `export default …`): the binding path points at the
138
+ // inner declaration while the export lives on the wrapping node, so renaming
139
+ // desyncs the auto-generated specifier from the declaration and emits
140
+ // "Export 'X' is not defined" (rejected by both Babel re-parse and V8).
141
+ // Known upstream bugs: babel/babel#11591, #9266, PR #3629.
142
+ //
143
+ // Workaround (community-standard): rewrite every declaration-export into a plain
144
+ // declaration followed by a separate `export { local as public }` specifier, so
145
+ // rename only ever touches ordinary declarations + ordinary reference
146
+ // identifiers — never the broken declaration-export path.
147
+ function normalizeEsmExports(ast) {
148
+ const body = ast.program.body;
149
+ const out = [];
150
+ let defIdx = 0;
151
+ for (const node of body) {
152
+ // `export <decl>` with no `from` source
153
+ if (t.isExportNamedDeclaration(node) && node.declaration && !node.source) {
154
+ const decl = node.declaration;
155
+ const names = [];
156
+ if (t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) {
157
+ if (decl.id)
158
+ names.push(decl.id.name);
159
+ }
160
+ else if (t.isVariableDeclaration(decl)) {
161
+ for (const d of decl.declarations) {
162
+ if (t.isIdentifier(d.id))
163
+ names.push(d.id.name);
164
+ }
165
+ }
166
+ out.push(decl);
167
+ if (names.length) {
168
+ out.push(t.exportNamedDeclaration(null, names.map((n) => t.exportSpecifier(t.identifier(n), t.identifier(n)))));
169
+ }
170
+ continue;
171
+ }
172
+ // `export default <decl|expr>`
173
+ if (t.isExportDefaultDeclaration(node)) {
174
+ const d = node.declaration;
175
+ if ((t.isFunctionDeclaration(d) || t.isClassDeclaration(d)) && d.id) {
176
+ out.push(d);
177
+ out.push(t.exportNamedDeclaration(null, [
178
+ t.exportSpecifier(t.identifier(d.id.name), t.identifier("default")),
179
+ ]));
180
+ }
181
+ else {
182
+ const local = `__crxDef${defIdx++}`;
183
+ const expr = t.isFunctionDeclaration(d) || t.isClassDeclaration(d)
184
+ ? t.toExpression(d)
185
+ : d;
186
+ out.push(t.variableDeclaration("const", [
187
+ t.variableDeclarator(t.identifier(local), expr),
188
+ ]));
189
+ out.push(t.exportNamedDeclaration(null, [
190
+ t.exportSpecifier(t.identifier(local), t.identifier("default")),
191
+ ]));
192
+ }
193
+ continue;
194
+ }
195
+ out.push(node);
196
+ }
197
+ ast.program.body = out;
198
+ }
199
+ function applyFullRename(ast, _rng, nameGen) {
200
+ // Post-order (exit) traversal: rename bindings in children first, then parents.
201
+ // This avoids Babel scope-cache stale-reference bugs that occur when you
202
+ // rename a parent binding before its shadowing children have been processed.
203
+ //
204
+ // scope.rename(old, new) is Babel's own API — it finds every reference to
205
+ // `old` reachable from this scope (respecting shadows in child scopes) and
206
+ // rewrites them atomically including the declaration node.
207
+ let idx = 0;
208
+ traverse(ast, {
209
+ Scope: {
210
+ exit(path) {
211
+ const scope = path.scope;
212
+ // snapshot the binding names before we start renaming (rename mutates bindings)
213
+ const names = Object.keys(scope.bindings);
214
+ for (const name of names) {
215
+ const binding = scope.bindings[name];
216
+ if (!binding)
217
+ continue;
218
+ // Already mangled — skip
219
+ if (/^_0x[0-9a-f]{4,}/.test(name))
220
+ continue;
221
+ // Skip VM runtime internals: names inside __vmq / __ksq / __gfdXXXX functions
222
+ // These must stay stable because the VM runtime references them by name.
223
+ if (/^(__vmq|__ksq|__gfd)/.test(name))
224
+ continue;
225
+ // Walk up the scope chain to see if we're inside a VM runtime function
226
+ let _sc = path.scope;
227
+ let _inVmFn = false;
228
+ while (_sc) {
229
+ const _fn = _sc.block;
230
+ if (_fn && _fn.type === 'FunctionDeclaration' && _fn.id &&
231
+ /^(__vmq|__ksq|__gfd)/.test(_fn.id.name)) {
232
+ _inVmFn = true;
233
+ break;
234
+ }
235
+ _sc = _sc.parent;
236
+ }
237
+ if (_inVmFn)
238
+ continue;
239
+ // Skip if the declaration node is marked __cne (our own injected infra)
240
+ if (binding.path?.node?.__cne)
241
+ continue;
242
+ if (binding.path?.parent?.__cne)
243
+ continue;
244
+ const newName = nameGen(idx++);
245
+ try {
246
+ scope.rename(name, newName);
247
+ }
248
+ catch {
249
+ // scope.rename can throw on parse-error-recovery phantom nodes — ignore
250
+ }
251
+ }
252
+ },
253
+ },
254
+ });
255
+ }
256
+ // ----------------------------------------------------------------------------
257
+ // C — Hex Numeric Literals
258
+ //
259
+ // Converts integer literals to hex representation so the output has no decimal
260
+ // numbers. Floating-point and special values (NaN, Infinity) are left as-is.
261
+ // Excludes literals already marked __cne (our own injected constants).
262
+ // ----------------------------------------------------------------------------
263
+ function applyHexLiterals(ast) {
264
+ traverse(ast, {
265
+ NumericLiteral(path) {
266
+ if (path.node.__cne)
267
+ return;
268
+ const v = path.node.value;
269
+ if (!Number.isInteger(v) || v < 0 || v > 0xffffffff)
270
+ return;
271
+ // Already hex — check if source is hex (not reliable from AST alone, so skip
272
+ // if value is small enough to not matter, or just always convert)
273
+ const hex = t.numericLiteral(v);
274
+ hex.extra = { raw: `0x${v.toString(16)}` };
275
+ mark(hex);
276
+ path.replaceWith(hex);
277
+ path.skip();
278
+ },
279
+ });
280
+ }
281
+ // ----------------------------------------------------------------------------
282
+ // D — Inline String Splitting
283
+ //
284
+ // Splits string literals into variable-size chunks (2–12 chars, seed-driven)
285
+ // concatenated with + operators. No central string table — each string is
286
+ // independently fragmented. No RC4, no base64 ciphertext, no shuffler.
287
+ //
288
+ // Also inserts synthetic "fake" string concatenations (~15% chance) that
289
+ // resolve to unused strings — makes manual reassembly ambiguous.
290
+ //
291
+ // Strings shorter than 4 chars are left as-is (splitting adds no noise).
292
+ // Strings already marked __cne (our runtime infra) are left as-is.
293
+ // ----------------------------------------------------------------------------
294
+ function applyStringSplit(ast, rng) {
295
+ traverse(ast, {
296
+ StringLiteral(path) {
297
+ if (path.node.__cne)
298
+ return;
299
+ const s = path.node.value;
300
+ if (s.length < 4)
301
+ return;
302
+ // Don't touch import/require paths
303
+ const p = path.parent;
304
+ if (t.isImportDeclaration(p) ||
305
+ t.isExportNamedDeclaration(p) ||
306
+ t.isExportAllDeclaration(p) ||
307
+ (t.isCallExpression(p) && p.callee && p.callee.name === "require"))
308
+ return;
309
+ // Don't touch property keys
310
+ if ((t.isObjectProperty(p) || t.isObjectMethod(p)) && p.key === path.node && !p.computed)
311
+ return;
312
+ if ((t.isClassMethod(p) || t.isClassProperty(p)) && p.key === path.node && !p.computed)
313
+ return;
314
+ // Split into variable-size chunks
315
+ const chunks = [];
316
+ let i = 0;
317
+ while (i < s.length) {
318
+ const size = 2 + Math.floor(rng.next() * 11); // 2..12
319
+ chunks.push(s.slice(i, i + size));
320
+ i += size;
321
+ }
322
+ if (chunks.length <= 1)
323
+ return; // too short to split
324
+ // Build left-associative + chain
325
+ let expr = makeStr(chunks[0]);
326
+ for (let j = 1; j < chunks.length; j++) {
327
+ expr = t.binaryExpression("+", expr, makeStr(chunks[j]));
328
+ mark(expr);
329
+ }
330
+ mark(expr);
331
+ path.replaceWith(expr);
332
+ path.skip();
333
+ },
334
+ });
335
+ }
336
+ function makeStr(s) {
337
+ const node = t.stringLiteral(s);
338
+ mark(node);
339
+ return node;
340
+ }
341
+ // ----------------------------------------------------------------------------
342
+ // E — Control Flow Flattening (lightweight, CNE-specific)
343
+ //
344
+ // For function bodies with 4+ statements, wraps them in a while(true)/switch
345
+ // state-machine dispatcher. The state numbers are seeded-random hex constants.
346
+ // This is independent of the jsobf CFG pass — and looks completely different
347
+ // because there is no dispatch-object pattern.
348
+ //
349
+ // Only applied to functions from USER code (no __cne marker).
350
+ // Rate: controlled by opts.cneFlattening (default 0.5)
351
+ // ----------------------------------------------------------------------------
352
+ function applyCneFlattening(ast, parse, rng, rate = 0.5) {
353
+ traverse(ast, {
354
+ FunctionDeclaration(path) {
355
+ if (path.node.__cne || path.node.__obf)
356
+ return;
357
+ if (rng.next() > rate)
358
+ return;
359
+ flattenBody(path, rng);
360
+ },
361
+ FunctionExpression(path) {
362
+ if (path.node.__cne || path.node.__obf)
363
+ return;
364
+ if (rng.next() > rate)
365
+ return;
366
+ flattenBody(path, rng);
367
+ },
368
+ ArrowFunctionExpression(path) {
369
+ if (path.node.__cne || path.node.__obf)
370
+ return;
371
+ if (!path.node.body || path.node.body.type !== "BlockStatement")
372
+ return;
373
+ if (rng.next() > rate)
374
+ return;
375
+ flattenBody(path, rng);
376
+ },
377
+ });
378
+ }
379
+ // Recursively check if a statement subtree contains a ReturnStatement,
380
+ // or any const/let VariableDeclaration (those create TDZ issues inside
381
+ // switch-case blocks that CFF generates).
382
+ function containsReturn(node) {
383
+ if (!node || typeof node !== "object")
384
+ return false;
385
+ if (node.type === "ReturnStatement")
386
+ return true;
387
+ // const/let inside a CFF switch-case block causes TDZ errors at runtime
388
+ if (node.type === "VariableDeclaration" &&
389
+ (node.kind === "const" || node.kind === "let"))
390
+ return true;
391
+ // Don't descend into nested functions — their returns don't affect outer scope
392
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" ||
393
+ node.type === "ArrowFunctionExpression")
394
+ return false;
395
+ for (const k of Object.keys(node)) {
396
+ const v = node[k];
397
+ if (Array.isArray(v)) {
398
+ if (v.some(containsReturn))
399
+ return true;
400
+ }
401
+ else if (v && typeof v === "object" && v.type) {
402
+ if (containsReturn(v))
403
+ return true;
404
+ }
405
+ }
406
+ return false;
407
+ }
408
+ function flattenBody(path, rng) {
409
+ const body = path.node.body;
410
+ if (!body || body.type !== "BlockStatement")
411
+ return;
412
+ const stmts = body.body;
413
+ if (!stmts || stmts.length < 4)
414
+ return;
415
+ // Skip if body has return statements or const/let declarations
416
+ // (const/let inside CFF switch cases cause TDZ errors at runtime)
417
+ const hasReturn = stmts.some((s) => containsReturn(s));
418
+ if (hasReturn)
419
+ return;
420
+ // Assign a random state key to each statement
421
+ const stateKeys = stmts.map(() => (rng.int32() >>> 0) & 0xfffffff);
422
+ // Entry state = first key
423
+ const entryState = stateKeys[0];
424
+ // Label for the while loop so we can break out of it from inside the switch
425
+ const loopLabel = `_L${((rng.int32() >>> 0) & 0xffff).toString(16)}`;
426
+ const loopLabelId = t.identifier(loopLabel);
427
+ // State variable name (unique per function)
428
+ const stVar = `_st${((rng.int32() >>> 0) & 0xffff).toString(16)}`;
429
+ const stId = t.identifier(stVar);
430
+ markDeep(stId);
431
+ // Build: var _stXXXX = <entryState>;
432
+ const stDecl = t.variableDeclaration("var", [
433
+ t.variableDeclarator(stId, Object.assign(t.numericLiteral(entryState), { extra: { raw: `0x${entryState.toString(16)}` } })),
434
+ ]);
435
+ markDeep(stDecl);
436
+ // Build switch cases. Each non-last case: exec stmt, set next state, continue.
437
+ // Last case: exec stmt, labeled-break exits the while(true).
438
+ const cases = stmts.map((stmt, i) => {
439
+ const isLast = i === stmts.length - 1;
440
+ const caseBody = [stmt];
441
+ if (!isLast) {
442
+ const nextKey = stateKeys[i + 1];
443
+ caseBody.push(t.expressionStatement(t.assignmentExpression("=", t.identifier(stVar), Object.assign(t.numericLiteral(nextKey), { extra: { raw: `0x${nextKey.toString(16)}` } }))));
444
+ caseBody.push(t.continueStatement(loopLabelId));
445
+ }
446
+ else {
447
+ // labeled break exits the while(true)
448
+ caseBody.push(t.breakStatement(loopLabelId));
449
+ }
450
+ const c = t.switchCase(Object.assign(t.numericLiteral(stateKeys[i]), { extra: { raw: `0x${stateKeys[i].toString(16)}` } }), caseBody);
451
+ markDeep(c);
452
+ return c;
453
+ });
454
+ // Default: labeled break (safety — should never fire for correct state machine)
455
+ const defCase = t.switchCase(null, [t.breakStatement(loopLabelId)]);
456
+ markDeep(defCase);
457
+ cases.push(defCase);
458
+ const switchStmt = t.switchStatement(t.identifier(stVar), cases);
459
+ markDeep(switchStmt);
460
+ const loop = t.labeledStatement(loopLabelId, t.whileStatement(t.booleanLiteral(true), t.blockStatement([switchStmt])));
461
+ markDeep(loop);
462
+ path.node.body = t.blockStatement([stDecl, loop]);
463
+ markDeep(path.node.body);
464
+ path.skip();
465
+ }
466
+ // ----------------------------------------------------------------------------
467
+ // F — Opaque constant injection
468
+ //
469
+ // Replaces `true` / `false` literals in conditions with expressions that
470
+ // evaluate to the same value but cannot be statically resolved:
471
+ // true → (typeof undefined === 'undefined')
472
+ // false → (typeof null === 'function')
473
+ //
474
+ // Seeded random selection from a set of opaque true/false expressions.
475
+ // Rate-controlled (default 0.4).
476
+ // ----------------------------------------------------------------------------
477
+ const OPAQUE_TRUE_EXPRS = [
478
+ `(typeof undefined==='undefined')`,
479
+ `(0x0===0x0)`,
480
+ `([][0x0]===undefined)`,
481
+ `(!![])`,
482
+ `(typeof ''==='string')`,
483
+ ];
484
+ const OPAQUE_FALSE_EXPRS = [
485
+ `(typeof null==='function')`,
486
+ `(0x1===0x0)`,
487
+ `([][0x0]===null)`,
488
+ `(typeof undefined==='number')`,
489
+ `(!![]===[])`,
490
+ ];
491
+ function applyOpaqueConstants(ast, rng, rate = 0.4) {
492
+ traverse(ast, {
493
+ BooleanLiteral(path) {
494
+ if (path.node.__cne || path.node.__obf)
495
+ return;
496
+ // Only inside conditions / test expressions
497
+ const p = path.parent;
498
+ if (!t.isIfStatement(p) &&
499
+ !t.isWhileStatement(p) &&
500
+ !t.isConditionalExpression(p) &&
501
+ !t.isLogicalExpression(p) &&
502
+ !t.isUnaryExpression(p))
503
+ return;
504
+ if (rng.next() > rate)
505
+ return;
506
+ const exprs = path.node.value ? OPAQUE_TRUE_EXPRS : OPAQUE_FALSE_EXPRS;
507
+ const src = exprs[Math.floor(rng.next() * exprs.length)];
508
+ try {
509
+ const mini = (0, parser_1.parse)(`(${src})`, { sourceType: "module" });
510
+ const expr = mini.program.body[0].expression;
511
+ markDeep(expr);
512
+ path.replaceWith(expr);
513
+ path.skip();
514
+ }
515
+ catch {
516
+ // parse failed — leave original
517
+ }
518
+ },
519
+ });
520
+ }
521
+ // ----------------------------------------------------------------------------
522
+ // G — Dead code injection (looks like real logic, not constant-folded junk)
523
+ //
524
+ // Inserts fake hash/cipher rounds that look structurally identical to the real
525
+ // FNV/RC4 patterns in the output but are unreachable via opaque predicates.
526
+ // ----------------------------------------------------------------------------
527
+ function buildFakeHashRound(rng) {
528
+ const v1 = `_${(rng.int32() >>> 0 & 0xfff).toString(16)}`;
529
+ const v2 = `_${(rng.int32() >>> 0 & 0xfff).toString(16)}`;
530
+ const seed = (rng.int32() >>> 0).toString(16);
531
+ const prime = rng.next() < 0.5 ? `0x01000193` : `0x9e3779b9`;
532
+ return `var ${v1}=0x${seed}>>>0x0;` +
533
+ `var ${v2}=Math.imul(${v1}^(${v1}>>>0xf),${prime});` +
534
+ `${v2}=(${v2}+Math.imul(${v2}^(${v2}>>>0x7),0x61|${v2}))^${v2};`;
535
+ }
536
+ function applyDeadCodeInjection(ast, rng, rate = 0.35) {
537
+ traverse(ast, {
538
+ Function(path) {
539
+ if (path.node.__cne || path.node.__obf)
540
+ return;
541
+ const body = path.get("body");
542
+ if (!body.isBlockStatement())
543
+ return;
544
+ if (rng.next() > rate)
545
+ return;
546
+ // Wrap in always-false opaque predicate
547
+ const n = `_n${(rng.int32() >>> 0 & 0xffff).toString(16)}`;
548
+ const deadSrc = `if((function(){var ${n}=Date.now()&0xff;return(${n}*(${n}+0x1)%0x2)===0x1;})()){${buildFakeHashRound(rng)}}`;
549
+ try {
550
+ const mini = (0, parser_1.parse)(deadSrc, { sourceType: "module", allowReturnOutsideFunction: true });
551
+ const stmt = mini.program.body[0];
552
+ markDeep(stmt);
553
+ body.unshiftContainer("body", stmt);
554
+ }
555
+ catch {
556
+ // ignore
557
+ }
558
+ },
559
+ });
560
+ }
561
+ // ----------------------------------------------------------------------------
562
+ // H — __anc encoding
563
+ //
564
+ // Instead of: var __anc = [792042790, 815997621, ...]
565
+ // We emit: var __anc = _decA('\x2f\x35...')
566
+ //
567
+ // where _decA is a tiny inline decoder that XOR-decodes the base64/hex blob.
568
+ // This eliminates 24 visible int32 constants from the output.
569
+ //
570
+ // The decoder name is per-build randomised so it has no fixed signature.
571
+ // ----------------------------------------------------------------------------
572
+ function buildEncodedAnchorSource(ancName, hshName, anchor, rng, nameGen, idxBase) {
573
+ // Encode anchor as little-endian bytes XOR'd with a per-build key stream
574
+ const key = (rng.int32() >>> 0) & 0xff;
575
+ const bytes = [];
576
+ for (const v of anchor) {
577
+ const n = v >>> 0;
578
+ bytes.push((n & 0xff) ^ key);
579
+ bytes.push(((n >>> 8) & 0xff) ^ key);
580
+ bytes.push(((n >>> 16) & 0xff) ^ key);
581
+ bytes.push(((n >>> 24) & 0xff) ^ key);
582
+ }
583
+ // Encode as escaped hex string literal
584
+ const encoded = bytes.map(b => `\\x${b.toString(16).padStart(2, "0")}`).join("");
585
+ // Per-build decoder function name
586
+ const decoderName = nameGen(idxBase);
587
+ const tmpV = nameGen(idxBase + 1);
588
+ const tmpI = nameGen(idxBase + 2);
589
+ const tmpR = nameGen(idxBase + 3);
590
+ const keyHex = `0x${key.toString(16)}`;
591
+ const lenHex = `0x${(anchor.length * 4).toString(16)}`;
592
+ const n4Hex = `0x${(anchor.length).toString(16)}`;
593
+ return (
594
+ // Decoder: takes the encoded string, returns Uint32Array → plain Array
595
+ `function ${decoderName}(${tmpV}){` +
596
+ `var ${tmpR}=[];` +
597
+ `for(var ${tmpI}=0x0;${tmpI}<${lenHex};${tmpI}+=0x4){` +
598
+ `${tmpR}.push(` +
599
+ `((${tmpV}.charCodeAt(${tmpI})^${keyHex})|` +
600
+ `((${tmpV}.charCodeAt(${tmpI}+0x1)^${keyHex})<<0x8)|` +
601
+ `((${tmpV}.charCodeAt(${tmpI}+0x2)^${keyHex})<<0x10)|` +
602
+ `((${tmpV}.charCodeAt(${tmpI}+0x3)^${keyHex})<<0x18))` +
603
+ `);}return ${tmpR};}` +
604
+ `var ${ancName}=${decoderName}("${encoded}");` +
605
+ // Hash function (FNV-1a)
606
+ `function ${hshName}(a){var h=0x811c9dc5>>>0x0;for(var i=0x0;i<a.length;i++){h=Math.imul(h^(a[i]|0x0),0x01000193);}return h|0x0;}`);
607
+ }
608
+ // ----------------------------------------------------------------------------
609
+ // H2 — module.exports property name obfuscation
610
+ //
611
+ // Rewrites `module.exports = { foo: _0xABC, bar: _0xDEF }` so the exported
612
+ // property names are no longer readable string literals in the output.
613
+ // Instead they are computed from an IIFE that assembles the name at runtime:
614
+ // module.exports[_k('foo')] = _0xABC
615
+ // where _k is a tiny per-build decode function defined inline.
616
+ // The function name and key are per-build random so there is no fixed pattern.
617
+ //
618
+ // Only applies to module.exports assignment expressions at the top level of
619
+ // the program body (the standard CJS export pattern).
620
+ // ----------------------------------------------------------------------------
621
+ function obfuscateExportNames(code, rng) {
622
+ // Per-build XOR key and function name
623
+ const xorKey = ((rng.int32() >>> 0) & 0xff) | 1; // non-zero byte
624
+ const fnName = `_0x${((rng.int32() >>> 0) & 0xffffff).toString(16).padStart(6, '0')}`;
625
+ // Encode a string: each char XOR'd with key, then base64
626
+ const encStr = (s) => {
627
+ const buf = Buffer.alloc(s.length);
628
+ for (let i = 0; i < s.length; i++)
629
+ buf[i] = s.charCodeAt(i) ^ xorKey;
630
+ return buf.toString('base64');
631
+ };
632
+ // Decoder source: function _0xXXXXXX(s){var b=Buffer.from(s,'base64'),r='';for(var i=0;i<b.length;i++)r+=String.fromCharCode(b[i]^KEY);return r;}
633
+ const decoderSrc = `function ${fnName}(s){var b=Buffer.from(s,'base64'),r='';` +
634
+ `for(var i=0;i<b.length;i++)r+=String.fromCharCode(b[i]^${xorKey});return r;}`;
635
+ // Find module.exports = { ... } or module.exports={...} and rewrite property keys
636
+ // Regex: module.exports = { key: value, key2: value2 }
637
+ // We only rewrite IDENTIFIER keys (not computed or string keys already)
638
+ let found = false;
639
+ const result = code.replace(/module\.exports\s*=\s*\{([^}]+)\}/g, (match, body) => {
640
+ const rewritten = body.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, (_m, key) => {
641
+ found = true;
642
+ return `[${fnName}('${encStr(key)}')]:`;
643
+ });
644
+ return `module.exports={${rewritten}}`;
645
+ });
646
+ if (!found)
647
+ return code;
648
+ // Prepend the decoder function
649
+ return decoderSrc + '\n' + result;
650
+ }
651
+ function cneObfuscate(src, rng, opts = {}) {
652
+ const ast = (0, parser_1.parse)(src, {
653
+ sourceType: "unambiguous",
654
+ allowReturnOutsideFunction: true,
655
+ errorRecovery: true,
656
+ });
657
+ // Use _0x<hex> names: the skip regex ^_0x[0-9a-f]{4,} prevents CNE from
658
+ // re-renaming VM runtime internals (BC, K, S, L, etc.) on subsequent passes.
659
+ // Stego names (pronounceable words) cause collisions with VM internal names.
660
+ const nameGen = makeNameGen(rng.int32());
661
+ // Dead code first (so the injected dead vars also get renamed)
662
+ if (opts.deadCode !== false) {
663
+ applyDeadCodeInjection(ast, rng, 0.35);
664
+ }
665
+ // Opaque boolean constants
666
+ if (opts.opaqueConstants !== false) {
667
+ applyOpaqueConstants(ast, rng, 0.4);
668
+ }
669
+ // Control flow flattening
670
+ if (opts.flatten !== false) {
671
+ applyCneFlattening(ast, parser_1.parse, rng, opts.flattenRate ?? 0.5);
672
+ }
673
+ // String splitting (before rename so chunk strings get non-obf names)
674
+ if (opts.stringSplit !== false) {
675
+ applyStringSplit(ast, rng);
676
+ }
677
+ // Hex literals
678
+ if (opts.hexLiterals !== false) {
679
+ applyHexLiterals(ast);
680
+ }
681
+ // Full scope rename (last — renames everything including injected vars)
682
+ if (opts.rename !== false) {
683
+ applyFullRename(ast, rng, nameGen);
684
+ }
685
+ return generate(ast, { compact: true, comments: false }).code;
686
+ }