From cfd0cc919db7cd262329b578e55dacc3a8882432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=A4=9C?= Date: Wed, 18 Mar 2026 22:50:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=9A?= =?UTF-8?q?=E7=94=A8JS=E9=80=86=E5=90=91=E5=B7=A5=E7=A8=8BSkill=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 包含完整的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 --- .gitignore | 3 + hello_js_reverse_skill/README.md | 167 +++++++ hello_js_reverse_skill/SKILL.md | 426 ++++++++++++++++++ .../examples/demo-analysis.md | 152 +++++++ .../references/anti-debug.md | 337 ++++++++++++++ .../references/crypto-patterns.md | 255 +++++++++++ .../references/environment-patch.md | 398 ++++++++++++++++ .../references/hook-techniques.md | 397 ++++++++++++++++ .../references/mcp-cookbook.md | 264 +++++++++++ .../references/obfuscation-guide.md | 198 ++++++++ .../references/protocol-analysis.md | 269 +++++++++++ .../references/workflow-overview.md | 128 ++++++ hello_js_reverse_skill/scripts/check-deps.sh | 87 ++++ .../scripts/crypto-identifier.js | 291 ++++++++++++ .../scripts/hook-generator.js | 346 ++++++++++++++ .../scripts/sandbox-runner.js | 359 +++++++++++++++ .../templates/browser-auto/main.js | 170 +++++++ .../templates/browser-auto/package.json | 13 + .../templates/node-request/main.js | 121 +++++ .../templates/node-request/package.json | 14 + .../templates/node-request/utils/encrypt.js | 71 +++ .../templates/node-request/utils/request.js | 128 ++++++ .../templates/vm-sandbox/main.js | 117 +++++ .../templates/vm-sandbox/package.json | 13 + .../templates/vm-sandbox/utils/request.js | 81 ++++ .../templates/vm-sandbox/utils/sandbox.js | 152 +++++++ .../templates/wasm-loader/main.js | 160 +++++++ .../templates/wasm-loader/package.json | 13 + .../templates/wasm-loader/utils/env-patch.js | 97 ++++ .../wasm-loader/utils/wasm-loader.js | 115 +++++ 30 files changed, 5342 insertions(+) create mode 100644 .gitignore create mode 100644 hello_js_reverse_skill/README.md create mode 100644 hello_js_reverse_skill/SKILL.md create mode 100644 hello_js_reverse_skill/examples/demo-analysis.md create mode 100644 hello_js_reverse_skill/references/anti-debug.md create mode 100644 hello_js_reverse_skill/references/crypto-patterns.md create mode 100644 hello_js_reverse_skill/references/environment-patch.md create mode 100644 hello_js_reverse_skill/references/hook-techniques.md create mode 100644 hello_js_reverse_skill/references/mcp-cookbook.md create mode 100644 hello_js_reverse_skill/references/obfuscation-guide.md create mode 100644 hello_js_reverse_skill/references/protocol-analysis.md create mode 100644 hello_js_reverse_skill/references/workflow-overview.md create mode 100755 hello_js_reverse_skill/scripts/check-deps.sh create mode 100644 hello_js_reverse_skill/scripts/crypto-identifier.js create mode 100644 hello_js_reverse_skill/scripts/hook-generator.js create mode 100644 hello_js_reverse_skill/scripts/sandbox-runner.js create mode 100644 hello_js_reverse_skill/templates/browser-auto/main.js create mode 100644 hello_js_reverse_skill/templates/browser-auto/package.json create mode 100644 hello_js_reverse_skill/templates/node-request/main.js create mode 100644 hello_js_reverse_skill/templates/node-request/package.json create mode 100644 hello_js_reverse_skill/templates/node-request/utils/encrypt.js create mode 100644 hello_js_reverse_skill/templates/node-request/utils/request.js create mode 100644 hello_js_reverse_skill/templates/vm-sandbox/main.js create mode 100644 hello_js_reverse_skill/templates/vm-sandbox/package.json create mode 100644 hello_js_reverse_skill/templates/vm-sandbox/utils/request.js create mode 100644 hello_js_reverse_skill/templates/vm-sandbox/utils/sandbox.js create mode 100644 hello_js_reverse_skill/templates/wasm-loader/main.js create mode 100644 hello_js_reverse_skill/templates/wasm-loader/package.json create mode 100644 hello_js_reverse_skill/templates/wasm-loader/utils/env-patch.js create mode 100644 hello_js_reverse_skill/templates/wasm-loader/utils/wasm-loader.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..879df49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +node_modules/ +*.log diff --git a/hello_js_reverse_skill/README.md b/hello_js_reverse_skill/README.md new file mode 100644 index 0000000..7950bcd --- /dev/null +++ b/hello_js_reverse_skill/README.md @@ -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**: 浏览器自动化 diff --git a/hello_js_reverse_skill/SKILL.md b/hello_js_reverse_skill/SKILL.md new file mode 100644 index 0000000..7149cc8 --- /dev/null +++ b/hello_js_reverse_skill/SKILL.md @@ -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 逆向 diff --git a/hello_js_reverse_skill/examples/demo-analysis.md b/hello_js_reverse_skill/examples/demo-analysis.md new file mode 100644 index 0000000..7200017 --- /dev/null +++ b/hello_js_reverse_skill/examples/demo-analysis.md @@ -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 纯算法模拟请求 diff --git a/hello_js_reverse_skill/references/anti-debug.md b/hello_js_reverse_skill/references/anti-debug.md new file mode 100644 index 0000000..dbedebe --- /dev/null +++ b/hello_js_reverse_skill/references/anti-debug.md @@ -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] 反反调试脚本已注入'); +})(); +``` diff --git a/hello_js_reverse_skill/references/crypto-patterns.md b/hello_js_reverse_skill/references/crypto-patterns.md new file mode 100644 index 0000000..5bde1bf --- /dev/null +++ b/hello_js_reverse_skill/references/crypto-patterns.md @@ -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}`) +``` diff --git a/hello_js_reverse_skill/references/environment-patch.md b/hello_js_reverse_skill/references/environment-patch.md new file mode 100644 index 0000000..d2416bd --- /dev/null +++ b/hello_js_reverse_skill/references/environment-patch.md @@ -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('', { + 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") + → 在环境检测处断点,确认检测逻辑 +``` diff --git a/hello_js_reverse_skill/references/hook-techniques.md b/hello_js_reverse_skill/references/hook-techniques.md new file mode 100644 index 0000000..57be8c1 --- /dev/null +++ b/hello_js_reverse_skill/references/hook-techniques.md @@ -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` 结果 diff --git a/hello_js_reverse_skill/references/mcp-cookbook.md b/hello_js_reverse_skill/references/mcp-cookbook.md new file mode 100644 index 0000000..4d22744 --- /dev/null +++ b/hello_js_reverse_skill/references/mcp-cookbook.md @@ -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 | diff --git a/hello_js_reverse_skill/references/obfuscation-guide.md b/hello_js_reverse_skill/references/obfuscation-guide.md new file mode 100644 index 0000000..f510a35 --- /dev/null +++ b/hello_js_reverse_skill/references/obfuscation-guide.md @@ -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 → 跟进关键函数调用 +``` diff --git a/hello_js_reverse_skill/references/protocol-analysis.md b/hello_js_reverse_skill/references/protocol-analysis.md new file mode 100644 index 0000000..46a4aa7 --- /dev/null +++ b/hello_js_reverse_skill/references/protocol-analysis.md @@ -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 指纹问题 +``` diff --git a/hello_js_reverse_skill/references/workflow-overview.md b/hello_js_reverse_skill/references/workflow-overview.md new file mode 100644 index 0000000..4ff78d1 --- /dev/null +++ b/hello_js_reverse_skill/references/workflow-overview.md @@ -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. 运行验证 +``` diff --git a/hello_js_reverse_skill/scripts/check-deps.sh b/hello_js_reverse_skill/scripts/check-deps.sh new file mode 100755 index 0000000..6947192 --- /dev/null +++ b/hello_js_reverse_skill/scripts/check-deps.sh @@ -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 diff --git a/hello_js_reverse_skill/scripts/crypto-identifier.js b/hello_js_reverse_skill/scripts/crypto-identifier.js new file mode 100644 index 0000000..7a5e00e --- /dev/null +++ b/hello_js_reverse_skill/scripts/crypto-identifier.js @@ -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 }; diff --git a/hello_js_reverse_skill/scripts/hook-generator.js b/hello_js_reverse_skill/scripts/hook-generator.js new file mode 100644 index 0000000..ed909e9 --- /dev/null +++ b/hello_js_reverse_skill/scripts/hook-generator.js @@ -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= [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= 目标接口路径过滤'); + console.log(' --function= 自定义函数名(type=custom 时)'); + console.log(' --output= 输出到文件'); + 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 }; diff --git a/hello_js_reverse_skill/scripts/sandbox-runner.js b/hello_js_reverse_skill/scripts/sandbox-runner.js new file mode 100644 index 0000000..3020445 --- /dev/null +++ b/hello_js_reverse_skill/scripts/sandbox-runner.js @@ -0,0 +1,359 @@ +/** + * VM 沙箱执行器 + * 用于在隔离的 Node.js 沙箱中执行从浏览器提取的 JS 代码 + * + * 使用方式: + * node sandbox-runner.js [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 [options]'); + console.log(''); + console.log('选项:'); + console.log(' --url= 设置 location.href'); + console.log(' --extract-cookie 启用 Cookie 拦截'); + console.log(' --verbose 输出详细日志'); + console.log(' --timeout= 执行超时(默认 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 }; diff --git a/hello_js_reverse_skill/templates/browser-auto/main.js b/hello_js_reverse_skill/templates/browser-auto/main.js new file mode 100644 index 0000000..f5e54d9 --- /dev/null +++ b/hello_js_reverse_skill/templates/browser-auto/main.js @@ -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); +}); diff --git a/hello_js_reverse_skill/templates/browser-auto/package.json b/hello_js_reverse_skill/templates/browser-auto/package.json new file mode 100644 index 0000000..699eb0f --- /dev/null +++ b/hello_js_reverse_skill/templates/browser-auto/package.json @@ -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" + } +} diff --git a/hello_js_reverse_skill/templates/node-request/main.js b/hello_js_reverse_skill/templates/node-request/main.js new file mode 100644 index 0000000..93abcb9 --- /dev/null +++ b/hello_js_reverse_skill/templates/node-request/main.js @@ -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); +}); diff --git a/hello_js_reverse_skill/templates/node-request/package.json b/hello_js_reverse_skill/templates/node-request/package.json new file mode 100644 index 0000000..4fdf03c --- /dev/null +++ b/hello_js_reverse_skill/templates/node-request/package.json @@ -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" + } +} diff --git a/hello_js_reverse_skill/templates/node-request/utils/encrypt.js b/hello_js_reverse_skill/templates/node-request/utils/encrypt.js new file mode 100644 index 0000000..c1f9dcd --- /dev/null +++ b/hello_js_reverse_skill/templates/node-request/utils/encrypt.js @@ -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, +}; diff --git a/hello_js_reverse_skill/templates/node-request/utils/request.js b/hello_js_reverse_skill/templates/node-request/utils/request.js new file mode 100644 index 0000000..33b07e9 --- /dev/null +++ b/hello_js_reverse_skill/templates/node-request/utils/request.js @@ -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 }; diff --git a/hello_js_reverse_skill/templates/vm-sandbox/main.js b/hello_js_reverse_skill/templates/vm-sandbox/main.js new file mode 100644 index 0000000..e19fcf8 --- /dev/null +++ b/hello_js_reverse_skill/templates/vm-sandbox/main.js @@ -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); +}); diff --git a/hello_js_reverse_skill/templates/vm-sandbox/package.json b/hello_js_reverse_skill/templates/vm-sandbox/package.json new file mode 100644 index 0000000..9c0e033 --- /dev/null +++ b/hello_js_reverse_skill/templates/vm-sandbox/package.json @@ -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" + } +} diff --git a/hello_js_reverse_skill/templates/vm-sandbox/utils/request.js b/hello_js_reverse_skill/templates/vm-sandbox/utils/request.js new file mode 100644 index 0000000..ae6e0f2 --- /dev/null +++ b/hello_js_reverse_skill/templates/vm-sandbox/utils/request.js @@ -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 }; diff --git a/hello_js_reverse_skill/templates/vm-sandbox/utils/sandbox.js b/hello_js_reverse_skill/templates/vm-sandbox/utils/sandbox.js new file mode 100644 index 0000000..b65615d --- /dev/null +++ b/hello_js_reverse_skill/templates/vm-sandbox/utils/sandbox.js @@ -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 }; diff --git a/hello_js_reverse_skill/templates/wasm-loader/main.js b/hello_js_reverse_skill/templates/wasm-loader/main.js new file mode 100644 index 0000000..6d4e727 --- /dev/null +++ b/hello_js_reverse_skill/templates/wasm-loader/main.js @@ -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); +}); diff --git a/hello_js_reverse_skill/templates/wasm-loader/package.json b/hello_js_reverse_skill/templates/wasm-loader/package.json new file mode 100644 index 0000000..eb0a7a4 --- /dev/null +++ b/hello_js_reverse_skill/templates/wasm-loader/package.json @@ -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" + } +} diff --git a/hello_js_reverse_skill/templates/wasm-loader/utils/env-patch.js b/hello_js_reverse_skill/templates/wasm-loader/utils/env-patch.js new file mode 100644 index 0000000..e7d3e97 --- /dev/null +++ b/hello_js_reverse_skill/templates/wasm-loader/utils/env-patch.js @@ -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 }; diff --git a/hello_js_reverse_skill/templates/wasm-loader/utils/wasm-loader.js b/hello_js_reverse_skill/templates/wasm-loader/utils/wasm-loader.js new file mode 100644 index 0000000..df613ee --- /dev/null +++ b/hello_js_reverse_skill/templates/wasm-loader/utils/wasm-loader.js @@ -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 };