refactor: 将文件从子目录提升到仓库根目录
所有文件从 hello_js_reverse_skill/ 子目录移至仓库根目录, 使项目结构更清晰,便于作为 Skill 直接引用。 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
# 反调试对抗手册
|
||||
|
||||
## 概述
|
||||
|
||||
现代网站常采用多种反调试技术阻止逆向分析。本手册涵盖常见反调试手段及其绕过策略。
|
||||
|
||||
## 1. debugger 语句
|
||||
|
||||
### 表现
|
||||
|
||||
```javascript
|
||||
// 无限 debugger 循环
|
||||
setInterval(function() { debugger; }, 100);
|
||||
|
||||
// 条件 debugger
|
||||
(function check() {
|
||||
debugger;
|
||||
check();
|
||||
})();
|
||||
|
||||
// 隐藏 debugger(通过 constructor)
|
||||
(function() {
|
||||
var a = new Function("debugger");
|
||||
setInterval(a, 1000);
|
||||
})();
|
||||
```
|
||||
|
||||
### 绕过方案
|
||||
|
||||
**方案 A:MCP 注入绕过**
|
||||
```
|
||||
[js-reverse] inject_before_load(script=反debugger脚本)
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 覆写 Function 构造器,过滤 debugger
|
||||
const _Function = Function;
|
||||
Function = function() {
|
||||
const body = arguments[arguments.length - 1];
|
||||
if (typeof body === 'string' && body.indexOf('debugger') !== -1) {
|
||||
arguments[arguments.length - 1] = body.replace(/debugger/g, '');
|
||||
}
|
||||
return _Function.apply(this, arguments);
|
||||
};
|
||||
Function.prototype = _Function.prototype;
|
||||
|
||||
// 覆写定时器,过滤 debugger
|
||||
const _setInterval = setInterval;
|
||||
setInterval = function(fn, ms) {
|
||||
if (typeof fn === 'function' && fn.toString().indexOf('debugger') !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (typeof fn === 'string' && fn.indexOf('debugger') !== -1) {
|
||||
return -1;
|
||||
}
|
||||
return _setInterval.apply(this, arguments);
|
||||
};
|
||||
```
|
||||
|
||||
**方案 B:断点条件设置 false**
|
||||
```
|
||||
[js-reverse] set_breakpoint_on_text(text="debugger", condition="false")
|
||||
```
|
||||
|
||||
## 2. 开发者工具检测
|
||||
|
||||
### 2.1 窗口尺寸检测
|
||||
|
||||
```javascript
|
||||
setInterval(function() {
|
||||
if (window.outerHeight - window.innerHeight > 200 ||
|
||||
window.outerWidth - window.innerWidth > 200) {
|
||||
// DevTools 已打开
|
||||
document.body.innerHTML = '';
|
||||
}
|
||||
}, 500);
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// 注入后锁定尺寸
|
||||
Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight });
|
||||
Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth });
|
||||
```
|
||||
|
||||
### 2.2 console.log 计时检测
|
||||
|
||||
```javascript
|
||||
setInterval(function() {
|
||||
var start = Date.now();
|
||||
console.log('check');
|
||||
console.clear();
|
||||
if (Date.now() - start > 10) {
|
||||
// DevTools 已打开(console.log 在 DevTools 打开时较慢)
|
||||
window.location = 'about:blank';
|
||||
}
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// 覆写 console 方法为空操作
|
||||
const noop = function() {};
|
||||
console.log = noop;
|
||||
console.clear = noop;
|
||||
```
|
||||
|
||||
### 2.3 toString 检测
|
||||
|
||||
```javascript
|
||||
var devtools = /./;
|
||||
devtools.toString = function() {
|
||||
// 只有在 DevTools 打开时,console.log 才会调用对象的 toString
|
||||
isDevToolsOpen = true;
|
||||
return '';
|
||||
};
|
||||
console.log(devtools);
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// 覆写 console.log,阻止 toString 调用检测
|
||||
const _log = console.log;
|
||||
console.log = function() {
|
||||
// 不调用 toString
|
||||
};
|
||||
```
|
||||
|
||||
## 3. 代码完整性检测
|
||||
|
||||
### 3.1 函数 toString 检测
|
||||
|
||||
```javascript
|
||||
function critical() {
|
||||
// 重要逻辑
|
||||
}
|
||||
if (critical.toString().indexOf('\n') !== -1) {
|
||||
// 代码被格式化,触发反调试
|
||||
while(true) {}
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// 保存原始 toString 结果
|
||||
const origToString = critical.toString();
|
||||
Object.defineProperty(critical, 'toString', {
|
||||
value: function() { return origToString; }
|
||||
});
|
||||
```
|
||||
|
||||
### 3.2 源码长度检测
|
||||
|
||||
```javascript
|
||||
if (someFunction.toString().length !== 1234) {
|
||||
// 代码被修改
|
||||
throw new Error('Integrity check failed');
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:不修改原始函数,使用 `inject_before_load` 在执行之前收集所需数据。
|
||||
|
||||
## 4. 时间差检测
|
||||
|
||||
```javascript
|
||||
var t1 = Date.now();
|
||||
// ... 执行代码 ...
|
||||
var t2 = Date.now();
|
||||
if (t2 - t1 > 100) {
|
||||
// 可能在断点处暂停了
|
||||
window.location = 'about:blank';
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// Hook Date.now 返回连续值
|
||||
const _now = Date.now;
|
||||
let fakeTime = _now();
|
||||
Date.now = function() {
|
||||
fakeTime += 1;
|
||||
return fakeTime;
|
||||
};
|
||||
|
||||
// 或使用 performance.now
|
||||
const _perfNow = performance.now;
|
||||
performance.now = function() {
|
||||
return _perfNow.call(performance);
|
||||
};
|
||||
```
|
||||
|
||||
## 5. 环境检测
|
||||
|
||||
### 5.1 Node.js 环境检测
|
||||
|
||||
```javascript
|
||||
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
|
||||
throw new Error('Node.js detected');
|
||||
}
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
throw new Error('CommonJS detected');
|
||||
}
|
||||
if (typeof global !== 'undefined') {
|
||||
throw new Error('Non-browser detected');
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:在 vm 沙箱中删除这些全局变量。
|
||||
|
||||
### 5.2 浏览器指纹检测
|
||||
|
||||
```javascript
|
||||
if (!window.chrome || !window.chrome.runtime) {
|
||||
throw new Error('Not Chrome');
|
||||
}
|
||||
if (navigator.webdriver) {
|
||||
throw new Error('WebDriver detected');
|
||||
}
|
||||
if (navigator.plugins.length === 0) {
|
||||
throw new Error('Headless browser detected');
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:补全环境变量(参考 `environment-patch.md`)。
|
||||
|
||||
### 5.3 Selenium / Puppeteer 检测
|
||||
|
||||
```javascript
|
||||
const checks = [
|
||||
'webdriver' in navigator,
|
||||
'_Selenium_IDE_Recorder' in window,
|
||||
'callSelenium' in document,
|
||||
'__webdriver_script_fn' in document,
|
||||
'$cdc_asdjflasutopfhvcZLmcfl_' in document,
|
||||
'_phantom' in window,
|
||||
'callPhantom' in window
|
||||
];
|
||||
if (checks.some(Boolean)) {
|
||||
throw new Error('Automation detected');
|
||||
}
|
||||
```
|
||||
|
||||
**绕过**:
|
||||
```javascript
|
||||
// 清除自动化指纹
|
||||
delete navigator.webdriver;
|
||||
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
||||
delete window._Selenium_IDE_Recorder;
|
||||
// ... 逐个清除
|
||||
```
|
||||
|
||||
## 6. Object.defineProperty 篡改
|
||||
|
||||
```javascript
|
||||
// 改变属性定义行为
|
||||
const _defineProperty = Object.defineProperty;
|
||||
Object.defineProperty = function(obj, prop, descriptor) {
|
||||
if (prop === 'cookie') {
|
||||
return ""; // 阻止 Cookie Hook
|
||||
}
|
||||
return _defineProperty.apply(this, arguments);
|
||||
};
|
||||
```
|
||||
|
||||
**绕过**:在注入脚本中保存原始引用。
|
||||
```javascript
|
||||
// 在所有代码之前保存原始方法
|
||||
const originalDefineProperty = Object.defineProperty;
|
||||
// 后续使用 originalDefineProperty 而非 Object.defineProperty
|
||||
```
|
||||
|
||||
## 7. Proxy 检测
|
||||
|
||||
```javascript
|
||||
// 某些代码会检测对象是否被 Proxy 包装
|
||||
try {
|
||||
new Proxy({}, {});
|
||||
} catch(e) {
|
||||
// 环境异常
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:使用 Proxy 做 Hook 时,确保 Proxy 行为与原始对象一致。
|
||||
|
||||
## 通用反反调试注入脚本
|
||||
|
||||
以下脚本适合通过 `inject_before_load` 在页面加载前注入:
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 保存原始方法引用
|
||||
const originals = {
|
||||
defineProperty: Object.defineProperty,
|
||||
getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor,
|
||||
setInterval: window.setInterval,
|
||||
setTimeout: window.setTimeout,
|
||||
Function: window.Function,
|
||||
eval: window.eval,
|
||||
dateNow: Date.now,
|
||||
};
|
||||
|
||||
// 1. 拦截 debugger
|
||||
const _Function = originals.Function;
|
||||
window.Function = function() {
|
||||
let body = arguments[arguments.length - 1];
|
||||
if (typeof body === 'string' && body.includes('debugger')) {
|
||||
arguments[arguments.length - 1] = body.replace(/debugger\s*;?/g, '');
|
||||
}
|
||||
return _Function.apply(this, arguments);
|
||||
};
|
||||
window.Function.prototype = _Function.prototype;
|
||||
|
||||
// 2. 过滤 debugger 定时器
|
||||
const _setInterval = originals.setInterval;
|
||||
window.setInterval = function(fn, ms) {
|
||||
if (typeof fn === 'function' && fn.toString().includes('debugger')) return -1;
|
||||
if (typeof fn === 'string' && fn.includes('debugger')) return -1;
|
||||
return _setInterval.apply(this, arguments);
|
||||
};
|
||||
|
||||
const _setTimeout = originals.setTimeout;
|
||||
window.setTimeout = function(fn, ms) {
|
||||
if (typeof fn === 'function' && fn.toString().includes('debugger')) return -1;
|
||||
if (typeof fn === 'string' && fn.includes('debugger')) return -1;
|
||||
return _setTimeout.apply(this, arguments);
|
||||
};
|
||||
|
||||
// 3. 隐藏 webdriver 属性
|
||||
originals.defineProperty.call(Object, navigator, 'webdriver', {
|
||||
get: () => undefined, configurable: true
|
||||
});
|
||||
|
||||
console.log('[AntiDebug] 反反调试脚本已注入');
|
||||
})();
|
||||
```
|
||||
@@ -0,0 +1,255 @@
|
||||
# 常见加密模式识别与还原
|
||||
|
||||
## 快速识别表
|
||||
|
||||
| 特征 | 可能的算法 | 验证方法 |
|
||||
|------|-----------|---------|
|
||||
| 32位十六进制 | MD5 | 用已知输入验证 |
|
||||
| 40位十六进制 | SHA-1 | 同上 |
|
||||
| 64位十六进制 | SHA-256 | 同上 |
|
||||
| 128位十六进制 | SHA-512 | 同上 |
|
||||
| `=` 结尾,含 `A-Za-z0-9+/` | Base64 | 直接 atob 解码 |
|
||||
| `=` 结尾,含 `A-Za-z0-9-_` | Base64url | 替换 `-_` 为 `+/` 后解码 |
|
||||
| 固定长度密文,16字节倍数 | AES (128-bit key) | 寻找 key/iv |
|
||||
| 固定长度密文,8字节倍数 | DES / 3DES | 寻找 8字节 key |
|
||||
| 超长数字字符串 | RSA | 寻找公钥 |
|
||||
| `CryptoJS` 关键词 | CryptoJS 库 | 直接搜索源码 |
|
||||
|
||||
## 1. MD5
|
||||
|
||||
### 标准 MD5
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
function md5(str) {
|
||||
return crypto.createHash('md5').update(str).digest('hex');
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义 MD5(非标准实现)
|
||||
|
||||
某些网站修改了 MD5 的内部参数(如 chrsz=16),导致输出与标准 MD5 不同。
|
||||
|
||||
**识别方法**:用相同输入对比标准 MD5 输出,如果不一致则为自定义实现。
|
||||
|
||||
**还原策略**:必须提取原始 JS 实现,在 Node.js 中直接执行。
|
||||
|
||||
### 常见 MD5 签名模式
|
||||
|
||||
```javascript
|
||||
// 模式1:简单拼接
|
||||
sign = md5(param1 + param2 + timestamp + secret)
|
||||
|
||||
// 模式2:排序拼接
|
||||
params = Object.keys(data).sort().map(k => k + '=' + data[k]).join('&')
|
||||
sign = md5(params + secret)
|
||||
|
||||
// 模式3:嵌套哈希
|
||||
sign = md5(md5(password) + timestamp)
|
||||
```
|
||||
|
||||
## 2. HMAC
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
function hmacSha256(message, secret) {
|
||||
return crypto.createHmac('sha256', secret).update(message).digest('hex');
|
||||
}
|
||||
```
|
||||
|
||||
**识别特征**:搜索 `HMAC`、`createHmac`、`CryptoJS.HmacSHA256`
|
||||
|
||||
## 3. AES
|
||||
|
||||
### AES-CBC
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function aesEncrypt(plaintext, key, iv) {
|
||||
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
|
||||
cipher.setAutoPadding(true); // PKCS7
|
||||
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
function aesDecrypt(ciphertext, key, iv) {
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
|
||||
decipher.setAutoPadding(true);
|
||||
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
}
|
||||
```
|
||||
|
||||
### AES-ECB
|
||||
|
||||
```javascript
|
||||
function aesEcbEncrypt(plaintext, key) {
|
||||
const cipher = crypto.createCipheriv('aes-128-ecb', key, null);
|
||||
cipher.setAutoPadding(true);
|
||||
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
return encrypted;
|
||||
}
|
||||
```
|
||||
|
||||
### CryptoJS 兼容
|
||||
|
||||
```javascript
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
// CryptoJS 默认使用 PKCS7 填充
|
||||
const encrypted = CryptoJS.AES.encrypt(plaintext, CryptoJS.enc.Utf8.parse(key), {
|
||||
iv: CryptoJS.enc.Utf8.parse(iv),
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
}).toString();
|
||||
```
|
||||
|
||||
**关键参数识别**:
|
||||
- key 长度:16字节=AES-128,24字节=AES-192,32字节=AES-256
|
||||
- iv 长度:固定16字节
|
||||
- 模式:CBC(需要iv)、ECB(不需要iv)、CTR、GCM
|
||||
- 填充:PKCS7(最常见)、ZeroPadding、NoPadding
|
||||
- 输出格式:Base64(最常见)、Hex
|
||||
|
||||
## 4. DES / 3DES
|
||||
|
||||
```javascript
|
||||
function desEncrypt(plaintext, key) {
|
||||
const cipher = crypto.createCipheriv('des-ecb', key, null); // 8字节key
|
||||
cipher.setAutoPadding(true);
|
||||
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
function tripleDesEncrypt(plaintext, key, iv) {
|
||||
const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); // 24字节key
|
||||
cipher.setAutoPadding(true);
|
||||
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
return encrypted;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. RSA
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function rsaEncrypt(plaintext, publicKey) {
|
||||
const buffer = Buffer.from(plaintext, 'utf8');
|
||||
const encrypted = crypto.publicEncrypt({
|
||||
key: publicKey,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
}, buffer);
|
||||
return encrypted.toString('base64');
|
||||
}
|
||||
```
|
||||
|
||||
**使用 node-forge(更灵活)**:
|
||||
|
||||
```javascript
|
||||
const forge = require('node-forge');
|
||||
|
||||
function rsaEncrypt(plaintext, publicKeyPem) {
|
||||
const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
|
||||
const encrypted = publicKey.encrypt(plaintext, 'RSAES-PKCS1-V1_5');
|
||||
return forge.util.encode64(encrypted);
|
||||
}
|
||||
```
|
||||
|
||||
**常见 RSA 公钥格式**:
|
||||
- PEM 格式(`-----BEGIN PUBLIC KEY-----`)
|
||||
- 模数(n) + 指数(e) 格式(需要手动构造 PEM)
|
||||
|
||||
## 6. Base64 变体
|
||||
|
||||
```javascript
|
||||
// 标准 Base64
|
||||
const encoded = Buffer.from(str).toString('base64');
|
||||
const decoded = Buffer.from(encoded, 'base64').toString();
|
||||
|
||||
// Base64url(URL安全变体)
|
||||
function base64url(str) {
|
||||
return Buffer.from(str).toString('base64')
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
// 自定义字符表 Base64
|
||||
function customBase64(str, table) {
|
||||
const std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
const b64 = Buffer.from(str).toString('base64');
|
||||
return b64.split('').map(c => {
|
||||
const idx = std.indexOf(c);
|
||||
return idx >= 0 ? table[idx] : c;
|
||||
}).join('');
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 异或加密(XOR)
|
||||
|
||||
```javascript
|
||||
function xorEncrypt(plaintext, key) {
|
||||
const result = [];
|
||||
for (let i = 0; i < plaintext.length; i++) {
|
||||
result.push(plaintext.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
||||
}
|
||||
return Buffer.from(result).toString('hex');
|
||||
}
|
||||
```
|
||||
|
||||
## 8. RC4
|
||||
|
||||
```javascript
|
||||
function rc4(data, key) {
|
||||
const s = Array.from({ length: 256 }, (_, i) => i);
|
||||
let j = 0;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
|
||||
[s[i], s[j]] = [s[j], s[i]];
|
||||
}
|
||||
let i = 0; j = 0;
|
||||
const result = [];
|
||||
for (let k = 0; k < data.length; k++) {
|
||||
i = (i + 1) % 256;
|
||||
j = (j + s[i]) % 256;
|
||||
[s[i], s[j]] = [s[j], s[i]];
|
||||
result.push(data.charCodeAt(k) ^ s[(s[i] + s[j]) % 256]);
|
||||
}
|
||||
return Buffer.from(result);
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 时间戳处理
|
||||
|
||||
```javascript
|
||||
// 毫秒级(13位)
|
||||
const tsMs = Date.now(); // 1710000000000
|
||||
|
||||
// 秒级(10位)
|
||||
const tsSec = Math.floor(Date.now() / 1000); // 1710000000
|
||||
|
||||
// 注意:某些网站使用服务端时间戳,需要从响应头或接口获取
|
||||
```
|
||||
|
||||
## 10. 参数签名常见拼接模式
|
||||
|
||||
```javascript
|
||||
// 模式1:固定格式
|
||||
sign = md5(`page=${page}&t=${timestamp}&key=${secret}`)
|
||||
|
||||
// 模式2:所有参数排序拼接
|
||||
const params = { page: 1, size: 10, t: Date.now() };
|
||||
const str = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&');
|
||||
sign = md5(str + secret)
|
||||
|
||||
// 模式3:JSON 字符串
|
||||
sign = md5(JSON.stringify(data) + secret)
|
||||
|
||||
// 模式4:管道分隔
|
||||
sign = md5(`${page}|${timestamp}|${secret}`)
|
||||
```
|
||||
@@ -0,0 +1,398 @@
|
||||
# 环境补全指南
|
||||
|
||||
## 概述
|
||||
|
||||
将浏览器端 JS 代码移植到 Node.js 执行时,需要模拟浏览器环境。本指南提供各种场景下的
|
||||
环境补全方案,从最小补全到完整模拟。
|
||||
|
||||
## 原则
|
||||
|
||||
1. **最小补全优先**:只补目标代码实际访问的 API,不要一次性补全所有浏览器 API
|
||||
2. **按需扩展**:运行报错时再补对应的属性/方法
|
||||
3. **真实值优先**:尽量使用真实浏览器采集的值,而非随意伪造
|
||||
|
||||
## 1. Node.js vm 沙箱最小环境
|
||||
|
||||
### 基础模板
|
||||
|
||||
```javascript
|
||||
const vm = require('vm');
|
||||
|
||||
function createMinimalSandbox(options = {}) {
|
||||
const sandbox = {
|
||||
// 全局对象
|
||||
window: null,
|
||||
self: null,
|
||||
globalThis: null,
|
||||
|
||||
// DOM 最小模拟
|
||||
document: {
|
||||
cookie: options.cookie || '',
|
||||
createElement: (tag) => ({
|
||||
tagName: tag.toUpperCase(),
|
||||
style: {},
|
||||
setAttribute: () => {},
|
||||
getAttribute: () => null,
|
||||
appendChild: () => {},
|
||||
innerHTML: '',
|
||||
src: '',
|
||||
}),
|
||||
getElementById: () => null,
|
||||
getElementsByTagName: () => [],
|
||||
getElementsByClassName: () => [],
|
||||
querySelector: () => null,
|
||||
querySelectorAll: () => [],
|
||||
head: { appendChild: () => {} },
|
||||
body: { appendChild: () => {} },
|
||||
location: { href: options.url || 'https://example.com', hostname: 'example.com' },
|
||||
referrer: options.referrer || '',
|
||||
title: '',
|
||||
readyState: 'complete',
|
||||
},
|
||||
|
||||
// Navigator
|
||||
navigator: {
|
||||
userAgent: options.userAgent || 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
appCodeName: 'Mozilla',
|
||||
appName: 'Netscape',
|
||||
appVersion: '5.0',
|
||||
platform: 'MacIntel',
|
||||
language: 'zh-CN',
|
||||
languages: ['zh-CN', 'zh', 'en'],
|
||||
cookieEnabled: true,
|
||||
onLine: true,
|
||||
plugins: { length: 3 },
|
||||
mimeTypes: { length: 4 },
|
||||
webdriver: false,
|
||||
hardwareConcurrency: 8,
|
||||
maxTouchPoints: 0,
|
||||
},
|
||||
|
||||
// Location
|
||||
location: {
|
||||
href: options.url || 'https://example.com',
|
||||
protocol: 'https:',
|
||||
host: 'example.com',
|
||||
hostname: 'example.com',
|
||||
port: '',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
hash: '',
|
||||
origin: 'https://example.com',
|
||||
},
|
||||
|
||||
// Screen
|
||||
screen: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
availWidth: 1920,
|
||||
availHeight: 1055,
|
||||
colorDepth: 24,
|
||||
pixelDepth: 24,
|
||||
},
|
||||
|
||||
// 定时器
|
||||
setTimeout: setTimeout,
|
||||
setInterval: setInterval,
|
||||
clearTimeout: clearTimeout,
|
||||
clearInterval: clearInterval,
|
||||
|
||||
// 内置对象
|
||||
String, Array, Object, Math, Date, RegExp, JSON, Map, Set, WeakMap, WeakSet,
|
||||
parseInt, parseFloat, isNaN, isFinite, NaN, Infinity, undefined,
|
||||
encodeURIComponent, decodeURIComponent,
|
||||
encodeURI, decodeURI,
|
||||
escape, unescape,
|
||||
Error, TypeError, RangeError, SyntaxError, ReferenceError,
|
||||
ArrayBuffer, Uint8Array, Int32Array, Float64Array, DataView,
|
||||
Promise,
|
||||
Proxy, Reflect,
|
||||
Symbol,
|
||||
|
||||
// Base64
|
||||
btoa: (str) => Buffer.from(str, 'binary').toString('base64'),
|
||||
atob: (b64) => Buffer.from(b64, 'base64').toString('binary'),
|
||||
|
||||
// Console(用于调试)
|
||||
console: console,
|
||||
};
|
||||
|
||||
// 循环引用
|
||||
sandbox.window = sandbox;
|
||||
sandbox.self = sandbox;
|
||||
sandbox.globalThis = sandbox;
|
||||
sandbox.top = sandbox;
|
||||
sandbox.parent = sandbox;
|
||||
sandbox.frames = sandbox;
|
||||
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
function runInSandbox(code, options = {}) {
|
||||
const sandbox = createMinimalSandbox(options);
|
||||
vm.createContext(sandbox);
|
||||
|
||||
try {
|
||||
vm.runInContext(code, sandbox, {
|
||||
timeout: options.timeout || 5000,
|
||||
filename: options.filename || 'sandbox.js',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('沙箱执行错误:', e.message);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return sandbox;
|
||||
}
|
||||
```
|
||||
|
||||
### Cookie 拦截模式
|
||||
|
||||
```javascript
|
||||
function createCookieTrapSandbox(options = {}) {
|
||||
const sandbox = createMinimalSandbox(options);
|
||||
const cookies = {};
|
||||
|
||||
Object.defineProperty(sandbox.document, 'cookie', {
|
||||
get() {
|
||||
return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
|
||||
},
|
||||
set(val) {
|
||||
const parts = val.split(';')[0].split('=');
|
||||
const name = parts[0].trim();
|
||||
const value = parts.slice(1).join('=').trim();
|
||||
cookies[name] = value;
|
||||
console.log(`[Sandbox] Cookie Set: ${name}=${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
sandbox._cookies = cookies;
|
||||
return sandbox;
|
||||
}
|
||||
```
|
||||
|
||||
## 2. WASM 环境补全
|
||||
|
||||
### 基础 WASM 加载
|
||||
|
||||
```javascript
|
||||
const fs = require('fs');
|
||||
|
||||
async function loadWasm(wasmPath, importObject = {}) {
|
||||
const wasmBuffer = fs.readFileSync(wasmPath);
|
||||
|
||||
const defaultImports = {
|
||||
env: {
|
||||
memory: new WebAssembly.Memory({ initial: 256 }),
|
||||
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
|
||||
abort: () => { throw new Error('WASM abort'); },
|
||||
...importObject.env,
|
||||
},
|
||||
wasi_snapshot_preview1: {
|
||||
fd_write: () => 0,
|
||||
fd_close: () => 0,
|
||||
fd_seek: () => 0,
|
||||
proc_exit: () => {},
|
||||
...importObject.wasi_snapshot_preview1,
|
||||
},
|
||||
...importObject,
|
||||
};
|
||||
|
||||
const result = await WebAssembly.instantiate(wasmBuffer, defaultImports);
|
||||
return result.instance;
|
||||
}
|
||||
```
|
||||
|
||||
### wasm-bindgen (Rust) 环境补全
|
||||
|
||||
```javascript
|
||||
class Window {
|
||||
constructor() {
|
||||
this.document = {
|
||||
body: {},
|
||||
createElement: () => ({}),
|
||||
getElementById: () => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function patchWasmBindgenEnv() {
|
||||
const win = new Window();
|
||||
win.window = win;
|
||||
win.self = win;
|
||||
|
||||
globalThis.Window = Window;
|
||||
globalThis.window = win;
|
||||
globalThis.self = win;
|
||||
globalThis.document = win.document;
|
||||
|
||||
// wasm-bindgen 可能检查 instanceof
|
||||
// 确保 win instanceof Window === true
|
||||
}
|
||||
```
|
||||
|
||||
### Emscripten 环境补全
|
||||
|
||||
```javascript
|
||||
const Module = {
|
||||
print: console.log,
|
||||
printErr: console.error,
|
||||
TOTAL_MEMORY: 16777216,
|
||||
noInitialRun: true,
|
||||
onRuntimeInitialized: function() {
|
||||
console.log('WASM Runtime Ready');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 3. jQuery 模拟
|
||||
|
||||
```javascript
|
||||
function createjQueryStub() {
|
||||
const $ = function(selector) {
|
||||
return {
|
||||
length: 1,
|
||||
val: () => '',
|
||||
text: () => '',
|
||||
html: () => '',
|
||||
attr: () => '',
|
||||
css: () => ({}),
|
||||
find: () => $(''),
|
||||
each: (fn) => { fn(0, {}); },
|
||||
click: () => {},
|
||||
on: () => {},
|
||||
ajax: $.ajax,
|
||||
};
|
||||
};
|
||||
|
||||
$.ajax = function(options) {
|
||||
console.log('[jQuery] $.ajax:', options.url, options.data);
|
||||
return { done: (fn) => ({ fail: () => ({}) }) };
|
||||
};
|
||||
|
||||
$.get = $.post = $.ajax;
|
||||
$.fn = $.prototype = {};
|
||||
$.extend = Object.assign;
|
||||
|
||||
return $;
|
||||
}
|
||||
```
|
||||
|
||||
## 4. XMLHttpRequest 模拟
|
||||
|
||||
```javascript
|
||||
function createXHRStub(interceptor) {
|
||||
return class XMLHttpRequest {
|
||||
constructor() {
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.responseText = '';
|
||||
this.response = '';
|
||||
}
|
||||
|
||||
open(method, url) {
|
||||
this._method = method;
|
||||
this._url = url;
|
||||
this.readyState = 1;
|
||||
}
|
||||
|
||||
setRequestHeader(name, value) {
|
||||
this._headers = this._headers || {};
|
||||
this._headers[name] = value;
|
||||
}
|
||||
|
||||
send(body) {
|
||||
if (interceptor) {
|
||||
interceptor({
|
||||
method: this._method,
|
||||
url: this._url,
|
||||
headers: this._headers,
|
||||
body: body,
|
||||
});
|
||||
}
|
||||
this.readyState = 4;
|
||||
this.status = 200;
|
||||
if (this.onreadystatechange) this.onreadystatechange();
|
||||
if (this.onload) this.onload();
|
||||
}
|
||||
|
||||
addEventListener(event, handler) {
|
||||
this['on' + event] = handler;
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 环境检测绕过清单
|
||||
|
||||
| 检测项 | Node.js 默认 | 需要补全的值 |
|
||||
|--------|-------------|-------------|
|
||||
| `typeof window` | `undefined` | `object` |
|
||||
| `typeof document` | `undefined` | `object` |
|
||||
| `typeof navigator` | `undefined` | `object` |
|
||||
| `typeof process` | `object` (暴露) | 需要删除 |
|
||||
| `typeof module` | `object` (暴露) | 需要删除 |
|
||||
| `typeof global` | `object` (暴露) | 视情况删除 |
|
||||
| `navigator.webdriver` | N/A | `false` / `undefined` |
|
||||
| `window.chrome` | N/A | `{ runtime: {} }` |
|
||||
| `navigator.plugins.length` | N/A | `> 0` |
|
||||
|
||||
### 清除 Node.js 特征
|
||||
|
||||
```javascript
|
||||
function hideNodeFeatures(sandbox) {
|
||||
delete sandbox.process;
|
||||
delete sandbox.module;
|
||||
delete sandbox.exports;
|
||||
delete sandbox.require;
|
||||
delete sandbox.global;
|
||||
delete sandbox.__filename;
|
||||
delete sandbox.__dirname;
|
||||
delete sandbox.Buffer;
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 完整环境补全模板(jsdom)
|
||||
|
||||
当最小补全不够时,使用 jsdom 提供完整 DOM 环境:
|
||||
|
||||
```javascript
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
function createFullBrowserEnv(options = {}) {
|
||||
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
||||
url: options.url || 'https://example.com',
|
||||
referrer: options.referrer || '',
|
||||
contentType: 'text/html',
|
||||
pretendToBeVisual: true,
|
||||
runScripts: 'dangerously',
|
||||
resources: 'usable',
|
||||
});
|
||||
|
||||
const { window } = dom;
|
||||
|
||||
// 补充 jsdom 缺少的 API
|
||||
if (!window.btoa) {
|
||||
window.btoa = (str) => Buffer.from(str, 'binary').toString('base64');
|
||||
}
|
||||
if (!window.atob) {
|
||||
window.atob = (b64) => Buffer.from(b64, 'base64').toString('binary');
|
||||
}
|
||||
|
||||
return { dom, window, document: window.document };
|
||||
}
|
||||
```
|
||||
|
||||
## MCP 辅助确定环境需求
|
||||
|
||||
```
|
||||
1. [js-reverse] search_in_sources(query="window.|document.|navigator.")
|
||||
→ 识别代码访问了哪些浏览器 API
|
||||
|
||||
2. [js-reverse] evaluate_script(function="列出所有被访问的全局变量")
|
||||
→ 确定需要补全的范围
|
||||
|
||||
3. [js-reverse] set_breakpoint_on_text(text="typeof window")
|
||||
→ 在环境检测处断点,确认检测逻辑
|
||||
```
|
||||
@@ -0,0 +1,397 @@
|
||||
# JS Hook 技术大全
|
||||
|
||||
## 概述
|
||||
|
||||
Hook 是 JS 逆向中最核心的技术之一。通过劫持/拦截 JS 原生函数或对象属性,可以捕获加密函数的
|
||||
输入输出、Cookie 的生成过程、网络请求的参数构造等关键信息。
|
||||
|
||||
## 注入方式
|
||||
|
||||
### 通过 MCP 注入(推荐)
|
||||
|
||||
```
|
||||
[js-reverse] inject_before_load(script=HookScript)
|
||||
→ 在页面脚本加载前注入,确保 Hook 在目标代码之前生效
|
||||
|
||||
[js-reverse] evaluate_script(function=HookScript)
|
||||
→ 在当前页面上下文执行,适合页面已加载后的动态注入
|
||||
```
|
||||
|
||||
### 通过浏览器控制台注入
|
||||
|
||||
在 chrome-devtools MCP 中使用:
|
||||
```
|
||||
[chrome-devtools] evaluate_script(function=HookScript)
|
||||
```
|
||||
|
||||
## Hook 模板库
|
||||
|
||||
### 1. Cookie Setter Hook
|
||||
|
||||
**用途**:监控所有 Cookie 写入操作,定位动态 Cookie 生成逻辑
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
let cookieTemp = '';
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
set: function(val) {
|
||||
console.log('[Hook] Cookie Set:', val);
|
||||
if (val.indexOf('目标cookie名') !== -1) {
|
||||
console.log('[Hook] ★ 目标Cookie捕获:', val);
|
||||
console.trace('[Hook] Cookie 调用栈');
|
||||
}
|
||||
cookieTemp = val;
|
||||
return val;
|
||||
},
|
||||
get: function() {
|
||||
return cookieTemp;
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
### 2. XHR Hook
|
||||
|
||||
**用途**:拦截所有 XMLHttpRequest 请求,捕获完整请求参数
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _open = XMLHttpRequest.prototype.open;
|
||||
const _send = XMLHttpRequest.prototype.send;
|
||||
const _setHeader = XMLHttpRequest.prototype.setRequestHeader;
|
||||
|
||||
XMLHttpRequest.prototype.open = function(method, url) {
|
||||
this._hookMethod = method;
|
||||
this._hookUrl = url;
|
||||
this._hookHeaders = {};
|
||||
console.log('[Hook] XHR Open:', method, url);
|
||||
return _open.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
|
||||
this._hookHeaders[name] = value;
|
||||
return _setHeader.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function(body) {
|
||||
console.log('[Hook] XHR Send:', {
|
||||
method: this._hookMethod,
|
||||
url: this._hookUrl,
|
||||
headers: this._hookHeaders,
|
||||
body: body
|
||||
});
|
||||
if (this._hookUrl.indexOf('目标接口') !== -1) {
|
||||
console.log('[Hook] ★ 目标请求捕获');
|
||||
console.trace('[Hook] 请求调用栈');
|
||||
}
|
||||
return _send.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 3. Fetch Hook
|
||||
|
||||
**用途**:拦截 fetch API 请求
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _fetch = window.fetch;
|
||||
window.fetch = function(url, options) {
|
||||
console.log('[Hook] Fetch:', url, options);
|
||||
if (typeof url === 'string' && url.indexOf('目标接口') !== -1) {
|
||||
console.log('[Hook] ★ 目标Fetch捕获');
|
||||
console.trace('[Hook] Fetch 调用栈');
|
||||
}
|
||||
return _fetch.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 4. $.ajax Hook(jQuery 场景)
|
||||
|
||||
**用途**:拦截 jQuery AJAX 请求,捕获加密参数
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
if (typeof $ === 'undefined' || typeof $.ajax === 'undefined') return;
|
||||
|
||||
const _ajax = $.ajax;
|
||||
$.ajax = function(options) {
|
||||
if (typeof options === 'object') {
|
||||
console.log('[Hook] $.ajax:', {
|
||||
url: options.url,
|
||||
method: options.type || options.method,
|
||||
data: options.data,
|
||||
headers: options.headers
|
||||
});
|
||||
|
||||
if (options.url && options.url.indexOf('目标接口') !== -1) {
|
||||
console.log('[Hook] ★ 目标Ajax捕获');
|
||||
if (options.data && options.data.m) {
|
||||
console.log('[Hook] 加密参数 m:', options.data.m);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _ajax.apply(this, arguments);
|
||||
};
|
||||
|
||||
// 同时 Hook ajaxSetup
|
||||
if ($.ajaxSetup) {
|
||||
const _setup = $.ajaxSetup;
|
||||
$.ajaxSetup = function(options) {
|
||||
console.log('[Hook] $.ajaxSetup:', options);
|
||||
return _setup.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
### 5. eval / Function Hook
|
||||
|
||||
**用途**:捕获动态生成和执行的代码
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
// eval Hook
|
||||
const _eval = window.eval;
|
||||
window.eval = function(code) {
|
||||
console.log('[Hook] eval 调用, 代码长度:', (typeof code === 'string') ? code.length : 'N/A');
|
||||
if (typeof code === 'string' && code.length < 5000) {
|
||||
console.log('[Hook] eval 代码:', code.substring(0, 500));
|
||||
}
|
||||
return _eval.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Function 构造器 Hook
|
||||
const _Function = Function;
|
||||
const handler = {
|
||||
construct(target, args) {
|
||||
const body = args[args.length - 1];
|
||||
console.log('[Hook] new Function, body 长度:', body ? body.length : 0);
|
||||
if (body && body.indexOf('目标关键词') !== -1) {
|
||||
console.log('[Hook] ★ 目标 Function 捕获:', body.substring(0, 500));
|
||||
}
|
||||
return new target(...args);
|
||||
},
|
||||
apply(target, thisArg, args) {
|
||||
const body = args[args.length - 1];
|
||||
console.log('[Hook] Function(), body 长度:', body ? body.length : 0);
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
};
|
||||
window.Function = new Proxy(_Function, handler);
|
||||
})();
|
||||
```
|
||||
|
||||
### 6. JSON.parse Hook
|
||||
|
||||
**用途**:捕获 JSON 解析操作,常用于响应数据解密场景
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _parse = JSON.parse;
|
||||
JSON.parse = function(text) {
|
||||
const result = _parse.apply(this, arguments);
|
||||
console.log('[Hook] JSON.parse:', typeof text === 'string' ? text.substring(0, 200) : text);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 7. JSON.stringify Hook
|
||||
|
||||
**用途**:捕获 JSON 序列化操作,常在签名前执行
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _stringify = JSON.stringify;
|
||||
JSON.stringify = function(obj) {
|
||||
const result = _stringify.apply(this, arguments);
|
||||
console.log('[Hook] JSON.stringify:', result ? result.substring(0, 200) : result);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 8. atob / btoa Hook
|
||||
|
||||
**用途**:捕获 Base64 编解码操作
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _atob = window.atob;
|
||||
const _btoa = window.btoa;
|
||||
|
||||
window.atob = function(str) {
|
||||
const result = _atob(str);
|
||||
console.log('[Hook] atob:', str.substring(0, 100), '→', result.substring(0, 100));
|
||||
return result;
|
||||
};
|
||||
|
||||
window.btoa = function(str) {
|
||||
const result = _btoa(str);
|
||||
console.log('[Hook] btoa:', str.substring(0, 100), '→', result.substring(0, 100));
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 9. setTimeout / setInterval Hook
|
||||
|
||||
**用途**:监控定时器调用,识别反调试和周期性操作
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _setTimeout = window.setTimeout;
|
||||
const _setInterval = window.setInterval;
|
||||
|
||||
window.setTimeout = function(fn, delay) {
|
||||
const fnStr = typeof fn === 'function' ? fn.toString().substring(0, 200) : String(fn).substring(0, 200);
|
||||
// 过滤 debugger 反调试
|
||||
if (fnStr.indexOf('debugger') !== -1) {
|
||||
console.log('[Hook] setTimeout 拦截 debugger,已跳过');
|
||||
return -1;
|
||||
}
|
||||
return _setTimeout.apply(this, arguments);
|
||||
};
|
||||
|
||||
window.setInterval = function(fn, delay) {
|
||||
const fnStr = typeof fn === 'function' ? fn.toString().substring(0, 200) : String(fn).substring(0, 200);
|
||||
if (fnStr.indexOf('debugger') !== -1) {
|
||||
console.log('[Hook] setInterval 拦截 debugger,已跳过');
|
||||
return -1;
|
||||
}
|
||||
return _setInterval.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 10. WebSocket Hook
|
||||
|
||||
**用途**:拦截 WebSocket 消息
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _WebSocket = window.WebSocket;
|
||||
window.WebSocket = function(url, protocols) {
|
||||
console.log('[Hook] WebSocket 连接:', url);
|
||||
const ws = new _WebSocket(url, protocols);
|
||||
|
||||
const _send = ws.send.bind(ws);
|
||||
ws.send = function(data) {
|
||||
console.log('[Hook] WS 发送:', data);
|
||||
return _send(data);
|
||||
};
|
||||
|
||||
ws.addEventListener('message', function(event) {
|
||||
console.log('[Hook] WS 接收:', event.data);
|
||||
});
|
||||
|
||||
return ws;
|
||||
};
|
||||
window.WebSocket.prototype = _WebSocket.prototype;
|
||||
})();
|
||||
```
|
||||
|
||||
### 11. window 属性访问 Hook(蜜罐检测)
|
||||
|
||||
**用途**:监控对 window 属性的访问,识别蜜罐检测
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const handler = {
|
||||
get(target, prop, receiver) {
|
||||
if (['__selenium', '__webdriver_script_fn', '__driver_evaluate',
|
||||
'$cdc_asdjflasutopfhvcZLmcfl_', 'callPhantom', '_phantom'].includes(prop)) {
|
||||
console.log('[Hook] ★ 蜜罐属性访问:', prop);
|
||||
return undefined;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
};
|
||||
// 注意:直接代理 window 风险较大,仅在需要时使用
|
||||
})();
|
||||
```
|
||||
|
||||
### 12. Canvas 指纹 Hook
|
||||
|
||||
**用途**:拦截 Canvas 指纹采集
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const _toDataURL = HTMLCanvasElement.prototype.toDataURL;
|
||||
HTMLCanvasElement.prototype.toDataURL = function() {
|
||||
console.log('[Hook] Canvas toDataURL 调用');
|
||||
console.trace('[Hook] Canvas 调用栈');
|
||||
return _toDataURL.apply(this, arguments);
|
||||
};
|
||||
|
||||
const _toBlob = HTMLCanvasElement.prototype.toBlob;
|
||||
HTMLCanvasElement.prototype.toBlob = function() {
|
||||
console.log('[Hook] Canvas toBlob 调用');
|
||||
return _toBlob.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
### 13. Navigator 属性 Hook
|
||||
|
||||
**用途**:伪装浏览器指纹
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
Object.defineProperty(navigator, 'webdriver', {
|
||||
get: () => false
|
||||
});
|
||||
Object.defineProperty(navigator, 'plugins', {
|
||||
get: () => [1, 2, 3, 4, 5]
|
||||
});
|
||||
Object.defineProperty(navigator, 'languages', {
|
||||
get: () => ['zh-CN', 'zh', 'en']
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
## 组合 Hook 模板
|
||||
|
||||
### 通用逆向分析 Hook(一键注入)
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
console.log('[Hook] ========== 通用逆向Hook已注入 ==========');
|
||||
|
||||
// 1. Cookie 监控
|
||||
let _cookie = document.cookie;
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
set(v) { console.log('[Cookie] Set:', v); _cookie += '; ' + v; },
|
||||
get() { return _cookie; }
|
||||
});
|
||||
|
||||
// 2. XHR 监控
|
||||
const _xhrOpen = XMLHttpRequest.prototype.open;
|
||||
const _xhrSend = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function(m, u) { this._m = m; this._u = u; return _xhrOpen.apply(this, arguments); };
|
||||
XMLHttpRequest.prototype.send = function(b) { console.log('[XHR]', this._m, this._u, b); return _xhrSend.apply(this, arguments); };
|
||||
|
||||
// 3. Fetch 监控
|
||||
const _fetch = window.fetch;
|
||||
window.fetch = function() { console.log('[Fetch]', ...arguments); return _fetch.apply(this, arguments); };
|
||||
|
||||
// 4. debugger 拦截
|
||||
const _si = window.setInterval;
|
||||
window.setInterval = function(fn, d) {
|
||||
if (typeof fn === 'function' && fn.toString().indexOf('debugger') > -1) return -1;
|
||||
return _si.apply(this, arguments);
|
||||
};
|
||||
|
||||
console.log('[Hook] ========== Hook注入完成 ==========');
|
||||
})();
|
||||
```
|
||||
|
||||
## MCP 注入最佳实践
|
||||
|
||||
1. **使用 `inject_before_load`**:确保 Hook 在目标代码之前生效
|
||||
2. **使用 `console.log` 输出**:通过 `list_console_messages` 收集结果
|
||||
3. **使用 `console.trace`**:在关键点输出调用栈
|
||||
4. **避免覆写原型链的 toString**:防止被反检测代码发现
|
||||
5. **使用 Proxy 代替直接覆写**:更隐蔽,不改变 `typeof` 结果
|
||||
@@ -0,0 +1,264 @@
|
||||
# MCP 工具使用手册
|
||||
|
||||
## 概述
|
||||
|
||||
本手册详细说明 `js-reverse` 和 `chrome-devtools` 两个 MCP 服务器的协作使用方式,
|
||||
提供常见逆向场景下的具体操作步骤。
|
||||
|
||||
## 工具速查表
|
||||
|
||||
### js-reverse MCP(逆向分析核心)
|
||||
|
||||
| 类别 | 工具 | 核心用途 |
|
||||
|------|------|---------|
|
||||
| **源码搜索** | `search_in_sources` | 在所有 JS 中搜索关键词 |
|
||||
| **源码获取** | `get_script_source` | 获取指定脚本的代码片段 |
|
||||
| **源码保存** | `save_script_source` | 将脚本保存到本地文件 |
|
||||
| **脚本列表** | `list_scripts` | 列出页面加载的所有脚本 |
|
||||
| **断点设置** | `set_breakpoint_on_text` | 按代码文本设置断点 |
|
||||
| **XHR 断点** | `break_on_xhr` | 在 XHR 请求时暂停 |
|
||||
| **函数追踪** | `trace_function` | 追踪函数调用,记录参数 |
|
||||
| **暂停信息** | `get_paused_info` | 获取断点处的栈和变量 |
|
||||
| **单步调试** | `step_into/over/out` | 单步执行 |
|
||||
| **脚本注入** | `inject_before_load` | 页面加载前注入 JS |
|
||||
| **脚本执行** | `evaluate_script` | 在页面执行 JS |
|
||||
| **网络请求** | `list_network_requests` | 列出网络请求 |
|
||||
| **请求详情** | `get_network_request` | 获取请求完整信息 |
|
||||
| **请求来源** | `get_request_initiator` | 获取请求的 JS 调用栈 |
|
||||
| **WebSocket** | `list_websocket_connections` | 列出 WS 连接 |
|
||||
| **WS 分析** | `analyze_websocket_messages` | 分析 WS 消息模式 |
|
||||
| **控制台** | `list_console_messages` | 读取控制台输出 |
|
||||
|
||||
### chrome-devtools MCP(浏览器自动化)
|
||||
|
||||
| 类别 | 工具 | 核心用途 |
|
||||
|------|------|---------|
|
||||
| **页面管理** | `new_page` / `navigate_page` / `close_page` | 页面操作 |
|
||||
| **UI 交互** | `click` / `fill` / `type_text` / `press_key` | 元素交互 |
|
||||
| **DOM 快照** | `take_snapshot` | 获取页面文本结构 |
|
||||
| **截图** | `take_screenshot` | 页面截图 |
|
||||
| **脚本执行** | `evaluate_script` | 执行 JS |
|
||||
| **设备模拟** | `emulate` | 模拟 UA/视口/网络 |
|
||||
| **网络请求** | `list_network_requests` / `get_network_request` | 请求监控 |
|
||||
| **等待** | `wait_for` | 等待文本出现 |
|
||||
| **对话框** | `handle_dialog` | 处理弹窗 |
|
||||
|
||||
## 场景化操作手册
|
||||
|
||||
### 场景 1:分析数据接口的加密参数
|
||||
|
||||
**目标**:找到接口中加密参数的生成逻辑
|
||||
|
||||
```
|
||||
步骤 1:打开目标页面
|
||||
[chrome-devtools] new_page(url="https://target.com")
|
||||
|
||||
步骤 2:获取页面结构
|
||||
[chrome-devtools] take_snapshot() → 确认页面正常加载
|
||||
|
||||
步骤 3:触发数据请求(如翻页)
|
||||
[chrome-devtools] click(uid="翻页按钮的uid")
|
||||
或
|
||||
[chrome-devtools] evaluate_script(function="document.querySelector('.next').click()")
|
||||
|
||||
步骤 4:查看网络请求
|
||||
[js-reverse] list_network_requests(resourceTypes=["XHR","Fetch"])
|
||||
|
||||
步骤 5:获取目标请求详情
|
||||
[js-reverse] get_network_request(reqid=目标请求ID)
|
||||
→ 记录 URL、Headers、Params、Response
|
||||
|
||||
步骤 6:获取请求调用栈
|
||||
[js-reverse] get_request_initiator(requestId=目标请求ID)
|
||||
→ 找到发起请求的 JS 函数
|
||||
|
||||
步骤 7:搜索加密参数
|
||||
[js-reverse] search_in_sources(query="参数名=")
|
||||
[js-reverse] search_in_sources(query="参数名")
|
||||
|
||||
步骤 8:读取相关源码
|
||||
[js-reverse] get_script_source(url="包含加密逻辑的脚本URL", startLine=X, endLine=Y)
|
||||
|
||||
步骤 9:设置断点验证
|
||||
[js-reverse] set_breakpoint_on_text(text="加密函数调用")
|
||||
触发请求 →
|
||||
[js-reverse] get_paused_info(includeScopes=true)
|
||||
→ 查看变量值确认加密逻辑
|
||||
```
|
||||
|
||||
### 场景 2:动态 Cookie 逆向
|
||||
|
||||
**目标**:找到 Cookie 的生成逻辑
|
||||
|
||||
```
|
||||
步骤 1:注入 Cookie Hook
|
||||
[js-reverse] inject_before_load(script="Cookie setter Hook 代码")
|
||||
|
||||
步骤 2:刷新页面触发 Cookie 生成
|
||||
[js-reverse] navigate_page(type="reload")
|
||||
|
||||
步骤 3:读取 Hook 输出
|
||||
[js-reverse] list_console_messages(types=["log"])
|
||||
→ 找到 Cookie 设置记录和调用栈
|
||||
|
||||
步骤 4:根据调用栈定位生成函数
|
||||
[js-reverse] search_in_sources(query="目标函数名")
|
||||
[js-reverse] get_script_source(url="...", startLine=X, endLine=Y)
|
||||
|
||||
步骤 5:分析生成逻辑
|
||||
[js-reverse] set_breakpoint_on_text(text="cookie生成代码片段")
|
||||
刷新页面 →
|
||||
[js-reverse] get_paused_info → 查看入参
|
||||
[js-reverse] step_into → 进入加密函数
|
||||
[js-reverse] get_paused_info → 查看中间值
|
||||
```
|
||||
|
||||
### 场景 3:混淆代码分析
|
||||
|
||||
**目标**:还原混淆的 JS 代码
|
||||
|
||||
```
|
||||
步骤 1:列出所有脚本
|
||||
[js-reverse] list_scripts()
|
||||
→ 找到可疑的混淆脚本(通常文件名无意义或体积很大)
|
||||
|
||||
步骤 2:保存混淆脚本到本地
|
||||
[js-reverse] save_script_source(url="混淆脚本URL", filePath="./project/obfuscated.js")
|
||||
|
||||
步骤 3:搜索关键特征
|
||||
[js-reverse] search_in_sources(query="_0x", urlFilter="混淆脚本URL")
|
||||
→ 确认混淆类型(OB混淆)
|
||||
|
||||
[js-reverse] search_in_sources(query="switch|while.*true", urlFilter="混淆脚本URL")
|
||||
→ 检查是否有控制流平坦化
|
||||
|
||||
步骤 4:在浏览器中还原字符串
|
||||
[js-reverse] evaluate_script(function="返回字符串数组的代码")
|
||||
→ 获取解密后的字符串映射
|
||||
|
||||
步骤 5:定位关键函数
|
||||
[js-reverse] search_in_sources(query="encrypt|sign|md5|aes", urlFilter="混淆脚本URL")
|
||||
→ 找到加密相关代码的大致位置
|
||||
[js-reverse] get_script_source → 获取上下文
|
||||
```
|
||||
|
||||
### 场景 4:WASM 逆向
|
||||
|
||||
**目标**:分析 WASM 加密函数的输入输出
|
||||
|
||||
```
|
||||
步骤 1:搜索 WASM 加载代码
|
||||
[js-reverse] search_in_sources(query="WebAssembly|.wasm|instantiate")
|
||||
|
||||
步骤 2:找到 WASM 文件
|
||||
[js-reverse] list_network_requests(resourceTypes=["Other"])
|
||||
→ 找到 .wasm 文件的请求
|
||||
|
||||
步骤 3:分析 WASM 调用
|
||||
[js-reverse] search_in_sources(query="exports.|instance.exports")
|
||||
→ 找到导出函数的调用代码
|
||||
|
||||
步骤 4:追踪输入输出
|
||||
[js-reverse] set_breakpoint_on_text(text="wasm导出函数调用")
|
||||
触发请求 →
|
||||
[js-reverse] get_paused_info → 查看入参
|
||||
[js-reverse] step_over → 执行函数
|
||||
[js-reverse] get_paused_info → 查看返回值
|
||||
|
||||
步骤 5:在浏览器中测试
|
||||
[js-reverse] evaluate_script(function="调用wasm函数并返回结果")
|
||||
→ 验证 I/O 关系
|
||||
|
||||
步骤 6:下载 WASM 文件
|
||||
[chrome-devtools] get_network_request(reqid=WASM请求ID, responseFilePath="./project/main.wasm")
|
||||
```
|
||||
|
||||
### 场景 5:WebSocket 通信分析
|
||||
|
||||
**目标**:分析 WebSocket 消息格式和加密
|
||||
|
||||
```
|
||||
步骤 1:列出 WS 连接
|
||||
[js-reverse] list_websocket_connections()
|
||||
|
||||
步骤 2:分析消息模式
|
||||
[js-reverse] analyze_websocket_messages(wsid=连接ID, direction="sent")
|
||||
[js-reverse] analyze_websocket_messages(wsid=连接ID, direction="received")
|
||||
|
||||
步骤 3:获取具体消息
|
||||
[js-reverse] get_websocket_messages(wsid=连接ID, show_content=true)
|
||||
|
||||
步骤 4:搜索 WS 相关代码
|
||||
[js-reverse] search_in_sources(query="WebSocket|ws.send|onmessage")
|
||||
```
|
||||
|
||||
## 高级技巧
|
||||
|
||||
### 使用 trace_function 自动记录函数调用
|
||||
|
||||
```
|
||||
[js-reverse] trace_function(
|
||||
functionName="encrypt",
|
||||
logArgs=true,
|
||||
logThis=false,
|
||||
pause=false
|
||||
)
|
||||
```
|
||||
→ 每次 `encrypt` 被调用时自动记录参数,不暂停执行
|
||||
|
||||
### 使用条件断点
|
||||
|
||||
```
|
||||
[js-reverse] set_breakpoint_on_text(
|
||||
text="fetch(",
|
||||
condition="arguments[0].indexOf('/api/data') !== -1"
|
||||
)
|
||||
```
|
||||
→ 只在请求特定接口时暂停
|
||||
|
||||
### 使用 inject_before_load 进行预分析
|
||||
|
||||
```
|
||||
[js-reverse] inject_before_load(script=`
|
||||
// 记录所有全局变量的创建
|
||||
const knownGlobals = new Set(Object.keys(window));
|
||||
setInterval(() => {
|
||||
const newGlobals = Object.keys(window).filter(k => !knownGlobals.has(k));
|
||||
if (newGlobals.length > 0) {
|
||||
console.log('[Monitor] 新全局变量:', newGlobals);
|
||||
newGlobals.forEach(k => knownGlobals.add(k));
|
||||
}
|
||||
}, 1000);
|
||||
`)
|
||||
```
|
||||
|
||||
### 使用 evaluate_script 提取运行时数据
|
||||
|
||||
```
|
||||
[js-reverse] evaluate_script(function=`
|
||||
// 提取当前页面的所有 Cookie
|
||||
JSON.stringify(document.cookie.split('; ').reduce((obj, pair) => {
|
||||
const [k, v] = pair.split('=');
|
||||
obj[k] = v;
|
||||
return obj;
|
||||
}, {}))
|
||||
`)
|
||||
```
|
||||
|
||||
## 两个 MCP 的分工原则
|
||||
|
||||
| 需求 | 使用 js-reverse | 使用 chrome-devtools |
|
||||
|------|----------------|---------------------|
|
||||
| 搜索 JS 源码 | ✅ `search_in_sources` | ❌ |
|
||||
| 设置断点 | ✅ `set_breakpoint_on_text` | ❌ |
|
||||
| 单步调试 | ✅ `step_into/over/out` | ❌ |
|
||||
| 函数追踪 | ✅ `trace_function` | ❌ |
|
||||
| 注入 Hook | ✅ `inject_before_load` | ❌ |
|
||||
| 页面截图 | ✅ 可以 | ✅ 更灵活(指定元素) |
|
||||
| UI 交互(点击/输入) | ❌ | ✅ `click/fill/type_text` |
|
||||
| 模拟 UA/设备 | ❌ | ✅ `emulate` |
|
||||
| DOM 快照 | ❌ | ✅ `take_snapshot` |
|
||||
| 保存请求响应到文件 | ❌ | ✅ `get_network_request(responseFilePath)` |
|
||||
| WebSocket 分析 | ✅ 完整支持 | ❌ |
|
||||
| 请求调用栈 | ✅ `get_request_initiator` | ❌ |
|
||||
| 执行 JS | ✅ 支持 `mainWorld` 选项 | ✅ 支持 `args` 传 uid |
|
||||
@@ -0,0 +1,198 @@
|
||||
# JS 混淆识别与还原指南
|
||||
|
||||
## 混淆类型速查
|
||||
|
||||
### 1. OB 混淆 (obfuscator.io)
|
||||
|
||||
**识别特征**:
|
||||
- 大量 `_0x` 前缀变量名(如 `_0x4a3b2c`)
|
||||
- 顶部有十六进制字符串数组(`var _0xabc = ['...', '...', ...]`)
|
||||
- 字符串数组旋转函数
|
||||
- 十六进制属性访问(`obj['_0x1234']` 替代 `obj.method`)
|
||||
|
||||
**还原步骤**:
|
||||
1. 定位字符串数组和旋转函数
|
||||
2. 执行旋转函数得到最终字符串数组
|
||||
3. 全局替换十六进制索引为实际字符串
|
||||
4. 简化数学表达式和逻辑运算
|
||||
5. 变量重命名提高可读性
|
||||
|
||||
**MCP 辅助**:
|
||||
```
|
||||
[js-reverse] search_in_sources(query="_0x") → 定位混淆入口
|
||||
[js-reverse] evaluate_script → 在浏览器中执行字符串数组还原
|
||||
[js-reverse] save_script_source → 保存到本地进一步分析
|
||||
```
|
||||
|
||||
### 2. 控制流平坦化 (CFF)
|
||||
|
||||
**识别特征**:
|
||||
```javascript
|
||||
var state = initialState;
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case 'A': /* ... */ state = 'C'; break;
|
||||
case 'B': /* ... */ state = 'D'; break;
|
||||
case 'C': /* ... */ state = 'B'; break;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**还原步骤**:
|
||||
1. 找到初始状态值
|
||||
2. 按状态转移顺序排列代码块
|
||||
3. 去掉 switch-case 包装,还原为顺序代码
|
||||
4. 简化多余的变量赋值
|
||||
|
||||
**MCP 辅助**:
|
||||
```
|
||||
[js-reverse] set_breakpoint_on_text(text="switch") → 在状态机入口断点
|
||||
[js-reverse] step_over → 逐步追踪状态转移
|
||||
[js-reverse] get_paused_info → 查看当前状态值
|
||||
```
|
||||
|
||||
### 3. eval / Function 打包
|
||||
|
||||
**识别特征**:
|
||||
```javascript
|
||||
eval(function(p,a,c,k,e,d){...}('encoded_string',...))
|
||||
// 或
|
||||
new Function('return ' + decryptedCode)()
|
||||
```
|
||||
|
||||
**还原步骤**:
|
||||
1. Hook `eval` 和 `Function` 构造器,拦截实际执行的代码
|
||||
2. 或者将 `eval()` 替换为 `console.log()` 查看解密后的代码
|
||||
3. 可能有多层嵌套,需要逐层解包
|
||||
|
||||
**MCP 辅助**:
|
||||
```
|
||||
[js-reverse] inject_before_load(script="eval/Function Hook")
|
||||
[js-reverse] list_console_messages → 读取解包后的代码
|
||||
```
|
||||
|
||||
### 4. AAEncode
|
||||
|
||||
**识别特征**:
|
||||
```javascript
|
||||
゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_'];
|
||||
// 全是日文颜文字字符
|
||||
```
|
||||
|
||||
**还原**:直接在浏览器 console 执行,或去掉最外层执行函数改为输出。
|
||||
|
||||
### 5. JJEncode
|
||||
|
||||
**识别特征**:
|
||||
```javascript
|
||||
$=~[];$={___:++$,$$$$:(![]+"")[$],...
|
||||
// 全是 $ 和特殊字符
|
||||
```
|
||||
|
||||
**还原**:同 AAEncode,直接执行或替换执行为输出。
|
||||
|
||||
### 6. JSFuck
|
||||
|
||||
**识别特征**:
|
||||
```javascript
|
||||
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]...
|
||||
// 仅使用 []()!+ 六种字符
|
||||
```
|
||||
|
||||
**还原**:直接在浏览器执行。
|
||||
|
||||
### 7. 自定义 VM / 字节码解释器
|
||||
|
||||
**识别特征**:
|
||||
- 超大数组作为"字节码"
|
||||
- 解释器循环,包含 `switch` 或函数查找表
|
||||
- 通常在 IIFE 中
|
||||
- 无法通过简单字符串替换还原
|
||||
|
||||
**还原策略**:
|
||||
1. **不要尝试反编译字节码**
|
||||
2. 找到解释器的输入和输出接口
|
||||
3. 通过 Hook 解释器的关键操作(函数调用、赋值、返回)来理解行为
|
||||
4. 直接在 Node.js 中运行字节码解释器
|
||||
|
||||
**MCP 辅助**:
|
||||
```
|
||||
[js-reverse] trace_function(functionName="解释器函数")
|
||||
[js-reverse] set_breakpoint_on_text(text="switch") → 在解释器循环断点
|
||||
[js-reverse] step_over → 观察每步操作
|
||||
```
|
||||
|
||||
### 8. JSVMP(JS 虚拟机保护)
|
||||
|
||||
**识别特征**:
|
||||
- 超大 JS 文件(200KB+)
|
||||
- 包含自定义解释器和操作码表
|
||||
- 函数名和变量名完全无意义
|
||||
- 改写浏览器原生 API
|
||||
|
||||
**还原策略**:
|
||||
1. **不要反编译,通过 I/O 定位**
|
||||
2. Hook 所有出口(XHR、Cookie、fetch)
|
||||
3. 追踪加密函数的输入和输出
|
||||
4. 用已知 I/O 反推算法
|
||||
|
||||
**MCP 辅助**:
|
||||
```
|
||||
[js-reverse] break_on_xhr(url="目标接口") → 在请求发出时暂停
|
||||
[js-reverse] get_paused_info → 获取调用栈
|
||||
[js-reverse] inject_before_load → 注入全局 Hook
|
||||
```
|
||||
|
||||
## 通用反混淆 Node.js 工具
|
||||
|
||||
### 使用 AST 进行基础还原
|
||||
|
||||
```javascript
|
||||
const parser = require('@babel/parser');
|
||||
const traverse = require('@babel/traverse').default;
|
||||
const generate = require('@babel/generator').default;
|
||||
const t = require('@babel/types');
|
||||
|
||||
function deobfuscate(code) {
|
||||
const ast = parser.parse(code);
|
||||
|
||||
traverse(ast, {
|
||||
// 还原十六进制字符串
|
||||
StringLiteral(path) {
|
||||
if (/^\\x/.test(path.node.extra?.raw || '')) {
|
||||
path.node.extra = undefined;
|
||||
}
|
||||
},
|
||||
// 还原计算属性为点号访问
|
||||
MemberExpression(path) {
|
||||
if (t.isStringLiteral(path.node.property) && /^[a-zA-Z_$]/.test(path.node.property.value)) {
|
||||
path.node.computed = false;
|
||||
path.node.property = t.identifier(path.node.property.value);
|
||||
}
|
||||
},
|
||||
// 折叠常量表达式
|
||||
BinaryExpression(path) {
|
||||
if (t.isNumericLiteral(path.node.left) && t.isNumericLiteral(path.node.right)) {
|
||||
const result = eval(`${path.node.left.value} ${path.node.operator} ${path.node.right.value}`);
|
||||
if (typeof result === 'number' && isFinite(result)) {
|
||||
path.replaceWith(t.numericLiteral(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return generate(ast, { comments: false }).code;
|
||||
}
|
||||
```
|
||||
|
||||
## 混淆代码分析的 MCP 工作流
|
||||
|
||||
```
|
||||
1. save_script_source → 保存混淆代码到本地
|
||||
2. search_in_sources → 搜索可能的入口函数
|
||||
3. set_breakpoint_on_text → 在入口设断点
|
||||
4. get_paused_info → 查看运行时变量,理解数据流
|
||||
5. evaluate_script → 在浏览器执行还原操作
|
||||
6. step_into → 跟进关键函数调用
|
||||
```
|
||||
@@ -0,0 +1,269 @@
|
||||
# 协议层分析与对抗
|
||||
|
||||
## 概述
|
||||
|
||||
有些网站不仅在 JS 层面做加密保护,还在协议层面进行检测(TLS 指纹、HTTP/2 强制等)。
|
||||
当算法全部还原正确但请求仍然失败时,需要考虑协议层问题。
|
||||
|
||||
## 1. TLS 指纹检测
|
||||
|
||||
### 原理
|
||||
|
||||
服务器通过分析 TLS Client Hello 报文中的特征来识别客户端类型:
|
||||
- Cipher Suites 列表和顺序
|
||||
- TLS 扩展列表和顺序
|
||||
- 支持的曲线(Elliptic Curves)
|
||||
- 签名算法
|
||||
- ALPN 协议列表
|
||||
|
||||
不同客户端(Chrome、Firefox、curl、Node.js)的 TLS 指纹不同。
|
||||
|
||||
### 检测表现
|
||||
|
||||
- 请求返回 `403 Forbidden`
|
||||
- 返回空响应或错误页面
|
||||
- 返回 `token failed`、`access denied` 等自定义错误
|
||||
- 同样的参数在浏览器中正常,在 Node.js 中失败
|
||||
|
||||
### 解决方案
|
||||
|
||||
#### 方案 A:使用浏览器自动化(最可靠)
|
||||
|
||||
```javascript
|
||||
// 使用 chrome-devtools MCP 在真实浏览器中执行请求
|
||||
// [chrome-devtools] evaluate_script
|
||||
const response = await fetch('https://target.com/api/data', {
|
||||
method: 'GET',
|
||||
headers: { 'Cookie': cookieValue },
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
```
|
||||
MCP 操作:
|
||||
[chrome-devtools] evaluate_script(function="fetch(url).then(r=>r.json()).then(d=>JSON.stringify(d))")
|
||||
```
|
||||
|
||||
#### 方案 B:curl-impersonate
|
||||
|
||||
```bash
|
||||
# 使用 curl-impersonate 模拟 Chrome 的 TLS 指纹
|
||||
curl_chrome116 https://target.com/api/data \
|
||||
-H "Cookie: session=xxx" \
|
||||
-H "User-Agent: Mozilla/5.0 ..."
|
||||
```
|
||||
|
||||
Node.js 中通过子进程调用:
|
||||
```javascript
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function fetchWithChromeTLS(url, headers = {}) {
|
||||
const headerArgs = Object.entries(headers)
|
||||
.map(([k, v]) => `-H "${k}: ${v}"`)
|
||||
.join(' ');
|
||||
const result = execSync(`curl_chrome116 "${url}" ${headerArgs} -s`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 30000,
|
||||
});
|
||||
return JSON.parse(result);
|
||||
}
|
||||
```
|
||||
|
||||
#### 方案 C:自定义 TLS(高级)
|
||||
|
||||
使用支持自定义 TLS 指纹的库(如 `tls-client` 的 Node.js 绑定)。
|
||||
|
||||
## 2. HTTP/2 协议
|
||||
|
||||
### 检测表现
|
||||
|
||||
- HTTP/1.1 请求返回错误
|
||||
- HTTP/2 请求正常返回数据
|
||||
- 抓包可以看到服务器仅接受 h2 连接
|
||||
|
||||
### Node.js HTTP/2 请求
|
||||
|
||||
```javascript
|
||||
const http2 = require('http2');
|
||||
|
||||
function http2Request(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const client = http2.connect(`${urlObj.protocol}//${urlObj.host}`);
|
||||
|
||||
client.on('error', reject);
|
||||
|
||||
const headers = {
|
||||
':method': options.method || 'GET',
|
||||
':path': urlObj.pathname + urlObj.search,
|
||||
':scheme': urlObj.protocol.replace(':', ''),
|
||||
':authority': urlObj.host,
|
||||
'user-agent': options.userAgent || 'Mozilla/5.0 ...',
|
||||
'accept': 'application/json',
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
if (options.cookie) {
|
||||
headers['cookie'] = options.cookie;
|
||||
}
|
||||
|
||||
const req = client.request(headers);
|
||||
|
||||
let data = '';
|
||||
req.on('data', (chunk) => { data += chunk; });
|
||||
req.on('end', () => {
|
||||
client.close();
|
||||
resolve(data);
|
||||
});
|
||||
req.on('error', reject);
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
async function main() {
|
||||
const data = await http2Request('https://target.com/api/data?page=1', {
|
||||
headers: {
|
||||
'cookie': 'm=xxx; sessionid=yyy',
|
||||
'referer': 'https://target.com/',
|
||||
}
|
||||
});
|
||||
console.log(JSON.parse(data));
|
||||
}
|
||||
```
|
||||
|
||||
## 3. User-Agent 校验
|
||||
|
||||
### 常见场景
|
||||
|
||||
- 某些页面要求特定 UA
|
||||
- 不同 UA 返回不同内容
|
||||
- 空 UA 或非浏览器 UA 被拒绝
|
||||
|
||||
### Node.js 设置
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
const client = axios.create({
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### MCP 模拟
|
||||
|
||||
```
|
||||
[chrome-devtools] emulate(userAgent="自定义UA")
|
||||
```
|
||||
|
||||
## 4. Referer 校验
|
||||
|
||||
### 常见场景
|
||||
|
||||
- API 接口检查 Referer 头
|
||||
- 必须来自特定页面的请求
|
||||
- 缺少 Referer 返回 403
|
||||
|
||||
### 解决方案
|
||||
|
||||
```javascript
|
||||
headers: {
|
||||
'Referer': 'https://target.com/page',
|
||||
'Origin': 'https://target.com',
|
||||
}
|
||||
```
|
||||
|
||||
## 5. CORS 与跨域
|
||||
|
||||
### 预检请求 (Preflight)
|
||||
|
||||
```javascript
|
||||
// 某些请求会先发 OPTIONS 预检
|
||||
// 确保模拟请求时也处理 CORS 相关头
|
||||
headers: {
|
||||
'Origin': 'https://target.com',
|
||||
'Access-Control-Request-Method': 'POST',
|
||||
'Access-Control-Request-Headers': 'content-type',
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 请求频率限制
|
||||
|
||||
### 常见策略
|
||||
|
||||
- IP 维度限流
|
||||
- Cookie/Session 维度限流
|
||||
- 请求间隔检测
|
||||
- 滑动窗口计数
|
||||
|
||||
### 对抗方式
|
||||
|
||||
```javascript
|
||||
// 请求间隔控制
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function fetchWithRateLimit(urls, delayMs = 1000) {
|
||||
const results = [];
|
||||
for (const url of urls) {
|
||||
const data = await fetch(url);
|
||||
results.push(data);
|
||||
await sleep(delayMs + Math.random() * 500); // 随机化延迟
|
||||
}
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## 7. IP 封禁对抗
|
||||
|
||||
### 代理轮换
|
||||
|
||||
```javascript
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
const proxies = [
|
||||
'http://proxy1:port',
|
||||
'http://proxy2:port',
|
||||
];
|
||||
|
||||
function getRandomProxy() {
|
||||
return proxies[Math.floor(Math.random() * proxies.length)];
|
||||
}
|
||||
|
||||
async function fetchWithProxy(url, options = {}) {
|
||||
const agent = new HttpsProxyAgent(getRandomProxy());
|
||||
return fetch(url, { ...options, agent });
|
||||
}
|
||||
```
|
||||
|
||||
## 诊断流程
|
||||
|
||||
```
|
||||
请求失败?
|
||||
│
|
||||
├─ 检查 HTTP 状态码
|
||||
│ ├─ 403 → TLS 指纹 / Referer / UA / IP 封禁
|
||||
│ ├─ 412 → 前置条件失败(Cookie / Token 过期)
|
||||
│ ├─ 429 → 频率限制
|
||||
│ └─ 200 但数据异常 → 参数错误 / 加密不对
|
||||
│
|
||||
├─ 同样的请求在浏览器中成功?
|
||||
│ ├─ 是 → TLS 指纹问题,切换到浏览器方案
|
||||
│ └─ 否 → 参数/Cookie 确实有问题,重新分析
|
||||
│
|
||||
├─ curl 命令测试
|
||||
│ ├─ curl 成功 → Node.js 请求头/参数构造问题
|
||||
│ └─ curl 也失败 → TLS / 协议 / IP 问题
|
||||
│
|
||||
└─ 使用 HTTP/2?
|
||||
├─ HTTP/2 成功 → 切换到 http2 模块
|
||||
└─ HTTP/2 也失败 → TLS 指纹问题
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
# JS 逆向工作流总览
|
||||
|
||||
## 解法模式决策树
|
||||
|
||||
```
|
||||
目标网站数据接口
|
||||
│
|
||||
├─ 能否抓到明确的数据接口?
|
||||
│ ├─ 否 → WebSocket?→ 场景7
|
||||
│ │ 页面渲染数据?→ 场景8(字体反爬/CSS干扰)
|
||||
│ └─ 是 ↓
|
||||
│
|
||||
├─ 请求中有加密参数?
|
||||
│ ├─ 否 → 直接请求即可,检查是否有 Cookie/Header 校验
|
||||
│ └─ 是 ↓
|
||||
│
|
||||
├─ 加密逻辑在哪里?
|
||||
│ ├─ 明文 JS(可直接定位)→ 模式A:纯 Node.js 还原
|
||||
│ ├─ 混淆 JS(OB/CFF/eval)→ 反混淆后 → 模式A 或 模式B
|
||||
│ ├─ 服务端返回 JS 动态执行 → 模式B:VM 沙箱执行
|
||||
│ ├─ WASM 二进制 → 模式C:WASM 加载
|
||||
│ ├─ JSVMP 虚拟机保护 → 先尝试 I/O 定位,失败则 → 模式D
|
||||
│ └─ 无法脱离浏览器环境 → 模式D:浏览器自动化
|
||||
│
|
||||
├─ 纯请求方案是否可行?
|
||||
│ ├─ 请求成功 → 完成
|
||||
│ └─ 请求失败(token failed / 403)↓
|
||||
│
|
||||
└─ TLS 指纹 / HTTP/2 检测?
|
||||
├─ 是 → 切换到模式D 或使用 HTTP/2
|
||||
└─ 否 → 检查 Cookie、时间戳、参数拼接错误
|
||||
```
|
||||
|
||||
## 模式详解
|
||||
|
||||
### 模式 A:纯 Node.js 算法还原
|
||||
|
||||
**适用条件**:
|
||||
- 加密算法可用标准库实现(MD5/SHA/AES/DES/RSA/HMAC)
|
||||
- 无浏览器环境检测
|
||||
- 无 TLS 指纹校验
|
||||
|
||||
**技术栈**:
|
||||
- `crypto`(Node.js 内置)— MD5/SHA/AES/DES/HMAC
|
||||
- `crypto-js` — CryptoJS 兼容实现
|
||||
- `node-rsa` / `node-forge` — RSA 加解密
|
||||
- `axios` / `node-fetch` — HTTP 请求
|
||||
- `http2`(Node.js 内置)— HTTP/2 请求
|
||||
|
||||
**模板**:`templates/node-request/`
|
||||
|
||||
### 模式 B:Node vm 沙箱执行
|
||||
|
||||
**适用条件**:
|
||||
- 服务端返回混淆 JS,需要 eval 执行生成 Cookie/Token
|
||||
- 加密逻辑依赖大量上下文,手动还原成本过高
|
||||
|
||||
**技术栈**:
|
||||
- `vm`(Node.js 内置)— 沙箱执行
|
||||
- `vm2` — 更安全的沙箱(可选)
|
||||
- `jsdom` — 模拟 DOM 环境(可选)
|
||||
|
||||
**注意事项**:
|
||||
- 必须构造最小沙箱环境(document、navigator、location 等)
|
||||
- Cookie setter 需要拦截
|
||||
- setTimeout/setInterval 需要提供但可能需要限制
|
||||
|
||||
**模板**:`templates/vm-sandbox/`
|
||||
|
||||
### 模式 C:WASM 加载还原
|
||||
|
||||
**适用条件**:
|
||||
- 加密函数通过 WebAssembly 实现
|
||||
- WASM 有明确的导出函数接口
|
||||
|
||||
**技术栈**:
|
||||
- `fs`(Node.js 内置)— 读取 .wasm 文件
|
||||
- `WebAssembly`(Node.js 内置)— 加载和实例化
|
||||
|
||||
**注意事项**:
|
||||
- 先验证 I/O,不要先反编译 WASM
|
||||
- 检查 `WebAssembly.Module.imports()` 了解环境依赖
|
||||
- wasm-bindgen 生成的 WASM 需要补 `Window` 类和 DOM
|
||||
- `unreachable` panic 通常是环境缺失
|
||||
|
||||
**模板**:`templates/wasm-loader/`
|
||||
|
||||
### 模式 D:浏览器自动化
|
||||
|
||||
**适用条件**:
|
||||
- TLS 指纹检测,纯请求无法通过
|
||||
- 复杂环境依赖(window 蜜罐、Canvas 指纹等)
|
||||
- 加密逻辑过于复杂,还原成本远大于自动化
|
||||
|
||||
**技术栈**:
|
||||
- `playwright` / `puppeteer` — 浏览器自动化
|
||||
- chrome-devtools MCP — 直接操作已有 Chrome
|
||||
|
||||
**注意事项**:
|
||||
- 复用本机 Chrome 实例避免指纹差异
|
||||
- 注入脚本截取中间数据
|
||||
- 控制请求频率避免触发风控
|
||||
|
||||
**模板**:`templates/browser-auto/`
|
||||
|
||||
## 通用调试流程
|
||||
|
||||
```
|
||||
1. 打开 Network 面板
|
||||
↓
|
||||
2. 触发目标操作(搜索/翻页/登录)
|
||||
↓
|
||||
3. 找到数据接口,分析参数
|
||||
↓
|
||||
4. 搜索参数名定位 JS 源码
|
||||
↓
|
||||
5. 设置 XHR 断点追踪调用栈
|
||||
↓
|
||||
6. 在关键函数设置断点
|
||||
↓
|
||||
7. 单步调试确认加密逻辑
|
||||
↓
|
||||
8. 还原算法并对比中间值
|
||||
↓
|
||||
9. 编写 Node.js 脚本
|
||||
↓
|
||||
10. 运行验证
|
||||
```
|
||||
Reference in New Issue
Block a user