deepspider 0.2.0 → 0.2.7
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/.github/workflows/publish.yml +63 -0
- package/.trellis/spec/backend/ci-cd-guidelines.md +73 -0
- package/.trellis/spec/backend/index.md +1 -0
- package/.trellis/spec/backend/quality-guidelines.md +36 -0
- package/.trellis/workspace/pony/index.md +3 -2
- package/.trellis/workspace/pony/journal-1.md +64 -0
- package/package.json +1 -1
- package/src/agent/prompts/system.js +28 -7
- package/src/agent/run.js +19 -0
- package/src/agent/skills/static-analysis/SKILL.md +120 -0
- package/src/agent/tools/report.js +64 -14
- package/src/agent/tools/runtime.js +6 -4
- package/src/browser/defaultHooks.js +308 -22
- package/src/browser/hooks/index.js +14 -18
- package/src/browser/ui/analysisPanel.js +87 -131
- 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
|
@@ -380,41 +380,327 @@ function getWebSocketHooks() {
|
|
|
380
380
|
|
|
381
381
|
/**
|
|
382
382
|
* Webpack 模块 Hook - 检测闭包内的加密库
|
|
383
|
+
* 通过特征检测而非变量名来识别加密库
|
|
383
384
|
*/
|
|
384
385
|
function getWebpackHooks() {
|
|
385
386
|
return `
|
|
386
|
-
// === Webpack Hook ===
|
|
387
|
+
// === Webpack Module Hook ===
|
|
387
388
|
(function() {
|
|
388
389
|
const deepspider = window.__deepspider__;
|
|
389
390
|
if (!deepspider) return;
|
|
390
391
|
|
|
391
|
-
//
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
392
|
+
// 已 Hook 的对象集合
|
|
393
|
+
const hookedObjects = new WeakSet();
|
|
394
|
+
|
|
395
|
+
// === 特征检测函数 ===
|
|
396
|
+
|
|
397
|
+
// 检测 CryptoJS 特征
|
|
398
|
+
function isCryptoJS(obj) {
|
|
399
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
400
|
+
// CryptoJS 特征:有 AES/DES/MD5 等属性,且有 enc.Utf8
|
|
401
|
+
return (obj.AES && obj.AES.encrypt && obj.AES.decrypt) ||
|
|
402
|
+
(obj.enc && obj.enc.Utf8 && obj.enc.Hex) ||
|
|
403
|
+
(obj.MD5 && typeof obj.MD5 === 'function') ||
|
|
404
|
+
(obj.SHA256 && typeof obj.SHA256 === 'function');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 检测 JSEncrypt 特征
|
|
408
|
+
function isJSEncrypt(obj) {
|
|
409
|
+
if (!obj || typeof obj !== 'function') return false;
|
|
410
|
+
const proto = obj.prototype;
|
|
411
|
+
return proto && proto.encrypt && proto.decrypt && proto.setPublicKey;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 检测 SM2/SM3/SM4 国密特征
|
|
415
|
+
function isSMCrypto(obj) {
|
|
416
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
417
|
+
return (obj.doEncrypt && obj.doDecrypt) ||
|
|
418
|
+
(obj.sm2 && obj.sm3) ||
|
|
419
|
+
(typeof obj.encrypt === 'function' && obj.cipherMode !== undefined);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 检测 node-forge 特征
|
|
423
|
+
function isForge(obj) {
|
|
424
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
425
|
+
return (obj.cipher && obj.md && obj.util) ||
|
|
426
|
+
(obj.pki && obj.pki.rsa);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// === Hook 函数 ===
|
|
430
|
+
|
|
431
|
+
// Hook CryptoJS 对象
|
|
432
|
+
function hookCryptoJSObject(obj, source) {
|
|
433
|
+
if (hookedObjects.has(obj)) return;
|
|
434
|
+
hookedObjects.add(obj);
|
|
435
|
+
|
|
436
|
+
// Hook 对称加密
|
|
437
|
+
['AES', 'DES', 'TripleDES', 'RC4', 'Rabbit'].forEach(function(cipher) {
|
|
438
|
+
if (!obj[cipher]) return;
|
|
439
|
+
['encrypt', 'decrypt'].forEach(function(method) {
|
|
440
|
+
if (!obj[cipher][method]) return;
|
|
441
|
+
const original = obj[cipher][method];
|
|
442
|
+
obj[cipher][method] = deepspider.native(function(data, key, options) {
|
|
443
|
+
const entry = deepspider.log('crypto', {
|
|
444
|
+
algo: cipher + '.' + method,
|
|
445
|
+
source: source,
|
|
446
|
+
data: String(data).slice(0, 100),
|
|
447
|
+
keyLen: key ? String(key).length : 0
|
|
448
|
+
});
|
|
449
|
+
deepspider.linkCrypto(entry);
|
|
450
|
+
return original.apply(this, arguments);
|
|
451
|
+
}, original);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Hook 哈希算法
|
|
456
|
+
['MD5', 'SHA1', 'SHA256', 'SHA512', 'SHA3', 'RIPEMD160'].forEach(function(algo) {
|
|
457
|
+
if (!obj[algo] || typeof obj[algo] !== 'function') return;
|
|
458
|
+
const original = obj[algo];
|
|
459
|
+
obj[algo] = deepspider.native(function() {
|
|
460
|
+
const entry = deepspider.log('crypto', {
|
|
461
|
+
algo: algo,
|
|
462
|
+
source: source,
|
|
463
|
+
inputLen: arguments[0] ? String(arguments[0]).length : 0
|
|
464
|
+
});
|
|
465
|
+
deepspider.linkCrypto(entry);
|
|
466
|
+
return original.apply(this, arguments);
|
|
467
|
+
}, original);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Hook HMAC
|
|
471
|
+
['HmacMD5', 'HmacSHA1', 'HmacSHA256', 'HmacSHA512'].forEach(function(algo) {
|
|
472
|
+
if (!obj[algo] || typeof obj[algo] !== 'function') return;
|
|
473
|
+
const original = obj[algo];
|
|
474
|
+
obj[algo] = deepspider.native(function() {
|
|
475
|
+
const entry = deepspider.log('crypto', {
|
|
476
|
+
algo: algo,
|
|
477
|
+
source: source,
|
|
478
|
+
inputLen: arguments[0] ? String(arguments[0]).length : 0
|
|
479
|
+
});
|
|
480
|
+
deepspider.linkCrypto(entry);
|
|
481
|
+
return original.apply(this, arguments);
|
|
482
|
+
}, original);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
console.log('[DeepSpider] CryptoJS Hook 已启用 (来源: ' + source + ')');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Hook JSEncrypt 构造函数
|
|
489
|
+
function hookJSEncryptObject(JSEncrypt, source) {
|
|
490
|
+
if (hookedObjects.has(JSEncrypt)) return;
|
|
491
|
+
hookedObjects.add(JSEncrypt);
|
|
492
|
+
|
|
493
|
+
const proto = JSEncrypt.prototype;
|
|
494
|
+
if (proto.encrypt) {
|
|
495
|
+
const origEnc = proto.encrypt;
|
|
496
|
+
proto.encrypt = deepspider.native(function(data) {
|
|
497
|
+
const entry = deepspider.log('crypto', {
|
|
498
|
+
algo: 'RSA.encrypt',
|
|
499
|
+
source: source,
|
|
500
|
+
data: String(data).slice(0, 100)
|
|
501
|
+
});
|
|
502
|
+
deepspider.linkCrypto(entry);
|
|
503
|
+
return origEnc.apply(this, arguments);
|
|
504
|
+
}, origEnc);
|
|
505
|
+
}
|
|
506
|
+
if (proto.decrypt) {
|
|
507
|
+
const origDec = proto.decrypt;
|
|
508
|
+
proto.decrypt = deepspider.native(function(data) {
|
|
509
|
+
const entry = deepspider.log('crypto', {
|
|
510
|
+
algo: 'RSA.decrypt',
|
|
511
|
+
source: source
|
|
512
|
+
});
|
|
513
|
+
deepspider.linkCrypto(entry);
|
|
514
|
+
return origDec.apply(this, arguments);
|
|
515
|
+
}, origDec);
|
|
516
|
+
}
|
|
517
|
+
console.log('[DeepSpider] JSEncrypt Hook 已启用 (来源: ' + source + ')');
|
|
518
|
+
}
|
|
400
519
|
|
|
401
|
-
// Hook
|
|
402
|
-
function
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
520
|
+
// Hook SM 国密对象
|
|
521
|
+
function hookSMCryptoObject(obj, source) {
|
|
522
|
+
if (hookedObjects.has(obj)) return;
|
|
523
|
+
hookedObjects.add(obj);
|
|
524
|
+
|
|
525
|
+
if (obj.doEncrypt) {
|
|
526
|
+
const origEnc = obj.doEncrypt;
|
|
527
|
+
obj.doEncrypt = deepspider.native(function(msg, pubKey) {
|
|
528
|
+
const entry = deepspider.log('crypto', {
|
|
529
|
+
algo: 'SM2.encrypt',
|
|
530
|
+
source: source,
|
|
531
|
+
msg: String(msg).slice(0, 100)
|
|
532
|
+
});
|
|
533
|
+
deepspider.linkCrypto(entry);
|
|
534
|
+
return origEnc.apply(this, arguments);
|
|
535
|
+
}, origEnc);
|
|
536
|
+
}
|
|
537
|
+
console.log('[DeepSpider] SM Crypto Hook 已启用 (来源: ' + source + ')');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// === 扫描并 Hook 模块导出 ===
|
|
541
|
+
function scanAndHook(exports, source) {
|
|
542
|
+
if (!exports || typeof exports !== 'object') return;
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
// 直接检测导出对象
|
|
546
|
+
if (isCryptoJS(exports)) {
|
|
547
|
+
hookCryptoJSObject(exports, source);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (isJSEncrypt(exports)) {
|
|
551
|
+
hookJSEncryptObject(exports, source);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (isSMCrypto(exports)) {
|
|
555
|
+
hookSMCryptoObject(exports, source);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// 检测 default 导出
|
|
560
|
+
if (exports.default) {
|
|
561
|
+
if (isCryptoJS(exports.default)) {
|
|
562
|
+
hookCryptoJSObject(exports.default, source + '.default');
|
|
563
|
+
} else if (isJSEncrypt(exports.default)) {
|
|
564
|
+
hookJSEncryptObject(exports.default, source + '.default');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 遍历导出的属性(限制深度避免性能问题)
|
|
569
|
+
const keys = Object.keys(exports);
|
|
570
|
+
for (let i = 0; i < Math.min(keys.length, 20); i++) {
|
|
571
|
+
const key = keys[i];
|
|
572
|
+
const val = exports[key];
|
|
573
|
+
if (val && typeof val === 'object' && !hookedObjects.has(val)) {
|
|
574
|
+
if (isCryptoJS(val)) {
|
|
575
|
+
hookCryptoJSObject(val, source + '.' + key);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (val && typeof val === 'function') {
|
|
579
|
+
if (isJSEncrypt(val)) {
|
|
580
|
+
hookJSEncryptObject(val, source + '.' + key);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
} catch (e) {
|
|
585
|
+
// 忽略访问错误
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// === Hook Webpack 模块系统 ===
|
|
590
|
+
|
|
591
|
+
// Hook Webpack 4 webpackJsonp
|
|
592
|
+
function hookWebpackJsonp() {
|
|
593
|
+
const jsonp = window.webpackJsonp;
|
|
594
|
+
if (!jsonp || jsonp.__deepspider_hooked__) return;
|
|
595
|
+
|
|
596
|
+
const origPush = jsonp.push;
|
|
597
|
+
jsonp.push = function(chunk) {
|
|
598
|
+
const result = origPush.apply(this, arguments);
|
|
599
|
+
|
|
600
|
+
// chunk[1] 是模块对象 { moduleId: function(module, exports, require) {} }
|
|
601
|
+
if (chunk && chunk[1]) {
|
|
602
|
+
const modules = chunk[1];
|
|
603
|
+
Object.keys(modules).forEach(function(moduleId) {
|
|
604
|
+
// 延迟扫描,等模块执行完
|
|
605
|
+
setTimeout(function() {
|
|
606
|
+
try {
|
|
607
|
+
// 尝试获取模块导出
|
|
608
|
+
if (window.__webpack_require__ && window.__webpack_require__.c) {
|
|
609
|
+
const mod = window.__webpack_require__.c[moduleId];
|
|
610
|
+
if (mod && mod.exports) {
|
|
611
|
+
scanAndHook(mod.exports, 'webpack:' + moduleId);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
} catch (e) {}
|
|
615
|
+
}, 10);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
};
|
|
620
|
+
jsonp.__deepspider_hooked__ = true;
|
|
621
|
+
console.log('[DeepSpider] Webpack4 jsonp Hook 已启用');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Hook Webpack 5 webpackChunk
|
|
625
|
+
function hookWebpackChunk() {
|
|
626
|
+
// Webpack 5 使用 self["webpackChunk" + name]
|
|
627
|
+
const chunkNames = Object.keys(self).filter(function(k) {
|
|
628
|
+
return k.startsWith('webpackChunk');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
chunkNames.forEach(function(name) {
|
|
632
|
+
const chunk = self[name];
|
|
633
|
+
if (!chunk || chunk.__deepspider_hooked__) return;
|
|
634
|
+
|
|
635
|
+
const origPush = chunk.push.bind(chunk);
|
|
636
|
+
chunk.push = function(data) {
|
|
637
|
+
const result = origPush(data);
|
|
638
|
+
|
|
639
|
+
// data[1] 是模块对象
|
|
640
|
+
if (data && data[1]) {
|
|
641
|
+
Object.keys(data[1]).forEach(function(moduleId) {
|
|
642
|
+
setTimeout(function() {
|
|
643
|
+
try {
|
|
644
|
+
// Webpack 5 的 require cache
|
|
645
|
+
const cache = window.__webpack_require__ && window.__webpack_require__.c;
|
|
646
|
+
if (cache && cache[moduleId] && cache[moduleId].exports) {
|
|
647
|
+
scanAndHook(cache[moduleId].exports, 'webpack5:' + moduleId);
|
|
648
|
+
}
|
|
649
|
+
} catch (e) {}
|
|
650
|
+
}, 10);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return result;
|
|
408
654
|
};
|
|
409
|
-
|
|
655
|
+
chunk.__deepspider_hooked__ = true;
|
|
656
|
+
console.log('[DeepSpider] Webpack5 chunk Hook 已启用: ' + name);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Hook __webpack_require__ 直接拦截模块加载
|
|
661
|
+
function hookWebpackRequire() {
|
|
662
|
+
if (!window.__webpack_require__ || window.__webpack_require__.__deepspider_hooked__) return;
|
|
663
|
+
|
|
664
|
+
const origRequire = window.__webpack_require__;
|
|
665
|
+
window.__webpack_require__ = function(moduleId) {
|
|
666
|
+
const result = origRequire.apply(this, arguments);
|
|
667
|
+
// 扫描返回的模块
|
|
668
|
+
setTimeout(function() {
|
|
669
|
+
scanAndHook(result, 'require:' + moduleId);
|
|
670
|
+
}, 0);
|
|
671
|
+
return result;
|
|
672
|
+
};
|
|
673
|
+
// 复制原有属性
|
|
674
|
+
Object.keys(origRequire).forEach(function(key) {
|
|
675
|
+
window.__webpack_require__[key] = origRequire[key];
|
|
676
|
+
});
|
|
677
|
+
window.__webpack_require__.__deepspider_hooked__ = true;
|
|
678
|
+
console.log('[DeepSpider] __webpack_require__ Hook 已启用');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// 定期检查并 Hook
|
|
682
|
+
function checkAndHook() {
|
|
683
|
+
hookWebpackJsonp();
|
|
684
|
+
hookWebpackChunk();
|
|
685
|
+
hookWebpackRequire();
|
|
686
|
+
|
|
687
|
+
// 扫描已加载的模块缓存
|
|
688
|
+
if (window.__webpack_require__ && window.__webpack_require__.c) {
|
|
689
|
+
const cache = window.__webpack_require__.c;
|
|
690
|
+
Object.keys(cache).forEach(function(moduleId) {
|
|
691
|
+
if (cache[moduleId] && cache[moduleId].exports) {
|
|
692
|
+
scanAndHook(cache[moduleId].exports, 'cache:' + moduleId);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
410
695
|
}
|
|
411
696
|
}
|
|
412
697
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
698
|
+
// 启动检查
|
|
699
|
+
checkAndHook();
|
|
700
|
+
const interval = setInterval(checkAndHook, 200);
|
|
701
|
+
setTimeout(function() { clearInterval(interval); }, 10000);
|
|
416
702
|
|
|
417
|
-
console.log('[DeepSpider] Webpack Hook 已启用');
|
|
703
|
+
console.log('[DeepSpider] Webpack Module Hook 已启用');
|
|
418
704
|
})();
|
|
419
705
|
`;
|
|
420
706
|
}
|
|
@@ -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:')) {
|