refactor: 将文件从子目录提升到仓库根目录

所有文件从 hello_js_reverse_skill/ 子目录移至仓库根目录,
使项目结构更清晰,便于作为 Skill 直接引用。

Made-with: Cursor
This commit is contained in:
白夜
2026-03-18 22:55:27 +08:00
parent cfd0cc919d
commit fb14cf7916
29 changed files with 0 additions and 0 deletions
+337
View File
@@ -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] 反反调试脚本已注入');
})();
```
+255
View File
@@ -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}`)
```
+398
View File
@@ -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")
→ 在环境检测处断点,确认检测逻辑
```
+397
View File
@@ -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` 结果
+264
View File
@@ -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 |
+198
View File
@@ -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 → 跟进关键函数调用
```
+269
View File
@@ -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 指纹问题
```
+128
View File
@@ -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. 运行验证
```