@jshookmcp/jshook 0.2.8 → 0.3.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/README.md +36 -5
- package/README.zh.md +36 -5
- package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-CqGDXmfc.mjs} +160 -54
- package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
- package/dist/ConsoleMonitor-DykL3IAw.mjs +2269 -0
- package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-ETyy0xyo.mjs} +1 -1
- package/dist/DetailedDataManager-HT49OrvF.mjs +217 -0
- package/dist/EventBus-DFKvADm3.mjs +141 -0
- package/dist/EvidenceGraphBridge-318Oi0Lf.mjs +153 -0
- package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-BDMsY2Dz.mjs} +27 -13
- package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-BN4UQWnX.mjs} +1 -1
- package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
- package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-DruMgsgj.mjs} +21 -21
- package/dist/HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs +566 -0
- package/dist/InstrumentationSession-DLH0vd-z.mjs +244 -0
- package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CMtviNW_.mjs} +3 -3
- package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-ITgb_NMi.mjs} +81 -78
- package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-CiL7Z3ey.mjs} +50 -21
- package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +20 -56
- package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
- package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-DMQ44gen.mjs} +16 -16
- package/dist/PageController-BPJNqqBN.mjs +431 -0
- package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
- package/dist/PrerequisiteError-TuyZIs6n.mjs +20 -0
- package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
- package/dist/ResponseBuilder-CJXWmWNw.mjs +143 -0
- package/dist/ReverseEvidenceGraph-C02-gXOh.mjs +269 -0
- package/dist/ScriptManager-ZuWD-0Jg.mjs +3003 -0
- package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-D-z0umeT.mjs} +2 -2
- package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
- package/dist/ToolCatalog-5OJdMiF0.mjs +582 -0
- package/dist/ToolError-jh9whhMd.mjs +15 -0
- package/dist/ToolProbe-DbCFGyrg.mjs +45 -0
- package/dist/ToolRegistry-B9krbTtI.mjs +180 -0
- package/dist/ToolRouter.policy-BGDAGyeH.mjs +344 -0
- package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
- package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-C2kjj0ze.mjs} +19 -13
- package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-CKrGOTpo.mjs} +3 -3
- package/dist/WorkflowEngine-DJ6M4opp.mjs +569 -0
- package/dist/analysis-BHeJW2Nb.mjs +1234 -0
- package/dist/antidebug-BRKeyt27.mjs +1081 -0
- package/dist/artifactRetention-CPXkUJXp.mjs +598 -0
- package/dist/artifacts-DkfosXH3.mjs +59 -0
- package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
- package/dist/betterSqlite3-DLSBZodi.mjs +74 -0
- package/dist/binary-instrument--V3MAhJ4.mjs +971 -0
- package/dist/bind-helpers-ClV34xdn.mjs +42 -0
- package/dist/boringssl-inspector-Bo_LOLaS.mjs +180 -0
- package/dist/browser-Dx3_S2cG.mjs +4369 -0
- package/dist/capabilities-CcHlvWgK.mjs +33 -0
- package/dist/concurrency-Drev_Vz9.mjs +41 -0
- package/dist/{constants-CCvsN80K.mjs → constants-CDZLOoVv.mjs} +105 -48
- package/dist/coordination-DgItD9DL.mjs +259 -0
- package/dist/debugger-RS3RSAqs.mjs +1288 -0
- package/dist/definitions-BEoYofW5.mjs +47 -0
- package/dist/definitions-BRaefg3u.mjs +365 -0
- package/dist/definitions-BbkvZkiv.mjs +96 -0
- package/dist/definitions-BtWSHJ3o.mjs +17 -0
- package/dist/definitions-C1gCHO0i.mjs +43 -0
- package/dist/definitions-CDOg_b-l.mjs +138 -0
- package/dist/definitions-CVPD9hzZ.mjs +54 -0
- package/dist/definitions-Cea8Lgl7.mjs +94 -0
- package/dist/definitions-DAgIyjxM.mjs +10 -0
- package/dist/definitions-DJA27nsL.mjs +66 -0
- package/dist/definitions-DKPFU3LW.mjs +25 -0
- package/dist/definitions-DPRpZQ96.mjs +47 -0
- package/dist/definitions-DUE5gmdn.mjs +18 -0
- package/dist/definitions-DYVjOtxa.mjs +26 -0
- package/dist/definitions-DcYLVLCo.mjs +37 -0
- package/dist/definitions-Pp5LI2H4.mjs +27 -0
- package/dist/definitions-j9KdHVNR.mjs +14 -0
- package/dist/definitions-uzkjBwa7.mjs +258 -0
- package/dist/definitions-va-AnLuQ.mjs +28 -0
- package/dist/encoding-DJeqHmpd.mjs +1079 -0
- package/dist/evidence-graph-bridge-DcYizFk2.mjs +136 -0
- package/dist/{factory-CibqTNC8.mjs → factory-C90tBff6.mjs} +41 -56
- package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
- package/dist/graphql-CoHrhweh.mjs +1197 -0
- package/dist/handlers-4jmR0nMs.mjs +898 -0
- package/dist/handlers-BAHPxcch.mjs +789 -0
- package/dist/handlers-BOs9b907.mjs +2600 -0
- package/dist/handlers-BWXEy6ef.mjs +917 -0
- package/dist/handlers-Bndn6QvE.mjs +111 -0
- package/dist/handlers-BqC4bD4s.mjs +681 -0
- package/dist/handlers-BtYq60bM2.mjs +276 -0
- package/dist/handlers-BzgcB4iv.mjs +799 -0
- package/dist/handlers-CRyRWj2b.mjs +859 -0
- package/dist/handlers-CVv2H1uq.mjs +592 -0
- package/dist/handlers-Dl5a7JS4.mjs +572 -0
- package/dist/handlers-Dx2d7jt7.mjs +2537 -0
- package/dist/handlers-Dz9PYsCa.mjs +2805 -0
- package/dist/handlers-HujRKC3b.mjs +661 -0
- package/dist/handlers.impl-XWXkQfyi.mjs +807 -0
- package/dist/hooks-B1B8NRHL.mjs +898 -0
- package/dist/index.mjs +491 -259
- package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
- package/dist/maintenance-PRMkLVRW.mjs +835 -0
- package/dist/manifest-67Bok-Si.mjs +58 -0
- package/dist/manifest-6lNTMZAB2.mjs +87 -0
- package/dist/manifest-B2duEHiH.mjs +90 -0
- package/dist/manifest-B6EY9Vm8.mjs +57 -0
- package/dist/manifest-B6nKSbyY.mjs +95 -0
- package/dist/manifest-BL8AQNPF.mjs +106 -0
- package/dist/manifest-BSZvJJmV.mjs +47 -0
- package/dist/manifest-BU7qzUyX.mjs +418 -0
- package/dist/manifest-Bl62e8WK.mjs +49 -0
- package/dist/manifest-Bo5cXjdt.mjs +82 -0
- package/dist/manifest-BpS4gtUK.mjs +1347 -0
- package/dist/manifest-Bv65_e2W.mjs +101 -0
- package/dist/manifest-BytNIF4Z.mjs +117 -0
- package/dist/manifest-C-xtsjS3.mjs +81 -0
- package/dist/manifest-CDYl7OhA.mjs +66 -0
- package/dist/manifest-CRZ3xmkD.mjs +61 -0
- package/dist/manifest-CoW6u4Tp.mjs +132 -0
- package/dist/manifest-Cq5zN_8A.mjs +50 -0
- package/dist/manifest-D7YZM_2e.mjs +194 -0
- package/dist/manifest-DE_VrAeQ.mjs +314 -0
- package/dist/manifest-DGsXSCpT.mjs +39 -0
- package/dist/manifest-DJ2vfEuW.mjs +156 -0
- package/dist/manifest-DPXDYhEu.mjs +80 -0
- package/dist/manifest-Dd4fQb0a.mjs +322 -0
- package/dist/manifest-Deq6opGg.mjs +223 -0
- package/dist/manifest-DfJTafJK.mjs +37 -0
- package/dist/manifest-DgOdgN_j.mjs +50 -0
- package/dist/manifest-DlbMW4v4.mjs +47 -0
- package/dist/manifest-DmVfbH0w.mjs +374 -0
- package/dist/manifest-Dog6Ddjr.mjs +109 -0
- package/dist/manifest-DvgU5FWb.mjs +58 -0
- package/dist/manifest-HsfDBs7j.mjs +50 -0
- package/dist/manifest-I8oQHvCG.mjs +186 -0
- package/dist/manifest-NvH_a-av.mjs +786 -0
- package/dist/manifest-cEJU1v0Z.mjs +129 -0
- package/dist/manifest-wOl5XLB12.mjs +112 -0
- package/dist/modules-tZozf0LQ.mjs +10635 -0
- package/dist/mojo-ipc-DXNEXEqb.mjs +640 -0
- package/dist/network-CPVvwvFg.mjs +3852 -0
- package/dist/{artifacts-BbdOMET5.mjs → outputPaths-um7lCRY3.mjs} +219 -216
- package/dist/parse-args-B4cY5Vx5.mjs +39 -0
- package/dist/platform-CYeFoTWp.mjs +2161 -0
- package/dist/process-BTbgcVc6.mjs +1306 -0
- package/dist/proxy-r8YN6nP1.mjs +192 -0
- package/dist/registry-Bl8ZQW61.mjs +34 -0
- package/dist/response-CWhh2aLo.mjs +34 -0
- package/dist/server/plugin-api.mjs +2 -2
- package/dist/shared-state-board-BoZnSoj-.mjs +586 -0
- package/dist/sourcemap-BIDHUVXy.mjs +934 -0
- package/dist/ssrf-policy-Dsqd-DTX.mjs +166 -0
- package/dist/streaming-Dal6utPp.mjs +725 -0
- package/dist/tool-builder-BHJp32mV.mjs +186 -0
- package/dist/transform-DRVgGG90.mjs +1011 -0
- package/dist/types-Bx92KJfT.mjs +4 -0
- package/dist/wasm-BYx5UOeG.mjs +1044 -0
- package/dist/webcrack-Be0_FccV.mjs +747 -0
- package/dist/workflow-BpuKEtvn.mjs +725 -0
- package/package.json +82 -49
- package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
- package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
- package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
- package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
- package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
- package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-nnMvEohD.mjs} +0 -0
- package/dist/{types-BBjOqye-.mjs → types-DDBWs9UP.mjs} +1 -1
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import { t as logger } from "./logger-Dh_xb7_2.mjs";
|
|
2
|
+
import { _t as JSVMP_MAX_ITERATIONS, fn as SANDBOX_EXEC_TIMEOUT_MS, gn as SANDBOX_TERMINATE_GRACE_MS, gt as JSVMP_DEOBFUSCATE_TIMEOUT_MS, hn as SANDBOX_STACK_SIZE_MB, mn as SANDBOX_MEMORY_LIMIT_MB } from "./constants-CDZLOoVv.mjs";
|
|
3
|
+
import { t as ProcessRegistry } from "./ProcessRegistry-zGg12QbE.mjs";
|
|
4
|
+
import { n as cpuLimit } from "./concurrency-Drev_Vz9.mjs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { readdir, rm, stat } from "node:fs/promises";
|
|
7
|
+
import * as parser from "@babel/parser";
|
|
8
|
+
import traverse from "@babel/traverse";
|
|
9
|
+
import * as t from "@babel/types";
|
|
10
|
+
import generate from "@babel/generator";
|
|
11
|
+
import { Worker } from "node:worker_threads";
|
|
12
|
+
//#region src/modules/security/ExecutionSandbox.ts
|
|
13
|
+
/**
|
|
14
|
+
* ExecutionSandbox — Safe code execution for untrusted JavaScript.
|
|
15
|
+
*
|
|
16
|
+
* Uses worker_threads + vm module to isolate dynamic code execution
|
|
17
|
+
* (e.g., deobfuscation eval, packer unpacking).
|
|
18
|
+
*
|
|
19
|
+
* Security:
|
|
20
|
+
* - Runs in a separate worker thread (process isolation)
|
|
21
|
+
* - vm.createContext with empty global (no require, no fs, no net)
|
|
22
|
+
* - Timeout enforcement via worker termination
|
|
23
|
+
* - Memory limit via worker resourceLimits
|
|
24
|
+
*/
|
|
25
|
+
const WORKER_SCRIPT = `
|
|
26
|
+
import { workerData, parentPort } from 'node:worker_threads';
|
|
27
|
+
import * as vm from 'node:vm';
|
|
28
|
+
|
|
29
|
+
const { code, timeoutMs } = workerData;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Create an isolated context with minimal globals
|
|
33
|
+
const sandbox = {
|
|
34
|
+
// Safe built-ins only
|
|
35
|
+
parseInt, parseFloat, isNaN, isFinite,
|
|
36
|
+
encodeURIComponent, decodeURIComponent,
|
|
37
|
+
encodeURI, decodeURI,
|
|
38
|
+
JSON: { parse: JSON.parse, stringify: JSON.stringify },
|
|
39
|
+
Math,
|
|
40
|
+
String, Number, Boolean, Array, Object, Map, Set,
|
|
41
|
+
Date, RegExp, Error, TypeError, RangeError,
|
|
42
|
+
Promise,
|
|
43
|
+
Symbol,
|
|
44
|
+
undefined,
|
|
45
|
+
NaN,
|
|
46
|
+
Infinity,
|
|
47
|
+
// Explicitly denied: require, process, __filename, __dirname, Buffer, setTimeout, setInterval, fetch
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const context = vm.createContext(sandbox, {
|
|
51
|
+
name: 'jshook-sandbox',
|
|
52
|
+
codeGeneration: { strings: false, wasm: false },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const script = new vm.Script(code, {
|
|
56
|
+
filename: 'sandbox-eval.js',
|
|
57
|
+
timeout: timeoutMs,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = script.runInContext(context, { timeout: timeoutMs });
|
|
61
|
+
parentPort.postMessage({ ok: true, output: result });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
parentPort.postMessage({
|
|
64
|
+
ok: false,
|
|
65
|
+
error: err.message || String(err),
|
|
66
|
+
timedOut: err.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
var ExecutionSandbox = class {
|
|
71
|
+
/**
|
|
72
|
+
* Execute JavaScript in an isolated sandbox.
|
|
73
|
+
* Wrapped in cpuLimit for global concurrency control.
|
|
74
|
+
*/
|
|
75
|
+
async execute(request) {
|
|
76
|
+
return cpuLimit(() => this.executeInternal(request));
|
|
77
|
+
}
|
|
78
|
+
executeInternal(request) {
|
|
79
|
+
const timeoutMs = request.timeoutMs ?? SANDBOX_EXEC_TIMEOUT_MS;
|
|
80
|
+
const memoryLimitMB = request.memoryLimitMB ?? SANDBOX_MEMORY_LIMIT_MB;
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
let settled = false;
|
|
84
|
+
const terminationTimeout = setTimeout(() => {
|
|
85
|
+
if (!settled) {
|
|
86
|
+
worker.terminate();
|
|
87
|
+
logger.warn(`[ExecutionSandbox] Worker terminated after ${timeoutMs + SANDBOX_TERMINATE_GRACE_MS}ms`);
|
|
88
|
+
finish({
|
|
89
|
+
ok: false,
|
|
90
|
+
error: "Execution timed out (worker terminated)",
|
|
91
|
+
timedOut: true
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, timeoutMs + SANDBOX_TERMINATE_GRACE_MS);
|
|
95
|
+
const workerOptions = {
|
|
96
|
+
eval: true,
|
|
97
|
+
workerData: {
|
|
98
|
+
code: request.code,
|
|
99
|
+
timeoutMs
|
|
100
|
+
},
|
|
101
|
+
resourceLimits: {
|
|
102
|
+
maxOldGenerationSizeMb: memoryLimitMB,
|
|
103
|
+
maxYoungGenerationSizeMb: Math.ceil(memoryLimitMB / 4),
|
|
104
|
+
stackSizeMb: SANDBOX_STACK_SIZE_MB
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
workerOptions.type = "module";
|
|
108
|
+
const worker = new Worker(WORKER_SCRIPT, workerOptions);
|
|
109
|
+
if (typeof worker.unref === "function") worker.unref();
|
|
110
|
+
ProcessRegistry.register(worker);
|
|
111
|
+
const finish = (result) => {
|
|
112
|
+
if (settled) return;
|
|
113
|
+
settled = true;
|
|
114
|
+
if (terminationTimeout) clearTimeout(terminationTimeout);
|
|
115
|
+
resolve({
|
|
116
|
+
...result,
|
|
117
|
+
durationMs: Date.now() - startTime
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
worker.on("message", (msg) => {
|
|
121
|
+
finish({
|
|
122
|
+
ok: msg.ok,
|
|
123
|
+
output: msg.output,
|
|
124
|
+
error: msg.error,
|
|
125
|
+
timedOut: msg.timedOut || false
|
|
126
|
+
});
|
|
127
|
+
worker.terminate();
|
|
128
|
+
});
|
|
129
|
+
worker.on("error", (err) => {
|
|
130
|
+
finish({
|
|
131
|
+
ok: false,
|
|
132
|
+
error: `Worker error: ${err.message}`,
|
|
133
|
+
timedOut: false
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
worker.on("exit", (code) => {
|
|
137
|
+
if (!settled) finish({
|
|
138
|
+
ok: false,
|
|
139
|
+
error: `Worker exited unexpectedly with code ${code}`,
|
|
140
|
+
timedOut: false
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/modules/deobfuscator/JSVMPDeobfuscator.restore.ts
|
|
148
|
+
async function restoreJSVMPCode(context, code, vmType, aggressive) {
|
|
149
|
+
const warnings = [];
|
|
150
|
+
const unresolvedParts = [];
|
|
151
|
+
if (vmType === "obfuscator.io") return restoreObfuscatorIO(context, code, aggressive, warnings, unresolvedParts);
|
|
152
|
+
if (vmType === "jsfuck") return restoreJSFuck(context, code, warnings);
|
|
153
|
+
if (vmType === "jjencode") return restoreJJEncode(context, code, warnings);
|
|
154
|
+
return restoreCustomVM(context, code, aggressive, warnings, unresolvedParts);
|
|
155
|
+
}
|
|
156
|
+
async function restoreObfuscatorIO(context, code, aggressive, warnings, unresolvedParts) {
|
|
157
|
+
let restored = code;
|
|
158
|
+
let confidence = .5;
|
|
159
|
+
try {
|
|
160
|
+
const stringArrayMatch = code.match(/var\s+(_0x[a-f0-9]+)\s*=\s*(\[.*?\]);/s);
|
|
161
|
+
if (stringArrayMatch) {
|
|
162
|
+
const arrayName = stringArrayMatch[1];
|
|
163
|
+
const arrayContent = stringArrayMatch[2];
|
|
164
|
+
logger.info(` : ${arrayName}`);
|
|
165
|
+
try {
|
|
166
|
+
const sandboxResult = await context.sandbox.execute({
|
|
167
|
+
code: `return ${arrayContent || "[]"};`,
|
|
168
|
+
timeoutMs: 3e3
|
|
169
|
+
});
|
|
170
|
+
const stringArray = sandboxResult.ok ? sandboxResult.output : void 0;
|
|
171
|
+
if (Array.isArray(stringArray)) {
|
|
172
|
+
logger.info(`String array detected, ${stringArray.length} strings found`);
|
|
173
|
+
const refPattern = new RegExp(`${arrayName}\\[(\\d+)\\]`, "g");
|
|
174
|
+
restored = restored.replace(refPattern, (_match, index) => {
|
|
175
|
+
const idx = parseInt(index, 10);
|
|
176
|
+
if (idx < stringArray.length) return JSON.stringify(stringArray[idx]);
|
|
177
|
+
return _match;
|
|
178
|
+
});
|
|
179
|
+
confidence += .2;
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
warnings.push(`: ${e}`);
|
|
183
|
+
unresolvedParts.push({
|
|
184
|
+
location: "String Array",
|
|
185
|
+
reason: "",
|
|
186
|
+
suggestion: ""
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
restored = restored.replace(/\(function\s*\(_0x[a-f0-9]+,\s*_0x[a-f0-9]+\)\s*\{[\s\S]*?\}\(_0x[a-f0-9]+,\s*0x[a-f0-9]+\)\);?/g, "");
|
|
191
|
+
if (aggressive) {
|
|
192
|
+
restored = restored.replace(/\(function\s*\(\)\s*\{([\s\S]*)\}\(\)\);?/g, "$1");
|
|
193
|
+
confidence += .1;
|
|
194
|
+
}
|
|
195
|
+
restored = restored.replace(/0x([0-9a-f]+)/gi, (_match, hex) => {
|
|
196
|
+
return String(parseInt(hex, 16));
|
|
197
|
+
});
|
|
198
|
+
restored = restored.replace(/;\s*;/g, ";");
|
|
199
|
+
restored = restored.replace(/\{\s*\}/g, "{}");
|
|
200
|
+
warnings.push("obfuscator.io detected, may need special handling");
|
|
201
|
+
return {
|
|
202
|
+
code: restored,
|
|
203
|
+
confidence: Math.min(confidence, 1),
|
|
204
|
+
warnings,
|
|
205
|
+
unresolvedParts: unresolvedParts.length > 0 ? unresolvedParts : void 0
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
warnings.push(`obfuscator.io: ${error}`);
|
|
209
|
+
return {
|
|
210
|
+
code,
|
|
211
|
+
confidence: .2,
|
|
212
|
+
warnings,
|
|
213
|
+
unresolvedParts
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function restoreJSFuck(context, code, warnings) {
|
|
218
|
+
try {
|
|
219
|
+
logger.info("JSFuck detected, attempting deobfuscation...");
|
|
220
|
+
try {
|
|
221
|
+
if (code.length > 1e5) {
|
|
222
|
+
warnings.push("JSFuck code detected, file too large to process directly.");
|
|
223
|
+
warnings.push("Consider using an online JSFuck decoder tool.");
|
|
224
|
+
return {
|
|
225
|
+
code,
|
|
226
|
+
confidence: .1,
|
|
227
|
+
warnings
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const sandboxResult = await context.sandbox.execute({
|
|
231
|
+
code: `return ${code};`,
|
|
232
|
+
timeoutMs: 5e3
|
|
233
|
+
});
|
|
234
|
+
const result = sandboxResult.ok ? sandboxResult.output : void 0;
|
|
235
|
+
if (typeof result === "string") {
|
|
236
|
+
logger.info(" JSFuck");
|
|
237
|
+
return {
|
|
238
|
+
code: result,
|
|
239
|
+
confidence: .9,
|
|
240
|
+
warnings: ["JSFuck"]
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
warnings.push("JSFuck");
|
|
244
|
+
return {
|
|
245
|
+
code,
|
|
246
|
+
confidence: .2,
|
|
247
|
+
warnings
|
|
248
|
+
};
|
|
249
|
+
} catch (execError) {
|
|
250
|
+
warnings.push(`JSFuck: ${execError}`);
|
|
251
|
+
warnings.push("Consider using an online JSFuck decoder tool.");
|
|
252
|
+
return {
|
|
253
|
+
code,
|
|
254
|
+
confidence: .1,
|
|
255
|
+
warnings
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
warnings.push(`JSFuck: ${error}`);
|
|
260
|
+
return {
|
|
261
|
+
code,
|
|
262
|
+
confidence: .1,
|
|
263
|
+
warnings
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function restoreJJEncode(context, code, warnings) {
|
|
268
|
+
try {
|
|
269
|
+
logger.info("JJEncode detected, attempting deobfuscation...");
|
|
270
|
+
try {
|
|
271
|
+
const lines = code.split("\n").filter((line) => line.trim());
|
|
272
|
+
if ((lines.length > 0 ? lines[lines.length - 1] : "")?.includes("$$$$")) {
|
|
273
|
+
const sandboxResult = await context.sandbox.execute({
|
|
274
|
+
code: `${code}; return $$$$()`,
|
|
275
|
+
timeoutMs: 5e3
|
|
276
|
+
});
|
|
277
|
+
const result = sandboxResult.ok ? sandboxResult.output : void 0;
|
|
278
|
+
if (typeof result === "string") {
|
|
279
|
+
logger.info(" JJEncode");
|
|
280
|
+
return {
|
|
281
|
+
code: result,
|
|
282
|
+
confidence: .9,
|
|
283
|
+
warnings: ["JJEncode"]
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const sandboxResult = await context.sandbox.execute({
|
|
288
|
+
code,
|
|
289
|
+
timeoutMs: 5e3
|
|
290
|
+
});
|
|
291
|
+
if (!sandboxResult.ok) logger.warn("JJEncode sandbox execution failed:", sandboxResult.error);
|
|
292
|
+
warnings.push("JJEncode deobfuscation may be incomplete");
|
|
293
|
+
warnings.push("Result may still contain JJEncode fragments");
|
|
294
|
+
return {
|
|
295
|
+
code,
|
|
296
|
+
confidence: .2,
|
|
297
|
+
warnings
|
|
298
|
+
};
|
|
299
|
+
} catch (execError) {
|
|
300
|
+
warnings.push(`JJEncode: ${execError}`);
|
|
301
|
+
warnings.push("Result may contain evaluation artifacts");
|
|
302
|
+
return {
|
|
303
|
+
code,
|
|
304
|
+
confidence: .1,
|
|
305
|
+
warnings
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
warnings.push(`JJEncode: ${error}`);
|
|
310
|
+
return {
|
|
311
|
+
code,
|
|
312
|
+
confidence: .1,
|
|
313
|
+
warnings
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function restoreCustomVM(_context, code, aggressive, warnings, unresolvedParts) {
|
|
318
|
+
warnings.push("AI-assisted deobfuscation removed, using fallback directly.");
|
|
319
|
+
return restoreCustomVMBasic(code, aggressive, warnings, unresolvedParts);
|
|
320
|
+
}
|
|
321
|
+
function restoreCustomVMBasic(code, aggressive, warnings, unresolvedParts) {
|
|
322
|
+
let restored = code;
|
|
323
|
+
let confidence = .3;
|
|
324
|
+
try {
|
|
325
|
+
restored = restored.replace(/if\s*\([^)]*\)\s*\{\s*\}/g, "");
|
|
326
|
+
restored = restored.replace(/!!\s*\(/g, "Boolean(");
|
|
327
|
+
restored = restored.replace(/""\s*\+\s*/g, "");
|
|
328
|
+
if (aggressive) {
|
|
329
|
+
restored = restored.replace(/debugger;?/g, "");
|
|
330
|
+
confidence += .1;
|
|
331
|
+
restored = restored.replace(/\?\s*([^:]+)\s*:\s*\1/g, "$1");
|
|
332
|
+
confidence += .05;
|
|
333
|
+
}
|
|
334
|
+
warnings.push("Analysis incomplete, partial results may be returned");
|
|
335
|
+
warnings.push("For better results, configure an LLM API key");
|
|
336
|
+
unresolvedParts.push({
|
|
337
|
+
location: "Custom VM",
|
|
338
|
+
reason: "VM",
|
|
339
|
+
suggestion: "VM protection detected, LLM-assisted analysis recommended"
|
|
340
|
+
});
|
|
341
|
+
return {
|
|
342
|
+
code: restored,
|
|
343
|
+
confidence,
|
|
344
|
+
warnings,
|
|
345
|
+
unresolvedParts: unresolvedParts.length > 0 ? unresolvedParts : void 0
|
|
346
|
+
};
|
|
347
|
+
} catch (error) {
|
|
348
|
+
warnings.push(`: ${error}`);
|
|
349
|
+
return {
|
|
350
|
+
code,
|
|
351
|
+
confidence: .1,
|
|
352
|
+
warnings,
|
|
353
|
+
unresolvedParts
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/modules/deobfuscator/JSVMPDeobfuscator.ts
|
|
359
|
+
var JSVMPDeobfuscator = class {
|
|
360
|
+
sandbox = new ExecutionSandbox();
|
|
361
|
+
async deobfuscate(options) {
|
|
362
|
+
const startTime = Date.now();
|
|
363
|
+
const { code, aggressive = false, extractInstructions = false, timeout = JSVMP_DEOBFUSCATE_TIMEOUT_MS, maxIterations = JSVMP_MAX_ITERATIONS } = options;
|
|
364
|
+
logger.info(" JSVMP...");
|
|
365
|
+
try {
|
|
366
|
+
const vmFeatures = this.detectJSVMP(code);
|
|
367
|
+
if (!vmFeatures) {
|
|
368
|
+
logger.info("JSVMP");
|
|
369
|
+
return {
|
|
370
|
+
isJSVMP: false,
|
|
371
|
+
deobfuscatedCode: code,
|
|
372
|
+
confidence: 0,
|
|
373
|
+
warnings: ["JSVMP"]
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
logger.info(`JSVMP analysis complete, complexity: ${vmFeatures.complexity}`);
|
|
377
|
+
logger.info(` : ${vmFeatures.instructionCount}`);
|
|
378
|
+
const vmType = this.identifyVMType(code, vmFeatures);
|
|
379
|
+
logger.info(` : ${vmType}`);
|
|
380
|
+
let instructions;
|
|
381
|
+
if (extractInstructions) {
|
|
382
|
+
logger.info(" ...");
|
|
383
|
+
instructions = this.extractInstructions(code, vmFeatures);
|
|
384
|
+
logger.info(` ${instructions.length} `);
|
|
385
|
+
}
|
|
386
|
+
logger.info(" ...");
|
|
387
|
+
const deobfuscationResult = await this.restoreCode(code, vmFeatures, vmType, aggressive, timeout, maxIterations);
|
|
388
|
+
const processingTime = Date.now() - startTime;
|
|
389
|
+
const result = {
|
|
390
|
+
isJSVMP: true,
|
|
391
|
+
vmType,
|
|
392
|
+
vmFeatures,
|
|
393
|
+
instructions,
|
|
394
|
+
deobfuscatedCode: deobfuscationResult.code,
|
|
395
|
+
confidence: deobfuscationResult.confidence,
|
|
396
|
+
warnings: deobfuscationResult.warnings,
|
|
397
|
+
unresolvedParts: deobfuscationResult.unresolvedParts,
|
|
398
|
+
stats: {
|
|
399
|
+
originalSize: code.length,
|
|
400
|
+
deobfuscatedSize: deobfuscationResult.code.length,
|
|
401
|
+
reductionRate: 1 - deobfuscationResult.code.length / code.length,
|
|
402
|
+
processingTime
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
logger.info(`JSVMP deobfuscation complete in ${processingTime}ms`);
|
|
406
|
+
logger.info(` : ${(result.confidence * 100).toFixed(1)}%`);
|
|
407
|
+
return result;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
logger.error("JSVMP", error);
|
|
410
|
+
return {
|
|
411
|
+
isJSVMP: false,
|
|
412
|
+
deobfuscatedCode: code,
|
|
413
|
+
confidence: 0,
|
|
414
|
+
warnings: [`: ${error}`]
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
detectJSVMP(code) {
|
|
419
|
+
try {
|
|
420
|
+
const ast = parser.parse(code, {
|
|
421
|
+
sourceType: "unambiguous",
|
|
422
|
+
plugins: ["jsx", "typescript"],
|
|
423
|
+
errorRecovery: true
|
|
424
|
+
});
|
|
425
|
+
let hasSwitch = false;
|
|
426
|
+
let hasInstructionArray = false;
|
|
427
|
+
let hasProgramCounter = false;
|
|
428
|
+
let instructionCount = 0;
|
|
429
|
+
let interpreterLocation = "";
|
|
430
|
+
let maxSwitchCases = 0;
|
|
431
|
+
let hasBytecodeArray = false;
|
|
432
|
+
let hasApplyCall = false;
|
|
433
|
+
let hasWhileLoop = false;
|
|
434
|
+
let bytecodePattern = false;
|
|
435
|
+
traverse(ast, {
|
|
436
|
+
SwitchStatement(path) {
|
|
437
|
+
const caseCount = path.node.cases.length;
|
|
438
|
+
if (caseCount > 10) {
|
|
439
|
+
hasSwitch = true;
|
|
440
|
+
if (caseCount > maxSwitchCases) {
|
|
441
|
+
maxSwitchCases = caseCount;
|
|
442
|
+
instructionCount = caseCount;
|
|
443
|
+
interpreterLocation = `Line ${path.node.loc?.start.line || 0}`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
ArrayExpression(path) {
|
|
448
|
+
if (path.node.elements.length > 50) hasInstructionArray = true;
|
|
449
|
+
},
|
|
450
|
+
UpdateExpression(path) {
|
|
451
|
+
if (path.node.operator === "++" || path.node.operator === "--") {
|
|
452
|
+
const arg = path.node.argument;
|
|
453
|
+
if (t.isIdentifier(arg) && arg.name.length <= 3) hasProgramCounter = true;
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
CallExpression(path) {
|
|
457
|
+
if (t.isIdentifier(path.node.callee, { name: "parseInt" }) && path.node.arguments.length >= 2) {
|
|
458
|
+
const firstArg = path.node.arguments[0];
|
|
459
|
+
if (t.isBinaryExpression(firstArg) && firstArg.operator === "+") {
|
|
460
|
+
bytecodePattern = true;
|
|
461
|
+
hasBytecodeArray = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property, { name: "apply" })) hasApplyCall = true;
|
|
465
|
+
},
|
|
466
|
+
WhileStatement(path) {
|
|
467
|
+
if (t.isBooleanLiteral(path.node.test, { value: true }) || t.isNumericLiteral(path.node.test, { value: 1 })) hasWhileLoop = true;
|
|
468
|
+
},
|
|
469
|
+
ForStatement(path) {
|
|
470
|
+
if (!path.node.test) hasWhileLoop = true;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (hasSwitch && (hasInstructionArray || hasProgramCounter) && (hasApplyCall || hasWhileLoop || bytecodePattern)) {
|
|
474
|
+
const complexity = instructionCount > 100 ? "high" : instructionCount > 50 ? "medium" : "low";
|
|
475
|
+
logger.info(" JSVMP:");
|
|
476
|
+
logger.info(` - Switch: ${hasSwitch} (${maxSwitchCases} cases)`);
|
|
477
|
+
logger.info(` - : ${hasInstructionArray}`);
|
|
478
|
+
logger.info(` - : ${hasProgramCounter}`);
|
|
479
|
+
logger.info(` - : ${hasBytecodeArray}`);
|
|
480
|
+
logger.info(` - Apply: ${hasApplyCall}`);
|
|
481
|
+
logger.info(` - : ${hasWhileLoop}`);
|
|
482
|
+
logger.info(` - : ${bytecodePattern}`);
|
|
483
|
+
return {
|
|
484
|
+
instructionCount,
|
|
485
|
+
interpreterLocation,
|
|
486
|
+
complexity,
|
|
487
|
+
hasSwitch,
|
|
488
|
+
hasInstructionArray,
|
|
489
|
+
hasProgramCounter
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
return null;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
logger.warn("JSVMP analysis failed", error);
|
|
495
|
+
return this.detectJSVMPWithRegex(code);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
detectJSVMPWithRegex(code) {
|
|
499
|
+
const hasSwitch = (code.match(/switch\s*\(/g)?.length || 0) > 0;
|
|
500
|
+
const bytecodePattern = /parseInt\s*\(\s*["']?\s*\+\s*\w+\[/g.test(code);
|
|
501
|
+
const applyPattern = /\.apply\s*\(/g.test(code);
|
|
502
|
+
const whilePattern = /while\s*\(\s*(true|1)\s*\)/g.test(code);
|
|
503
|
+
if (hasSwitch && (bytecodePattern || applyPattern || whilePattern)) {
|
|
504
|
+
logger.info(" JSVMP");
|
|
505
|
+
return {
|
|
506
|
+
instructionCount: 0,
|
|
507
|
+
interpreterLocation: "Unknown",
|
|
508
|
+
complexity: "medium",
|
|
509
|
+
hasSwitch: true,
|
|
510
|
+
hasInstructionArray: bytecodePattern,
|
|
511
|
+
hasProgramCounter: applyPattern
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
identifyVMType(code, _features) {
|
|
517
|
+
if (code.includes("_0x") && code.includes("function(_0x")) return "obfuscator.io";
|
|
518
|
+
if (/^\s*\[\s*\]\s*\[\s*\(/.test(code)) return "jsfuck";
|
|
519
|
+
if (code.includes("$=~[];")) return "jjencode";
|
|
520
|
+
return "custom";
|
|
521
|
+
}
|
|
522
|
+
extractInstructions(code, features) {
|
|
523
|
+
const instructions = [];
|
|
524
|
+
try {
|
|
525
|
+
traverse(parser.parse(code, {
|
|
526
|
+
sourceType: "unambiguous",
|
|
527
|
+
plugins: ["jsx", "typescript"]
|
|
528
|
+
}), { SwitchStatement: (path) => {
|
|
529
|
+
if (path.node.cases.length === features.instructionCount) path.node.cases.forEach((caseNode, index) => {
|
|
530
|
+
const opcode = caseNode.test ? t.isNumericLiteral(caseNode.test) ? caseNode.test.value : t.isStringLiteral(caseNode.test) ? caseNode.test.value : index : index;
|
|
531
|
+
const type = this.inferInstructionType(caseNode);
|
|
532
|
+
instructions.push({
|
|
533
|
+
opcode,
|
|
534
|
+
name: `INST_${opcode}`,
|
|
535
|
+
type,
|
|
536
|
+
description: `Instruction ${opcode}`
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
} });
|
|
540
|
+
} catch (error) {
|
|
541
|
+
logger.warn("", error);
|
|
542
|
+
}
|
|
543
|
+
return instructions;
|
|
544
|
+
}
|
|
545
|
+
inferInstructionType(caseNode) {
|
|
546
|
+
const code = generate(caseNode).code;
|
|
547
|
+
const consequent = caseNode.consequent;
|
|
548
|
+
let hasAssignment = false;
|
|
549
|
+
let hasArrayAccess = false;
|
|
550
|
+
let hasFunctionCall = false;
|
|
551
|
+
let hasArithmetic = false;
|
|
552
|
+
let hasControlFlow = false;
|
|
553
|
+
for (const stmt of consequent) {
|
|
554
|
+
if (t.isExpressionStatement(stmt)) {
|
|
555
|
+
const expr = stmt.expression;
|
|
556
|
+
if (t.isAssignmentExpression(expr)) hasAssignment = true;
|
|
557
|
+
if (t.isMemberExpression(expr) && t.isNumericLiteral(expr.property)) hasArrayAccess = true;
|
|
558
|
+
if (t.isCallExpression(expr)) hasFunctionCall = true;
|
|
559
|
+
if (t.isBinaryExpression(expr)) {
|
|
560
|
+
if ([
|
|
561
|
+
"+",
|
|
562
|
+
"-",
|
|
563
|
+
"*",
|
|
564
|
+
"/",
|
|
565
|
+
"%",
|
|
566
|
+
"**"
|
|
567
|
+
].includes(expr.operator)) hasArithmetic = true;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (t.isIfStatement(stmt) || t.isWhileStatement(stmt) || t.isBreakStatement(stmt) || t.isContinueStatement(stmt) || t.isReturnStatement(stmt)) hasControlFlow = true;
|
|
571
|
+
}
|
|
572
|
+
if ((code.includes("push") || code.includes(".push(")) && (hasArrayAccess || code.includes("["))) return "load";
|
|
573
|
+
if (hasAssignment && !hasArithmetic && !hasFunctionCall) return "store";
|
|
574
|
+
if (hasArithmetic || code.match(/[+\-*/%]/)) return "arithmetic";
|
|
575
|
+
if (hasControlFlow || code.includes("break") || code.includes("continue")) return "control";
|
|
576
|
+
if (hasFunctionCall || code.includes(".apply(") || code.includes(".call(")) return "call";
|
|
577
|
+
return "unknown";
|
|
578
|
+
}
|
|
579
|
+
async restoreCode(code, _features, vmType, aggressive, _timeout, _maxIterations) {
|
|
580
|
+
this.restoreCustomVMBasic;
|
|
581
|
+
return restoreJSVMPCode({ sandbox: this.sandbox }, code, vmType, aggressive);
|
|
582
|
+
}
|
|
583
|
+
restoreCustomVMBasic(code, aggressive, warnings, unresolvedParts) {
|
|
584
|
+
return restoreCustomVMBasic(code, aggressive, warnings, unresolvedParts);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/modules/deobfuscator/webcrack.ts
|
|
589
|
+
const DEFAULT_OPTIONS = {
|
|
590
|
+
jsx: true,
|
|
591
|
+
mangle: false,
|
|
592
|
+
unminify: true,
|
|
593
|
+
unpack: true
|
|
594
|
+
};
|
|
595
|
+
const MAX_BUNDLE_MODULES = 100;
|
|
596
|
+
async function importOptionalModule(specifier) {
|
|
597
|
+
return await import(specifier);
|
|
598
|
+
}
|
|
599
|
+
function normalizeOptions(options) {
|
|
600
|
+
return {
|
|
601
|
+
jsx: options.jsx ?? DEFAULT_OPTIONS.jsx,
|
|
602
|
+
mangle: options.mangle ?? DEFAULT_OPTIONS.mangle,
|
|
603
|
+
unminify: options.unminify ?? DEFAULT_OPTIONS.unminify,
|
|
604
|
+
unpack: options.unpack ?? DEFAULT_OPTIONS.unpack
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function isSupportedNodeVersion() {
|
|
608
|
+
const [majorPart = "0", minorPart = "0"] = process.versions.node.split(".");
|
|
609
|
+
const major = Number.parseInt(majorPart, 10);
|
|
610
|
+
const minor = Number.parseInt(minorPart, 10);
|
|
611
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor)) return false;
|
|
612
|
+
if (major === 20) return minor >= 19;
|
|
613
|
+
if (major === 22) return minor >= 12;
|
|
614
|
+
return major > 22;
|
|
615
|
+
}
|
|
616
|
+
function matchesRule(module, rule) {
|
|
617
|
+
const target = rule.target === "path" ? module.path : module.code;
|
|
618
|
+
const matchType = rule.matchType ?? "includes";
|
|
619
|
+
if (matchType === "exact") return target === rule.pattern;
|
|
620
|
+
if (matchType === "regex") try {
|
|
621
|
+
return new RegExp(rule.pattern, "m").test(target);
|
|
622
|
+
} catch {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
return target.includes(rule.pattern);
|
|
626
|
+
}
|
|
627
|
+
function applyBundleMappings(bundle, mappings) {
|
|
628
|
+
const remapped = /* @__PURE__ */ new Map();
|
|
629
|
+
if (!mappings || mappings.length === 0) return remapped;
|
|
630
|
+
for (const module of bundle.modules.values()) for (const rule of mappings) {
|
|
631
|
+
if (!rule.path || !rule.pattern) continue;
|
|
632
|
+
if (matchesRule(module, rule)) {
|
|
633
|
+
if (module.path !== rule.path) {
|
|
634
|
+
remapped.set(module.id, { fromPath: module.path });
|
|
635
|
+
module.path = rule.path;
|
|
636
|
+
}
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return remapped;
|
|
641
|
+
}
|
|
642
|
+
function summarizeBundle(bundle, options, remapped) {
|
|
643
|
+
const maxBundleModules = options.maxBundleModules ?? MAX_BUNDLE_MODULES;
|
|
644
|
+
const modules = Array.from(bundle.modules.values()).toSorted((left, right) => {
|
|
645
|
+
if (left.isEntry !== right.isEntry) return left.isEntry ? -1 : 1;
|
|
646
|
+
return left.path.localeCompare(right.path);
|
|
647
|
+
}).slice(0, maxBundleModules).map((module) => ({
|
|
648
|
+
id: module.id,
|
|
649
|
+
path: module.path,
|
|
650
|
+
isEntry: module.isEntry,
|
|
651
|
+
size: module.code.length,
|
|
652
|
+
code: options.includeModuleCode ? module.code : void 0,
|
|
653
|
+
mappedPathFrom: remapped.get(module.id)?.fromPath
|
|
654
|
+
}));
|
|
655
|
+
return {
|
|
656
|
+
type: bundle.type,
|
|
657
|
+
entryId: bundle.entryId,
|
|
658
|
+
moduleCount: bundle.modules.size,
|
|
659
|
+
truncated: bundle.modules.size > maxBundleModules,
|
|
660
|
+
mappingsApplied: remapped.size,
|
|
661
|
+
modules
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
async function collectSavedArtifacts(rootDir, currentDir = rootDir) {
|
|
665
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
666
|
+
const artifacts = [];
|
|
667
|
+
for (const entry of entries) {
|
|
668
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
669
|
+
if (entry.isDirectory()) {
|
|
670
|
+
artifacts.push(...await collectSavedArtifacts(rootDir, fullPath));
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (!entry.isFile()) continue;
|
|
674
|
+
const metadata = await stat(fullPath);
|
|
675
|
+
artifacts.push({
|
|
676
|
+
path: path.relative(rootDir, fullPath).replace(/\\/g, "/"),
|
|
677
|
+
size: metadata.size,
|
|
678
|
+
type: "file"
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return artifacts.toSorted((left, right) => left.path.localeCompare(right.path));
|
|
682
|
+
}
|
|
683
|
+
async function runWebcrack(code, options) {
|
|
684
|
+
const optionsUsed = normalizeOptions(options);
|
|
685
|
+
if (!isSupportedNodeVersion()) {
|
|
686
|
+
const reason = `webcrack requires Node.js 20.19+ or 22.12+; current runtime is ${process.versions.node}`;
|
|
687
|
+
logger.warn(reason);
|
|
688
|
+
return {
|
|
689
|
+
applied: false,
|
|
690
|
+
code,
|
|
691
|
+
optionsUsed,
|
|
692
|
+
reason
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
await import("isolated-vm");
|
|
697
|
+
} catch {
|
|
698
|
+
logger.warn("isolated-vm is unavailable (likely Node 24 incompatibility). Deobfuscation sandbox is disabled — do not process untrusted code.");
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
const { webcrack } = await importOptionalModule("webcrack");
|
|
702
|
+
const result = await webcrack(code, {
|
|
703
|
+
jsx: optionsUsed.jsx,
|
|
704
|
+
unpack: optionsUsed.unpack,
|
|
705
|
+
deobfuscate: true,
|
|
706
|
+
unminify: optionsUsed.unminify,
|
|
707
|
+
mangle: optionsUsed.mangle
|
|
708
|
+
});
|
|
709
|
+
const remapped = result.bundle ? applyBundleMappings(result.bundle, options.mappings) : /* @__PURE__ */ new Map();
|
|
710
|
+
let savedTo;
|
|
711
|
+
let savedArtifacts;
|
|
712
|
+
if (typeof options.outputDir === "string" && options.outputDir.trim().length > 0) {
|
|
713
|
+
savedTo = path.resolve(options.outputDir);
|
|
714
|
+
const cwd = process.cwd();
|
|
715
|
+
const relFromCwd = path.relative(cwd, savedTo);
|
|
716
|
+
if (path.isAbsolute(relFromCwd) || relFromCwd.startsWith("..") || savedTo === "/" || savedTo === path.parse(savedTo).root) throw new Error(`outputDir must resolve to a path within the project root. Got: ${savedTo}`);
|
|
717
|
+
if (options.forceOutput) await rm(savedTo, {
|
|
718
|
+
recursive: true,
|
|
719
|
+
force: true
|
|
720
|
+
});
|
|
721
|
+
await result.save(savedTo);
|
|
722
|
+
savedArtifacts = await collectSavedArtifacts(savedTo);
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
applied: true,
|
|
726
|
+
code: result.code,
|
|
727
|
+
bundle: result.bundle ? summarizeBundle(result.bundle, {
|
|
728
|
+
includeModuleCode: options.includeModuleCode,
|
|
729
|
+
maxBundleModules: options.maxBundleModules
|
|
730
|
+
}, remapped) : void 0,
|
|
731
|
+
savedTo,
|
|
732
|
+
savedArtifacts,
|
|
733
|
+
optionsUsed
|
|
734
|
+
};
|
|
735
|
+
} catch (error) {
|
|
736
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
737
|
+
logger.warn("webcrack execution failed, falling back to legacy pipeline", error);
|
|
738
|
+
return {
|
|
739
|
+
applied: false,
|
|
740
|
+
code,
|
|
741
|
+
optionsUsed,
|
|
742
|
+
reason
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
//#endregion
|
|
747
|
+
export { JSVMPDeobfuscator as n, runWebcrack as t };
|