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/layers.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Criptor — Layer Configuration System
|
|
4
|
+
// Every protection layer is independently toggleable. Each file processed
|
|
5
|
+
// gets its own unique seed derived from (globalSeed ^ fileIndex ^ fileHash),
|
|
6
|
+
// so 10 files get 10 completely different obfuscations even with the same config.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PRESETS = exports.PRESET_LEGACY = exports.PRESET_STEALTH = exports.PRESET_MAXIMUM = exports.PRESET_BALANCED = void 0;
|
|
10
|
+
exports.deriveFileSeed = deriveFileSeed;
|
|
11
|
+
exports.layerOn = layerOn;
|
|
12
|
+
// ── Named Presets ────────────────────────────────────────────────────────────
|
|
13
|
+
// PRESET_LIGHT has been REMOVED.
|
|
14
|
+
// Reason: the light preset (no CNE flattening, no callgraph poison, no canary)
|
|
15
|
+
// was shown to be trivially reversible when combined with VM virtualization.
|
|
16
|
+
// An automated agent fully reconstructed both virtualised functions in under
|
|
17
|
+
// 15 minutes from the output. The minimum safe preset for VM use is BALANCED.
|
|
18
|
+
// Use PRESET_BALANCED as the entry-level option.
|
|
19
|
+
/** Balanced — all structural layers on, zero jsobf fingerprint */
|
|
20
|
+
exports.PRESET_BALANCED = {
|
|
21
|
+
banner: false,
|
|
22
|
+
aiDirective: false,
|
|
23
|
+
aiHoneypots: false,
|
|
24
|
+
aiDeepDirectives: false,
|
|
25
|
+
aiContextExhaustion: false,
|
|
26
|
+
vm: true,
|
|
27
|
+
mba: true, mbaDepth: 2, mbaThreshold: 0.8,
|
|
28
|
+
opaque: true, opaqueThreshold: 0.6,
|
|
29
|
+
stringCipher: true, stringThreshold: 0.9,
|
|
30
|
+
integrityAnchors: true,
|
|
31
|
+
moduleIife: true,
|
|
32
|
+
sandboxPoison: true,
|
|
33
|
+
canary: true, canaryPoisonRate: 0.5,
|
|
34
|
+
adversarialTokens: true, adversarialTokenRate: 0.3,
|
|
35
|
+
timingOracle: true,
|
|
36
|
+
aiCallgraphPoison: true, aiCallgraphPoisonCount: 120,
|
|
37
|
+
aiSemanticPoison: true,
|
|
38
|
+
aiAntiPattern: true, aiTemporalKeys: true,
|
|
39
|
+
// CNE settings
|
|
40
|
+
cneFlatten: true, cneFlattenRate: 0.4,
|
|
41
|
+
cneDeadCode: true,
|
|
42
|
+
};
|
|
43
|
+
/** Maximum — every structural layer at full strength, zero jsobf fingerprint */
|
|
44
|
+
exports.PRESET_MAXIMUM = {
|
|
45
|
+
banner: false,
|
|
46
|
+
aiDirective: false,
|
|
47
|
+
aiHoneypots: false,
|
|
48
|
+
aiDeepDirectives: false,
|
|
49
|
+
aiContextExhaustion: false,
|
|
50
|
+
vm: true,
|
|
51
|
+
mba: true, mbaDepth: 3, mbaThreshold: 0.9,
|
|
52
|
+
opaque: true, opaqueThreshold: 0.7,
|
|
53
|
+
stringCipher: true, stringThreshold: 0.95,
|
|
54
|
+
integrityAnchors: true,
|
|
55
|
+
moduleIife: true,
|
|
56
|
+
sandboxPoison: true,
|
|
57
|
+
canary: true, canaryPoisonRate: 0.8,
|
|
58
|
+
adversarialTokens: true, adversarialTokenRate: 0.5,
|
|
59
|
+
timingOracle: true,
|
|
60
|
+
aiCallgraphPoison: true, aiCallgraphPoisonCount: 200,
|
|
61
|
+
aiSemanticPoison: true,
|
|
62
|
+
aiAntiPattern: true, aiTemporalKeys: true,
|
|
63
|
+
// CNE settings
|
|
64
|
+
cneFlatten: true, cneFlattenRate: 0.6,
|
|
65
|
+
cneDeadCode: true,
|
|
66
|
+
};
|
|
67
|
+
/** Stealth — identical to maximum (alias kept for back-compat) */
|
|
68
|
+
exports.PRESET_STEALTH = {
|
|
69
|
+
banner: false,
|
|
70
|
+
aiDirective: false,
|
|
71
|
+
aiHoneypots: false,
|
|
72
|
+
aiDeepDirectives: false,
|
|
73
|
+
aiContextExhaustion: false,
|
|
74
|
+
vm: true,
|
|
75
|
+
mba: true, mbaDepth: 3, mbaThreshold: 0.9,
|
|
76
|
+
opaque: true, opaqueThreshold: 0.7,
|
|
77
|
+
stringCipher: true, stringThreshold: 0.95,
|
|
78
|
+
integrityAnchors: true,
|
|
79
|
+
moduleIife: true,
|
|
80
|
+
sandboxPoison: true,
|
|
81
|
+
canary: true, canaryPoisonRate: 0.8,
|
|
82
|
+
adversarialTokens: true, adversarialTokenRate: 0.5,
|
|
83
|
+
timingOracle: true,
|
|
84
|
+
aiCallgraphPoison: true, aiCallgraphPoisonCount: 200,
|
|
85
|
+
aiSemanticPoison: true,
|
|
86
|
+
aiAntiPattern: true, aiTemporalKeys: true,
|
|
87
|
+
cneFlatten: true, cneFlattenRate: 0.6,
|
|
88
|
+
cneDeadCode: true,
|
|
89
|
+
};
|
|
90
|
+
/** Legacy preset — uses javascript-obfuscator (known fingerprint, kept for testing) */
|
|
91
|
+
exports.PRESET_LEGACY = {
|
|
92
|
+
banner: false,
|
|
93
|
+
aiDirective: false,
|
|
94
|
+
aiHoneypots: false,
|
|
95
|
+
aiDeepDirectives: false,
|
|
96
|
+
aiContextExhaustion: false,
|
|
97
|
+
vm: true,
|
|
98
|
+
mba: true, mbaDepth: 3, mbaThreshold: 0.9,
|
|
99
|
+
opaque: true, opaqueThreshold: 0.7,
|
|
100
|
+
stringCipher: true, stringThreshold: 0.95,
|
|
101
|
+
integrityAnchors: true,
|
|
102
|
+
jsobf: true, signatureBreak: true, moduleIife: true,
|
|
103
|
+
sandboxPoison: true,
|
|
104
|
+
canary: true, canaryPoisonRate: 0.8,
|
|
105
|
+
adversarialTokens: true, adversarialTokenRate: 0.5,
|
|
106
|
+
timingOracle: true,
|
|
107
|
+
aiCallgraphPoison: true, aiCallgraphPoisonCount: 120,
|
|
108
|
+
aiSemanticPoison: true,
|
|
109
|
+
aiAntiPattern: true, aiTemporalKeys: true,
|
|
110
|
+
legacyJsobf: true,
|
|
111
|
+
};
|
|
112
|
+
exports.PRESETS = {
|
|
113
|
+
// "light" removed (June 2026) — proven trivially reversible
|
|
114
|
+
// "balanced" removed (June 2026) — all users get maximum protection equally
|
|
115
|
+
maximum: exports.PRESET_MAXIMUM,
|
|
116
|
+
stealth: exports.PRESET_STEALTH,
|
|
117
|
+
legacy: exports.PRESET_LEGACY, // javascript-obfuscator path (for comparison/testing)
|
|
118
|
+
};
|
|
119
|
+
/** Derive a per-file seed from master seed + file index + content hash */
|
|
120
|
+
function deriveFileSeed(masterSeed, fileIndex, content) {
|
|
121
|
+
// FNV-1a over first 512 chars of content for speed
|
|
122
|
+
let h = 0x811c9dc5 >>> 0;
|
|
123
|
+
const sample = content.slice(0, 512);
|
|
124
|
+
for (let i = 0; i < sample.length; i++) {
|
|
125
|
+
h = Math.imul(h ^ sample.charCodeAt(i), 0x01000193) >>> 0;
|
|
126
|
+
}
|
|
127
|
+
// XOR master seed with file index and content hash
|
|
128
|
+
return (masterSeed ^ (fileIndex * 0x9e3779b9) ^ h) >>> 0;
|
|
129
|
+
}
|
|
130
|
+
/** Resolve a layer option with a default value */
|
|
131
|
+
function layerOn(opts, key, def = true) {
|
|
132
|
+
const v = opts[key];
|
|
133
|
+
if (v === undefined)
|
|
134
|
+
return def;
|
|
135
|
+
return Boolean(v);
|
|
136
|
+
}
|
package/opaque-pred.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.falsePredicateSource = falsePredicateSource;
|
|
4
|
+
// IMPORTANT: n trebuie sa ramana MIC (<= ~1023) ca produsele n*n / n*n*n din identitati
|
|
5
|
+
// sa fie intregi EXACTI (sub 2^53). Cu n mare, n*n*n e float => identitatile devin
|
|
6
|
+
// nesigure => predicatul "mereu fals" devine uneori adevarat => cod mort executat.
|
|
7
|
+
const SOURCES = [
|
|
8
|
+
"Date.now() & 255",
|
|
9
|
+
"Date.now() & 1023",
|
|
10
|
+
"Date.now() % 251",
|
|
11
|
+
"(Date.now() >>> 8) & 511",
|
|
12
|
+
"(Date.now() & 127) + 3",
|
|
13
|
+
];
|
|
14
|
+
// corp(n) -> expresie mereu FALSA
|
|
15
|
+
const FALSE_BODIES = [
|
|
16
|
+
(n) => `${n}*(${n}+1)%2===1`, // produs de consecutivi: par
|
|
17
|
+
(n) => `(${n}*${n}-${n})%2===1`, // n^2-n = n(n-1): par
|
|
18
|
+
(n) => `((${n}&1)*((${n}+1)&1))===1`, // n, n+1 nu pot fi ambele impare
|
|
19
|
+
(n) => `(${n}*${n}%4)===2`, // patrate mod 4 in {0,1}
|
|
20
|
+
(n) => `((${n}|1)&1)===0`, // (impar|1) e impar
|
|
21
|
+
(n) => `(${n}*${n}*${n}-${n})%6!==0`, // n^3-n divizibil cu 6
|
|
22
|
+
];
|
|
23
|
+
function pick(arr, rng) {
|
|
24
|
+
return arr[Math.floor(rng.next() * arr.length)];
|
|
25
|
+
}
|
|
26
|
+
// Expresie sursa mereu-falsa, cu n evaluat o singura data printr-un IIFE.
|
|
27
|
+
function falsePredicateSource(rng) {
|
|
28
|
+
const src = pick(SOURCES, rng);
|
|
29
|
+
const body = pick(FALSE_BODIES, rng);
|
|
30
|
+
const v = "_n" + ((rng.int32() >>> 0).toString(36));
|
|
31
|
+
return `(function () { var ${v} = ${src}; return ${body(v)}; })()`;
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jsguardian",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "JSGuardian CLI — military-grade JavaScript obfuscation from the command line",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"jsguardian": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"*.js"
|
|
11
|
+
],
|
|
12
|
+
"keywords": ["javascript", "obfuscator", "obfuscation", "protection", "cli", "jsguardian"],
|
|
13
|
+
"author": "JSGuardian <hello@jsguardian.com>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://jsguardian.com",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/LCBO/criptor"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@babel/core": "^7.25.0",
|
|
25
|
+
"@babel/generator": "^7.25.0",
|
|
26
|
+
"@babel/parser": "^7.25.0",
|
|
27
|
+
"@babel/traverse": "^7.25.0",
|
|
28
|
+
"@babel/types": "^7.25.0",
|
|
29
|
+
"javascript-obfuscator": "^4.1.1",
|
|
30
|
+
"jsonwebtoken": "^9.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/pipeline.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.isAlreadyProtected = isAlreadyProtected;
|
|
40
|
+
exports.obfuscateCode = obfuscateCode;
|
|
41
|
+
exports.obfuscateBatch = obfuscateBatch;
|
|
42
|
+
const parser_1 = require("@babel/parser");
|
|
43
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
44
|
+
const generator_1 = __importDefault(require("@babel/generator"));
|
|
45
|
+
const t = __importStar(require("@babel/types"));
|
|
46
|
+
const javascript_obfuscator_1 = __importDefault(require("javascript-obfuscator"));
|
|
47
|
+
const prng_1 = require("./prng");
|
|
48
|
+
const transforms_1 = require("./transforms");
|
|
49
|
+
const transform_vm_1 = require("./transform-vm");
|
|
50
|
+
const ai_confusion_1 = require("./ai-confusion");
|
|
51
|
+
const signature_break_1 = require("./signature-break");
|
|
52
|
+
const integrity_1 = require("./integrity");
|
|
53
|
+
const cne_1 = require("./cne");
|
|
54
|
+
const jsobf_config_1 = require("./jsobf-config");
|
|
55
|
+
const vm_poison_1 = require("./vm-poison");
|
|
56
|
+
const ai_callgraph_poison_1 = require("./ai-callgraph-poison");
|
|
57
|
+
const ai_semantic_poison_1 = require("./ai-semantic-poison");
|
|
58
|
+
const ai_antipattern_1 = require("./ai-antipattern");
|
|
59
|
+
const temporal_keys_1 = require("./temporal-keys");
|
|
60
|
+
const canary_1 = require("./canary");
|
|
61
|
+
const adversarial_tokens_1 = require("./adversarial-tokens");
|
|
62
|
+
const timing_oracle_1 = require("./timing-oracle");
|
|
63
|
+
const layers_1 = require("./layers");
|
|
64
|
+
// interop ESM/CJS pentru exporturile default ale lui babel
|
|
65
|
+
const traverse = traverse_1.default.default || traverse_1.default;
|
|
66
|
+
const generate = generator_1.default.default || generator_1.default;
|
|
67
|
+
// ── Double-protection detection ──────────────────────────────────────────────
|
|
68
|
+
// wrapInModuleIife (Layer 6b, on in every real preset) emits these two exact
|
|
69
|
+
// substrings verbatim — they survive to the final output unchanged. Requiring
|
|
70
|
+
// BOTH the head and the .call() tail makes a false positive on hand-written /
|
|
71
|
+
// bundled UMD code effectively impossible.
|
|
72
|
+
const CRIPTOR_IIFE_HEAD = "(function(module,exports,require,Buffer,process,global,__dirname,__filename){";
|
|
73
|
+
const CRIPTOR_IIFE_TAIL = "}).call(typeof globalThis!=='undefined'?globalThis:this,typeof module!=='undefined'?module:{exports:{}}";
|
|
74
|
+
/**
|
|
75
|
+
* True if `src` already carries Criptor's own structural signature, i.e. it is
|
|
76
|
+
* the output of a previous protection run. Re-protecting such code stacks a new
|
|
77
|
+
* KrakVM / integrity layer on top of the old one, whose baked-in hash no longer
|
|
78
|
+
* matches — the file then throws a tamper error at runtime and is unusable.
|
|
79
|
+
*/
|
|
80
|
+
function isAlreadyProtected(src) {
|
|
81
|
+
return src.includes(CRIPTOR_IIFE_HEAD) && src.includes(CRIPTOR_IIFE_TAIL);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Obfuscate a single source file.
|
|
85
|
+
* Every layer is independently gated by `opts`.
|
|
86
|
+
* `fileIndex` is used to derive a per-file seed — pass 0 for a single file,
|
|
87
|
+
* or the array index when processing a batch.
|
|
88
|
+
*/
|
|
89
|
+
function obfuscateCode(src, opts = {}, fileIndex = 0) {
|
|
90
|
+
// ── Guard: refuse to protect already-protected input ───────────────────────
|
|
91
|
+
if (!opts.allowDoubleProtect && isAlreadyProtected(src)) {
|
|
92
|
+
throw new Error("Input is already protected by Criptor — refusing to protect it again. " +
|
|
93
|
+
"Re-protecting obfuscated output stacks a second integrity/VM layer whose " +
|
|
94
|
+
"baked-in hash no longer matches, which crashes the file at runtime " +
|
|
95
|
+
"(\"Illegal Instruction\" / \"Access Violation\" tamper errors). " +
|
|
96
|
+
"Protect the ORIGINAL source instead. To override, set allowDoubleProtect=true.");
|
|
97
|
+
}
|
|
98
|
+
// ── Shebang extraction ─────────────────────────────────────────────────────
|
|
99
|
+
// A `#!/usr/bin/env node` line is only legal as the VERY FIRST bytes of a file.
|
|
100
|
+
// Layers that prepend injected source (honeypots, directives, anchors) would
|
|
101
|
+
// push it off line 1 → "Invalid or unexpected token" at runtime. Strip it now,
|
|
102
|
+
// re-attach at the very top of the final output.
|
|
103
|
+
let shebang = "";
|
|
104
|
+
const _sb = src.match(/^#![^\n]*\n?/);
|
|
105
|
+
if (_sb) {
|
|
106
|
+
shebang = _sb[0].endsWith("\n") ? _sb[0] : _sb[0] + "\n";
|
|
107
|
+
src = src.slice(_sb[0].length);
|
|
108
|
+
}
|
|
109
|
+
// ── Seed derivation ────────────────────────────────────────────────────────
|
|
110
|
+
const masterSeed = opts.seed ?? 0x9e3779b9;
|
|
111
|
+
const seed = fileIndex === 0 ? masterSeed : (0, layers_1.deriveFileSeed)(masterSeed, fileIndex, src);
|
|
112
|
+
const rng = (0, prng_1.makeRng)(seed);
|
|
113
|
+
// ── Parse ──────────────────────────────────────────────────────────────────
|
|
114
|
+
const ast = (0, parser_1.parse)(src, {
|
|
115
|
+
sourceType: "module",
|
|
116
|
+
allowReturnOutsideFunction: true,
|
|
117
|
+
plugins: opts.typescript ? ["typescript"] : [],
|
|
118
|
+
});
|
|
119
|
+
// ── ESM normalisation ──────────────────────────────────────────────────────
|
|
120
|
+
// Rewrite declaration-exports / default-exports into `decl + export {…}` form
|
|
121
|
+
// BEFORE any rename pass, working around Babel's buggy scope.rename for
|
|
122
|
+
// declaration exports (babel/babel#11591). Must run first so every downstream
|
|
123
|
+
// stage (VM, rename, wrap) sees the canonical, rename-safe shape.
|
|
124
|
+
(0, cne_1.normalizeEsmExports)(ast);
|
|
125
|
+
// ── Layer 5: Integrity anchors (compute early, all layers reference Hbuild) ─
|
|
126
|
+
const anchor = [];
|
|
127
|
+
for (let i = 0; i < 24; i++)
|
|
128
|
+
anchor.push(rng.int32());
|
|
129
|
+
const Hbuild = (0, integrity_1.hshBuild)(anchor);
|
|
130
|
+
// ── Layer 3: AI Honeypots ──────────────────────────────────────────────────
|
|
131
|
+
if ((0, layers_1.layerOn)(opts, "aiHoneypots", true)) {
|
|
132
|
+
(0, ai_confusion_1.applyAiConfusion)(ast, traverse, parser_1.parse, t, rng, opts);
|
|
133
|
+
}
|
|
134
|
+
// ── Layer 3b: AI Directive vars ────────────────────────────────────────────
|
|
135
|
+
if ((0, layers_1.layerOn)(opts, "aiDirective", true)) {
|
|
136
|
+
(0, ai_confusion_1.injectAiDirective)(ast, t);
|
|
137
|
+
}
|
|
138
|
+
// ── Layer 3c: Deep directives ─────────────────────────────────────────────
|
|
139
|
+
if ((0, layers_1.layerOn)(opts, "aiDeepDirectives", false)) {
|
|
140
|
+
(0, ai_confusion_1.injectDeepDirectives)(ast, traverse, parser_1.parse, t, rng);
|
|
141
|
+
}
|
|
142
|
+
// ── Layer 3e: Callgraph Poison (mutual-recursion cluster flood) ─────────────
|
|
143
|
+
// Inject BEFORE VM so poison functions are present when VM scans for
|
|
144
|
+
// FunctionDeclarations. VM skips them (marked __obf); MBA/CNE run over them.
|
|
145
|
+
if ((0, layers_1.layerOn)(opts, "aiCallgraphPoison", false)) {
|
|
146
|
+
const _cgCount = opts.aiCallgraphPoisonCount ?? 120;
|
|
147
|
+
(0, ai_callgraph_poison_1.applyCallgraphPoison)(ast, t, parser_1.parse, rng, _cgCount);
|
|
148
|
+
}
|
|
149
|
+
// ── Layer 2: VM Bytecode Virtualization (KrakVM) ────────────────────────────
|
|
150
|
+
if ((0, layers_1.layerOn)(opts, "vm", true)) {
|
|
151
|
+
(0, transform_vm_1.applyVm)(ast, traverse, parser_1.parse, t, rng);
|
|
152
|
+
}
|
|
153
|
+
// Note: sandbox poison (Layer 12) removed — KrakVM does not embed VM opcodes
|
|
154
|
+
// for sandbox detection. The charCodeAt poison IIFE is still available via
|
|
155
|
+
// the legacy plain-JS path (wrapInModuleIife handles env isolation).
|
|
156
|
+
// ── Layer 9: Timing Oracle AST pass — AFTER VM ────────────────────────────
|
|
157
|
+
// Per-build random name for the drift detector — eliminates '__drift' fingerprint
|
|
158
|
+
const _driftKey = `_0x${((rng.int32() >>> 0) & 0xffffff).toString(16).padStart(6, '0')}`;
|
|
159
|
+
if ((0, layers_1.layerOn)(opts, "timingOracle", false)) {
|
|
160
|
+
(0, timing_oracle_1.applyTimingOracle)(ast, traverse, t, rng, _driftKey);
|
|
161
|
+
}
|
|
162
|
+
// ── Layer 8: Adversarial Token Injection — AFTER VM ───────────────────────
|
|
163
|
+
if ((0, layers_1.layerOn)(opts, "adversarialTokens", false)) {
|
|
164
|
+
(0, adversarial_tokens_1.applyAdversarialTokens)(ast, traverse, t, rng, opts);
|
|
165
|
+
}
|
|
166
|
+
// ── Stage A: MBA Constants ─────────────────────────────────────────────────
|
|
167
|
+
if ((0, layers_1.layerOn)(opts, "mba", true)) {
|
|
168
|
+
(0, transforms_1.applyConstMba)(ast, traverse, t, rng, opts);
|
|
169
|
+
}
|
|
170
|
+
// ── Stage B: Opaque Predicates ─────────────────────────────────────────────
|
|
171
|
+
if ((0, layers_1.layerOn)(opts, "opaque", true)) {
|
|
172
|
+
(0, transforms_1.applyOpaque)(ast, traverse, parser_1.parse, t, rng, opts);
|
|
173
|
+
}
|
|
174
|
+
// ── Stage C: String Cipher ─────────────────────────────────────────────────
|
|
175
|
+
let _cipherBuildKey = 0;
|
|
176
|
+
let _cipherEntries = [];
|
|
177
|
+
if ((0, layers_1.layerOn)(opts, "stringCipher", true)) {
|
|
178
|
+
_cipherBuildKey = rng.int32() >>> 0;
|
|
179
|
+
const effectiveH = (0, layers_1.layerOn)(opts, "integrityAnchors", true) ? Hbuild : 0;
|
|
180
|
+
_cipherEntries = (0, transforms_1.applyStringCipher)(ast, traverse, parser_1.parse, t, rng, _cipherBuildKey, effectiveH, opts);
|
|
181
|
+
}
|
|
182
|
+
// ── Layer 5: Integrity anchor emission ────────────────────────────────────
|
|
183
|
+
if ((0, layers_1.layerOn)(opts, "integrityAnchors", true)) {
|
|
184
|
+
// CNE path: base64-encoded XOR blob (no 24 plaintext int32s)
|
|
185
|
+
// Legacy path: plain array (jsobf renames further)
|
|
186
|
+
const _useLegacyAnc = (0, layers_1.layerOn)(opts, "legacyJsobf", false);
|
|
187
|
+
const anchorSrc = _useLegacyAnc
|
|
188
|
+
? (0, integrity_1.anchorSource)("__anc", anchor) + "\n" + (0, integrity_1.hshSource)("__hsh")
|
|
189
|
+
: (0, integrity_1.anchorSourceEncoded)("__anc", "__hsh", anchor, rng);
|
|
190
|
+
const integrityAst = (0, parser_1.parse)(anchorSrc, { sourceType: "module" });
|
|
191
|
+
// Mark ALL anchor nodes __obf + __cne so no subsequent pass touches them
|
|
192
|
+
const _markAll = (node) => {
|
|
193
|
+
if (!node || typeof node !== "object")
|
|
194
|
+
return;
|
|
195
|
+
node.__obf = true;
|
|
196
|
+
node.__cne = true;
|
|
197
|
+
for (const k of Object.keys(node)) {
|
|
198
|
+
const v = node[k];
|
|
199
|
+
if (Array.isArray(v))
|
|
200
|
+
v.forEach(_markAll);
|
|
201
|
+
else if (v && typeof v === "object" && v.type)
|
|
202
|
+
_markAll(v);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
integrityAst.program.body.forEach(_markAll);
|
|
206
|
+
ast.program.body.unshift(...integrityAst.program.body);
|
|
207
|
+
}
|
|
208
|
+
let code = generate(ast, { compact: false, comments: false }).code;
|
|
209
|
+
// ── Layer 4: Obfuscation engine ───────────────────────────────────────────
|
|
210
|
+
const _useLegacyJsobf = (0, layers_1.layerOn)(opts, "legacyJsobf", false);
|
|
211
|
+
if (_useLegacyJsobf && (0, layers_1.layerOn)(opts, "jsobf", true)) {
|
|
212
|
+
// ── LEGACY PATH: javascript-obfuscator ───────────────────────────────────
|
|
213
|
+
// Timing oracle + semantic poison go BEFORE jsobf so jsobf obfuscates them.
|
|
214
|
+
// Sandbox poison is now an AST node (Layer 12) injected earlier —
|
|
215
|
+
// it lives in the Babel AST and gets fed through jsobf alongside user code.
|
|
216
|
+
if ((0, layers_1.layerOn)(opts, "timingOracle", false)) {
|
|
217
|
+
code = (0, timing_oracle_1.buildDriftDetectorSource)(rng, _driftKey) + "\n" + code;
|
|
218
|
+
}
|
|
219
|
+
if ((0, layers_1.layerOn)(opts, "aiSemanticPoison", false)) {
|
|
220
|
+
code = (0, ai_semantic_poison_1.buildSemanticPoisonSource)(rng) + "\n" + code;
|
|
221
|
+
}
|
|
222
|
+
if ((0, layers_1.layerOn)(opts, "aiAntiPattern", false)) {
|
|
223
|
+
code = (0, ai_antipattern_1.buildAntiPatternSource)(rng) + "\n" + code;
|
|
224
|
+
}
|
|
225
|
+
if ((0, layers_1.layerOn)(opts, "aiTemporalKeys", false)) {
|
|
226
|
+
code = (0, temporal_keys_1.buildTemporalKeySource)(rng) + "\n" + code;
|
|
227
|
+
}
|
|
228
|
+
const _intOn = (0, layers_1.layerOn)(opts, "integrityAnchors", true);
|
|
229
|
+
code = javascript_obfuscator_1.default.obfuscate(code, (0, jsobf_config_1.nodeJsObfOptions)(rng, _intOn)).getObfuscatedCode();
|
|
230
|
+
if ((0, layers_1.layerOn)(opts, "signatureBreak", true)) {
|
|
231
|
+
code = (0, signature_break_1.breakSignatures)(code, rng);
|
|
232
|
+
}
|
|
233
|
+
if ((0, layers_1.layerOn)(opts, "moduleIife", true)) {
|
|
234
|
+
code = (0, vm_poison_1.wrapInModuleIife)(code);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (!_useLegacyJsobf) {
|
|
238
|
+
// ── CNE PATH: Criptor Native Engine — zero public deobfuscation tooling ──
|
|
239
|
+
// CNE runs on the pure Babel-generated code (which already contains the
|
|
240
|
+
// Layer 12 sandbox __vmq() call as an AST node — it gets renamed + hex-
|
|
241
|
+
// encoded + optionally flattened like any other user expression).
|
|
242
|
+
code = (0, cne_1.cneObfuscate)(code, rng, {
|
|
243
|
+
rename: opts.cneRename !== false,
|
|
244
|
+
hexLiterals: opts.cneHexLiterals !== false,
|
|
245
|
+
stringSplit: opts.cneStringSplit !== false,
|
|
246
|
+
flatten: opts.cneFlatten !== false,
|
|
247
|
+
flattenRate: opts.cneFlattenRate ?? 0.5,
|
|
248
|
+
opaqueConstants: opts.cneOpaqueConstants !== false,
|
|
249
|
+
deadCode: opts.cneDeadCode !== false,
|
|
250
|
+
});
|
|
251
|
+
// Obfuscate module.exports property names (hides validateLicense, hasFeature etc.)
|
|
252
|
+
code = (0, cne_1.obfuscateExportNames)(code, rng);
|
|
253
|
+
// Drift detector — raw string, injected after CNE (intact regex/structure)
|
|
254
|
+
if ((0, layers_1.layerOn)(opts, "timingOracle", false)) {
|
|
255
|
+
code = (0, timing_oracle_1.buildDriftDetectorSource)(rng, _driftKey) + "\n" + code;
|
|
256
|
+
}
|
|
257
|
+
// Layer 3f: Semantic Poison — raw string, post-CNE (values intact)
|
|
258
|
+
if ((0, layers_1.layerOn)(opts, "aiSemanticPoison", false)) {
|
|
259
|
+
code = (0, ai_semantic_poison_1.buildSemanticPoisonSource)(rng) + "\n" + code;
|
|
260
|
+
}
|
|
261
|
+
// Layer 3g: Anti-Pattern Injection — prototype pollution + ASI traps + homoglyphs
|
|
262
|
+
if ((0, layers_1.layerOn)(opts, "aiAntiPattern", false)) {
|
|
263
|
+
code = (0, ai_antipattern_1.buildAntiPatternSource)(rng) + "\n" + code;
|
|
264
|
+
}
|
|
265
|
+
// Layer 3h: Temporal Keys — epoch-day-keyed fake decoder (wrong in frozen sandbox)
|
|
266
|
+
if ((0, layers_1.layerOn)(opts, "aiTemporalKeys", false)) {
|
|
267
|
+
code = (0, temporal_keys_1.buildTemporalKeySource)(rng) + "\n" + code;
|
|
268
|
+
}
|
|
269
|
+
// NOTE: sandboxPoison string injection removed — replaced by Layer 12
|
|
270
|
+
// AST node injection above (buildSandboxVmCallStmt). The check and the
|
|
271
|
+
// poison now execute inside __vmq, invisible from the outer shell.
|
|
272
|
+
// Module IIFE wraps everything: CNE output + drift detector
|
|
273
|
+
if ((0, layers_1.layerOn)(opts, "moduleIife", true)) {
|
|
274
|
+
code = (0, vm_poison_1.wrapInModuleIife)(code);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ── Layer 11: Canary __dec at global scope (outside IIFE) ─────────────────
|
|
278
|
+
// A fake __dec/__ca/__bk with wrong key — attacker who calls it gets garbage.
|
|
279
|
+
// In CNE mode the real decoder is also renamed — doubly hidden.
|
|
280
|
+
if ((0, layers_1.layerOn)(opts, "stringCipher", true)) {
|
|
281
|
+
const canaryRng = (0, prng_1.makeRng)((_cipherBuildKey ^ 0xc0ffee) >>> 0);
|
|
282
|
+
const canary = (0, transforms_1.buildCanaryDecoder)(_cipherBuildKey, _cipherEntries, canaryRng);
|
|
283
|
+
if (canary)
|
|
284
|
+
code = code + "\n" + canary;
|
|
285
|
+
}
|
|
286
|
+
// ── Layer 7: Differential Canary (license function shadow) ───────────────
|
|
287
|
+
// CNE path: canary source is passed through CNE rename+hex so it looks
|
|
288
|
+
// like obfuscated code rather than a readable JS block at the file tail.
|
|
289
|
+
// Legacy path: appended as-is (jsobf will have already obfuscated it).
|
|
290
|
+
if ((0, layers_1.layerOn)(opts, "canary", false)) {
|
|
291
|
+
if (!_useLegacyJsobf) {
|
|
292
|
+
const _canarySrc = (0, canary_1.buildCanarySource)(rng, opts);
|
|
293
|
+
if (_canarySrc) {
|
|
294
|
+
const _canaryObf = (0, cne_1.cneObfuscate)(_canarySrc, rng, {
|
|
295
|
+
rename: true, hexLiterals: true, stringSplit: true,
|
|
296
|
+
flatten: false, opaqueConstants: false, deadCode: false,
|
|
297
|
+
});
|
|
298
|
+
code = code + "\n" + _canaryObf;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
code = (0, canary_1.injectCanary)(code, rng, opts);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// ── Layer 3c-X: Context Exhaustion footer ─────────────────────────────────
|
|
306
|
+
if ((0, layers_1.layerOn)(opts, "aiContextExhaustion", false)) {
|
|
307
|
+
code = code + "\n" + ai_confusion_1.CONTEXT_EXHAUSTION_FOOTER;
|
|
308
|
+
}
|
|
309
|
+
// ── Layer 1: Copyright / AI Directive Banner ───────────────────────────────
|
|
310
|
+
if ((0, layers_1.layerOn)(opts, "banner", true)) {
|
|
311
|
+
const banner = opts.customCopyright
|
|
312
|
+
? (0, ai_confusion_1.buildCustomBanner)(opts.customCopyright)
|
|
313
|
+
: ai_confusion_1.AI_DIRECTIVE_BANNER;
|
|
314
|
+
code = banner + "\n" + code;
|
|
315
|
+
}
|
|
316
|
+
// Re-attach the shebang as the very first line (if the source had one).
|
|
317
|
+
if (shebang)
|
|
318
|
+
code = shebang + code;
|
|
319
|
+
return code;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Obfuscate multiple files with the same config but unique per-file seeds.
|
|
323
|
+
* Returns array of obfuscated strings in the same order as inputs.
|
|
324
|
+
*/
|
|
325
|
+
function obfuscateBatch(files, opts = {}) {
|
|
326
|
+
return files.map((f, i) => obfuscateCode(f.content, opts, i));
|
|
327
|
+
}
|
package/prng.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeRng = makeRng;
|
|
4
|
+
exports.ksGen = ksGen;
|
|
5
|
+
function makeRng(seed) {
|
|
6
|
+
let s = seed >>> 0;
|
|
7
|
+
function next() {
|
|
8
|
+
s = (s + 0x6d2b79f5) | 0;
|
|
9
|
+
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
10
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
11
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
next,
|
|
15
|
+
int32: () => (next() * 4294967296) | 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Generator de keystream pe octeti (folosit la criptarea stringurilor in build).
|
|
19
|
+
// Trebuie sa fie BIT-IDENTIC cu bucla din runtime-ul injectat (vezi transforms.ts).
|
|
20
|
+
function ksGen(seed) {
|
|
21
|
+
let s = seed >>> 0;
|
|
22
|
+
return () => {
|
|
23
|
+
s = (s + 0x6d2b79f5) | 0;
|
|
24
|
+
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
25
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
26
|
+
return ((t ^ (t >>> 14)) >>> 0) & 0xff;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.breakSignatures = breakSignatures;
|
|
40
|
+
// Layer NOU 4 / Stage F — spargerea semnaturilor de deobfuscator.
|
|
41
|
+
// webcrack / synchrony recunosc javascript-obfuscator dupa forma string-array-ului:
|
|
42
|
+
// o declaratie cu un ARRAY LITERAL de N stringuri, plus un rotator IIFE. Aici luam
|
|
43
|
+
// acel array literal si il reconstruim element-cu-element, in ordine permutata, intr-un
|
|
44
|
+
// IIFE => extractorul "ia array-ul literal" nu mai gaseste un literal de luat.
|
|
45
|
+
// Comportamentul la runtime e identic (acelasi array, aceeasi ordine finala).
|
|
46
|
+
//
|
|
47
|
+
// Orice eroare => returnam codul neschimbat (corectitudinea primeaza).
|
|
48
|
+
const parser_1 = require("@babel/parser");
|
|
49
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
50
|
+
const generator_1 = __importDefault(require("@babel/generator"));
|
|
51
|
+
const t = __importStar(require("@babel/types"));
|
|
52
|
+
const traverse = traverse_1.default.default || traverse_1.default;
|
|
53
|
+
const generate = generator_1.default.default || generator_1.default;
|
|
54
|
+
function breakSignatures(code, rng) {
|
|
55
|
+
try {
|
|
56
|
+
const ast = (0, parser_1.parse)(code, { sourceType: "unambiguous" });
|
|
57
|
+
let changed = false;
|
|
58
|
+
traverse(ast, {
|
|
59
|
+
ArrayExpression(path) {
|
|
60
|
+
if (changed)
|
|
61
|
+
return; // doar primul array mare (string-array-ul jsobf)
|
|
62
|
+
const els = path.node.elements;
|
|
63
|
+
if (!els || els.length < 6)
|
|
64
|
+
return;
|
|
65
|
+
const strCount = els.filter((e) => e && e.type === "StringLiteral").length;
|
|
66
|
+
if (strCount < els.length * 0.6)
|
|
67
|
+
return; // majoritar stringuri
|
|
68
|
+
// construim: (function(){ var a = new Array(N); a[i]=el_i (ordine permutata); return a; })()
|
|
69
|
+
const idx = els.map((_, i) => i);
|
|
70
|
+
for (let i = idx.length - 1; i > 0; i--) {
|
|
71
|
+
const j = Math.floor(rng.next() * (i + 1));
|
|
72
|
+
const tmp = idx[i];
|
|
73
|
+
idx[i] = idx[j];
|
|
74
|
+
idx[j] = tmp;
|
|
75
|
+
}
|
|
76
|
+
const aId = t.identifier("_sa");
|
|
77
|
+
const stmts = [
|
|
78
|
+
t.variableDeclaration("var", [
|
|
79
|
+
t.variableDeclarator(aId, t.newExpression(t.identifier("Array"), [
|
|
80
|
+
t.numericLiteral(els.length),
|
|
81
|
+
])),
|
|
82
|
+
]),
|
|
83
|
+
];
|
|
84
|
+
for (const i of idx) {
|
|
85
|
+
stmts.push(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(aId, t.numericLiteral(i), true), els[i] || t.identifier("undefined"))));
|
|
86
|
+
}
|
|
87
|
+
stmts.push(t.returnStatement(aId));
|
|
88
|
+
const iife = t.callExpression(t.functionExpression(null, [], t.blockStatement(stmts)), []);
|
|
89
|
+
path.replaceWith(iife);
|
|
90
|
+
path.skip();
|
|
91
|
+
changed = true;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
if (!changed)
|
|
95
|
+
return code;
|
|
96
|
+
return generate(ast, { compact: true, comments: false }).code;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return code;
|
|
100
|
+
}
|
|
101
|
+
}
|