deepspider 0.2.6 → 0.2.9
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/.trellis/spec/backend/ci-cd-guidelines.md +73 -0
- package/.trellis/spec/backend/deepagents-guide.md +43 -0
- package/.trellis/spec/backend/hook-guidelines.md +40 -0
- package/.trellis/spec/backend/index.md +1 -0
- package/.trellis/spec/backend/quality-guidelines.md +77 -0
- package/.trellis/workspace/pony/index.md +3 -2
- package/.trellis/workspace/pony/journal-1.md +64 -0
- package/CLAUDE.md +23 -0
- package/README.md +6 -0
- package/package.json +3 -2
- package/src/agent/prompts/system.js +63 -255
- package/src/agent/run.js +48 -17
- package/src/agent/skills/static-analysis/SKILL.md +120 -0
- package/src/agent/tools/browser.js +99 -0
- package/src/agent/tools/report.js +64 -14
- package/src/agent/tools/runtime.js +6 -4
- package/src/agent/tools/utils.js +0 -1
- package/src/browser/defaultHooks.js +325 -27
- package/src/browser/hooks/index.js +14 -18
- package/src/browser/ui/analysisPanel.js +461 -388
- package/src/env/HookBase.js +38 -18
- package/src/browser/hooks/crypto.js +0 -55
- package/src/browser/hooks/native.js +0 -9
- package/src/browser/hooks/network.js +0 -33
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { tool } from '@langchain/core/tools';
|
|
9
|
-
import { writeFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { PATHS, ensureDir,
|
|
9
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { join, basename } from 'path';
|
|
11
|
+
import { PATHS, ensureDir, DEEPSPIDER_HOME } from '../../config/paths.js';
|
|
12
12
|
|
|
13
13
|
const OUTPUT_DIR = PATHS.REPORTS_DIR;
|
|
14
14
|
|
|
@@ -25,6 +25,31 @@ function escapeHtml(str) {
|
|
|
25
25
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* 从文件路径读取代码内容
|
|
30
|
+
* 支持相对路径和绝对路径
|
|
31
|
+
*/
|
|
32
|
+
function readCodeFromFile(filePath) {
|
|
33
|
+
if (!filePath) return null;
|
|
34
|
+
|
|
35
|
+
let fullPath = filePath;
|
|
36
|
+
if (!fullPath.startsWith('/')) {
|
|
37
|
+
fullPath = join(PATHS.OUTPUT_DIR, filePath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!existsSync(fullPath)) {
|
|
41
|
+
console.warn('[report] 代码文件不存在:', fullPath);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return readFileSync(fullPath, 'utf-8');
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn('[report] 读取代码文件失败:', e.message);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
/**
|
|
29
54
|
* 生成 HTML 报告页面
|
|
30
55
|
*/
|
|
@@ -72,34 +97,57 @@ function generateHtmlPage(title, markdown, pythonCode, jsCode) {
|
|
|
72
97
|
|
|
73
98
|
/**
|
|
74
99
|
* 保存分析报告
|
|
100
|
+
* 支持两种模式:
|
|
101
|
+
* 1. 传入代码文件路径(推荐)- pythonCodeFile/jsCodeFile
|
|
102
|
+
* 2. 传入代码内容(兼容)- pythonCode/jsCode
|
|
75
103
|
*/
|
|
76
104
|
export const saveAnalysisReport = tool(
|
|
77
|
-
async ({ domain, title, markdown, pythonCode, jsCode }) => {
|
|
105
|
+
async ({ domain, title, markdown, pythonCode, pythonCodeFile, jsCode, jsCodeFile }) => {
|
|
78
106
|
try {
|
|
79
107
|
const domainDir = join(OUTPUT_DIR, extractDomain(domain));
|
|
80
108
|
ensureDir(domainDir);
|
|
81
109
|
|
|
82
110
|
const paths = {};
|
|
83
111
|
|
|
112
|
+
// 优先从文件读取代码
|
|
113
|
+
let finalPythonCode = pythonCode;
|
|
114
|
+
let finalJsCode = jsCode;
|
|
115
|
+
|
|
116
|
+
if (pythonCodeFile) {
|
|
117
|
+
const code = readCodeFromFile(pythonCodeFile);
|
|
118
|
+
if (code) {
|
|
119
|
+
finalPythonCode = code;
|
|
120
|
+
console.log('[report] 从文件读取 Python 代码:', pythonCodeFile);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (jsCodeFile) {
|
|
125
|
+
const code = readCodeFromFile(jsCodeFile);
|
|
126
|
+
if (code) {
|
|
127
|
+
finalJsCode = code;
|
|
128
|
+
console.log('[report] 从文件读取 JS 代码:', jsCodeFile);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
84
132
|
// 保存 Markdown
|
|
85
133
|
paths.markdown = join(domainDir, 'analysis.md');
|
|
86
134
|
writeFileSync(paths.markdown, markdown, 'utf-8');
|
|
87
135
|
|
|
88
136
|
// 保存 Python 代码
|
|
89
|
-
if (
|
|
137
|
+
if (finalPythonCode) {
|
|
90
138
|
paths.python = join(domainDir, 'decrypt.py');
|
|
91
|
-
writeFileSync(paths.python,
|
|
139
|
+
writeFileSync(paths.python, finalPythonCode, 'utf-8');
|
|
92
140
|
}
|
|
93
141
|
|
|
94
142
|
// 保存 JS 代码
|
|
95
|
-
if (
|
|
143
|
+
if (finalJsCode) {
|
|
96
144
|
paths.javascript = join(domainDir, 'decrypt.js');
|
|
97
|
-
writeFileSync(paths.javascript,
|
|
145
|
+
writeFileSync(paths.javascript, finalJsCode, 'utf-8');
|
|
98
146
|
}
|
|
99
147
|
|
|
100
148
|
// 生成 HTML
|
|
101
149
|
paths.html = join(domainDir, 'report.html');
|
|
102
|
-
const html = generateHtmlPage(title || domain, markdown,
|
|
150
|
+
const html = generateHtmlPage(title || domain, markdown, finalPythonCode, finalJsCode);
|
|
103
151
|
writeFileSync(paths.html, html, 'utf-8');
|
|
104
152
|
|
|
105
153
|
console.log('[report] 已保存:', domainDir);
|
|
@@ -110,13 +158,15 @@ export const saveAnalysisReport = tool(
|
|
|
110
158
|
},
|
|
111
159
|
{
|
|
112
160
|
name: 'save_analysis_report',
|
|
113
|
-
description:
|
|
161
|
+
description: `保存分析报告。推荐先用 artifact_save 保存代码文件,再传入文件路径。`,
|
|
114
162
|
schema: z.object({
|
|
115
|
-
domain: z.string().describe('
|
|
163
|
+
domain: z.string().describe('网站域名'),
|
|
116
164
|
title: z.string().optional().describe('报告标题'),
|
|
117
|
-
markdown: z.string().describe('Markdown
|
|
118
|
-
|
|
119
|
-
|
|
165
|
+
markdown: z.string().describe('Markdown 摘要'),
|
|
166
|
+
pythonCodeFile: z.string().optional().describe('Python 代码文件路径(推荐)'),
|
|
167
|
+
pythonCode: z.string().optional().describe('Python 代码内容(不推荐)'),
|
|
168
|
+
jsCodeFile: z.string().optional().describe('JS 代码文件路径'),
|
|
169
|
+
jsCode: z.string().optional().describe('JS 代码内容'),
|
|
120
170
|
}),
|
|
121
171
|
}
|
|
122
172
|
);
|
|
@@ -12,10 +12,12 @@ let hookManager = null;
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 标记 Hook 已注入(供外部调用)
|
|
15
|
+
* 注意:Hook 脚本由 browser/client.js 自动注入,此处仅标记状态
|
|
15
16
|
*/
|
|
16
17
|
export function markHookInjected() {
|
|
17
18
|
if (!hookManager) {
|
|
18
19
|
hookManager = new HookManager();
|
|
20
|
+
hookManager.markInjected();
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -25,13 +27,13 @@ export function markHookInjected() {
|
|
|
25
27
|
export const launchBrowser = tool(
|
|
26
28
|
async ({ headless }) => {
|
|
27
29
|
const browser = await getBrowser({ headless });
|
|
28
|
-
//
|
|
30
|
+
// Hook 已由 browser/client.js 自动注入,此处仅标记状态
|
|
29
31
|
if (!hookManager) {
|
|
30
32
|
hookManager = new HookManager();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
hookManager.markInjected();
|
|
34
|
+
hookManager.bindConsole(browser.getPage());
|
|
33
35
|
}
|
|
34
|
-
return JSON.stringify({ success: true, message: '
|
|
36
|
+
return JSON.stringify({ success: true, message: '浏览器已就绪,Hook 已注入' });
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
39
|
name: 'launch_browser',
|
package/src/agent/tools/utils.js
CHANGED
|
@@ -7,7 +7,6 @@ import { join } from 'path';
|
|
|
7
7
|
import { PATHS, ensureDir, generateFilename } from '../../config/paths.js';
|
|
8
8
|
|
|
9
9
|
// 导出路径常量(兼容旧代码)
|
|
10
|
-
export const OUTPUT_DIR = PATHS.OUTPUT_DIR;
|
|
11
10
|
export const SCREENSHOTS_DIR = PATHS.SCREENSHOTS_DIR;
|
|
12
11
|
export const REPORTS_DIR = PATHS.REPORTS_DIR;
|
|
13
12
|
export const UNPACKED_DIR = PATHS.UNPACKED_DIR;
|
|
@@ -185,11 +185,14 @@ function getJSONHooks() {
|
|
|
185
185
|
|
|
186
186
|
const OriginalParse = JSON.parse;
|
|
187
187
|
const OriginalStringify = JSON.stringify;
|
|
188
|
-
const MIN_LOG_LENGTH = 50;
|
|
188
|
+
const MIN_LOG_LENGTH = 50;
|
|
189
|
+
|
|
190
|
+
// 内部数据标记 - 跳过 DeepSpider 内部操作
|
|
191
|
+
const INTERNAL_MARKER = '"__ds__":true';
|
|
189
192
|
|
|
190
193
|
JSON.parse = deepspider.native(function(text, reviver) {
|
|
191
194
|
const textStr = String(text);
|
|
192
|
-
if (textStr.length >= MIN_LOG_LENGTH) {
|
|
195
|
+
if (textStr.length >= MIN_LOG_LENGTH && !textStr.includes(INTERNAL_MARKER)) {
|
|
193
196
|
deepspider.log('json', {
|
|
194
197
|
action: 'parse',
|
|
195
198
|
input: textStr.slice(0, 200),
|
|
@@ -201,7 +204,7 @@ function getJSONHooks() {
|
|
|
201
204
|
|
|
202
205
|
JSON.stringify = deepspider.native(function(value, replacer, space) {
|
|
203
206
|
const result = OriginalStringify.call(JSON, value, replacer, space);
|
|
204
|
-
if (result && result.length >= MIN_LOG_LENGTH) {
|
|
207
|
+
if (result && result.length >= MIN_LOG_LENGTH && !result.includes(INTERNAL_MARKER)) {
|
|
205
208
|
deepspider.log('json', {
|
|
206
209
|
action: 'stringify',
|
|
207
210
|
output: result.slice(0, 200),
|
|
@@ -297,18 +300,27 @@ function getStorageHooks() {
|
|
|
297
300
|
const deepspider = window.__deepspider__;
|
|
298
301
|
if (!deepspider) return;
|
|
299
302
|
|
|
303
|
+
// 内部使用的 key 前缀,不记录日志
|
|
304
|
+
const INTERNAL_PREFIX = 'deepspider_';
|
|
305
|
+
|
|
300
306
|
function hookStorage(storage, name) {
|
|
301
307
|
const origGet = storage.getItem.bind(storage);
|
|
302
308
|
const origSet = storage.setItem.bind(storage);
|
|
303
309
|
|
|
304
310
|
storage.getItem = deepspider.native(function(key) {
|
|
305
311
|
const value = origGet(key);
|
|
306
|
-
|
|
312
|
+
// 跳过内部 key
|
|
313
|
+
if (!key.startsWith(INTERNAL_PREFIX)) {
|
|
314
|
+
deepspider.log('storage', { action: 'get', storage: name, key: key, value: value?.slice(0, 100) });
|
|
315
|
+
}
|
|
307
316
|
return value;
|
|
308
317
|
}, origGet);
|
|
309
318
|
|
|
310
319
|
storage.setItem = deepspider.native(function(key, value) {
|
|
311
|
-
|
|
320
|
+
// 跳过内部 key
|
|
321
|
+
if (!key.startsWith(INTERNAL_PREFIX)) {
|
|
322
|
+
deepspider.log('storage', { action: 'set', storage: name, key: key, value: String(value).slice(0, 100) });
|
|
323
|
+
}
|
|
312
324
|
return origSet(key, value);
|
|
313
325
|
}, origSet);
|
|
314
326
|
}
|
|
@@ -380,41 +392,327 @@ function getWebSocketHooks() {
|
|
|
380
392
|
|
|
381
393
|
/**
|
|
382
394
|
* Webpack 模块 Hook - 检测闭包内的加密库
|
|
395
|
+
* 通过特征检测而非变量名来识别加密库
|
|
383
396
|
*/
|
|
384
397
|
function getWebpackHooks() {
|
|
385
398
|
return `
|
|
386
|
-
// === Webpack Hook ===
|
|
399
|
+
// === Webpack Module Hook ===
|
|
387
400
|
(function() {
|
|
388
401
|
const deepspider = window.__deepspider__;
|
|
389
402
|
if (!deepspider) return;
|
|
390
403
|
|
|
391
|
-
//
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
404
|
+
// 已 Hook 的对象集合
|
|
405
|
+
const hookedObjects = new WeakSet();
|
|
406
|
+
|
|
407
|
+
// === 特征检测函数 ===
|
|
408
|
+
|
|
409
|
+
// 检测 CryptoJS 特征
|
|
410
|
+
function isCryptoJS(obj) {
|
|
411
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
412
|
+
// CryptoJS 特征:有 AES/DES/MD5 等属性,且有 enc.Utf8
|
|
413
|
+
return (obj.AES && obj.AES.encrypt && obj.AES.decrypt) ||
|
|
414
|
+
(obj.enc && obj.enc.Utf8 && obj.enc.Hex) ||
|
|
415
|
+
(obj.MD5 && typeof obj.MD5 === 'function') ||
|
|
416
|
+
(obj.SHA256 && typeof obj.SHA256 === 'function');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 检测 JSEncrypt 特征
|
|
420
|
+
function isJSEncrypt(obj) {
|
|
421
|
+
if (!obj || typeof obj !== 'function') return false;
|
|
422
|
+
const proto = obj.prototype;
|
|
423
|
+
return proto && proto.encrypt && proto.decrypt && proto.setPublicKey;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 检测 SM2/SM3/SM4 国密特征
|
|
427
|
+
function isSMCrypto(obj) {
|
|
428
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
429
|
+
return (obj.doEncrypt && obj.doDecrypt) ||
|
|
430
|
+
(obj.sm2 && obj.sm3) ||
|
|
431
|
+
(typeof obj.encrypt === 'function' && obj.cipherMode !== undefined);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 检测 node-forge 特征
|
|
435
|
+
function isForge(obj) {
|
|
436
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
437
|
+
return (obj.cipher && obj.md && obj.util) ||
|
|
438
|
+
(obj.pki && obj.pki.rsa);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// === Hook 函数 ===
|
|
442
|
+
|
|
443
|
+
// Hook CryptoJS 对象
|
|
444
|
+
function hookCryptoJSObject(obj, source) {
|
|
445
|
+
if (hookedObjects.has(obj)) return;
|
|
446
|
+
hookedObjects.add(obj);
|
|
447
|
+
|
|
448
|
+
// Hook 对称加密
|
|
449
|
+
['AES', 'DES', 'TripleDES', 'RC4', 'Rabbit'].forEach(function(cipher) {
|
|
450
|
+
if (!obj[cipher]) return;
|
|
451
|
+
['encrypt', 'decrypt'].forEach(function(method) {
|
|
452
|
+
if (!obj[cipher][method]) return;
|
|
453
|
+
const original = obj[cipher][method];
|
|
454
|
+
obj[cipher][method] = deepspider.native(function(data, key, options) {
|
|
455
|
+
const entry = deepspider.log('crypto', {
|
|
456
|
+
algo: cipher + '.' + method,
|
|
457
|
+
source: source,
|
|
458
|
+
data: String(data).slice(0, 100),
|
|
459
|
+
keyLen: key ? String(key).length : 0
|
|
460
|
+
});
|
|
461
|
+
deepspider.linkCrypto(entry);
|
|
462
|
+
return original.apply(this, arguments);
|
|
463
|
+
}, original);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Hook 哈希算法
|
|
468
|
+
['MD5', 'SHA1', 'SHA256', 'SHA512', 'SHA3', 'RIPEMD160'].forEach(function(algo) {
|
|
469
|
+
if (!obj[algo] || typeof obj[algo] !== 'function') return;
|
|
470
|
+
const original = obj[algo];
|
|
471
|
+
obj[algo] = deepspider.native(function() {
|
|
472
|
+
const entry = deepspider.log('crypto', {
|
|
473
|
+
algo: algo,
|
|
474
|
+
source: source,
|
|
475
|
+
inputLen: arguments[0] ? String(arguments[0]).length : 0
|
|
476
|
+
});
|
|
477
|
+
deepspider.linkCrypto(entry);
|
|
478
|
+
return original.apply(this, arguments);
|
|
479
|
+
}, original);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Hook HMAC
|
|
483
|
+
['HmacMD5', 'HmacSHA1', 'HmacSHA256', 'HmacSHA512'].forEach(function(algo) {
|
|
484
|
+
if (!obj[algo] || typeof obj[algo] !== 'function') return;
|
|
485
|
+
const original = obj[algo];
|
|
486
|
+
obj[algo] = deepspider.native(function() {
|
|
487
|
+
const entry = deepspider.log('crypto', {
|
|
488
|
+
algo: algo,
|
|
489
|
+
source: source,
|
|
490
|
+
inputLen: arguments[0] ? String(arguments[0]).length : 0
|
|
491
|
+
});
|
|
492
|
+
deepspider.linkCrypto(entry);
|
|
493
|
+
return original.apply(this, arguments);
|
|
494
|
+
}, original);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
console.log('[DeepSpider] CryptoJS Hook 已启用 (来源: ' + source + ')');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Hook JSEncrypt 构造函数
|
|
501
|
+
function hookJSEncryptObject(JSEncrypt, source) {
|
|
502
|
+
if (hookedObjects.has(JSEncrypt)) return;
|
|
503
|
+
hookedObjects.add(JSEncrypt);
|
|
504
|
+
|
|
505
|
+
const proto = JSEncrypt.prototype;
|
|
506
|
+
if (proto.encrypt) {
|
|
507
|
+
const origEnc = proto.encrypt;
|
|
508
|
+
proto.encrypt = deepspider.native(function(data) {
|
|
509
|
+
const entry = deepspider.log('crypto', {
|
|
510
|
+
algo: 'RSA.encrypt',
|
|
511
|
+
source: source,
|
|
512
|
+
data: String(data).slice(0, 100)
|
|
513
|
+
});
|
|
514
|
+
deepspider.linkCrypto(entry);
|
|
515
|
+
return origEnc.apply(this, arguments);
|
|
516
|
+
}, origEnc);
|
|
517
|
+
}
|
|
518
|
+
if (proto.decrypt) {
|
|
519
|
+
const origDec = proto.decrypt;
|
|
520
|
+
proto.decrypt = deepspider.native(function(data) {
|
|
521
|
+
const entry = deepspider.log('crypto', {
|
|
522
|
+
algo: 'RSA.decrypt',
|
|
523
|
+
source: source
|
|
524
|
+
});
|
|
525
|
+
deepspider.linkCrypto(entry);
|
|
526
|
+
return origDec.apply(this, arguments);
|
|
527
|
+
}, origDec);
|
|
528
|
+
}
|
|
529
|
+
console.log('[DeepSpider] JSEncrypt Hook 已启用 (来源: ' + source + ')');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Hook SM 国密对象
|
|
533
|
+
function hookSMCryptoObject(obj, source) {
|
|
534
|
+
if (hookedObjects.has(obj)) return;
|
|
535
|
+
hookedObjects.add(obj);
|
|
536
|
+
|
|
537
|
+
if (obj.doEncrypt) {
|
|
538
|
+
const origEnc = obj.doEncrypt;
|
|
539
|
+
obj.doEncrypt = deepspider.native(function(msg, pubKey) {
|
|
540
|
+
const entry = deepspider.log('crypto', {
|
|
541
|
+
algo: 'SM2.encrypt',
|
|
542
|
+
source: source,
|
|
543
|
+
msg: String(msg).slice(0, 100)
|
|
544
|
+
});
|
|
545
|
+
deepspider.linkCrypto(entry);
|
|
546
|
+
return origEnc.apply(this, arguments);
|
|
547
|
+
}, origEnc);
|
|
548
|
+
}
|
|
549
|
+
console.log('[DeepSpider] SM Crypto Hook 已启用 (来源: ' + source + ')');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// === 扫描并 Hook 模块导出 ===
|
|
553
|
+
function scanAndHook(exports, source) {
|
|
554
|
+
if (!exports || typeof exports !== 'object') return;
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
// 直接检测导出对象
|
|
558
|
+
if (isCryptoJS(exports)) {
|
|
559
|
+
hookCryptoJSObject(exports, source);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (isJSEncrypt(exports)) {
|
|
563
|
+
hookJSEncryptObject(exports, source);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (isSMCrypto(exports)) {
|
|
567
|
+
hookSMCryptoObject(exports, source);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 检测 default 导出
|
|
572
|
+
if (exports.default) {
|
|
573
|
+
if (isCryptoJS(exports.default)) {
|
|
574
|
+
hookCryptoJSObject(exports.default, source + '.default');
|
|
575
|
+
} else if (isJSEncrypt(exports.default)) {
|
|
576
|
+
hookJSEncryptObject(exports.default, source + '.default');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
400
579
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
580
|
+
// 遍历导出的属性(限制深度避免性能问题)
|
|
581
|
+
const keys = Object.keys(exports);
|
|
582
|
+
for (let i = 0; i < Math.min(keys.length, 20); i++) {
|
|
583
|
+
const key = keys[i];
|
|
584
|
+
const val = exports[key];
|
|
585
|
+
if (val && typeof val === 'object' && !hookedObjects.has(val)) {
|
|
586
|
+
if (isCryptoJS(val)) {
|
|
587
|
+
hookCryptoJSObject(val, source + '.' + key);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (val && typeof val === 'function') {
|
|
591
|
+
if (isJSEncrypt(val)) {
|
|
592
|
+
hookJSEncryptObject(val, source + '.' + key);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} catch (e) {
|
|
597
|
+
// 忽略访问错误
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// === Hook Webpack 模块系统 ===
|
|
602
|
+
|
|
603
|
+
// Hook Webpack 4 webpackJsonp
|
|
604
|
+
function hookWebpackJsonp() {
|
|
605
|
+
const jsonp = window.webpackJsonp;
|
|
606
|
+
if (!jsonp || jsonp.__deepspider_hooked__) return;
|
|
607
|
+
|
|
608
|
+
const origPush = jsonp.push;
|
|
609
|
+
jsonp.push = function(chunk) {
|
|
610
|
+
const result = origPush.apply(this, arguments);
|
|
611
|
+
|
|
612
|
+
// chunk[1] 是模块对象 { moduleId: function(module, exports, require) {} }
|
|
613
|
+
if (chunk && chunk[1]) {
|
|
614
|
+
const modules = chunk[1];
|
|
615
|
+
Object.keys(modules).forEach(function(moduleId) {
|
|
616
|
+
// 延迟扫描,等模块执行完
|
|
617
|
+
setTimeout(function() {
|
|
618
|
+
try {
|
|
619
|
+
// 尝试获取模块导出
|
|
620
|
+
if (window.__webpack_require__ && window.__webpack_require__.c) {
|
|
621
|
+
const mod = window.__webpack_require__.c[moduleId];
|
|
622
|
+
if (mod && mod.exports) {
|
|
623
|
+
scanAndHook(mod.exports, 'webpack:' + moduleId);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
} catch (e) {}
|
|
627
|
+
}, 10);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return result;
|
|
631
|
+
};
|
|
632
|
+
jsonp.__deepspider_hooked__ = true;
|
|
633
|
+
console.log('[DeepSpider] Webpack4 jsonp Hook 已启用');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Hook Webpack 5 webpackChunk
|
|
637
|
+
function hookWebpackChunk() {
|
|
638
|
+
// Webpack 5 使用 self["webpackChunk" + name]
|
|
639
|
+
const chunkNames = Object.keys(self).filter(function(k) {
|
|
640
|
+
return k.startsWith('webpackChunk');
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
chunkNames.forEach(function(name) {
|
|
644
|
+
const chunk = self[name];
|
|
645
|
+
if (!chunk || chunk.__deepspider_hooked__) return;
|
|
646
|
+
|
|
647
|
+
const origPush = chunk.push.bind(chunk);
|
|
648
|
+
chunk.push = function(data) {
|
|
649
|
+
const result = origPush(data);
|
|
650
|
+
|
|
651
|
+
// data[1] 是模块对象
|
|
652
|
+
if (data && data[1]) {
|
|
653
|
+
Object.keys(data[1]).forEach(function(moduleId) {
|
|
654
|
+
setTimeout(function() {
|
|
655
|
+
try {
|
|
656
|
+
// Webpack 5 的 require cache
|
|
657
|
+
const cache = window.__webpack_require__ && window.__webpack_require__.c;
|
|
658
|
+
if (cache && cache[moduleId] && cache[moduleId].exports) {
|
|
659
|
+
scanAndHook(cache[moduleId].exports, 'webpack5:' + moduleId);
|
|
660
|
+
}
|
|
661
|
+
} catch (e) {}
|
|
662
|
+
}, 10);
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
408
666
|
};
|
|
409
|
-
|
|
667
|
+
chunk.__deepspider_hooked__ = true;
|
|
668
|
+
console.log('[DeepSpider] Webpack5 chunk Hook 已启用: ' + name);
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Hook __webpack_require__ 直接拦截模块加载
|
|
673
|
+
function hookWebpackRequire() {
|
|
674
|
+
if (!window.__webpack_require__ || window.__webpack_require__.__deepspider_hooked__) return;
|
|
675
|
+
|
|
676
|
+
const origRequire = window.__webpack_require__;
|
|
677
|
+
window.__webpack_require__ = function(moduleId) {
|
|
678
|
+
const result = origRequire.apply(this, arguments);
|
|
679
|
+
// 扫描返回的模块
|
|
680
|
+
setTimeout(function() {
|
|
681
|
+
scanAndHook(result, 'require:' + moduleId);
|
|
682
|
+
}, 0);
|
|
683
|
+
return result;
|
|
684
|
+
};
|
|
685
|
+
// 复制原有属性
|
|
686
|
+
Object.keys(origRequire).forEach(function(key) {
|
|
687
|
+
window.__webpack_require__[key] = origRequire[key];
|
|
688
|
+
});
|
|
689
|
+
window.__webpack_require__.__deepspider_hooked__ = true;
|
|
690
|
+
console.log('[DeepSpider] __webpack_require__ Hook 已启用');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// 定期检查并 Hook
|
|
694
|
+
function checkAndHook() {
|
|
695
|
+
hookWebpackJsonp();
|
|
696
|
+
hookWebpackChunk();
|
|
697
|
+
hookWebpackRequire();
|
|
698
|
+
|
|
699
|
+
// 扫描已加载的模块缓存
|
|
700
|
+
if (window.__webpack_require__ && window.__webpack_require__.c) {
|
|
701
|
+
const cache = window.__webpack_require__.c;
|
|
702
|
+
Object.keys(cache).forEach(function(moduleId) {
|
|
703
|
+
if (cache[moduleId] && cache[moduleId].exports) {
|
|
704
|
+
scanAndHook(cache[moduleId].exports, 'cache:' + moduleId);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
410
707
|
}
|
|
411
708
|
}
|
|
412
709
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
710
|
+
// 启动检查
|
|
711
|
+
checkAndHook();
|
|
712
|
+
const interval = setInterval(checkAndHook, 200);
|
|
713
|
+
setTimeout(function() { clearInterval(interval); }, 10000);
|
|
416
714
|
|
|
417
|
-
console.log('[DeepSpider] Webpack Hook 已启用');
|
|
715
|
+
console.log('[DeepSpider] Webpack Module Hook 已启用');
|
|
418
716
|
})();
|
|
419
717
|
`;
|
|
420
718
|
}
|
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepSpider - Hook 管理器
|
|
3
|
+
* 注意:Hook 脚本已由 browser/client.js 通过 defaultHooks.js 自动注入
|
|
4
|
+
* 此类仅用于日志管理和状态跟踪
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
import { cryptoHook } from './crypto.js';
|
|
6
|
-
import { networkHook } from './network.js';
|
|
7
|
-
import { nativeProtect } from './native.js';
|
|
8
|
-
|
|
9
7
|
export class HookManager {
|
|
10
8
|
constructor() {
|
|
11
9
|
this.logs = [];
|
|
12
10
|
this.onLog = null;
|
|
11
|
+
this.injected = false;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* 标记 Hook 已注入(由 client.js 调用)
|
|
17
16
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
nativeProtect,
|
|
21
|
-
cryptoHook,
|
|
22
|
-
networkHook,
|
|
23
|
-
].join('\n\n');
|
|
17
|
+
markInjected() {
|
|
18
|
+
this.injected = true;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
|
-
*
|
|
22
|
+
* 检查是否已注入
|
|
28
23
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// 在新文档加载前注入
|
|
33
|
-
await page.addInitScript(script);
|
|
24
|
+
isInjected() {
|
|
25
|
+
return this.injected;
|
|
26
|
+
}
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
/**
|
|
29
|
+
* 绑定页面 console 监听(用于收集 Hook 日志)
|
|
30
|
+
*/
|
|
31
|
+
bindConsole(page) {
|
|
36
32
|
page.on('console', (msg) => {
|
|
37
33
|
const text = msg.text();
|
|
38
34
|
if (text.includes('[DeepSpider:')) {
|