with one click
ruishu-reverse
瑞数(Ruishu/Rivers Security)反爬防护纯算逆向 — Cookie T 生成 + URL 后缀处理
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
瑞数(Ruishu/Rivers Security)反爬防护纯算逆向 — Cookie T 生成 + URL 后缀处理
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | ruishu-reverse |
| description | 瑞数(Ruishu/Rivers Security)反爬防护纯算逆向 — Cookie T 生成 + URL 后缀处理 |
| triggers | ["瑞数","ruishu","rivers security","412 防护","Cookie T","动态JS反爬","anti-bot bypass"] |
目标: 任何 Claude 实例读完本文档, 都能独立完成瑞数保护站点的 Cookie T 纯算生成 + URL 后缀处理。 已验证: 9+ 站点 HTTP 200。
拿到目标 URL 后, 按此流程选择方案:
1. HTTP GET 目标 URL
├── 非 412 → 不是瑞数防护, 退出
└── 412 + $_ts.cd + $_ts.nsd → 确认瑞数
│
2. 是否只需 GET 请求?
├── 是 (80%场景) → 纯算方案 [阶段 0→5]
└── 需要 POST
│
3. POST 是否需要 URL 后缀?
├── 不需要 (99%站点) → 纯算 Cookie + 普通 POST [阶段 0→5]
└── 需要后缀
├── 稳定方案 → JsRpc (浏览器注入, 通杀)
└── 轻量方案 → sdenv VM 内 XHR (单次POST后需重建实例)
快速验证是否需要后缀: 纯算 Cookie T + 普通 POST → 200 则不需要, 400/412 则需要。
浏览器 GET → 412 + HTML (含 $_ts.nsd, $_ts.cd, mainjs URL, Set-Cookie: xxxS=...)
↓
mainjs 执行 → 解码 cd → 提取 45 组 keys + VM 字节码
↓
动态生成 ~296KB eval 代码 (变量名由 nsd 种子决定, 每次不同)
↓
eval 执行 → 三层嵌套 VM → 收集指纹 → 组装 basearr (154-166B TLV)
↓
basearr → Huffman → XOR → AES-CBC → CRC32 → AES-CBC → Base64 → Cookie T
↓
浏览器带 Cookie S+T 重新 GET → 200
我们纯算替代的部分:
| 浏览器做的 | 纯算替代 | 阶段 | 详情 |
|---|---|---|---|
| mainjs → eval 代码 | Coder 重写 | 3 | coder_rewrite.md |
| cd → 提取 keys | extractKeys | 2 | key_extraction.md |
| VM 收集指纹 → basearr | 数据驱动适配 | 4 | basearr_adaptation.md |
| basearr → 加密 → Cookie T | generateCookie | 1 | encryption_chain.md |
| 阶段 | 输入 | 输出 | 验证标准 | 通用? |
|---|---|---|---|---|
| 0 侦察 | 目标 URL | 412 HTML + mainjs + Cookie S + sdenv 参考数据 | sdenv Cookie → 200 | 通用 |
| 1 加密链 | sdenv Cookie T + keys | generateCookie(basearr, keys) → Cookie T | sdenv basearr + 纯算加密 → 200 | 通用, 一次性 |
| 2 密钥提取 | $_ts.cd | keys[0..44] (45 组) | keys 和 sdenv 提取的一致 | 通用, 一次性 |
| 3 Coder | mainjs + nsd + cd | eval 代码 + codeUid + functionsNameSort | eval 代码逐字节一致 | 通用, 一次性 |
| 4 basearr | 参考数据 + keys | buildBasearr(config, keys) → basearr | 纯算全链路 → 200 | 每站点 ~1h |
| 5 端到端 | 全部 | 纯算 HTTP GET → 200 | 连续 3+ 次 200 | 组装即可 |
执行顺序: 严格 0 → 1 → 2 → 3 → 4 → 5, 每步验证通过后再进下一步。
核心: 用 sdenv 采集 3-5 组真实数据 → 逐字节对比 → 找到每个字节的来源。绝对不要去读内层 VM 代码 (740 个 state, 三层嵌套 — 这是陷阱)。
采集 5 session → 每个 TLV 字段拆开 → 逐字节标注:
固定 (所有 session 相同) → 硬编码
来自 keys (匹配 keys[N]) → 动态提取
时间相关 (有规律变化) → 找公式
随机 (无规律) → Math.random
未知 → 需更多数据或更深分析
真实经验: 花 2 天读 VM 代码完全浪费。转向数据驱动后, 1 天内解决所有问题。
核心: eval code 是合法 JS, 用 acorn 解析 AST → 建立 rt[N] 函数映射 → 递归追踪调用链 → 提取核心算法。几小时完成手工需要数周的工作量。
产出: 14 个 AST 工具, ~20h, 从 296KB 混淆代码中完整逆向后缀核心函数。详见 ast_methodology.md
| 目标在哪一层? | 用什么方法 |
|---|---|
| eval code JS 函数 | AST (精确高效) |
| basearr 数据结构 | 数据驱动 (快速可靠) |
| r2mKa VM 字节码 | AST 提取 opcode + 自动反汇编 |
| 运行时动态值 (时间戳等) | sdenv 采集 |
| 弯路 | 代价 | 正确做法 |
|---|---|---|
| 反编译内层 VM 理解 basearr | 2 天浪费 | 数据驱动: 5 session 采集, 10 分钟解决 |
| 照搬 rs-reverse 公式 (idx*7+6 等) | 1 天白费 | 数据驱动: 公式是版本特定的, 不通用 |
| 补环境跑 eval 代码 | document.all 需 C++ Addon | Coder 重写 mainjs 逻辑 |
| 硬编码 type=2 值 | 换 session 就错 | cp1 索引→值映射 (5 session 反推) |
| 跳过混合验证直接做 basearr | 400 了不知道哪步错 | 先 sdenv basearr + 纯算加密 = 200, 证明加密正确 |
| 运行时栈追踪反推 opcode 语义 | 80B/天效率 | AST 静态提取: 400B/小时 (效率 80 倍) |
| HTTP 下载 mainjs 用 string 拼接 | UTF-8 多字节字符损坏 | Buffer 拼接 + toString('utf-8') |
keys[7].split(';')[5] + 'T', 不是硬编码for (let i = 0; i < Math.min(gen.length, ref.length); i++) {
if (gen[i] !== ref[i]) {
console.log('差异 @' + i + ':', JSON.stringify(gen.substring(i, i+60)));
console.log('参考:', JSON.stringify(ref.substring(i, i+60)));
break;
}
}
适配新站点时, 逐项检查打勾:
[8,0] 还是 5B? 从参考读取// PRNG (所有版本通用)
seed = 15679 * (seed & 0xFFFF) + 2531011
// Huffman 权重 (所有版本通用)
byte=0 → weight=45, byte=255 → weight=6, 其余 → weight=1
// AES-128-CBC
外层: key=keys[16], IV=随机16B 内层: key=keys[17], IV=全零16B
// CRC32 多项式
0xEDB88320
// 自定义 Base64 字母表 (所有版本通用)
'qrcklmDoExthWJiHAp1sVYKU3RFMQw8IGfPO92bvLNj.7zXBaSnu0TC6gy_4Ze5d'
// BASESTR (cd 解码用, 比 Base64 字母表多 24 个字符)
'qrcklmDoExthWJiHAp1sVYKU3RFMQw8IGfPO92bvLNj.7zXBaSnu0TC6gy_4Ze5d{}|~ !#$%()*+,-;=?@[]^'
// getLine 乘数 (mainjs op88)
55295
// 变量名字符集
'_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
// _ifElse 二叉搜索步长表
[4, 16, 64, 256, 1024, 4096, 16384, 65536]
// Cookie 名
keys[7].split(';')[5] + 'T'
// URL 后缀参数名
keys[7].split(';')[1]
basearr (154-166B)
→ Huffman 编码 (~118B)
→ 前 16 字节 XOR keys[2][0:15]
→ AES-128-CBC (key=keys[17], IV=全零, PKCS7) → ~128B
→ 拼 packet: [2, 8, r2mkaTime(4B), now(4B), 48, keys[2](48B), lenEnc, cipher]
→ CRC32 → [crc(4B), packet] → ~193B
→ AES-128-CBC (key=keys[16], IV=随机16B, PKCS7) → ~224B
→ 自定义 Base64 → "0" + 299 字符
| key | 长度 | 含义 | 用途 |
|---|---|---|---|
| keys[2] | 48B | KEYS48 | XOR 前 16B + packet 内嵌全部 48B |
| keys[7] | 变长 | 配置串 (分号分隔) | Cookie 名 [5]+'T', 后缀参数名 [1] |
| keys[16] | 16B | KEY2 | 外层 AES 密钥 |
| keys[17] | 16B | KEY1 | 内层 AES 密钥 |
| keys[19] | 变长 | 时间戳串 | type=10[6..9] |
| keys[21] | 变长 | r2mkaTime 串 | nonce 时间 |
| keys[22] | 变长 | 加密数据 | type=6 AES 解密 |
| keys[24-26] | 变长 | 数值串 | type=10 参数 |
| keys[29-32] | 各 4B | 变量名 | type=2 映射 (cp1 索引→值) |
| keys[33-34] | 变长 | 数值串 | codeUid 计算参数 |
瑞数的变量名不是固定的! nsd 不同 → grenKeys(918, nsd) 洗牌不同 → eval 代码中所有变量名变化。
Session 1 (nsd=84277): _$eX, _$hR, _$cR, _$bO ...
Session 2 (nsd=91234): _$f3, _$gT, _$aK, _$dP ...
hook 定位必须用结构特征, 不用变量名:
// ❌ 错误: 用变量名 (下次就变了)
const target = 'function _$hr(){var _$jZ=[324];';
// ✅ 正确: 用结构特征 (永远不变)
const statePattern = /function\s+(_\$\w+)\(\)\{var\s+(_\$\w+)=\[324\]/;
// ✅ 正确: 用代码长度
if (code.length > 250000) { /* 这是 eval 代码 */ }
// ✅ 正确: 用常量特征
if (code.includes('15679') && code.includes('2531011')) { /* 找到 PRNG */ }
| 工具 | 安装 | 用途 | 阶段 |
|---|---|---|---|
| Node.js crypto/http | 内置 | AES 加解密, HTTP 请求 | 全部 |
| sdenv | npx pnpm add sdenv | 参考数据采集, VM 内 XHR | 0, 4, 6 |
| js-beautify | npm i js-beautify | 格式化 mainjs (可选) | 3 |
| acorn + acorn-walk | npm i acorn acorn-walk | AST 分析 (后缀逆向) | 6 |
注意: npm 11.x + Node 24 有依赖解析死循环 bug, 安装 sdenv 必须用 pnpm。 编译原生模块需要 VS Build Tools (Windows) 或 gcc (Linux)。
瑞数的变量名每次加载都不同! 必须在同一个 session 中采集全套配套数据。 分开采集 (先拿 412, 再拿 mainjs) 的话 nsd 已经变了, 数据对不上!
使用 scripts/collect_session.js 一次性采集:
captured/
├── session.json nsd + cd + Cookie S/T + basearr + 时间戳
├── keys_raw.json 45 组密钥 (index + length + data)
├── ts_init.js $_ts 初始化脚本 (含 cd)
├── eval_code.js 296KB eval 代码 (配套变量名)
└── mainjs.js mainjs 源码 (静态, 可单独下载)
| 文件 | 内容 |
|---|---|
| references/encryption_chain.md | 阶段 1: Huffman/AES/CRC32/Base64 完整加密解密实现 |
| references/key_extraction.md | 阶段 2: cd 解码 + XOR 偏移推导 + 45 组 keys 提取 |
| references/coder_rewrite.md | 阶段 3: 外层 VM 重写 + 75+55 opcode + 9 步调试过程 |
| references/basearr_adaptation.md | 阶段 4: TLV 结构 + 每个 type 实现 + 数据驱动案例 |
| references/ast_methodology.md | AST 反编译 4 步流水线 + 14 个工具 + 方法对比 |
| references/vm_hook_cookbook.md | 7 种 VM 注入技术 + console 侧信道导出 |
| references/suffix_analysis.md | URL 后缀 88B/120B 结构 + SHA-1 签名 + 现有方案 |
| 文件 | 用途 |
|---|---|
| scripts/collect_session.js | 配套数据一次性采集 (sdenv + VM 注入) |
| scripts/hybrid_verify.js | 混合验证: sdenv basearr + 纯算加密 → 200 |
| scripts/pure_run.js | 纯算全流程模板 (零第三方依赖) |
| scripts/collect_type2.js | type=2 多 session 采集 + 映射推导 |
| scripts/sdenv_client.js | sdenv 客户端 (Cookie + VM 内 XHR) |
| 文件 | 说明 |
|---|---|
| lib/coder.js | 外层 VM 重写器 (362 行, 验证: eval 代码 100% 字节一致) |
| lib/basearr.js | basearr 生成器 (304 行, 验证: HTTP 200) |