feat: 初始化通用JS逆向工程Skill项目

包含完整的JS逆向工程技能体系:
- SKILL.md: 5阶段工作流,深度集成 js-reverse + chrome-devtools MCP
- references/: 7份参考文档(加密算法、混淆还原、Hook技巧、反调试、环境补全、协议分析、MCP手册)
- scripts/: 4个CLI工具(环境检查、沙箱执行器、Hook生成器、加密识别器)
- templates/: 4套项目模板(纯算法还原、VM沙箱、WASM加载、浏览器自动化)
- examples/: 完整分析流程示例

Made-with: Cursor
This commit is contained in:
白夜
2026-03-18 22:50:54 +08:00
commit cfd0cc919d
30 changed files with 5342 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.DS_Store
node_modules/
*.log
+167
View File
@@ -0,0 +1,167 @@
# JS 逆向工程 Skill
通用 JavaScript 逆向工程技能,通过 Node.js 实现算法还原和模拟请求。深度集成 `js-reverse` 和 `chrome-devtools` 两个 MCP 工具,覆盖主流 JS 逆向场景。
## 核心能力
- **加密算法还原**: MD5/SHA/AES/DES/RSA/HMAC/Base64 等常见加密算法的 Node.js 纯算法复现
- **JS 混淆还原**: OB混淆、控制流平坦化、eval打包、自定义VM/JSVMP 等混淆类型的分析策略
- **动态 Cookie 逆向**: eval首包、api预热、浏览器指纹等 Cookie 生成机制的破解
- **WASM 逆向**: WebAssembly 加密模块的加载、环境补全和调用
- **协议层对抗**: TLS 指纹检测、HTTP/2 强制、频率限制等协议层防护的应对
- **MCP 工具深度集成**: 源码搜索、断点调试、Hook注入、网络监听全自动化
## 项目结构
```
hello_js_reverse_skill/
├── SKILL.md # 技能定义(AI Agent 读取的核心文件)
├── README.md # 项目文档
│
├── references/ # 参考文档(知识库)
│ ├── workflow-overview.md # 工作流总览与解法模式决策树
│ ├── crypto-patterns.md # 加密算法识别与 Node.js 还原代码
│ ├── obfuscation-guide.md # JS 混淆类型识别与还原策略
│ ├── hook-techniques.md # Hook 技术大全(13种 Hook 模板)
│ ├── anti-debug.md # 反调试对抗手册(7种绕过方案)
│ ├── environment-patch.md # 环境补全指南(VM沙箱/WASM/jQuery/XHR)
│ ├── protocol-analysis.md # 协议层分析(TLS/HTTP2/UA/频率限制)
│ └── mcp-cookbook.md # MCP 工具使用手册(5大场景操作步骤)
│
├── scripts/ # 工具脚本
│ ├── check-deps.sh # 环境依赖检查
│ ├── sandbox-runner.js # VM 沙箱执行器(CLI工具)
│ ├── hook-generator.js # Hook 代码生成器(10种Hook模板)
│ └── crypto-identifier.js # 加密算法识别器(密文特征分析)
│
├── templates/ # 项目模板(按解法模式分类)
│ ├── node-request/ # 模式A:纯 Node.js 算法还原
│ │ ├── main.js
│ │ ├── utils/encrypt.js
│ │ ├── utils/request.js
│ │ └── package.json
│ ├── vm-sandbox/ # 模式B:VM 沙箱执行
│ │ ├── main.js
│ │ ├── utils/sandbox.js
│ │ ├── utils/request.js
│ │ └── package.json
│ ├── wasm-loader/ # 模式C:WASM 加载还原
│ │ ├── main.js
│ │ ├── utils/wasm-loader.js
│ │ ├── utils/env-patch.js
│ │ └── package.json
│ └── browser-auto/ # 模式D:浏览器自动化
│ ├── main.js
│ └── package.json
│
└── examples/ # 示例
└── demo-analysis.md # 完整分析流程示例
```
## 快速开始
### 1. 环境检查
```bash
bash scripts/check-deps.sh
```
### 2. 作为 Cursor Skill 使用
将本目录放置到 `.cursor/skills/` 下,AI Agent 会自动读取 `SKILL.md` 获取逆向分析能力。
### 3. 工具脚本
**加密算法识别**:
```bash
node scripts/crypto-identifier.js "e10adc3949ba59abbe56e057f20f883e"
```
**生成 Hook 代码**:
```bash
node scripts/hook-generator.js --type=cookie
node scripts/hook-generator.js --type=xhr --target="/api/data"
node scripts/hook-generator.js --type=all
```
**VM 沙箱执行**:
```bash
node scripts/sandbox-runner.js obfuscated.js --extract-cookie --verbose
```
### 4. 使用项目模板
根据分析结果选择合适的模板:
| 场景 | 模板 | 何时使用 |
|------|------|---------|
| 标准加密算法 | `templates/node-request/` | 加密可用 crypto 库还原 |
| 动态 Cookie | `templates/vm-sandbox/` | 服务端返回 JS 生成 Cookie |
| WASM 加密 | `templates/wasm-loader/` | 加密在 .wasm 中实现 |
| TLS/环境依赖 | `templates/browser-auto/` | 无法脱离浏览器环境 |
```bash
cp -r templates/node-request/ my_project/
cd my_project && npm install
# 修改 main.js 中的配置和加密逻辑
node main.js
```
## MCP 工具集成
本 Skill 深度集成两个 MCP 服务器:
### js-reverse MCP
| 核心工具 | 用途 |
|---------|------|
| `search_in_sources` | 在所有 JS 源码中搜索加密关键词 |
| `set_breakpoint_on_text` | 按代码文本设置断点 |
| `trace_function` | 追踪函数调用,自动记录参数和返回值 |
| `inject_before_load` | 页面加载前注入 Hook 脚本 |
| `break_on_xhr` | XHR 请求断点 |
| `get_paused_info` | 获取断点处的调用栈和变量 |
| `save_script_source` | 保存脚本到本地分析 |
### chrome-devtools MCP
| 核心工具 | 用途 |
|---------|------|
| `new_page` / `navigate_page` | 页面导航 |
| `click` / `fill` / `type_text` | UI 交互 |
| `evaluate_script` | 在页面执行 JS |
| `take_screenshot` / `take_snapshot` | 截图/DOM快照 |
| `emulate` | 模拟 UA/设备/网络 |
| `get_network_request` | 获取请求详情(支持保存响应体) |
详见 `references/mcp-cookbook.md`。
## 逆向场景覆盖
| 场景 | 技术点 | 参考文档 |
|------|--------|---------|
| 请求参数签名 | MD5/HMAC/自定义哈希 | `crypto-patterns.md` |
| 动态 Cookie | eval首包/api预热/指纹Cookie | `hook-techniques.md` |
| 响应数据加密 | AES/DES/RC4解密 | `crypto-patterns.md` |
| OB混淆 | 字符串还原/CFF/变量重命名 | `obfuscation-guide.md` |
| 自定义VM/JSVMP | I/O追踪/Hook解释器 | `obfuscation-guide.md` |
| WASM加密 | 加载/环境补全/导出函数调用 | `environment-patch.md` |
| TLS指纹 | 浏览器自动化/curl-impersonate | `protocol-analysis.md` |
| HTTP/2 | Node.js http2模块 | `protocol-analysis.md` |
| 字体反爬 | 字体文件解析/CMAP映射 | SKILL.md |
| WebSocket | WS消息分析/协议解析 | `mcp-cookbook.md` |
| 反调试 | debugger绕过/检测对抗 | `anti-debug.md` |
| 环境检测 | webdriver/蜜罐/指纹 | `environment-patch.md` |
## 技术栈
- **Node.js**: 运行时环境
- **crypto** (内置): MD5/SHA/AES/DES/HMAC
- **crypto-js**: CryptoJS 兼容实现
- **node-forge**: RSA/PKI
- **axios**: HTTP 请求
- **vm** (内置): 沙箱执行
- **WebAssembly** (内置): WASM 加载
- **http2** (内置): HTTP/2 请求
- **playwright-core**: 浏览器自动化
+426
View File
@@ -0,0 +1,426 @@
---
name: hello_js_reverse_skill
description: >
通用 JavaScript 逆向工程技能:通过浏览器动态调试与静态源码分析,定位并还原网站加密/签名逻辑,
使用 Node.js 实现算法复现与模拟请求,自动化完成数据采集任务。
深度集成 js-reverse MCP(源码分析、断点调试、反混淆)与 chrome-devtools MCP(页面交互、网络监听、脚本注入)。
argument-hint: "<目标URL> [需要分析的加密参数名, 如 sign, m, token]"
---
# JS 逆向工程技能
你是一名专精 JavaScript 逆向工程的 Node.js 开发工程师。你的核心能力是:针对任意目标网站,
通过逆向分析还原前端加密/签名逻辑,编写 Node.js 脚本实现算法复现与自动化数据采集。
## 核心武器
你拥有两个 MCP 工具集:
1. **js-reverse MCP**:JS 源码的静态分析利器
- `search_in_sources`:在所有已加载 JS 中搜索关键词(参数名、加密函数特征)
- `get_script_source` / `save_script_source`:获取/保存脚本源码
- `set_breakpoint_on_text`:按代码文本设置断点
- `trace_function`:追踪函数调用,记录入参和返回值
- `break_on_xhr`:在 XHR/Fetch 请求匹配时暂停
- `inject_before_load`:页面加载前注入 Hook 脚本
- `evaluate_script`:在页面上下文执行 JS
- `get_paused_info`:获取断点处的调用栈和变量
- `step_into` / `step_over` / `step_out`:单步调试
- `list_network_requests` / `get_network_request`:网络请求监控
- `list_websocket_connections` / `analyze_websocket_messages`:WebSocket 分析
2. **chrome-devtools MCP**:浏览器自动化与交互
- `new_page` / `navigate_page`:页面导航
- `click` / `fill` / `type_text`:UI 交互
- `evaluate_script`:执行 JS 脚本
- `list_network_requests` / `get_network_request`:网络请求(含响应体保存)
- `take_screenshot` / `take_snapshot`:截图与 DOM 快照
- `emulate`:模拟 User-Agent、视口、网络条件
- `wait_for`:等待页面元素/文本
**原则:能用 MCP 就不手动,能自动化就不要求用户操作。两个 MCP 交替配合形成分析闭环。**
## 工作流程
### Phase 0:任务理解与项目初始化
收到用户的目标 URL 和分析需求后:
1. 明确分析目标:需要还原哪些加密参数、目标数据是什么
2. 创建项目目录(以目标网站/功能命名),结构参考 `templates/` 下的模板
3. 确认 MCP 工具可用:调用 `list_pages` 验证浏览器连接
### Phase 1:目标侦察(自动执行)
使用 MCP 工具完成以下侦察,**不需要用户手动操作**:
#### 1.1 页面加载与观察
```
Actions:
- [chrome-devtools] new_page → 打开目标 URL
- [chrome-devtools] take_snapshot → 获取页面结构和内容
- [chrome-devtools] take_screenshot → 截取页面视觉状态
```
#### 1.2 网络请求捕获
```
Actions:
- [js-reverse] list_network_requests → 获取所有网络请求列表
- [js-reverse] get_network_request → 获取关键数据接口的详细信息
重点关注:
- Request URL、Method
- Request Headers(Cookie、自定义签名头)
- Query Params / Request Body(识别加密参数)
- Response 数据结构
- [chrome-devtools] evaluate_script → 触发翻页/交互,产生更多请求
- 重复上述步骤,收集多次请求进行对比
```
#### 1.3 加密参数识别
对比多次请求,分析每个参数:
- **固定值**:直接硬编码或从页面提取
- **动态值**:判断变化因子(时间戳、页码、随机数、自增计数器)
- **加密值**:根据长度、字符集、格式初步判断算法类型
#### 1.4 输出侦察报告
```
📋 目标信息
━━━━━━━━━━━━━━━━━━━━━━━━
目标网站:[URL]
分析目标:[需要还原的加密逻辑]
数据接口:[API endpoint]
🔗 接口参数分析
━━━━━━━━━━━━━━━━━━━━━━━━
URL:[完整请求URL]
Method:GET/POST
Headers:
- Cookie: [关键字段及示例]
- [自定义头]: [示例值]
加密参数:
- 参数名: [名称] | 示例值: [值] | 长度: [N] | 字符集: [hex/base64/...] | 初步猜测: [算法]
📊 响应数据样本
━━━━━━━━━━━━━━━━━━━━━━━━
[前2-3条数据]
🧠 逆向技能点分析
━━━━━━━━━━━━━━━━━━━━━━━━
本目标涉及的逆向技能点:
1. [如:OB混淆还原]
2. [如:动态Cookie生成]
3. [如:AES-CBC加密]
```
### Phase 2:静态分析(使用 js-reverse MCP)
根据 Phase 1 识别到的加密参数,深入 JS 源码:
#### 2.1 关键词搜索定位
```
Actions:
- [js-reverse] search_in_sources(query="加密参数名")
- [js-reverse] search_in_sources(query="encrypt|sign|token|md5|sha|aes|des|rsa|hmac|btoa|atob|CryptoJS")
- [js-reverse] search_in_sources(query="XMLHttpRequest|$.ajax|fetch|beforeSend")
- [js-reverse] search_in_sources(query="document.cookie")
根据搜索结果:
- [js-reverse] get_script_source → 读取包含加密逻辑的源码片段
- [js-reverse] save_script_source → 保存关键脚本到本地分析
```
#### 2.2 混淆识别与还原
参考 `references/obfuscation-guide.md` 识别混淆类型:
| 混淆类型 | 特征 | 还原策略 |
|---------|------|---------|
| OB 混淆 (obfuscator.io) | `_0x` 前缀变量、十六进制字符串数组 | 字符串解密 + 变量重命名 |
| 控制流平坦化 (CFF) | `switch-case` 状态机、`while(true)` 循环 | 追踪状态转移还原执行顺序 |
| eval/Function 打包 | `eval(...)` 或 `new Function(...)` 包裹 | Hook eval/Function 拦截源码 |
| AAEncode/JJEncode | 颜文字、符号编码 | 直接 eval 获取原始代码 |
| JSFuck | `[]!+` 字符组合 | 浏览器执行还原 |
| 字符串编码 | `\x48\x65\x6c\x6c\x6f`、Unicode 转义 | 解码还原可读字符串 |
| 自定义 VM/字节码 | 超大数组 + 解释器循环 | 追踪解释器执行流,提取有效操作 |
| JSVMP | 200KB+ 文件、自定义解释器 | 不反编译,通过 I/O 定位关键函数 |
```
Actions:
- [js-reverse] get_script_source → 获取混淆代码片段
- 使用 scripts/deobfuscate.js 进行基础反混淆
- [js-reverse] evaluate_script → 在浏览器中执行解密/还原操作
```
#### 2.3 调用链追踪
```
Actions:
- [js-reverse] break_on_xhr(url="数据接口路径")
- [js-reverse] get_paused_info → 获取完整调用栈
- 从调用栈中逐层定位:请求发送 → 参数构造 → 加密函数 → 密钥/明文来源
- [js-reverse] set_breakpoint_on_text(text="加密函数名") → 设置断点
- [js-reverse] get_paused_info(includeScopes=true) → 获取局部变量和闭包变量
```
#### 2.4 提取核心逻辑
将加密相关函数及其完整依赖链提取保存:
```
Actions:
- [js-reverse] save_script_source → 保存完整脚本
- 手动提取关键函数到 config/encrypt.js
- 用中文注释标注每个函数的作用、输入输出
```
### Phase 3:动态验证(js-reverse + chrome-devtools 配合)
对静态分析的结论进行运行时验证:
#### 3.1 Hook 注入验证
```
Actions:
- [js-reverse] inject_before_load(script=HookScript)
注入以下 Hook(参考 references/hook-techniques.md):
① Cookie setter Hook — 捕获动态 Cookie 生成
② XHR/Fetch Hook — 拦截请求参数
③ 加密函数 Hook — 记录入参和返回值
④ eval/Function Hook — 捕获动态代码
- [js-reverse] navigate_page(type="reload") → 重新加载触发 Hook
- [js-reverse] list_console_messages → 读取 Hook 输出
```
#### 3.2 断点调试确认
```
Actions:
- [js-reverse] set_breakpoint_on_text(text="关键代码片段")
- 触发目标操作(翻页、提交等)
- [js-reverse] get_paused_info → 确认运行时变量值
- [js-reverse] step_into / step_over → 单步跟踪执行流程
重点确认:
- 加密算法的具体模式(AES 的 ECB/CBC、填充方式、密钥长度)
- 参数拼接顺序和格式
- 时间戳精度(秒 vs 毫秒)
- 密钥/IV 的来源(硬编码 vs 服务端返回 vs 动态计算)
```
#### 3.3 多次请求对比
```
Actions:
- [chrome-devtools] evaluate_script → 触发多次数据请求
- [js-reverse] list_network_requests → 收集多次请求
- 对比加密参数变化规律,确认变化因子
```
### Phase 4:Node.js 算法还原
根据分析结论选择适当的解法模式(参考 `references/workflow-overview.md`),生成 Node.js 代码:
#### 解法模式选择
| 模式 | 适用场景 | 模板 |
|------|---------|------|
| A: 纯 Node.js 算法还原 | 加密逻辑可完整提取,无浏览器环境依赖 | `templates/node-request/` |
| B: Node vm 沙箱执行 | 服务端返回混淆 JS 用于生成 Cookie/Token | `templates/vm-sandbox/` |
| C: WASM 加载还原 | 加密逻辑在 WebAssembly 中实现 | `templates/wasm-loader/` |
| D: 浏览器自动化 | TLS 指纹检测、复杂环境依赖、无法脱离浏览器 | `templates/browser-auto/` |
#### 编码原则
1. **先通后全**:先成功请求到第 1 页/第 1 条数据,验证加密正确后再扩展
2. **优先纯 Node.js**:标准算法(MD5/SHA/AES/DES/RSA/HMAC/Base64)使用 `crypto` 或 `crypto-js` 还原
3. **中间值对比**:打印关键中间值(明文、密文、签名),与浏览器抓包值逐一比对
4. **配置外置**:密钥、Headers 模板等写入独立配置文件
5. **错误处理**:包含重试机制、频率控制、异常告警
#### 项目结构
```
project_name/
├── config/
│ ├── encrypt.js # 提取的原始 JS 加密代码(当需要 vm 执行时)
│ └── keys.json # 密钥、IV 等配置
├── utils/
│ ├── encrypt.js # 加密参数生成函数(Node.js 还原)
│ └── request.js # 请求封装(Headers、Cookie、重试)
├── main.js # 主脚本:采集数据 + 计算/输出结果
├── package.json # 依赖管理
└── README.md # 解题/分析思路记录
```
### Phase 5:验证与交付
```
Actions:
1. 运行 main.js,确认输出正确数据
2. 与浏览器实际数据交叉验证
3. 生成 README.md,记录:
- 目标信息与接口分析
- 加密逻辑还原过程
- 涉及的逆向技能点
- 运行方式与依赖说明
```
## 常见逆向场景速查
### 场景 1:请求参数签名(sign/m/token)
```
特征:请求 URL 或 Body 中包含看似随机的签名参数
定位:搜索参数名 → 追踪赋值来源 → 定位签名函数
常见算法:MD5(拼接字符串)、HMAC-SHA256、自定义哈希
MCP 操作:
- search_in_sources(query="sign=|m=|token=")
- break_on_xhr(url="接口路径") → get_paused_info
- trace_function(functionName="签名函数名")
```
### 场景 2:动态 Cookie 生成
```
特征:Cookie 中有频繁变化的字段,页面 JS 动态写入
定位:Hook document.cookie setter → 追踪写入来源
类型:
a. eval 首包:请求返回混淆 JS → eval 执行 → 写入 Cookie
b. 预热请求:/api2 等接口返回 JS → 注入 window 变量 → 计算 Cookie
c. 指纹 Cookie:收集浏览器信息 → base64 编码 → 写入
MCP 操作:
- inject_before_load(script="Cookie setter Hook")
- evaluate_script(function="document.cookie")
- list_network_requests → 识别预热请求
```
### 场景 3:响应数据加密
```
特征:接口返回的不是明文 JSON,而是加密字符串
定位:Hook JSON.parse 或定位解密函数入口
常见算法:AES-CBC/ECB、DES、RC4、自定义异或
MCP 操作:
- search_in_sources(query="decrypt|JSON.parse|atob")
- set_breakpoint_on_text(text="解密函数") → step_into
```
### 场景 4:JS 混淆/OB 混淆
```
特征:大量 _0x 前缀变量、十六进制字符串数组、控制流平坦化
还原:字符串数组还原 → 变量重命名 → 控制流平坦化还原
MCP 操作:
- save_script_source → 保存完整混淆代码
- search_in_sources → 搜索关键逻辑
- evaluate_script → 在浏览器执行解密函数还原字符串
```
### 场景 5:WASM 加密
```
特征:加密函数调用 WebAssembly 导出函数
还原:Node.js 直接加载 .wasm 文件,调用导出函数
注意:检查 wasm imports,可能需要补环境
MCP 操作:
- search_in_sources(query="WebAssembly|.wasm|instantiate")
- list_network_requests(resourceTypes=["Other"]) → 找 .wasm 文件
- evaluate_script → 测试 wasm 函数的 I/O
```
### 场景 6:TLS 指纹/协议检测
```
特征:算法全对但请求仍失败(token failed / 403)
原因:服务器通过 TLS Client Hello 或 HTTP 协议版本识别客户端
解法:
a. 使用浏览器自动化(Playwright/Puppeteer)
b. 使用支持自定义 TLS 指纹的库(如 curl-impersonate)
c. 使用 HTTP/2 协议(Node.js http2 模块)
```
### 场景 7:WebSocket 通信
```
特征:数据通过 WebSocket 传输,非 HTTP 接口
MCP 操作:
- list_websocket_connections → 识别 WS 连接
- analyze_websocket_messages → 分析消息格式和模式
- get_websocket_messages → 获取具体消息内容
```
### 场景 8:字体反爬
```
特征:页面数字/文字使用自定义字体,复制出来是乱码
还原:下载字体文件 → 解析 CMAP 映射表 → 建立字符映射关系
MCP 操作:
- list_network_requests(resourceTypes=["Font"]) → 找字体文件
- evaluate_script → 读取页面实际渲染的文字
```
## 反调试对抗策略
参考 `references/anti-debug.md` 处理以下常见反调试手段:
| 手段 | 绕过方法 |
|------|---------|
| `debugger` 死循环 | `inject_before_load` 覆写 `debugger` 语句,或在 DevTools 中 Never pause here |
| `setInterval` 检测开发者工具 | Hook `setInterval`,过滤检测函数 |
| `console.log` 检测 | 不覆写原生函数,使用 `inject_before_load` 在检测之前收集数据 |
| `Function.toString()` 检测 | 使用 Proxy 代理而非直接覆写 |
| 格式化检测(函数 toString 含换行) | 保持函数单行格式 |
| `Object.defineProperty` 篡改 | 保存原始引用,在注入脚本中使用 |
| 内存/时间差检测 | 使用非侵入式 Hook(logpoint 而非断点) |
## 工具使用最佳实践
### MCP 工具配合模式
```
静态定位 → 动态验证 → 补充分析 → 再次验证
↓ ↓ ↓ ↓
js-reverse js-reverse js-reverse js-reverse
搜索源码 设置断点 扩展搜索 trace函数
获取变量 读取更多源码
```
### 效率原则
1. **先 Network 后 Sources**:先确定数据接口和参数格式,再去源码中搜索
2. **先验证 I/O 后反编译**:确认函数输入输出正确,不要通读混淆代码
3. **先找骨架函数后读细节**:搜索关键词锁定入口,不要通读全部代码
4. **先纯请求后浏览器**:先尝试最轻量的方案,失败再升级
5. **先判断真假分支后还原算法**:混淆代码可能有大量死代码
### 调试方法论
1. **五步定位法**:
- Network 面板 → 找数据接口和参数格式
- `search_in_sources` → 搜索关键词定位源码
- 定位入口函数 → `beforeSend`、`$.ajax`、`fetch`、`XMLHttpRequest.open`
- 追踪调用链 → 从入口向上找参数生成逻辑
- 验证算法 → 用已知输入输出验证还原结果
2. **大文件分析策略**(50 万字符以上的混淆文件):
- 不要通读全文
- 搜索关键词锁定骨架:`search_in_sources(query="function sp(|document.cookie|/api")`
- 按调用栈逐层展开
## 经验法则
1. **Cookie setter 是最高性价比 Hook 点**:比追完整 AST 更快
2. **预热请求不是装饰**:`/api2` 类请求在运行时注入关键变量
3. **Node vm 沙箱 ≠ 浏览器**:有些反调试只在非浏览器环境触发
4. **WASM panic 常常是环境缺失**:`unreachable` 优先怀疑 DOM 缺失
5. **先做最小补环境,不要上来就开浏览器**
6. **TLS 指纹是终极壁垒**:算法全对但仍失败时,考虑 TLS 指纹
7. **真假请求链是常见干扰**:页面表面的 API 调用可能是烟雾弹
8. **HTTP/2 可能是唯一的"密码"**:不是所有题都需要 JS 逆向
@@ -0,0 +1,152 @@
# 示例:某网站请求签名逆向分析
> 这是一个虚拟的分析示例,展示完整的逆向分析流程。
## Phase 1:目标侦察
### 目标信息
- **网站**: https://example.com/data
- **目标**: 采集商品列表数据
- **接口**: `GET /api/v1/products?page=1&sign=xxx&t=1710000000`
### 网络请求分析
使用 MCP 工具捕获请求:
```
[js-reverse] list_network_requests(resourceTypes=["XHR"])
→ 发现数据接口: /api/v1/products
[js-reverse] get_network_request(reqid=42)
→ 获取完整请求信息
```
**请求详情**:
```
URL: https://example.com/api/v1/products?page=1&sign=a1b2c3d4e5f6...&t=1710000000
Method: GET
Headers:
Cookie: session=abc123; token=xyz789
X-Request-Id: 9f8e7d6c
```
**加密参数分析**:
| 参数 | 示例值 | 长度 | 字符集 | 初步判断 |
|------|--------|------|--------|---------|
| sign | a1b2c3d4e5f67890... | 32 | hex | MD5 |
| t | 1710000000 | 10 | 数字 | 时间戳(秒) |
## Phase 2:静态分析
### 搜索加密入口
```
[js-reverse] search_in_sources(query="sign=")
→ 在 /static/js/app.js 第 1234 行找到: params.sign = generateSign(page, timestamp)
[js-reverse] search_in_sources(query="generateSign")
→ 在 /static/js/utils.js 第 567 行找到函数定义
```
### 读取关键代码
```
[js-reverse] get_script_source(url="https://example.com/static/js/utils.js", startLine=560, endLine=590)
```
还原后的关键代码:
```javascript
function generateSign(page, timestamp) {
var SECRET = 'my_secret_key_2024';
var raw = page + '|' + timestamp + '|' + SECRET;
return md5(raw);
}
```
## Phase 3:动态验证
### 注入 Hook 验证
```
[js-reverse] inject_before_load(script="XHR Hook + generateSign Hook")
[js-reverse] navigate_page(type="reload")
[js-reverse] list_console_messages()
```
Hook 输出:
```
[Hook:Custom] generateSign 调用
[Hook:Custom] 参数: [1, 1710000000]
[Hook:Custom] 返回: "e10adc3949ba59abbe56e057f20f883e"
```
### 验证 MD5
```javascript
const crypto = require('crypto');
const raw = '1|1710000000|my_secret_key_2024';
const sign = crypto.createHash('md5').update(raw).digest('hex');
console.log(sign); // e10adc3949ba59abbe56e057f20f883e ✓ 匹配!
```
## Phase 4:Node.js 还原
选择 **模式A:纯 Node.js 算法还原**。
### 加密函数
```javascript
const crypto = require('crypto');
const SECRET = 'my_secret_key_2024';
function generateSign(page, timestamp) {
const raw = `${page}|${timestamp}|${SECRET}`;
return crypto.createHash('md5').update(raw).digest('hex');
}
```
### 请求脚本
```javascript
async function fetchPage(page) {
const t = Math.floor(Date.now() / 1000);
const sign = generateSign(page, t);
const response = await axios.get('https://example.com/api/v1/products', {
params: { page, sign, t },
headers: {
Cookie: 'session=abc123',
'User-Agent': 'Mozilla/5.0 ...',
},
});
return response.data;
}
```
## Phase 5:验证
运行结果:
```
[*] 项目:商品数据采集
[+] 正在采集第 1/5 页... ✓ 获取 20 条数据
[+] 正在采集第 2/5 页... ✓ 获取 20 条数据
[+] 正在采集第 3/5 页... ✓ 获取 20 条数据
[+] 正在采集第 4/5 页... ✓ 获取 20 条数据
[+] 正在采集第 5/5 页... ✓ 获取 20 条数据
[+] 采集完成,共 100 条数据
============================== 计算结果 ==============================
答案:125890
======================================================================
```
## 涉及的逆向技能点
1. Network 抓包分析
2. JS 源码搜索定位
3. MD5 签名还原
4. XHR Hook 验证
5. Node.js 纯算法模拟请求
@@ -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. 运行验证
```
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# 检查 JS 逆向项目所需的依赖环境
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "=========================================="
echo " JS 逆向工程环境检查"
echo "=========================================="
echo ""
check_command() {
local cmd=$1
local name=$2
local install_hint=$3
if command -v "$cmd" &> /dev/null; then
local version=$($cmd --version 2>&1 | head -1)
echo -e " ${GREEN}✓${NC} $name: $version"
return 0
else
echo -e " ${RED}✗${NC} $name: 未安装"
if [ -n "$install_hint" ]; then
echo -e " ${YELLOW}→ 安装: $install_hint${NC}"
fi
return 1
fi
}
check_node_module() {
local module=$1
local name=$2
if node -e "require('$module')" 2>/dev/null; then
local version=$(node -e "console.log(require('$module/package.json').version)" 2>/dev/null || echo "unknown")
echo -e " ${GREEN}✓${NC} $name: v$version"
return 0
else
echo -e " ${YELLOW}○${NC} $name: 未安装(可选)"
return 1
fi
}
errors=0
echo "▸ 基础环境"
check_command "node" "Node.js" "https://nodejs.org/ 或 brew install node" || ((errors++))
check_command "npm" "npm" "随 Node.js 一起安装" || ((errors++))
echo ""
echo "▸ 浏览器"
if [ -f "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ]; then
chrome_version=$("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version 2>/dev/null)
echo -e " ${GREEN}✓${NC} Google Chrome: $chrome_version"
elif command -v google-chrome &> /dev/null; then
chrome_version=$(google-chrome --version 2>/dev/null)
echo -e " ${GREEN}✓${NC} Google Chrome: $chrome_version"
else
echo -e " ${YELLOW}○${NC} Google Chrome: 未检测到(浏览器自动化模式需要)"
fi
echo ""
echo "▸ 全局 npm 包"
check_node_module "crypto-js" "crypto-js"
check_node_module "axios" "axios"
check_node_module "node-forge" "node-forge"
check_node_module "jsdom" "jsdom"
echo ""
echo "▸ 可选工具"
check_command "curl" "curl" "系统自带"
check_command "jq" "jq" "brew install jq"
echo ""
echo "=========================================="
if [ $errors -gt 0 ]; then
echo -e " ${RED}检查完成: $errors 个必需依赖缺失${NC}"
echo ""
echo " 快速安装缺失依赖:"
echo " npm install -g crypto-js axios node-forge"
exit 1
else
echo -e " ${GREEN}检查完成: 环境就绪${NC}"
exit 0
fi
@@ -0,0 +1,291 @@
/**
* 加密算法识别器
* 根据密文特征自动识别可能的加密算法
*
* 使用方式:
* node crypto-identifier.js "e10adc3949ba59abbe56e057f20f883e"
* node crypto-identifier.js --file=captured_params.json
*/
const PATTERNS = [
{
name: 'MD5',
regex: /^[0-9a-f]{32}$/i,
description: '32位十六进制字符串',
length: 32,
charset: 'hex',
verify: '对比标准 MD5 输出。如果不一致,可能是自定义 MD5(如 chrsz=16)',
},
{
name: 'MD5 (大写)',
regex: /^[0-9A-F]{32}$/,
description: '32位大写十六进制',
length: 32,
charset: 'HEX',
},
{
name: 'SHA-1',
regex: /^[0-9a-f]{40}$/i,
description: '40位十六进制字符串',
length: 40,
charset: 'hex',
},
{
name: 'SHA-256',
regex: /^[0-9a-f]{64}$/i,
description: '64位十六进制字符串',
length: 64,
charset: 'hex',
},
{
name: 'SHA-512',
regex: /^[0-9a-f]{128}$/i,
description: '128位十六进制字符串',
length: 128,
charset: 'hex',
},
{
name: 'Base64',
regex: /^[A-Za-z0-9+/]+=*$/,
description: '标准 Base64 编码',
charset: 'base64',
verify: '尝试 atob() 解码,检查结果是否有意义',
},
{
name: 'Base64url',
regex: /^[A-Za-z0-9_-]+$/,
description: 'URL安全 Base64 编码(无填充)',
charset: 'base64url',
verify: '替换 -_ 为 +/ 后尝试解码',
},
{
name: 'AES-CBC/ECB (Base64)',
regex: /^[A-Za-z0-9+/]+=*$/,
test: (s) => {
const raw = Buffer.from(s, 'base64');
return raw.length > 0 && raw.length % 16 === 0;
},
description: 'Base64 编码,解码后长度为 16 的倍数',
verify: '需要找到 key (16/24/32字节) 和 iv (16字节,CBC模式)',
},
{
name: 'AES-CBC/ECB (Hex)',
regex: /^[0-9a-f]+$/i,
test: (s) => s.length >= 32 && s.length % 32 === 0,
description: '十六进制编码,长度为 32 的倍数',
verify: '需要找到 key 和 iv',
},
{
name: 'DES-ECB/CBC (Base64)',
regex: /^[A-Za-z0-9+/]+=*$/,
test: (s) => {
const raw = Buffer.from(s, 'base64');
return raw.length > 0 && raw.length % 8 === 0 && raw.length % 16 !== 0;
},
description: 'Base64 编码,解码后长度为 8 的倍数但不是 16 的倍数',
verify: '需要找到 8 字节 key',
},
{
name: 'RSA (Base64)',
regex: /^[A-Za-z0-9+/]+=*$/,
test: (s) => {
const raw = Buffer.from(s, 'base64');
return raw.length >= 64;
},
description: '较长的 Base64 字符串(64字节以上)',
verify: '需要找到公钥/私钥',
},
{
name: 'RSA (Hex)',
regex: /^[0-9a-f]+$/i,
test: (s) => s.length >= 128,
description: '较长的十六进制字符串',
verify: '搜索 RSA、publicKey、modulus 关键词',
},
{
name: 'HMAC-SHA256',
regex: /^[0-9a-f]{64}$/i,
description: '64位十六进制(与 SHA-256 相同格式)',
verify: '搜索 HMAC、createHmac、HmacSHA256 关键词',
},
{
name: 'UUID',
regex: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
description: 'UUID 格式',
},
{
name: '时间戳 (秒)',
regex: /^1[0-9]{9}$/,
description: '10位数字,Unix 时间戳(秒)',
},
{
name: '时间戳 (毫秒)',
regex: /^1[0-9]{12}$/,
description: '13位数字,Unix 时间戳(毫秒)',
},
{
name: 'JWT',
regex: /^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,
description: 'JSON Web Token(三段 Base64url)',
verify: '分别解码三段:Header.Payload.Signature',
},
{
name: '十六进制编码字符串',
regex: /^([0-9a-f]{2})+$/i,
test: (s) => s.length > 10 && s.length % 2 === 0,
description: '连续十六进制对',
verify: '尝试 Buffer.from(str, "hex").toString()',
},
];
function identify(ciphertext) {
const results = [];
const trimmed = ciphertext.trim();
for (const pattern of PATTERNS) {
if (pattern.regex.test(trimmed)) {
if (pattern.test && !pattern.test(trimmed)) continue;
if (pattern.length && trimmed.length !== pattern.length) continue;
results.push({
algorithm: pattern.name,
confidence: calculateConfidence(trimmed, pattern),
description: pattern.description,
verify: pattern.verify || '',
});
}
}
results.sort((a, b) => b.confidence - a.confidence);
return results;
}
function calculateConfidence(text, pattern) {
let score = 50;
if (pattern.length && text.length === pattern.length) score += 30;
if (pattern.charset === 'hex' && /^[0-9a-f]+$/i.test(text)) score += 10;
if (pattern.charset === 'base64' && text.endsWith('=')) score += 10;
if (pattern.test && pattern.test(text)) score += 20;
return Math.min(score, 95);
}
function analyzeMultiple(samples) {
const allResults = samples.map(s => ({ sample: s, matches: identify(s) }));
const lengthSet = new Set(samples.map(s => s.length));
const charsetConsistent = samples.every(s => /^[0-9a-f]+$/i.test(s)) ||
samples.every(s => /^[A-Za-z0-9+/]+=*$/.test(s));
const analysis = {
sampleCount: samples.length,
lengths: [...lengthSet],
fixedLength: lengthSet.size === 1,
charsetConsistent,
results: allResults,
};
if (analysis.fixedLength && analysis.charsetConsistent) {
analysis.recommendation = '所有样本长度相同且字符集一致,很可能是同一算法的不同输出';
} else if (!analysis.fixedLength) {
analysis.recommendation = '样本长度不一致,可能涉及变长输入或不同算法';
}
return analysis;
}
function formatReport(ciphertext) {
const results = identify(ciphertext);
let report = '';
report += `\n🔍 密文分析报告\n`;
report += `${'━'.repeat(50)}\n`;
report += `输入: ${ciphertext.substring(0, 80)}${ciphertext.length > 80 ? '...' : ''}\n`;
report += `长度: ${ciphertext.length}\n`;
report += `字符集: ${detectCharset(ciphertext)}\n`;
report += `${'━'.repeat(50)}\n`;
if (results.length === 0) {
report += `\n❌ 未识别到已知的加密模式\n`;
report += `建议:搜索 JS 源码中的加密函数名\n`;
} else {
report += `\n可能的算法(按可信度排序):\n\n`;
results.forEach((r, i) => {
report += ` ${i + 1}. ${r.algorithm} (${r.confidence}%)\n`;
report += ` 描述: ${r.description}\n`;
if (r.verify) report += ` 验证: ${r.verify}\n`;
report += `\n`;
});
}
// Base64 解码尝试
if (/^[A-Za-z0-9+/]+=*$/.test(ciphertext)) {
try {
const decoded = Buffer.from(ciphertext, 'base64');
report += `📦 Base64 解码结果:\n`;
report += ` 二进制长度: ${decoded.length} 字节\n`;
if (decoded.length % 16 === 0) report += ` ⚠ 长度是 16 的倍数(可能是 AES)\n`;
if (decoded.length % 8 === 0) report += ` ⚠ 长度是 8 的倍数(可能是 DES)\n`;
const textAttempt = decoded.toString('utf-8');
if (/^[\x20-\x7e]+$/.test(textAttempt)) {
report += ` 文本: ${textAttempt.substring(0, 100)}\n`;
} else {
report += ` 十六进制: ${decoded.toString('hex').substring(0, 100)}\n`;
}
} catch (e) {}
}
return report;
}
function detectCharset(str) {
if (/^[0-9]+$/.test(str)) return '纯数字';
if (/^[0-9a-f]+$/i.test(str)) return '十六进制';
if (/^[A-Za-z0-9+/]+=*$/.test(str)) return 'Base64';
if (/^[A-Za-z0-9_-]+$/.test(str)) return 'Base64url / 字母数字';
if (/^[\x20-\x7e]+$/.test(str)) return '可打印ASCII';
return '混合/二进制';
}
// CLI
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('用法:');
console.log(' node crypto-identifier.js "<密文>"');
console.log(' node crypto-identifier.js --file=params.json');
console.log(' node crypto-identifier.js --compare "<样本1>" "<样本2>" ...');
process.exit(0);
}
if (args[0] === '--compare') {
const samples = args.slice(1);
const analysis = analyzeMultiple(samples);
console.log('\n📊 多样本对比分析');
console.log('━'.repeat(50));
console.log(`样本数量: ${analysis.sampleCount}`);
console.log(`长度分布: ${analysis.lengths.join(', ')}`);
console.log(`固定长度: ${analysis.fixedLength ? '是' : '否'}`);
console.log(`字符集一致: ${analysis.charsetConsistent ? '是' : '否'}`);
if (analysis.recommendation) console.log(`\n💡 ${analysis.recommendation}`);
analysis.results.forEach((r, i) => {
console.log(`\n--- 样本 ${i + 1}: ${r.sample.substring(0, 40)}... ---`);
r.matches.slice(0, 3).forEach(m => {
console.log(` ${m.algorithm} (${m.confidence}%)`);
});
});
} else if (args[0].startsWith('--file=')) {
const fs = require('fs');
const data = JSON.parse(fs.readFileSync(args[0].substring(7), 'utf-8'));
const samples = Array.isArray(data) ? data : Object.values(data);
samples.forEach((s, i) => {
console.log(formatReport(String(s)));
});
} else {
console.log(formatReport(args[0]));
}
}
module.exports = { identify, analyzeMultiple, formatReport };
@@ -0,0 +1,346 @@
/**
* Hook 代码生成器
* 根据逆向目标快速生成 Hook 注入代码
*
* 使用方式:
* node hook-generator.js --type=cookie
* node hook-generator.js --type=xhr --target="/api/data"
* node hook-generator.js --type=all
* node hook-generator.js --type=custom --function="encrypt" --module="CryptoJS"
*/
const HOOK_TEMPLATES = {
cookie: (options = {}) => {
const target = options.target || '';
return `(function() {
var _cookie = document.cookie || '';
Object.defineProperty(document, 'cookie', {
set: function(val) {
${target ? `if (val.indexOf('${target}') !== -1) {` : '{'}
console.log('[Hook:Cookie] Set:', val);
console.trace('[Hook:Cookie] 调用栈');
}
var parts = val.split(';');
var mainPart = parts[0];
if (_cookie) _cookie += '; ';
_cookie += mainPart;
},
get: function() { return _cookie; },
configurable: true
});
console.log('[Hook:Cookie] Cookie Hook 已注入');
})();`;
},
xhr: (options = {}) => {
const target = options.target || '';
return `(function() {
var _open = XMLHttpRequest.prototype.open;
var _send = XMLHttpRequest.prototype.send;
var _setHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.open = function(method, url) {
this.__hookMethod = method;
this.__hookUrl = url;
this.__hookHeaders = {};
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) {
${target ? `if (this.__hookUrl && this.__hookUrl.indexOf('${target}') !== -1) {` : '{'}
console.log('[Hook:XHR] ★', this.__hookMethod, this.__hookUrl);
console.log('[Hook:XHR] Headers:', JSON.stringify(this.__hookHeaders));
console.log('[Hook:XHR] Body:', body);
console.trace('[Hook:XHR] 调用栈');
}
return _send.apply(this, arguments);
};
console.log('[Hook:XHR] XHR Hook 已注入');
})();`;
},
fetch: (options = {}) => {
const target = options.target || '';
return `(function() {
var _fetch = window.fetch;
window.fetch = function(input, init) {
var url = typeof input === 'string' ? input : (input.url || '');
${target ? `if (url.indexOf('${target}') !== -1) {` : '{'}
console.log('[Hook:Fetch] ★', url);
console.log('[Hook:Fetch] Options:', JSON.stringify(init || {}));
console.trace('[Hook:Fetch] 调用栈');
}
return _fetch.apply(this, arguments);
};
console.log('[Hook:Fetch] Fetch Hook 已注入');
})();`;
},
eval: () => {
return `(function() {
var _eval = window.eval;
window.eval = function(code) {
console.log('[Hook:eval] 代码长度:', typeof code === 'string' ? code.length : 'N/A');
if (typeof code === 'string' && code.length < 10000) {
console.log('[Hook:eval] 内容:', code.substring(0, 500));
}
return _eval.apply(this, arguments);
};
var _Function = Function;
window.Function = function() {
var body = arguments[arguments.length - 1];
console.log('[Hook:Function] body长度:', body ? body.length : 0);
if (body && body.length < 5000) {
console.log('[Hook:Function] 内容:', body.substring(0, 500));
}
return _Function.apply(this, arguments);
};
window.Function.prototype = _Function.prototype;
console.log('[Hook:eval] eval/Function Hook 已注入');
})();`;
},
json: () => {
return `(function() {
var _parse = JSON.parse;
var _stringify = JSON.stringify;
JSON.parse = function(text) {
var result = _parse.apply(this, arguments);
console.log('[Hook:JSON] parse:', typeof text === 'string' ? text.substring(0, 200) : typeof text);
return result;
};
JSON.stringify = function(obj) {
var result = _stringify.apply(this, arguments);
console.log('[Hook:JSON] stringify:', result ? result.substring(0, 200) : result);
return result;
};
console.log('[Hook:JSON] JSON Hook 已注入');
})();`;
},
base64: () => {
return `(function() {
var _atob = window.atob;
var _btoa = window.btoa;
window.atob = function(str) {
var result = _atob(str);
console.log('[Hook:Base64] atob:', str.substring(0, 80), '→', result.substring(0, 80));
return result;
};
window.btoa = function(str) {
var result = _btoa(str);
console.log('[Hook:Base64] btoa:', str.substring(0, 80), '→', result.substring(0, 80));
return result;
};
console.log('[Hook:Base64] Base64 Hook 已注入');
})();`;
},
websocket: () => {
return `(function() {
var _WS = window.WebSocket;
window.WebSocket = function(url, protocols) {
console.log('[Hook:WS] 连接:', url);
var ws = new _WS(url, protocols);
var _send = ws.send.bind(ws);
ws.send = function(data) {
console.log('[Hook:WS] 发送:', typeof data === 'string' ? data.substring(0, 200) : '[binary]');
return _send(data);
};
ws.addEventListener('message', function(e) {
console.log('[Hook:WS] 接收:', typeof e.data === 'string' ? e.data.substring(0, 200) : '[binary]');
});
return ws;
};
window.WebSocket.prototype = _WS.prototype;
console.log('[Hook:WS] WebSocket Hook 已注入');
})();`;
},
debugger_bypass: () => {
return `(function() {
var _Function = Function;
Function = function() {
var body = arguments[arguments.length - 1];
if (typeof body === 'string' && body.indexOf('debugger') !== -1) {
arguments[arguments.length - 1] = body.replace(/debugger\\s*;?/g, '');
}
return _Function.apply(this, arguments);
};
Function.prototype = _Function.prototype;
var _si = window.setInterval;
window.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 _si.apply(this, arguments);
};
var _st = window.setTimeout;
window.setTimeout = 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 _st.apply(this, arguments);
};
console.log('[Hook:AntiDebug] debugger 绕过已注入');
})();`;
},
stealth: () => {
return `(function() {
Object.defineProperty(navigator, 'webdriver', { get: function() { return undefined; }, configurable: true });
window.chrome = window.chrome || {};
window.chrome.runtime = window.chrome.runtime || {};
Object.defineProperty(navigator, 'plugins', {
get: function() {
return [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
{ name: 'Native Client', filename: 'internal-nacl-plugin' }
];
}
});
Object.defineProperty(navigator, 'languages', {
get: function() { return ['zh-CN', 'zh', 'en']; }
});
var _getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Intel Inc.';
if (param === 37446) return 'Intel Iris OpenGL Engine';
return _getParameter.apply(this, arguments);
};
console.log('[Hook:Stealth] 隐身模式已注入');
})();`;
},
custom: (options = {}) => {
const funcName = options.function || 'targetFunction';
return `(function() {
// 追踪 ${funcName} 函数
if (typeof ${funcName} !== 'undefined') {
var _orig = ${funcName};
${funcName} = function() {
console.log('[Hook:Custom] ${funcName} 调用');
console.log('[Hook:Custom] 参数:', JSON.stringify(Array.from(arguments)));
var result = _orig.apply(this, arguments);
console.log('[Hook:Custom] 返回:', JSON.stringify(result));
console.trace('[Hook:Custom] 调用栈');
return result;
};
console.log('[Hook:Custom] ${funcName} Hook 已注入');
} else {
console.warn('[Hook:Custom] ${funcName} 未定义,等待加载...');
// 使用 getter 延迟 Hook
var _origVal;
Object.defineProperty(window, '${funcName}', {
get: function() { return _origVal; },
set: function(val) {
if (typeof val === 'function') {
var _fn = val;
_origVal = function() {
console.log('[Hook:Custom] ${funcName} 调用');
console.log('[Hook:Custom] 参数:', JSON.stringify(Array.from(arguments)));
var result = _fn.apply(this, arguments);
console.log('[Hook:Custom] 返回:', JSON.stringify(result));
return result;
};
} else {
_origVal = val;
}
},
configurable: true
});
}
})();`;
},
};
function generateHook(type, options = {}) {
const generator = HOOK_TEMPLATES[type];
if (!generator) {
throw new Error(`未知的 Hook 类型: ${type}。可用类型: ${Object.keys(HOOK_TEMPLATES).join(', ')}`);
}
return generator(options);
}
function generateAllHooks(options = {}) {
const hooks = [
HOOK_TEMPLATES.debugger_bypass(),
HOOK_TEMPLATES.stealth(),
HOOK_TEMPLATES.cookie(options),
HOOK_TEMPLATES.xhr(options),
HOOK_TEMPLATES.fetch(options),
HOOK_TEMPLATES.eval(),
HOOK_TEMPLATES.base64(),
];
return hooks.join('\n\n');
}
// CLI
if (require.main === module) {
const args = process.argv.slice(2);
const options = {};
args.forEach(arg => {
if (arg.startsWith('--type=')) options.type = arg.substring(7);
else if (arg.startsWith('--target=')) options.target = arg.substring(9);
else if (arg.startsWith('--function=')) options.function = arg.substring(11);
else if (arg.startsWith('--output=')) options.output = arg.substring(9);
});
if (!options.type) {
console.log('用法: node hook-generator.js --type=<type> [options]');
console.log('');
console.log('类型:');
console.log(' cookie Cookie setter 拦截');
console.log(' xhr XMLHttpRequest 拦截');
console.log(' fetch Fetch API 拦截');
console.log(' eval eval/Function 拦截');
console.log(' json JSON.parse/stringify 拦截');
console.log(' base64 atob/btoa 拦截');
console.log(' websocket WebSocket 拦截');
console.log(' debugger_bypass debugger 反调试绕过');
console.log(' stealth 浏览器指纹隐身');
console.log(' custom 自定义函数拦截');
console.log(' all 生成所有 Hook(组合包)');
console.log('');
console.log('选项:');
console.log(' --target=<url> 目标接口路径过滤');
console.log(' --function=<name> 自定义函数名(type=custom 时)');
console.log(' --output=<file> 输出到文件');
process.exit(0);
}
let code;
if (options.type === 'all') {
code = generateAllHooks(options);
} else {
code = generateHook(options.type, options);
}
if (options.output) {
const fs = require('fs');
fs.writeFileSync(options.output, code);
console.log(`Hook 代码已保存到: ${options.output}`);
} else {
console.log(code);
}
}
module.exports = { generateHook, generateAllHooks, HOOK_TEMPLATES };
@@ -0,0 +1,359 @@
/**
* VM 沙箱执行器
* 用于在隔离的 Node.js 沙箱中执行从浏览器提取的 JS 代码
*
* 使用方式:
* node sandbox-runner.js <js-file> [options]
* node sandbox-runner.js code.js --url=https://target.com --extract-cookie
*/
const vm = require('vm');
const fs = require('fs');
const path = require('path');
class SandboxRunner {
constructor(options = {}) {
this.options = {
url: options.url || 'https://example.com',
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',
timeout: options.timeout || 10000,
extractCookie: options.extractCookie || false,
verbose: options.verbose || false,
...options,
};
this.cookies = {};
this.logs = [];
this.errors = [];
}
createSandbox() {
const self = this;
const urlObj = new URL(this.options.url);
const sandbox = {
window: null,
self: null,
globalThis: null,
top: null,
parent: null,
frames: null,
document: this._createDocument(),
navigator: this._createNavigator(),
location: this._createLocation(urlObj),
screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1055, colorDepth: 24, pixelDepth: 24 },
history: { length: 1, pushState: () => {}, replaceState: () => {}, back: () => {}, forward: () => {} },
setTimeout: (fn, ms) => setTimeout(fn, Math.min(ms || 0, 5000)),
setInterval: (fn, ms) => setInterval(fn, Math.max(ms || 100, 100)),
clearTimeout, clearInterval,
requestAnimationFrame: (fn) => setTimeout(fn, 16),
cancelAnimationFrame: clearTimeout,
String, Array, Object, Math, Date, RegExp, JSON, Map, Set, WeakMap, WeakSet,
Number, Boolean, Symbol, BigInt,
parseInt, parseFloat, isNaN, isFinite, NaN, Infinity, undefined,
encodeURIComponent, decodeURIComponent, encodeURI, decodeURI,
escape, unescape,
Error, TypeError, RangeError, SyntaxError, ReferenceError, URIError, EvalError,
ArrayBuffer, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array,
Float32Array, Float64Array, DataView,
Promise, Proxy, Reflect,
TextEncoder: typeof TextEncoder !== 'undefined' ? TextEncoder : class {},
TextDecoder: typeof TextDecoder !== 'undefined' ? TextDecoder : class {},
URL: typeof URL !== 'undefined' ? URL : class {},
URLSearchParams: typeof URLSearchParams !== 'undefined' ? URLSearchParams : class {},
btoa: (str) => Buffer.from(str, 'binary').toString('base64'),
atob: (b64) => Buffer.from(b64, 'base64').toString('binary'),
console: {
log: (...args) => { self.logs.push(args.join(' ')); if (self.options.verbose) console.log('[Sandbox]', ...args); },
warn: (...args) => { self.logs.push('[WARN] ' + args.join(' ')); },
error: (...args) => { self.errors.push(args.join(' ')); if (self.options.verbose) console.error('[Sandbox Error]', ...args); },
info: (...args) => { self.logs.push('[INFO] ' + args.join(' ')); },
debug: () => {},
trace: () => {},
clear: () => {},
},
XMLHttpRequest: this._createXHR(),
fetch: async () => new Response('{}'),
Event: class Event { constructor(type) { this.type = type; } },
CustomEvent: class CustomEvent { constructor(type, opts) { this.type = type; this.detail = opts?.detail; } },
MutationObserver: class MutationObserver { observe() {} disconnect() {} },
IntersectionObserver: class IntersectionObserver { observe() {} disconnect() {} },
ResizeObserver: class ResizeObserver { observe() {} disconnect() {} },
};
sandbox.window = sandbox;
sandbox.self = sandbox;
sandbox.globalThis = sandbox;
sandbox.top = sandbox;
sandbox.parent = sandbox;
sandbox.frames = sandbox;
// Cookie 拦截
if (this.options.extractCookie) {
this._setupCookieTrap(sandbox);
}
return sandbox;
}
_createDocument() {
const createElement = (tag) => ({
tagName: tag.toUpperCase(),
style: new Proxy({}, { get: () => '', set: () => true }),
classList: { add: () => {}, remove: () => {}, contains: () => false },
setAttribute: () => {},
getAttribute: () => null,
removeAttribute: () => {},
appendChild: (child) => child,
removeChild: () => {},
insertBefore: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true,
innerHTML: '', innerText: '', textContent: '',
src: '', href: '', id: '', className: '',
parentNode: null, parentElement: null,
childNodes: [], children: [], firstChild: null, lastChild: null,
nextSibling: null, previousSibling: null,
offsetWidth: 100, offsetHeight: 100,
getBoundingClientRect: () => ({ top: 0, left: 0, bottom: 100, right: 100, width: 100, height: 100 }),
querySelector: () => null,
querySelectorAll: () => [],
getElementsByTagName: () => [],
getElementsByClassName: () => [],
cloneNode: () => createElement(tag),
});
return {
cookie: '',
createElement,
createElementNS: (ns, tag) => createElement(tag),
createTextNode: (text) => ({ nodeType: 3, textContent: text }),
createDocumentFragment: () => ({ appendChild: () => {}, childNodes: [] }),
getElementById: () => null,
getElementsByTagName: (tag) => tag === 'script' ? [1, 2, 3] : [],
getElementsByClassName: () => [],
getElementsByName: () => [],
querySelector: () => null,
querySelectorAll: () => [],
addEventListener: () => {},
removeEventListener: () => {},
head: { appendChild: () => {} },
body: { appendChild: () => {}, innerHTML: '' },
documentElement: { style: {} },
readyState: 'complete',
title: '',
referrer: '',
domain: '',
URL: this.options.url,
characterSet: 'UTF-8',
contentType: 'text/html',
};
}
_createNavigator() {
return {
userAgent: this.options.userAgent,
appCodeName: 'Mozilla',
appName: 'Netscape',
appVersion: '5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
platform: 'MacIntel',
product: 'Gecko',
productSub: '20030107',
vendor: 'Google Inc.',
vendorSub: '',
language: 'zh-CN',
languages: ['zh-CN', 'zh', 'en'],
cookieEnabled: true,
onLine: true,
doNotTrack: null,
hardwareConcurrency: 8,
maxTouchPoints: 0,
webdriver: false,
plugins: { length: 3, item: () => null, namedItem: () => null, refresh: () => {} },
mimeTypes: { length: 4, item: () => null, namedItem: () => null },
permissions: { query: async () => ({ state: 'granted' }) },
mediaDevices: { enumerateDevices: async () => [] },
geolocation: { getCurrentPosition: () => {} },
connection: { effectiveType: '4g', downlink: 10 },
getBattery: async () => ({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 }),
};
}
_createLocation(urlObj) {
return {
href: urlObj.href,
protocol: urlObj.protocol,
host: urlObj.host,
hostname: urlObj.hostname,
port: urlObj.port,
pathname: urlObj.pathname,
search: urlObj.search,
hash: urlObj.hash,
origin: urlObj.origin,
assign: () => {},
replace: () => {},
reload: () => {},
toString: () => urlObj.href,
};
}
_createXHR() {
return class XMLHttpRequest {
constructor() {
this.readyState = 0;
this.status = 0;
this.statusText = '';
this.responseText = '';
this.response = '';
this.responseType = '';
this.withCredentials = false;
}
open() { this.readyState = 1; }
setRequestHeader() {}
send() {
this.readyState = 4;
this.status = 200;
this.responseText = '{}';
this.response = '{}';
if (this.onreadystatechange) this.onreadystatechange();
if (this.onload) this.onload();
}
addEventListener(e, fn) { this['on' + e] = fn; }
abort() {}
getResponseHeader() { return null; }
getAllResponseHeaders() { return ''; }
};
}
_setupCookieTrap(sandbox) {
const self = this;
const originalCookie = sandbox.document.cookie || '';
Object.defineProperty(sandbox.document, 'cookie', {
get() {
return Object.entries(self.cookies)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
},
set(val) {
const mainPart = val.split(';')[0];
const eqIdx = mainPart.indexOf('=');
if (eqIdx > 0) {
const name = mainPart.substring(0, eqIdx).trim();
const value = mainPart.substring(eqIdx + 1).trim();
self.cookies[name] = value;
if (self.options.verbose) {
console.log(`[Cookie Trap] ${name}=${value}`);
}
}
},
configurable: true,
});
if (originalCookie) {
originalCookie.split(';').forEach(pair => {
const [k, ...rest] = pair.trim().split('=');
if (k) self.cookies[k.trim()] = rest.join('=');
});
}
}
run(code, filename = 'sandbox.js') {
const sandbox = this.createSandbox();
vm.createContext(sandbox);
try {
const result = vm.runInContext(code, sandbox, {
timeout: this.options.timeout,
filename,
displayErrors: true,
});
return {
success: true,
result,
cookies: { ...this.cookies },
logs: [...this.logs],
errors: [...this.errors],
sandbox,
};
} catch (e) {
return {
success: false,
error: e.message,
stack: e.stack,
cookies: { ...this.cookies },
logs: [...this.logs],
errors: [...this.errors],
};
}
}
runFile(filePath) {
const code = fs.readFileSync(filePath, 'utf-8');
return this.run(code, path.basename(filePath));
}
}
// CLI 入口
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('用法: node sandbox-runner.js <js-file> [options]');
console.log('');
console.log('选项:');
console.log(' --url=<url> 设置 location.href');
console.log(' --extract-cookie 启用 Cookie 拦截');
console.log(' --verbose 输出详细日志');
console.log(' --timeout=<ms> 执行超时(默认 10000)');
process.exit(0);
}
const filePath = args[0];
const options = {};
args.slice(1).forEach(arg => {
if (arg === '--extract-cookie') options.extractCookie = true;
else if (arg === '--verbose') options.verbose = true;
else if (arg.startsWith('--url=')) options.url = arg.substring(6);
else if (arg.startsWith('--timeout=')) options.timeout = parseInt(arg.substring(10));
});
const runner = new SandboxRunner(options);
const result = runner.runFile(filePath);
console.log('\n========== 执行结果 ==========');
console.log('状态:', result.success ? '成功' : '失败');
if (!result.success) {
console.log('错误:', result.error);
}
if (Object.keys(result.cookies).length > 0) {
console.log('\nCookies:');
Object.entries(result.cookies).forEach(([k, v]) => {
console.log(` ${k} = ${v}`);
});
}
if (result.logs.length > 0) {
console.log('\n日志:');
result.logs.forEach(log => console.log(` ${log}`));
}
if (result.errors.length > 0) {
console.log('\n错误日志:');
result.errors.forEach(err => console.log(` ${err}`));
}
}
module.exports = { SandboxRunner };
@@ -0,0 +1,170 @@
/**
* 主脚本模板 - 浏览器自动化
*
* 适用场景:TLS 指纹检测、复杂环境依赖、window 蜜罐
* 使用 Playwright 控制真实 Chrome 浏览器执行请求
*
* TODO: 根据逆向分析结果修改配置和逻辑
*/
const { chromium } = require('playwright-core');
// ============ 配置区域 ============
const CONFIG = {
name: '示例项目 - 浏览器自动化',
description: '采集全部5页数据',
// 目标URL
pageURL: 'https://target.com/page',
// Chrome 路径(macOS 默认路径)
chromePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
// Linux: '/usr/bin/google-chrome'
// Windows: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
// 是否显示浏览器
headless: false,
totalPages: 5,
delay: 2000,
// 登录 Cookie
cookies: [
// { name: 'sessionid', value: 'xxx', domain: 'target.com', path: '/' },
],
// 注入脚本(可选)
injectScript: null,
};
// ============ 主逻辑 ============
async function main() {
console.log(`[*] 项目:${CONFIG.name}`);
console.log(`[*] 目标:${CONFIG.description}`);
console.log('');
// 启动浏览器
console.log('[*] 启动浏览器...');
const browser = await chromium.launch({
executablePath: CONFIG.chromePath,
headless: CONFIG.headless,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
],
});
const context = await browser.newContext({
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',
viewport: { width: 1920, height: 1080 },
locale: 'zh-CN',
});
// 设置 Cookie
if (CONFIG.cookies.length > 0) {
await context.addCookies(CONFIG.cookies);
}
// 隐藏自动化特征
await context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
window.chrome = { runtime: {} };
});
// 注入自定义脚本
if (CONFIG.injectScript) {
await context.addInitScript(CONFIG.injectScript);
}
const page = await context.newPage();
try {
// 导航到目标页面
console.log('[*] 打开目标页面...');
await page.goto(CONFIG.pageURL, { waitUntil: 'networkidle', timeout: 30000 });
console.log('[+] 页面加载完成');
// 采集数据
const allData = [];
for (let pageNum = 1; pageNum <= CONFIG.totalPages; pageNum++) {
// TODO: 根据实际页面结构修改数据提取逻辑
const pageData = await extractPageData(page, pageNum);
allData.push(...pageData);
console.log(`[+] 正在采集第 ${pageNum}/${CONFIG.totalPages} 页... ✓ 获取 ${pageData.length} 条数据`);
if (pageNum < CONFIG.totalPages) {
// 翻页
await goToNextPage(page, pageNum + 1);
await sleep(CONFIG.delay);
}
}
console.log(`\n[+] 采集完成,共 ${allData.length} 条数据`);
// 计算结果
const result = allData.reduce((sum, val) => sum + val, 0);
console.log('\n' + '='.repeat(30) + ' 计算结果 ' + '='.repeat(30));
console.log(`答案:${result}`);
console.log('='.repeat(70));
} catch (error) {
console.error('[!] 错误:', error.message);
await page.screenshot({ path: 'error_screenshot.png' });
console.log('[*] 已保存错误截图: error_screenshot.png');
} finally {
await browser.close();
}
}
/**
* 从页面提取数据
* TODO: 根据实际页面结构修改
*/
async function extractPageData(page, pageNum) {
// 方式1:从页面DOM提取
const data = await page.evaluate(() => {
const rows = document.querySelectorAll('.data-row');
return Array.from(rows).map(row => {
const value = row.querySelector('.value')?.textContent?.trim();
return parseFloat(value) || 0;
});
});
return data;
// 方式2:拦截网络响应
// const responsePromise = page.waitForResponse(resp => resp.url().includes('/api/data'));
// // 触发数据加载...
// const response = await responsePromise;
// const json = await response.json();
// return json.data;
}
/**
* 翻到下一页
* TODO: 根据实际页面结构修改翻页逻辑
*/
async function goToNextPage(page, targetPage) {
// 方式1:点击翻页按钮
await page.click(`.pagination a:has-text("${targetPage}")`);
await page.waitForLoadState('networkidle');
// 方式2:等待特定元素出现
// await page.waitForSelector('.data-loaded');
// 方式3:直接导航到带页码的URL
// await page.goto(`${CONFIG.pageURL}?page=${targetPage}`, { waitUntil: 'networkidle' });
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
main().catch(error => {
console.error('\n[!] 运行错误:', error.message);
process.exit(1);
});
@@ -0,0 +1,13 @@
{
"name": "js-reverse-browser-auto",
"version": "1.0.0",
"description": "JS逆向 - 浏览器自动化模板",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "node main.js --test"
},
"dependencies": {
"playwright-core": "^1.45.0"
}
}
@@ -0,0 +1,121 @@
/**
* 主脚本模板 - 纯 Node.js 算法还原
*
* 适用场景:加密逻辑可完整提取,无浏览器环境依赖
*
* TODO: 根据逆向分析结果修改以下配置和逻辑
*/
const { generateM, generateSign } = require('./utils/encrypt');
const { RequestClient } = require('./utils/request');
// ============ 配置区域 ============
const CONFIG = {
// 目标信息
name: '示例项目',
description: '采集全部5页数据,计算数值总和',
// 接口配置
baseURL: 'https://target.com',
apiPath: '/api/data',
referer: 'https://target.com/page',
// 采集配置
totalPages: 5,
delay: 1500,
// 认证信息
cookies: {
// sessionid: 'your_session_id',
},
// 自定义请求头
headers: {
// 'X-Custom-Header': 'value',
},
};
// ============ 主逻辑 ============
async function fetchPage(client, page) {
// TODO: 根据实际接口修改参数构造逻辑
const m = generateM(page);
const timestamp = Math.floor(Date.now() / 1000);
const params = {
page,
m,
t: timestamp,
};
const response = await client.requestWithDelay({
method: 'GET',
url: CONFIG.apiPath,
params,
});
return response.data;
}
function extractData(responseData) {
// TODO: 根据实际响应格式修改数据提取逻辑
if (responseData && responseData.data) {
return responseData.data;
}
return [];
}
function calculateResult(allData) {
// TODO: 根据题目要求修改计算逻辑
// 示例:求所有数值的总和
const sum = allData.reduce((acc, item) => {
const value = typeof item === 'number' ? item : (item.value || 0);
return acc + value;
}, 0);
return sum;
}
async function main() {
console.log(`[*] 项目:${CONFIG.name}`);
console.log(`[*] 目标:${CONFIG.description}`);
console.log('');
const client = new RequestClient({
baseURL: CONFIG.baseURL,
cookies: CONFIG.cookies,
headers: CONFIG.headers,
referer: CONFIG.referer,
delay: CONFIG.delay,
});
const allData = [];
for (let page = 1; page <= CONFIG.totalPages; page++) {
try {
const responseData = await fetchPage(client, page);
const pageData = extractData(responseData);
allData.push(...pageData);
console.log(`[+] 正在采集第 ${page}/${CONFIG.totalPages} 页... ✓ 获取 ${pageData.length} 条数据`);
} catch (error) {
console.error(`[-] 第 ${page} 页采集失败: ${error.message}`);
if (error.response) {
console.error(` 状态码: ${error.response.status}`);
console.error(` 响应: ${JSON.stringify(error.response.data).substring(0, 200)}`);
}
}
}
console.log(`\n[+] 采集完成,共 ${allData.length} 条数据`);
const result = calculateResult(allData);
console.log('\n' + '='.repeat(30) + ' 计算结果 ' + '='.repeat(30));
console.log(`答案:${result}`);
console.log('='.repeat(70));
}
main().catch(error => {
console.error('\n[!] 运行错误:', error.message);
process.exit(1);
});
@@ -0,0 +1,14 @@
{
"name": "js-reverse-project",
"version": "1.0.0",
"description": "JS逆向 - 纯Node.js算法还原模板",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "node main.js --test"
},
"dependencies": {
"axios": "^1.7.0",
"crypto-js": "^4.2.0"
}
}
@@ -0,0 +1,71 @@
/**
* 加密参数生成模块
* 根据逆向分析还原的加密逻辑,生成请求所需的加密参数
*/
const crypto = require('crypto');
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
function sha256(str) {
return crypto.createHash('sha256').update(str).digest('hex');
}
function hmacSha256(message, secret) {
return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
function aesEncrypt(plaintext, key, iv, mode = 'cbc') {
const algorithm = `aes-${key.length * 8}-${mode}`;
const cipher = crypto.createCipheriv(algorithm, key, mode === 'ecb' ? null : iv);
cipher.setAutoPadding(true);
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function aesDecrypt(ciphertext, key, iv, mode = 'cbc') {
const algorithm = `aes-${key.length * 8}-${mode}`;
const decipher = crypto.createDecipheriv(algorithm, key, mode === 'ecb' ? null : iv);
decipher.setAutoPadding(true);
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* 生成请求签名
* TODO: 根据实际逆向分析修改此函数
* @param {Object} params - 请求参数
* @returns {string} 签名值
*/
function generateSign(params) {
const { page, timestamp } = params;
// 示例:sign = md5(page + "|" + timestamp + "|" + SECRET)
const SECRET = 'your_secret_here';
return md5(`${page}|${timestamp}|${SECRET}`);
}
/**
* 生成加密参数 m
* TODO: 根据实际逆向分析修改此函数
* @param {number} page - 页码
* @returns {string} 加密后的参数
*/
function generateM(page) {
const timestamp = Math.floor(Date.now() / 1000);
const sign = generateSign({ page, timestamp });
return `${sign}|${timestamp}`;
}
module.exports = {
md5,
sha256,
hmacSha256,
aesEncrypt,
aesDecrypt,
generateSign,
generateM,
};
@@ -0,0 +1,128 @@
/**
* 请求封装模块
* 封装 HTTP 请求,处理 Headers、Cookie、重试、频率控制
*/
const axios = require('axios');
const DEFAULT_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',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'X-Requested-With': 'XMLHttpRequest',
};
class RequestClient {
constructor(options = {}) {
this.baseURL = options.baseURL || '';
this.cookies = options.cookies || {};
this.headers = { ...DEFAULT_HEADERS, ...options.headers };
this.delay = options.delay || 1000;
this.maxRetries = options.maxRetries || 3;
this.referer = options.referer || '';
this.client = axios.create({
baseURL: this.baseURL,
timeout: options.timeout || 30000,
headers: this.headers,
maxRedirects: 5,
});
}
getCookieString() {
return Object.entries(this.cookies)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
setCookie(name, value) {
this.cookies[name] = value;
}
parseCookies(setCookieHeader) {
if (!setCookieHeader) return;
const headers = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
headers.forEach(h => {
const mainPart = h.split(';')[0];
const eqIdx = mainPart.indexOf('=');
if (eqIdx > 0) {
this.cookies[mainPart.substring(0, eqIdx).trim()] = mainPart.substring(eqIdx + 1).trim();
}
});
}
async request(config) {
const finalConfig = {
...config,
headers: {
...this.headers,
...config.headers,
'Cookie': this.getCookieString(),
},
};
if (this.referer) {
finalConfig.headers['Referer'] = this.referer;
}
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await this.client.request(finalConfig);
this.parseCookies(response.headers['set-cookie']);
return response;
} catch (error) {
lastError = error;
const status = error.response?.status;
console.error(`[Request] 第 ${attempt}/${this.maxRetries} 次请求失败: ${status || error.message}`);
if (status === 429) {
const waitTime = this.delay * attempt * 2;
console.log(`[Request] 频率限制,等待 ${waitTime}ms...`);
await this.sleep(waitTime);
} else if (status >= 500) {
await this.sleep(this.delay * attempt);
} else if (status === 403 || status === 412) {
console.error('[Request] 访问被拒绝,可能需要更新 Cookie 或检查加密参数');
throw error;
} else {
throw error;
}
}
}
throw lastError;
}
async get(url, params = {}, extraHeaders = {}) {
return this.request({
method: 'GET',
url,
params,
headers: extraHeaders,
});
}
async post(url, data = {}, extraHeaders = {}) {
return this.request({
method: 'POST',
url,
data,
headers: extraHeaders,
});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async requestWithDelay(config) {
const response = await this.request(config);
const jitter = Math.random() * this.delay * 0.5;
await this.sleep(this.delay + jitter);
return response;
}
}
module.exports = { RequestClient, DEFAULT_HEADERS };
@@ -0,0 +1,117 @@
/**
* 主脚本模板 - VM 沙箱执行
*
* 适用场景:服务端返回混淆JS用于生成Cookie/Token
* 流程:
* 1. 请求接口 → 返回混淆 JS
* 2. VM 沙箱执行 JS → 提取 Cookie
* 3. 携带 Cookie 请求数据接口
*
* TODO: 根据逆向分析结果修改配置和逻辑
*/
const { executeAndExtractCookie } = require('./utils/sandbox');
const { TwoPhaseClient } = require('./utils/request');
// ============ 配置区域 ============
const CONFIG = {
name: '示例项目 - 动态Cookie',
description: '采集全部5页数据',
baseURL: 'https://target.com',
// 第一阶段:获取动态JS的接口
jsEndpoint: '/api/init',
// 第二阶段:数据接口
dataEndpoint: '/api/data',
referer: 'https://target.com/page',
totalPages: 5,
delay: 2000,
cookies: {
// sessionid: 'your_session_id',
},
};
// ============ 主逻辑 ============
async function getDynamicCookie(client) {
console.log('[*] 获取动态Cookie...');
// 第一阶段:请求返回JS代码的接口
const jsCode = await client.fetchDynamicJS(CONFIG.jsEndpoint);
// 在VM沙箱中执行,提取Cookie
const result = executeAndExtractCookie(jsCode, {
url: CONFIG.baseURL + CONFIG.referer,
initialCookie: client.getCookieString(),
});
if (result.success && Object.keys(result.cookies).length > 0) {
console.log(`[+] Cookie 提取成功: ${result.cookieString.substring(0, 100)}...`);
client.setCookies(result.cookies);
return true;
} else {
console.error('[-] Cookie 提取失败:', result.error || '未生成Cookie');
return false;
}
}
async function fetchPage(client, page) {
// TODO: 根据实际接口修改参数
const params = { page };
return client.fetchWithDelay(CONFIG.dataEndpoint, params);
}
async function main() {
console.log(`[*] 项目:${CONFIG.name}`);
console.log(`[*] 目标:${CONFIG.description}`);
console.log('');
const client = new TwoPhaseClient({
baseURL: CONFIG.baseURL,
cookies: CONFIG.cookies,
delay: CONFIG.delay,
});
// 获取动态Cookie
const cookieOk = await getDynamicCookie(client);
if (!cookieOk) {
console.error('[!] 无法获取动态Cookie,退出');
process.exit(1);
}
// 采集数据
const allData = [];
for (let page = 1; page <= CONFIG.totalPages; page++) {
try {
// 某些网站每次请求都需要刷新Cookie
// await getDynamicCookie(client);
const responseData = await fetchPage(client, page);
const pageData = responseData.data || [];
allData.push(...pageData);
console.log(`[+] 正在采集第 ${page}/${CONFIG.totalPages} 页... ✓ 获取 ${pageData.length} 条数据`);
} catch (error) {
console.error(`[-] 第 ${page} 页采集失败: ${error.message}`);
}
}
console.log(`\n[+] 采集完成,共 ${allData.length} 条数据`);
// TODO: 根据需求计算结果
const result = allData.reduce((sum, item) => sum + (item.value || 0), 0);
console.log('\n' + '='.repeat(30) + ' 计算结果 ' + '='.repeat(30));
console.log(`答案:${result}`);
console.log('='.repeat(70));
}
main().catch(error => {
console.error('\n[!] 运行错误:', error.message);
process.exit(1);
});
@@ -0,0 +1,13 @@
{
"name": "js-reverse-vm-sandbox",
"version": "1.0.0",
"description": "JS逆向 - VM沙箱执行模板",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "node main.js --test"
},
"dependencies": {
"axios": "^1.7.0"
}
}
@@ -0,0 +1,81 @@
/**
* 请求封装模块(VM沙箱版本)
* 支持两阶段请求:先获取动态JS生成Cookie,再携带Cookie请求数据
*/
const axios = require('axios');
const DEFAULT_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',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'X-Requested-With': 'XMLHttpRequest',
};
class TwoPhaseClient {
constructor(options = {}) {
this.baseURL = options.baseURL || '';
this.cookies = options.cookies || {};
this.headers = { ...DEFAULT_HEADERS, ...options.headers };
this.delay = options.delay || 1500;
this.client = axios.create({
baseURL: this.baseURL,
timeout: options.timeout || 30000,
headers: this.headers,
});
}
getCookieString() {
return Object.entries(this.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
}
setCookies(cookieObj) {
Object.assign(this.cookies, cookieObj);
}
/**
* 第一阶段:获取动态JS代码
* @param {string} url - 返回JS代码的接口
* @returns {string} JS 代码字符串
*/
async fetchDynamicJS(url, params = {}) {
const response = await this.client.get(url, {
params,
headers: { Cookie: this.getCookieString() },
transformResponse: [(data) => data],
});
return response.data;
}
/**
* 第二阶段:携带Cookie请求数据
*/
async fetchData(url, params = {}) {
const response = await this.client.get(url, {
params,
headers: { Cookie: this.getCookieString() },
});
return response.data;
}
async post(url, data = {}) {
const response = await this.client.post(url, data, {
headers: { Cookie: this.getCookieString() },
});
return response.data;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async fetchWithDelay(url, params = {}) {
const data = await this.fetchData(url, params);
const jitter = Math.random() * this.delay * 0.3;
await this.sleep(this.delay + jitter);
return data;
}
}
module.exports = { TwoPhaseClient, DEFAULT_HEADERS };
@@ -0,0 +1,152 @@
/**
* VM 沙箱模块
* 用于执行服务端返回的混淆 JS 代码,提取动态生成的 Cookie/Token
*/
const vm = require('vm');
function createSandbox(options = {}) {
const cookies = {};
const logs = [];
const sandbox = {
window: null,
self: null,
globalThis: null,
top: null,
parent: null,
document: {
cookie: options.initialCookie || '',
createElement: (tag) => ({
tagName: tag.toUpperCase(), style: {}, src: '',
setAttribute: () => {}, getAttribute: () => null,
appendChild: () => {}, innerHTML: '',
}),
getElementById: () => null,
getElementsByTagName: (tag) => tag === 'script' ? [1, 2, 3] : [],
getElementsByClassName: () => [],
querySelector: () => null,
querySelectorAll: () => [],
head: { appendChild: () => {} },
body: { appendChild: () => {} },
location: { href: options.url || 'https://example.com' },
referrer: '',
readyState: 'complete',
},
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, webdriver: false,
plugins: { length: 3 }, mimeTypes: { length: 4 },
hardwareConcurrency: 8, maxTouchPoints: 0,
},
location: {
href: options.url || 'https://example.com',
protocol: 'https:', host: 'example.com',
hostname: 'example.com', pathname: '/', search: '', hash: '',
origin: 'https://example.com',
},
screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1055, colorDepth: 24, pixelDepth: 24 },
setTimeout: (fn, ms) => setTimeout(fn, Math.min(ms || 0, 3000)),
setInterval: (fn, ms) => { /* 不执行定时器避免死循环 */ return -1; },
clearTimeout, clearInterval,
String, Array, Object, Math, Date, RegExp, JSON, Map, Set,
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, Number, Boolean,
btoa: (str) => Buffer.from(str, 'binary').toString('base64'),
atob: (b64) => Buffer.from(b64, 'base64').toString('binary'),
console: {
log: (...args) => logs.push(args.join(' ')),
warn: () => {}, error: () => {}, info: () => {},
debug: () => {}, trace: () => {}, clear: () => {},
},
XMLHttpRequest: class {
open() {} setRequestHeader() {} send() {
this.readyState = 4; this.status = 200; this.responseText = '{}';
if (this.onreadystatechange) this.onreadystatechange();
}
addEventListener(e, fn) { this['on' + e] = fn; }
},
};
sandbox.window = sandbox;
sandbox.self = sandbox;
sandbox.globalThis = sandbox;
sandbox.top = sandbox;
sandbox.parent = sandbox;
sandbox.frames = sandbox;
// Cookie 拦截
Object.defineProperty(sandbox.document, 'cookie', {
get() {
return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
},
set(val) {
const mainPart = val.split(';')[0];
const eqIdx = mainPart.indexOf('=');
if (eqIdx > 0) {
const name = mainPart.substring(0, eqIdx).trim();
const value = mainPart.substring(eqIdx + 1).trim();
cookies[name] = value;
}
},
configurable: true,
});
if (options.initialCookie) {
options.initialCookie.split(';').forEach(pair => {
const [k, ...rest] = pair.trim().split('=');
if (k) cookies[k.trim()] = rest.join('=');
});
}
return { sandbox, cookies, logs };
}
/**
* 执行混淆 JS 代码并提取 Cookie
* @param {string} code - 混淆的 JS 代码
* @param {Object} options - 配置选项
* @returns {Object} { cookies, logs, success }
*/
function executeAndExtractCookie(code, options = {}) {
const { sandbox, cookies, logs } = createSandbox(options);
vm.createContext(sandbox);
try {
vm.runInContext(code, sandbox, {
timeout: options.timeout || 5000,
filename: 'dynamic.js',
});
return {
success: true,
cookies: { ...cookies },
cookieString: Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; '),
logs,
};
} catch (error) {
return {
success: false,
error: error.message,
cookies: { ...cookies },
logs,
};
}
}
module.exports = { createSandbox, executeAndExtractCookie };
@@ -0,0 +1,160 @@
/**
* 主脚本模板 - WASM 加载还原
*
* 适用场景:加密函数通过 WebAssembly 实现
* 流程:
* 1. 下载/加载 .wasm 文件
* 2. 补全运行环境
* 3. 调用 WASM 导出的加密函数
* 4. 使用加密结果构造请求
*
* TODO: 根据逆向分析结果修改配置和逻辑
*/
const { loadWasmFromFile, loadWasmFromURL, analyzeWasm } = require('./utils/wasm-loader');
const { patchWasmBindgen } = require('./utils/env-patch');
const axios = require('axios');
const path = require('path');
// ============ 配置区域 ============
const CONFIG = {
name: '示例项目 - WASM加密',
description: '采集全部5页数据',
baseURL: 'https://target.com',
dataEndpoint: '/api/data',
// WASM 文件路径或 URL
wasmSource: './main.wasm', // 本地文件
// wasmSource: 'https://target.com/main.wasm', // 远程 URL
// WASM 导出函数名
encryptFunction: 'encode',
// WASM 类型: 'plain' | 'emscripten' | 'wasm-bindgen' | 'go'
wasmType: 'plain',
totalPages: 5,
delay: 1500,
cookies: {},
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
},
};
// ============ 主逻辑 ============
let wasmExports = null;
async function initWasm() {
console.log('[*] 加载 WASM 模块...');
// 根据 WASM 类型补全环境
if (CONFIG.wasmType === 'wasm-bindgen') {
patchWasmBindgen();
}
// 分析 WASM 结构
if (CONFIG.wasmSource.startsWith('http')) {
console.log('[*] 从远程下载 WASM...');
const result = await loadWasmFromURL(CONFIG.wasmSource);
wasmExports = result.exports;
console.log('[+] WASM 导出函数:', Object.keys(result.exports).filter(k => typeof result.exports[k] === 'function'));
} else {
const wasmPath = path.resolve(CONFIG.wasmSource);
// 先分析 WASM 结构
const analysis = await analyzeWasm(wasmPath);
console.log('[*] WASM 导出:', analysis.exports.map(e => `${e.name}(${e.kind})`).join(', '));
console.log('[*] WASM 导入:', analysis.imports.map(i => `${i.module}.${i.name}`).join(', '));
const result = await loadWasmFromFile(wasmPath);
wasmExports = result.exports;
}
// 验证加密函数存在
if (typeof wasmExports[CONFIG.encryptFunction] !== 'function') {
throw new Error(`WASM 导出函数 "${CONFIG.encryptFunction}" 不存在。可用函数: ${Object.keys(wasmExports).filter(k => typeof wasmExports[k] === 'function').join(', ')}`);
}
console.log(`[+] WASM 加载成功,加密函数: ${CONFIG.encryptFunction}`);
}
function generateEncryptedParam(page) {
// TODO: 根据逆向分析修改参数生成逻辑
const timestamp = Math.floor(Date.now() / 1000);
// 调用 WASM 导出的加密函数
const encrypted = wasmExports[CONFIG.encryptFunction](timestamp, page);
// TODO: 根据实际格式组装参数
return `${encrypted}|${timestamp}|${page}`;
}
async function fetchPage(page) {
const m = generateEncryptedParam(page);
const response = await axios.get(`${CONFIG.baseURL}${CONFIG.dataEndpoint}`, {
params: { page, m },
headers: {
...CONFIG.headers,
Cookie: Object.entries(CONFIG.cookies).map(([k, v]) => `${k}=${v}`).join('; '),
Referer: CONFIG.baseURL,
},
});
return response.data;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
console.log(`[*] 项目:${CONFIG.name}`);
console.log(`[*] 目标:${CONFIG.description}`);
console.log('');
// 初始化 WASM
await initWasm();
console.log('');
// 验证:测试加密函数
const testResult = generateEncryptedParam(1);
console.log(`[*] 加密测试: page=1 → m=${testResult}`);
console.log('');
// 采集数据
const allData = [];
for (let page = 1; page <= CONFIG.totalPages; page++) {
try {
const responseData = await fetchPage(page);
const pageData = responseData.data || [];
allData.push(...pageData);
console.log(`[+] 正在采集第 ${page}/${CONFIG.totalPages} 页... ✓ 获取 ${pageData.length} 条数据`);
if (page < CONFIG.totalPages) {
await sleep(CONFIG.delay + Math.random() * 500);
}
} catch (error) {
console.error(`[-] 第 ${page} 页采集失败: ${error.message}`);
}
}
console.log(`\n[+] 采集完成,共 ${allData.length} 条数据`);
// TODO: 计算结果
const result = allData.reduce((sum, item) => sum + (item.value || 0), 0);
console.log('\n' + '='.repeat(30) + ' 计算结果 ' + '='.repeat(30));
console.log(`答案:${result}`);
console.log('='.repeat(70));
}
main().catch(error => {
console.error('\n[!] 运行错误:', error.message);
process.exit(1);
});
@@ -0,0 +1,13 @@
{
"name": "js-reverse-wasm",
"version": "1.0.0",
"description": "JS逆向 - WASM加载还原模板",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "node main.js --test"
},
"dependencies": {
"axios": "^1.7.0"
}
}
@@ -0,0 +1,97 @@
/**
* WASM 环境补全模块
* 为不同类型的 WASM(Emscripten、wasm-bindgen 等)提供最小运行环境
*/
/**
* 补全 wasm-bindgen (Rust) 生成的 WASM 所需的环境
* 常见于 Rust 编写的加密模块
*/
function patchWasmBindgen() {
class Window {
constructor() {
this.document = {
body: {},
head: {},
createElement: () => ({}),
getElementById: () => null,
querySelector: () => null,
};
}
}
const win = new Window();
win.window = win;
win.self = win;
win.globalThis = win;
globalThis.Window = Window;
globalThis.window = win;
globalThis.self = win;
globalThis.document = win.document;
// wasm-bindgen 可能使用 TextEncoder/TextDecoder
if (typeof TextEncoder === 'undefined') {
const { TextEncoder, TextDecoder } = require('util');
globalThis.TextEncoder = TextEncoder;
globalThis.TextDecoder = TextDecoder;
}
return win;
}
/**
* 补全 Emscripten 生成的 WASM 所需的环境
*/
function patchEmscripten() {
return {
print: console.log,
printErr: console.error,
TOTAL_MEMORY: 16777216,
TOTAL_STACK: 5242880,
noInitialRun: true,
noExitRuntime: true,
onRuntimeInitialized: null,
preRun: [],
postRun: [],
};
}
/**
* 补全 Go WASM 所需的环境
*/
function patchGoWasm() {
globalThis.process = globalThis.process || { argv: ['node'], env: {} };
globalThis.fs = globalThis.fs || require('fs');
// Go WASM 需要 performance.now
if (typeof performance === 'undefined') {
globalThis.performance = {
now: () => Date.now(),
};
}
// Go WASM 需要 crypto.getRandomValues
if (typeof globalThis.crypto === 'undefined') {
const nodeCrypto = require('crypto');
globalThis.crypto = {
getRandomValues: (arr) => {
const bytes = nodeCrypto.randomBytes(arr.length);
arr.set(bytes);
return arr;
},
};
}
}
/**
* 清理环境补丁(测试完成后调用)
*/
function cleanupPatches() {
delete globalThis.Window;
delete globalThis.window;
delete globalThis.self;
delete globalThis.document;
}
module.exports = { patchWasmBindgen, patchEmscripten, patchGoWasm, cleanupPatches };
@@ -0,0 +1,115 @@
/**
* WASM 加载与执行模块
* 在 Node.js 中加载 WebAssembly 模块并调用导出函数
*/
const fs = require('fs');
const path = require('path');
/**
* 加载本地 WASM 文件
* @param {string} wasmPath - WASM 文件路径
* @param {Object} importObject - 导入对象(环境依赖)
* @returns {Object} WASM 实例的 exports
*/
async function loadWasmFromFile(wasmPath, importObject = {}) {
const wasmBuffer = fs.readFileSync(wasmPath);
return loadWasm(wasmBuffer, importObject);
}
/**
* 从 URL 下载并加载 WASM
* @param {string} url - WASM 文件 URL
* @param {Object} importObject - 导入对象
* @returns {Object} WASM 实例的 exports
*/
async function loadWasmFromURL(url, importObject = {}) {
const axios = require('axios');
const response = await axios.get(url, { responseType: 'arraybuffer' });
return loadWasm(response.data, importObject);
}
/**
* 从二进制数据加载 WASM
*/
async function loadWasm(wasmBuffer, importObject = {}) {
const defaultImports = {
env: {
memory: new WebAssembly.Memory({ initial: 256, maximum: 512 }),
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
abort: (msg, file, line, col) => {
console.error(`WASM abort at ${file}:${line}:${col}: ${msg}`);
},
__memory_base: 0,
__table_base: 0,
...importObject.env,
},
wasi_snapshot_preview1: {
fd_write: () => 0,
fd_read: () => 0,
fd_close: () => 0,
fd_seek: () => 0,
fd_fdstat_get: () => 0,
environ_sizes_get: () => 0,
environ_get: () => 0,
proc_exit: (code) => { throw new Error(`WASM proc_exit(${code})`); },
clock_time_get: () => BigInt(Date.now()) * 1000000n,
...importObject.wasi_snapshot_preview1,
},
...importObject,
};
// 检查 WASM 需要哪些导入
const module = await WebAssembly.compile(wasmBuffer);
const requiredImports = WebAssembly.Module.imports(module);
// 确保所有需要的导入都存在
for (const imp of requiredImports) {
if (!defaultImports[imp.module]) {
defaultImports[imp.module] = {};
}
if (!defaultImports[imp.module][imp.name]) {
switch (imp.kind) {
case 'function':
defaultImports[imp.module][imp.name] = () => {};
break;
case 'global':
defaultImports[imp.module][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0);
break;
case 'memory':
defaultImports[imp.module][imp.name] = new WebAssembly.Memory({ initial: 256 });
break;
case 'table':
defaultImports[imp.module][imp.name] = new WebAssembly.Table({ initial: 0, element: 'anyfunc' });
break;
}
}
}
const instance = await WebAssembly.instantiate(module, defaultImports);
return {
exports: instance.exports,
module,
instance,
imports: requiredImports,
};
}
/**
* 分析 WASM 模块的导出和导入
*/
async function analyzeWasm(wasmPath) {
const wasmBuffer = fs.readFileSync(wasmPath);
const module = await WebAssembly.compile(wasmBuffer);
const exports = WebAssembly.Module.exports(module);
const imports = WebAssembly.Module.imports(module);
return {
exports: exports.map(e => ({ name: e.name, kind: e.kind })),
imports: imports.map(i => ({ module: i.module, name: i.name, kind: i.kind })),
};
}
module.exports = { loadWasmFromFile, loadWasmFromURL, loadWasm, analyzeWasm };