Skip to content
 
 

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

瑞数 (Ruishu) 反爬逆向 — 纯算 + AST 反编译 + JsRpc 通杀 + 顶级逆向 Skill

市面上最全的瑞数 (Rivers Security) 动态安全防护突破方案合集。不仅包含 Cookie T 纯算生成、URL 后缀 AST 深度逆向、JsRpc 通杀、sdenv 补环境四种实战方案, 还从真实逆向经验中蒸馏出一份可复制的顶级 Agent Skill — 让任何 AI Agent 读完即可独立完成瑞数逆向。

v2.0 更新 (2026-04-10)

瑞数于 2026-04-07 进行了服务端改版, 导致纯算方案返回 412。已修复。

失效原因

瑞数服务端更新了 basearr 中的 type=2 环境检测值, 这是导致 412 的唯一原因:

参数 旧值 (v1.0) 新值 (v2.0)
type=2 检测值 [103, 101, 224, 181] [225, 203, 100, 181]
cp1 索引映射 {11:103, 5:101, 23:224, 8:181} {22:225, 21:203, 30:100, 3:181}

怎么修的 — 数据驱动, 不猜

纯靠数据对比定位, 过程:

1. sdenv 采集真实 basearr — 跑 3 组配套数据 (同一 session 中采集 nsd + cd + Cookie T + 解密出 basearr)

2. 逐字节对比 — 把真实 basearr 和纯算生成的逐字节比, 立刻发现 type=2 全是 0:

type=2: ref=[225,203,100,181]  gen=[0,0,0,0]   ← 旧映射表查不到, 全返回 0

3. 顺带发现 UA/Platform 也不一致 — type=3 中的 CRC32(UA) 和 platform 字段:

ref UA CRC: 0x9fad00ee   ← CRC32(Mac UA) 匹配
gen UA CRC: 0x11469713   ← CRC32(Win UA)

ref platform: "MacIntel"  ← sdenv 的 jsdom 默认值
gen platform: "Win32"     ← 我们写的

本质是 sdenv 的 jsdom 环境中 navigator.platform 默认为 "MacIntel", VM 收集到的指纹就是 Mac 的。

4. 验证 UA 是否必须改 — 测试了 4 种 UA 组合 (Mac/Win basearr × Mac/Win HTTP), 全部 200。结论: 服务端不校验 UA CRC32, type=2 才是唯一失效原因。UA/Platform 改为 Mac 只是为了与 sdenv 真实数据对齐。

没有猜, 全是数据对出来的 — 这就是 agentskill/SKILL.md 中的「数据驱动方法论」。

涉及文件

文件 改了什么 是否必须
reverse/scripts/basearr.js buildType2() 中的 indexToValue 映射表 必须 — 这是 412 的唯一原因
reverse/scripts/run.js UA 字符串 + platform 参数 可选 — 与 sdenv 真实数据对齐
reverse/scripts/client.js UA 字符串 + platform 默认值 可选 — 与 sdenv 真实数据对齐

纯算方案的局限性

纯算不是一劳永逸的。 type=2 的环境检测值是服务端动态配置的, 瑞数可以随时更新。下次失效时, 用同样的方法修复:

  1. sdenv 采集 3 组真实数据 (在同一 session 中配套采集 nsd + cd + Cookie T)
  2. 用纯算解密 Cookie T → 提取真实 basearr
  3. 和纯算生成的 basearr 逐字节对比, 找到哪些字段变了
  4. 更新 basearr.js 中的对应值

完整方法论见 agentskill/SKILL.md 中的「方法论」章节。参考采集脚本见 agentskill/scripts/collect_session.js

生产环境推荐

方案 稳定性 效率 适用场景
JsRpc 高 (不受改版影响) 中 (需要浏览器实例) 生产环境首选, 通杀所有瑞数站点
sdenv 高 (跟随瑞数逻辑) 不方便开浏览器时, 或 CI/CD 场景
纯算 低 (改版即失效) 极高 (毫秒级) 高频采集 + 愿意定期维护的场景

注意: 新一代瑞数 (如国家药监局使用的瑞数 6) 增加了更复杂的环境检测, 纯算几乎不可能完全还原, 建议直接使用 JsRpc 或 sdenv。


成果总览

方案 原理 适用场景 状态
纯算 Cookie T 纯 JS 实现加密链, 零浏览器依赖 GET/POST (不需要后缀的站点, 99%) 已完成, HTTP 200
JsRpc 通杀 WebSocket 调用浏览器中的瑞数 VM 所有站点, 包括需要后缀的 已完成, 通杀验证
sdenv 补环境 魔改 jsdom 模拟浏览器环境 Cookie + 后缀, 单实例 已完成, 可用
URL 后缀 AST 逆向 AST 反编译 eval code + VM 字节码 深度逆向研究 已完成 80%, 卡在 VM 层
顶级逆向 Skill 工程化 skill 体系: 主文档 + 7 篇参考 + 5 个脚本 + 2 个 lib AI Agent 自动化逆向 已完成, 可直接复制使用

目标���点

中国知识产权海关备案查询系统 http://202.127.48.145:8888/

该站点使用瑞数 4/5 代动态安全防护, 对所有请求进行加密 Cookie 验证。


快速开始

纯算方案 (推荐, 零依赖)

cd reverse/scripts
node client.js
const { RuishuPureClient } = require('./reverse/scripts/client.js');

const client = new RuishuPureClient();
await client.init();

// GET 页面
const page = await client.get('/zscq/search/jsp/vBrandSearchIndex.jsp');

// POST 查询商标
const result = await client.post(
    '/searchUser/searchAction!getVRecordListPage.do',
    'page=1&rows=10&sidx=RECORD_NUM&sord=desc&_search=false&nd=' + Date.now()
);

// 搜索 (支持中文)
const search = await client.post(
    '/searchUser/searchAction!getVRecordListPage.do',
    'page=1&rows=10&RECORD_NAME=' + encodeURIComponent('华为') + '&nd=' + Date.now()
);

Cookie 过期自动刷新, 无需手动处理。

JsRpc 方案 (通杀所有瑞数站点)

cd jsrpc && npm install

# 1. 启动服务端
node server.js

# 2. 浏览器打开目标站点 → F12 → Console → 粘贴 inject.js

# 3. 调用
node client.js                              # 自动测试
node client.js get /path                    # GET
node client.js post /api "key=value"        # POST (自动带后缀)

瑞数防护原理

浏览器首次访问
    ↓
服务器返回 HTTP 412 + 混淆 JS
  ├── Cookie S (HttpOnly, 服务端标识)
  ├── <meta> 标签 (加密 content)
  ├── <script> $_ts = { nsd, cd, cp, aebi } (配置数据)
  └── <script src="mainjs.js"> (205KB 混淆代码)
    ↓
mainjs 执行:
  1. 解密 $_ts.cd → 提取 45 组密钥
  2. 用 nsd 种子生成 918 个随机变量名
  3. 动态生成 296KB eval 代码
  4. eval() → 启动三层嵌套 VM
    ↓
VM 执行:
  1. 收集浏览器指纹 (Canvas, WebGL, UA, 屏幕...)
  2. 组装 basearr (154B TLV 结构)
  3. basearr → Huffman → XOR → AES-CBC → CRC32 → AES-CBC → Base64
  4. 写入 Cookie T = "0" + Base64 结果
  5. 劫持 XHR.open (给请求加 URL 后缀)
    ↓
浏览器带 Cookie S + Cookie T 再次访问 → 200

三层 VM 架构

Layer 1: mainjs 字节码解释器 (~100 opcodes)
  └─ 解析配置, 生成 eval 代码

Layer 2: eval code 外层 VM — _$$o (741 state codes)
  └─ Cookie T 生成, XHR 劫持, DOM 遍历, 事件监听

Layer 3: 内层 VM — r2mKa (114 opcodes, 407 函数, 43925B 字节码)
  └─ AES/CRC32/Huffman/Base64, 环境指纹收集

项目结构

rs_reverse/
│
├── reverse/                         ★ 纯算实现 (核心成果)
│   ├── scripts/
│   │   ├── client.js                纯算客户端 (233行, 一键使用)
│   │   ├── run.js                   Cookie T 全流程脚本
│   │   ├── coder.js                 外层 VM 重写 (362行)
│   │   ├── basearr.js               明文数据构建 (304行)
│   │   └── pure_e2e.js              端到端验证
│   ├── captured/
│   │   └── mainjs.js                缓存的 mainjs
│   ├── test/
│   │   └── api_test.js              API 接口测试
│   └── REVERSE_ARCHIVE.md           完整逆向过程记录
│
├── houzhui/                         ★ URL 后缀 AST 深度逆向
│   ├── ast/                         14 个 AST 分析工具
│   │   ├── ast_trace_rt239.js       定位后缀核心函数 rt[239] (15KB)
│   │   ├── ast_deep_bs.js           拆解 56 个子函数
│   │   ├── ast_extract_opcodes.js   提取 409 个 VM opcodes
│   │   ├── ast_r2mka_disasm.js      自动反汇编 (6328行输出)
│   │   ├── ast_bytecode_to_js.js    栈模拟翻译 (字节码→伪JS)
│   │   ├── ast_suffix_structure.js  后缀 88B/120B 结构分析
│   │   ├── ast_find_xtea_huffman.js 定位加密函数 (SHA-1/XTEA)
│   │   ├── ast_verify_all.js        440 个 rt[N] 完整映射
│   │   ├── ast_session_chain.js     AES 解密链提取
│   │   ├── ast_cookie_s_decrypt.js  Cookie S 解密路径
│   │   ├── ast_cookie_s_complete.js 核心函数翻译
│   │   ├── ast_translate_child40.js TLV 解析器翻译
│   │   ├── ast_trace_session49.js   49B session 追踪
│   │   └── ast_trace_49b.js         纯 AST 完整 49B 路径
│   └── SUFFIX_REVERSE.md            后缀逆向完整记录
│
├── jsrpc/                           ★ JsRpc 通杀方案
│   ├── server.js                    WebSocket RPC 服务端
│   ├── inject.js                    浏览器注入脚本
│   ├── client.js                    Node.js 客户端
│   └── README.md                    使用说明
│
├── sdenv/                           补环境方案 (魔改 jsdom)
│   ├── client.js                    sdenv 客户端
│   └── README.md                    技术总结
│
├── agentskill/                      ★ 顶级逆向 Skill (可复制给任何 AI Agent)
│   ├── SKILL.md                     主入口 (362行, 决策树+方法论+排错指南)
│   ├── references/                  按需加载的详细参考 (7 篇)
│   │   ├── encryption_chain.md      阶段1: 加密链 (Huffman/AES/CRC32/Base64)
│   │   ├── key_extraction.md        阶段2: 密钥提取
│   │   ├── coder_rewrite.md         阶段3: 外层VM重写 + 调试6个坑
│   │   ├── basearr_adaptation.md    阶段4: basearr数据驱动适配
│   │   ├── ast_methodology.md       AST反编译4步流水线
│   │   ├── suffix_analysis.md       URL后缀结构+SHA-1签名
│   │   └── vm_hook_cookbook.md       7种VM注入技术
│   ├── scripts/                     可直接执行的脚本 (5 个)
│   │   ├── collect_session.js       配套数据一次性采集
│   │   ├── hybrid_verify.js         混合验证 (证明加密链正确)
│   │   ├── pure_run.js              纯算全流程模板
│   │   ├── collect_type2.js         type=2多session采集
│   │   └── sdenv_client.js          sdenv客户端
│   └── lib/                         格式化的参考实现
│       ├── coder.js                 外层VM重写器 (508行, 验证100%字节一致)
│       └── basearr.js               basearr生成器 (282行, 验证HTTP 200)
│
├── rs-reverse-src/                  pysunday/rs-reverse 开源参考 (纯算借鉴来源)
│   ├── src/handler/Coder.js         外层 VM 重写 (我们 coder.js 的参照)
│   ├── src/handler/Cookie.js        加密链实现 (Huffman/AES/CRC32)
│   ├── src/handler/basearr/         7 个站点的 basearr 适配器
│   ├── src/handler/parser/          密钥提取/r2mka 解析
│   └── src/handler/grenKeys.js      变量名生成 (PRNG + Fisher-Yates)
│
└── captured/                        抓包数据 (本地保留, 不上传 GitHub)

四种方案详解

1. 纯算 Cookie T (reverse/scripts/)

完全不依赖浏览器, 纯 Node.js 实现整条加密链。

本方案借鉴了 pysunday/rs-reverse 开源项目 (已收录在 rs-reverse-src/), 该项目已在 9+ 个瑞数站点验证通过。我们在其基础上:

  • 参照其 Coder.js 的模块结构, 重写了外层 VM 解释器 (coder.js)
  • 参照其 basearr/len157.js 的逐字段分析方法, 用数据驱动适配了我们的站点 (basearr.js)
  • 参照其加密链 (Cookie.js) 实现了 Huffman → XOR → AES-CBC → CRC32 → AES-CBC → Base64
  • 密钥提取 (extractKeys) 参照其 tscd.js 的 XOR offset 推导思路, 简化为已知 keys[0]="64" 的硬编码方式
$_ts.cd → extractKeys() → 45 组密钥
mainjs + nsd → Coder 重写 → eval 代码 + codeUid
已知参数 → buildBasearr() → 154B TLV 明文
basearr → Huffman → XOR → AES-CBC → CRC32 → AES-CBC → Base64 → Cookie T

核心文件:

  • coder.js — 外层 VM 重写 (362行), 从 mainjs 生成 eval 代码, 逐字节与原始一致
  • basearr.js — 明文数据构建 (304行), 8 种 TLV type 全覆盖
  • client.js — 完整客户端 (233行), Cookie 自动更新

与 rs-reverse 的主要差异:

rs-reverse 本项目
XOR offset 从任务树动态计算 硬编码推导 (keys[0]="64")
basearr 适配 7 个站点适配器, 手工逆向 数据驱动: sdenv 采集 5 组 → 逐字节对比
r2mka 解析 完整任务树 → fixedValue20 未实现 (数据驱动替代)
type=2 r2mka 查找 cp1 索引映射 (5 session 采集)

2. JsRpc 通杀 (jsrpc/)

通过 WebSocket 调用浏览器中真实运行的瑞数 VM, Cookie + 后缀全自动:

Node.js ←HTTP→ JsRpc Server ←WebSocket→ 浏览器中的瑞数 VM

已验证: 商标查询站点 + 国家药品监督管理局 (瑞数6, 最严格)

3. sdenv 补环境 (sdenv/)

魔改 jsdom 模拟浏览器环境, 让瑞数 JS 在 Node.js 中真实执行:

  • 优点: 自动生成 Cookie, 所有瑞数站点通用
  • 注意: sdenv 本身不生成 URL 后缀, 需要后缀的站点建议用 JsRpc

4. URL 后缀 AST 逆向 (houzhui/)

对 URL 后缀进行深度逆向研究, 使用 AST 作为核心分析工具。

后缀结构 (88B / 120B)

"0" + URLSafeBase64([
    [0-3]   4B nonce        随机
    [4-5]   flag + marker
    [6-54]  49B session     Cookie S 解密 (VM 内部)
    [55]    0x20(无search) / 0x40(有search)
    [56-87] 32B signature   SHA-1(行为数据 + URL)
])

AST 反编译链路 (核心方法论)

eval_code.js (296KB)
    ↓ acorn.parse() → AST
    ↓ 遍历 _$_I 的 if(op===N) 分支
opcode 语义映射 (409 条)
    ↓ 反汇编器逐条翻译字节码
汇编代码 (6328 行)
    ↓ 栈模拟翻译 (自动)
可读伪 JS 代码 (1653 行)
    ↓ 人工语义标注
带注释的可执行代码

关键发现:

  • 后缀签名使用 SHA-1 (不是 XTEA/AES)
  • 32B signature = 鼠标/键盘行为统计编码 (不是加密)
  • URL 数据通过 createElement('a') 解析 后 XOR 编码进后缀
  • 当前卡在: 49B session 在 VM 字节码内部计算, AST 无法触及

为什么 AST 是 JSVMP 逆向的最优解

方法 效率 说明
AST 静态分析 ★★★★★ 20h → 52 个函数, ~400B/h, opcode 100% 精确
运行时栈追踪 ★★ 2天 → 1 个函数, ~80B/天, 需要猜 opcode 语义
手工阅读混淆代码 296KB 混淆代码, 不现实

顶级逆向 Skill (agentskill/)

从真实逆向经验中蒸馏出的工程化 AI Agent Skill, 不是一份笔记, 而是一套可以直接复制给任何 AI Agent 执行的完整知识体系。

为什么叫"顶级"

市面上的瑞数资料要么是零散的博客文章, 要么是开源项目的源码 (如 rs-reverse)。没有人把完整的逆向方法论 + 踩坑经验 + 排错指南 + 可执行代码打包成一个 AI 可以直接消费的 skill。 这是第一个。

三层架构设计

agentskill/
├── SKILL.md (362行)          ← AI 首先加载这个, 就能决策和执行
│   ├── YAML frontmatter       Agent 自动发现 (Claude Code / Codex / Cursor)
│   ├── 决策树                  拿到 URL → 5 步判断走哪条路
│   ├── 6 阶段总览表            每阶段: 输入→输出→验证标准 (3行搞定)
│   ├── 方法论                  数据驱动 + AST (各写一次, 不重复)
│   ├── 弯路警告                6 个真实踩坑 (省你 5+ 天)
│   ├── 排错指南                412/400/Coder不匹配/keys失败/type=2
│   └── 站点适配 Checklist      11 项打勾即可
│
├── references/ (7篇)         ← 按需加载, 不污染 context window
│   加密链 / 密钥提取 / Coder重写 / basearr适配
│   AST方法论 / 后缀分析 / VM注入手册
│
├── scripts/ (5个)            ← 可直接 node 执行
│   数据采集 / 混合验证 / 纯算全流程 / type=2采集 / sdenv客户端
│
└── lib/ (2个)                ← 格式化的参考实现
    coder.js (508行) + basearr.js (282行)

对比原来的单文件方案: 主文档从 3300 行 / 53K tokens 压缩到 362 行 / ~6K tokens (缩减 89%), AI 首次加载成本降低一个数量级, 同时通过 references/ 保留了所有深度内容。

核心方法论

  • 数据驱动 (Cookie T / basearr): 采集 3-5 组真实数据 → 逐字节对比 → 找来源。不读 VM 代码。
  • AST 分析 (后缀 / eval code): acorn 解析 → rt[N] 函数映射 → 调用链追踪。几小时完成手工数周的量。
  • 弯路警告: 2 天读 VM 代码 = 浪费; 1 天照搬 rs-reverse 公式 = 浪费; 数据驱动 10 分钟解决。

使用方式

给 AI Agent: 把 agentskill/ 目录放到项目中, Agent 读 SKILL.md 即可开始工作。

给人类: 读 SKILL.md 的决策树和排错指南, 按 Checklist 适配新站点。


方法论

数据驱动  →  回答 "改什么"      (basearr 每个字节的来源)
AST 分析  →  回答 "怎么理解代码"  (eval code 函数定位 + 调用链)
VM Hook   →  回答 "怎么拿到真实数据" (为数据驱动提供采集手段)

数据驱动 (Cookie T / basearr)

遇到不理解的字节 → 多采几组真实数据 → 逐字节对比 → 找规律

不要读 VM 代码, 用 sdenv 采集 3-5 组真实数据, 逐字节对比找到每个字段的来源。 我们花了 2 天读 VM 代码理解 basearr — 完全浪费。转向数据驱动后 1 天内解决。

VM Hook — 数据驱动的采集手段:

数据驱动的前提是能拿到真实数据。通过 sdenv + vm.runInContext 拦截, 可以在瑞数 VM 执行过程中注入 hook, 捕获中间数据:

Hook 技术 用途 示例
vm.runInContext 拦截 捕获 eval 代码、提取 cd/nsd code.length > 250000 识别 eval 代码
Object.defineProperty 劫持 捕获 Cookie 写入时刻 劫持 Document.prototype.cookie setter
IIFE 函数包装 追踪特定函数的输入输出 包装加密入口函数, 记录 basearr → cipher
逗号表达式注入 不改变控制流的中间值记录 (console.log(x), originalCall(x))
Phase 标记 区分不同执行阶段 只在加密阶段记录, 避免噪音

完整的 22 步 hook 进化过程见 reverse/REVERSE_ARCHIVE.md

AST 分析 (URL 后缀 / eval code)

eval code 是合法 JS → acorn 解析 → 函数映射 → 调用链追踪 → 自动反汇编

VM 解释器 _$_I 的源码就在 eval code 里, AST 直接读出 409 个 opcode 的 JS 实现。 14 个 AST 工具, ~20h, 产出 6328 行反汇编 + 1653 行伪代码。

当你能打开黑盒读电路图时, 永远不要从外面猜。


API 参考

查询商标备案记录

POST /searchUser/searchAction!getVRecordListPage.do
Content-Type: application/x-www-form-urlencoded

page=1&rows=10&sidx=RECORD_NUM&sord=desc&_search=false&nd=<timestamp>
可选: RECORD_NAME=<商标名>&COUNTRY=<国家代码>

响应: { total, page, records, rows: [{ RECORD_NUM, REGISTER_NUM, APPLY_USER_NAME, ... }] }

获取国家列表

POST /param/paramAction!getParamTypeList.do
ns=<timestamp>&code=Country

响应: [{ value: "142", name: "中国" }, { value: "502", name: "美国" }, ...]

技术栈

  • Node.js 18+ — 运行环境
  • acorn + acorn-walk — AST 解析 (后缀逆向)
  • crypto (Node.js 内置) — AES-128-CBC, CRC32
  • 无外部依赖 — 纯算方案零第三方库

致谢

免责声明

本项目仅供安全研究与学习交流使用, 严禁用于任何非法用途。滥用本项目逆向成果所造成的一切法律责任和后果, 与本作者无关。

License

MIT

About

瑞数 (Ruishu) 反爬逆向全解 — 从纯算 Cookie T 到 JsRpc 通杀, 附完整方法论与 AI 可直接使用的顶级瑞数逆向skill文档

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages