乐于分享
好东西不私藏

【万字记录】AI自动乱杀瑞数6代,从412到200的完整实录,检测点+指纹环境通杀

【万字记录】AI自动乱杀瑞数6代,从412到200的完整实录,检测点+指纹环境通杀

小肩膀教育教程完整教学AI逆向零基础编程实战

前言

瑞数信息(RuiShu)的反爬虫产品在国内政企网站中部署极为广泛,从省级政府门户到国企官网,到处都能看到它的身影。

瑞数防护目前已迭代到第6代,相比前几代有了质的飞跃:自定义VM虚拟机执行字节码、字符串表随机洗牌、30+种自动化工具检测、Canvas/WebGL双重指纹采集、双Cookie联动验证——形成了从HTTP层到JS层到行为层的五重防护体系。

市面上大多数瑞数分析文章止步于”用Playwright跑一下”或”大概分析了一下流程”。本文的目标不一样:用全量DOM Trace + AI辅助,彻底摸清瑞数6代的每一个环境检测点,然后纯Node.js补环境硬生成Cookie,不用浏览器,不用Playwright,直接从412到200。

最终效果:node rs6_crack.js 一行命令,0.5秒出结果,成功获取107条页面数据。


一、目标信息

项目
目标URL https://fzgg.gansu.gov.cn/fzgg/tzgg/list.shtml
目标站点
甘肃省发展和改革委员会
反爬系统
瑞数信息(RuiShu)第6代动态安全防护
目标Cookie 4hP44ZykCTt5P

(浏览器端JS计算生成)
依赖Cookie 4hP44ZykCTt5O

(服务器端Set-Cookie下发,HttpOnly)
核心JS文件 /tQrlMwxgEtCS/xsWaJeZftrRw.294cc83.js

(约177KB)
最终目标
纯Node.js补环境生成Cookie,获取页面数据

二、为什么要用DOMtrace + AI补环境

2.1 传统方式的痛点

逆向瑞数最大的难点不在于”算法有多复杂”,而在于你根本不知道它检测了什么

瑞数6代的核心JS是一个177KB的虚拟机,所有关键操作都通过字节码执行,字符串全部经过Caesar +2偏移编码再Fisher-Yates洗牌。你想知道它访问了navigator.webdriver?对不起,在字节码里这就是一个索引号,而且这个索引号每次加载都不一样。

传统的分析方式:

  • 手动调试
     → 瑞数有时间检测,断点一打就超时
  • 静态分析字节码
     → 字符串表洗牌,每次索引不同
  • 猜着补环境
     → 补了navigator少了screen,补了screen少了WebGL,永远补不完

2.2 DOMtrace方案的优势

全量DOM Trace的核心思路:不去逆虚拟机内部,而是在更底层——DOM API层面——记录瑞数JS执行时所有的API调用。

就像给瑞数的JS装了一个”行车记录仪”,它访问了什么属性、调用了什么方法、传了什么参数、返回了什么值,全部一览无遗。

我们的DOMtrace日志一共36,310行,完整记录了瑞数6代从加载到Cookie生成的每一个DOM操作。

2.3 AI辅助补环境

有了36,310行的DOMtrace日志,接下来就是”按图施工”——AI根据日志中出现的每一个API调用,精准构建Node.js模拟环境。不多不少,瑞数访问什么我们就模拟什么。

这就是全量DOMtrace + AI补环境的方法论:

  1. DOMtrace记录所有API调用(what)
  2. AI分析每个调用的用途(why)
  3. AI构建精准的模拟环境(how)
  4. 在Node.js VM沙箱中执行瑞数原始JS,直接拿Cookie

三、瑞数6代完整请求流程

3.1 三步验证机制

               ┌─────────────────────────────────────┐               │         客户端(浏览器/Node.js)       │               └──────────────────┬──────────────────┘                                  │           ┌──────────────────────┼──────────────────────┐           │                      │                       │   第1步:首次请求           第2步:JS执行             第3步:验证请求   (无Cookie)             (生成Cookie P)            (带双Cookie)           │                      │                       │           ▼                      ▼                       ▼  ┌────────────────┐   ┌──────────────────┐    ┌────────────────┐  │ GET /path      │   │ 浏览器执行JS      │    │ GET /path      │  │ 无Cookie       │   │                  │    │ Cookie: O + P  │  └───────┬────────┘   │ 1. 解析$_ts.cd   │    └───────┬────────┘          │            │ 2. 收集环境指纹    │            │          ▼            │ 3. 计算Cookie P   │            ▼  ┌────────────────┐   │ 4. 设置cookie     │    ┌────────────────┐  │ 412 响应       │   │ 5. 触发跳转       │    │ 200 OK         │  │ Set-Cookie: O  │   └──────────────────┘    │ 正式页面内容     │  │ (HttpOnly)     │                           └────────────────┘  │ HTML+JS加载器  │  └────────────────┘

3.2 第1步:首次请求 → 412响应

用curl模拟不携带任何Cookie的请求:

curl -s -D headers.txt -o body.html \  -H "User-Agent: Mozilla/5.0 ..." \  "https://fzgg.gansu.gov.cn/fzgg/tzgg/list.shtml"

响应头:

HTTP/1.1 412 Precondition FailedServer: ******Set-Cookie: 4hP44ZykCTt5O=600B32.O_qMmWnBU_XASM1y3TVTT9wgee1R...;            Path=/; expires=Thu, 17 Apr 2036 19:46:28 GMT;            Secure; HttpOnly

关键发现:

  • 状态码 412
    (Precondition Failed):瑞数6代的标志性首次响应码
  • Set-Cookie: 4hP44ZykCTt5O
    :HttpOnly + Secure,有效期10年的长期Cookie
  • 响应体约6KB的HTML,包含瑞数JS加载器

3.3 412响应体HTML结构解析

<!DOCTYPE html><html><head>  <!-- ① Content-Type声明 -->  <metahttp-equiv="Content-Type"content="text/html; charset=utf-8">  <!-- ② 动态Token(meta标签,每次请求不同) -->  <metacontent="Kr.iZgkDy2aTPzHQqFd9fF2W9TC27fWQLRW..."r="m">  <!-- ③ 内联Script:设置$_ts全局配置 -->  <scripttype="text/javascript"r='m'>    $_ts=window['$_ts'];    if(!$_ts) $_ts={};    $_ts.nsd=45613;                    // 动态数字种子(每次不同)    $_ts.cd="qtxqrrAloAEqDGVF...";     // 加密配置数据(~5.6KB)    if($_ts.lcd) $_ts.lcd();  </script>  <!-- ④ 外部JS:核心VM引擎(文件名固定,内容不变) -->  <scripttype="text/javascript"charset="utf-8"    src="/tQrlMwxgEtCS/xsWaJeZftrRw.294cc83.js" r='m'></script></head><body></body></html><!-- ⑤ 触发Cookie生成(函数名每次不同) --><scripttype="text/javascript"r='m'>_$j4();</script>

四个关键动态参数(每次请求都变化):

参数
示例值
说明
meta[r="m"]

 content
Kr.iZgkDy2aTPzHQ...
动态Token/盐值,约50字符
$_ts.nsd 45613
数字种子,用于字符串表洗牌
$_ts.cd qtxqrrAloAEqDGVF...
加密配置数据,约5.6KB
触发函数名
_$j4()
入口函数名,每次不同

多次请求对比——证明真的是每次都变:

请求1: meta="O_76ifnp9oVe..."  nsd=48695  call=_$iJ()请求2: meta="4Xqk9KawEIWs..."  nsd=7550   call=_$b9()请求3: meta="N5ITqIpZEnNf..."  nsd=56585  call=_$j7()请求4: meta="hXjZDfPuRUI5..."  nsd=58280  call=_$bn()请求5: meta="i0vo5qtLQXR9..."  nsd=58896  call=_$dD()请求6: meta="Kr.iZgkDy2aT..."  nsd=45613  call=_$j4()

四、核心JS引擎深度分析

4.1 VM虚拟机架构

瑞数6代的核心JS(177KB)是一个自定义虚拟机,结构如下:

if($_ts.cd){  (function(_$c1, _$fy){  // _$fy = 控制流字节码数组    var _$cY 0;          // 程序计数器    // VM主循环(指令分发器)    while(1){      _$be = _$dq[_$__++];  // 取指令      if(_$be 12){        if(_$be === 0){          // 初始化全局引用          _$_A = window;          _$jx = String;          _$cD = Array;          _$e_ = document;          _$_e = Math.random;          _$kx = Math.round;          _$gh = Date;        }        // ... 更多指令分发      }    }  })([/*参数1*/], [[/*二维字节码数组*/]]);}

4.2 字符串混淆:+2 Caesar偏移 + Fisher-Yates洗牌

瑞数对所有关键字符串使用双重混淆:

第一层:Caesar +2 偏移编码

编码后
解码后
{iiec} cookie
\|i{og}hn document
fi{yncih location
nyaHyg} tagName
{l}yn}?f}g}hn createElement
a}n;nnlczon} getAttribute
RGFBnnjL}ko}mn XMLHttpRequest
niMnlcha toString

第二层:运行时洗牌

字符串表在加载后通过 _$_g() 函数进行 Fisher-Yates 随机洗牌,洗牌种子与 $_ts.nsd 相关。这意味着同一个字节码索引在不同请求中指向不同的字符串,静态分析完全无效。


五、DOMtrace全量分析:瑞数到底检测了什么

这是本文的核心部分。通过36,310行DOMtrace日志,我们完整还原了瑞数6代的所有环境检测。

5.1 JS执行流程总览

domtrace line 1:   get window           → 内联script开始执行domtrace line 2:   get window           → 外部JS加载domtrace line 3:   get documentdomtrace line 4:   typeof检测           → 字符变量表初始化domtrace line 5-6String.fromCharCode  → 生成ā占位符domtrace line 9:   Array.join("")       → 拼接生成eval代码domtrace line 10:  Array.join("")       → 生成完整VM入口代码domtrace line 11:  eval                 → 进入二层VM

5.2 Navigator属性访问(line 108-137)

瑞数通过动态构建字符串的方式检测浏览器特征,而不是直接硬编码:

// line 108: 动态构建 "navigator" 字符串Array.join("") → ["n","a","v","i","g","a","t","o","r"] → "navigator"// line 110: 读取 navigator.userAgentget navigator.userAgent → "Mozilla/5.0 (Windows NT 10.0; ...)"// line 803: 读取 navigator.platformget navigator.platform → "Win32"// line 809: 读取 navigator.maxTouchPointsget navigator.maxTouchPoints → 10

5.3 浏览器类型全面探测(line 111-128)

瑞数会逐一检测市面上几乎所有浏览器:

// line 112"MSBlobBuilder"            → IE特性检测// line 114"webkitPersistentStorage"   → Chrome特性检测// line 117"UCWebExt,ucweb"            → UC浏览器// line 118"qb_bridge"                 → QQ浏览器// line 120"dolphin,dolphininfo"       → 海豚浏览器// line 121"chrome"                    → Chrome// line 122"qihoo"                     → 360浏览器// line 123"safari"                    → Safari// line 128"mimeTypes"                 → 插件检测// line 130"application/x-shockwave-flash" → Flash插件

5.4 自动化工具检测——重头戏(line 134-137, 2008-2013)

瑞数6代的自动化检测覆盖面极其恐怖

// PhantomJS检测"callPhantom,_phantom"// Hook框架检测(几乎覆盖了所有主流Hook工具)"$hook$,$hdx$,$sdx$,$uie$,$$lsr,$$lsp,$$lsrb,$$logger, $$ACXUTILS,_ACX_EVAL_PASS,_ACX_HOOKS, $readyCodeAlreadyExecutedInThisFrame"// 安全扫描器检测"netsparker,__ns,__nsAppendText,eoWebBrowser"// HP WebInspect检测"hp_identifier"// WebDriver全面检测"webdriver""_Selenium_IDE_Recorder,_selenium,callSelenium""__driver_evaluate,__webdriver_evaluate, __selenium_evaluate,__fxdriver_evaluate, __driver_unwrapped,__webdriver_unwrapped, __selenium_unwrapped,__fxdriver_unwrapped, __webdriver_script_func,__webdriver_script_fn"

它甚至还布设了事件陷阱(line 677-681):

addEventListener("driver-evaluate", handler, null)addEventListener("webdriver-evaluate", handler, null)addEventListener("selenium-evaluate", handler, null)

如果自动化工具触发了这些自定义事件,瑞数会立即捕获并标记为机器人。

5.5 Node.js环境检测(line 622-652)

这是补环境最关键的部分——瑞数专门检测你是不是在Node.js里跑的:

// window instanceof Window 检测new Function("try{return (window instanceof Window);}catch(e){}")→ 必须返回 true,否则判定为非浏览器环境// Node.js __filename 检测new Function("try{return __filename;}catch(e){}")→ 在Node.js中会返回文件路径,浏览器中返回 undefined// Node.js __dirname 检测new Function("try{return __dirname;}catch(e){}")→ 同上// SpiderMonkey Shell检测new Function("return typeof __loadScript == \"function\" && ...")// jsdom检测new Function("return typeof _globalObject != \"undefined\" && ...")// 中国爬虫框架检测"shenjian" → 检查神箭爬虫

5.6 Canvas 2D 指纹采集(line 3876-3889)

createElement("canvas")canvas.width = 200canvas.height = 50getContext("2d")ctx.textBaseline = "top"ctx.font = "18px 'Arial'"ctx.fillStyle = "#f82"                    // 橙色背景ctx.fillRect(0010030)              // 画矩形ctx.fillStyle = "#17e"                    // 蓝色文字ctx.fillText("ActiveXObject"316)     // 第一层文字ctx.fillStyle = "rgba(240,110,53,0.4)"   // 半透明覆盖ctx.fillText("ActiveXObject"518)     // 第二层文字(偏移)canvas.toDataURL()                       // 导出为PNG指纹

5.7 WebGL 指纹采集(line 3890-3992)

完整的WebGL渲染管线,包括顶点着色器、片元着色器、扩展列表扫描:

createElement("canvas")getContext("webgl")// 完整渲染管线createBuffer → bindBuffer → bufferDatacreateProgramcreateShader(VERTEX_SHADER) → shaderSource → compileShadercreateShader(FRAGMENT_SHADER) → shaderSource → compileShaderattachShader × 2 → linkProgram → useProgramdrawArrays// 顶点着色器代码"attribute vec2 attrVertex;varying vec2 varyinTexCoordinate; uniform vec2 uniformOffset; void main(){   varyinTexCoordinate=attrVertex+uniformOffset;   gl_Position=vec4(attrVertex,0,1); }"// 片元着色器代码"precision mediump float;varying vec2 varyinTexCoordinate; void main(){   gl_FragColor=vec4(varyinTexCoordinate,0,1); }"// WebGL canvas指纹canvas.toDataURL()// 扩展列表扫描(28个扩展逐一测试)getSupportedExtensions() → 28个扩展// 着色器精度测试(8次)getShaderPrecisionFormat()// VERTEX_SHADER + HIGH_FLOAT: rangeMin=127, rangeMax=127, precision=23// VERTEX_SHADER + HIGH_INT:   rangeMin=31,  rangeMax=30,  precision=0

WebGL扩展完整列表(28个):

ANGLE_instanced_arrays, EXT_blend_minmax, EXT_color_buffer_half_float,EXT_float_blend, EXT_frag_depth, EXT_shader_texture_lod, EXT_sRGB,EXT_texture_compression_bptc, EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic, OES_element_index_uint,OES_fbo_render_mipmap, OES_standard_derivatives, OES_texture_float,OES_texture_float_linear, OES_texture_half_float,OES_texture_half_float_linear, OES_vertex_array_object,WEBGL_color_buffer_float, WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb, WEBGL_debug_renderer_info,WEBGL_debug_shaders, WEBGL_depth_texture, WEBGL_draw_buffers,WEBGL_lose_context, WEBGL_provoking_vertex

5.8 屏幕与窗口指纹(line 4206-4221)

screen.availHeight    → 1019screen.availLeft      → 0screen.availTop       → 0screen.availWidth     → 1707screen.colorDepth     → 24screen.height         → 1067screen.width          → 1707screen.pixelDepth     → 24window.devicePixelRatio → 1.5window.innerWidth     → 1281window.innerHeight    → 832

最终组合成指纹字符串(line 4221):

true,true,true,true,true,true,1.5,Mozilla/5.0(Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0,,,[object LockManager],en-US,1019,0,0,1707,24,1067,1707,24

5.9 matchMedia 指纹(line 4062-4084)

matchMedia("(any-pointer: fine )")   → matchestruematchMedia("(any-pointer: coarse )") → matchesfalsematchMedia("(any-hover: hover )")    → matchestruematchMedia("(any-hover: none )")     → matchesfalse

5.10 Storage 操作(line 22-24, 691-715)

// 清理旧数据localStorage.removeItem("___TS___")// 设备ID生成与存储localStorage.getItem("$_YWTU")    → null (首次访问)localStorage.setItem("$_YWTU""5aCKZ._U8NfQ6XRKqIIiK7lR1et42oXLxaf_bz6vyRG")localStorage.setItem("$_YVTX""Wq")  // 访问标记// IndexedDB持久化IDBFactory.open("EkcP"1)

5.11 事件监听器注册

window.addEventListener("unload", fn, null)     // 页面卸载window.addEventListener("load", fn, null)        // 页面加载setInterval(fn, 5000)                            // 5秒定时器setInterval(fn, 2047)                            // ~2秒定时器setInterval(fn, 1000)                            // 1秒定时器(cookie刷新)document.addEventListener("keydown", fn, true)   // 键盘document.addEventListener("touchend", fn, true)  // 触摸document.addEventListener("click", fn, true)     // 点击

六、Cookie生成核心流程

6.1 Cookie名称来源(line 57)

Cookie名称 4hP44ZykCTt5 是从配置数据中解码出来的:

// String.fromCharCode解码配置字符串输入: [117,110,105,74,90,...,46,50,57,52,99,99,56,51,46,106,115]输出: "uniJZu9e;UAta9QfS;ICrVeDka;dY2Eap9v;Pl0SPFxN;4hP44ZykCTt5;       tQrlMwxgEtCS;h3WCHhB8wgTX;xsWaJeZftrRw.294cc83.js;..."// 分号分隔的配置表,索引5 = "4hP44ZykCTt5" 就是Cookie名称前缀

6.2 Cookie能力测试(line 101-102)

// SET: 写入测试cookiedocument.cookie = "enable_4hP44ZykCTt5=true; Secure"// GET: 立刻读回验证document.cookie → "enable_4hP44ZykCTt5=true"// 如果读不回来,直接判定环境异常!

6.3 Cookie值生成(line 900-905)

// line 900: 构建版本号String.fromCharCode(49,49,46,54,55,56) → "11.678"// line 902: 加密种子字节数组typeof [63,131,213,156,56,147,240,133,169,36] → "object"// line 903: 拼接生成~260字符的加密TokenArray.join("") → "HgHYvkPsZEKNxHMhzUVDTcH_mLIgdV..."// line 904: 生成前缀String.fromCharCode(48) → "0"// ★ line 905: 设置目标Cookie ★document.cookie = "4hP44ZykCTt5P=0HgHYvkPsZEKNxHMhzUVDTcH_mLIgdV...;    path=/; expires=Mon, 27 Apr 2026 19:48:46 GMT; Secure"

Cookie值结构:

0HgHYvkPsZEKNxHMhzUVDTcH_mLIgdV...│└───────────────────────────────│         加密Token主体(~260字符)└ 版本前缀 "0"

Token使用自定义Base64变种编码(. 和 _ 替代标准的 + 和 /)。

6.4 Cookie周期性刷新

Cookie 4hP44ZykCTt5P 在页面生命周期中被多次重新生成:

domtrace行号
时间偏移
说明
905
+0ms
首次生成(第一阶段JS)
1196
+15ms
第二次更新
2145
+36ms
第三次更新(定时器触发)
8648
+594ms
第二阶段JS首次生成
32987
+21.6s
定时器触发再次生成

七、瑞数6代五重防护体系全景

┌───────────────────────────────────────────────┐│              第一层:HTTP层防护                 ││  · 首次请求返回412 + Set-Cookie(HttpOnly)       ││  · 无有效Cookie的请求返回400空页面               │└─────────────────┬─────────────────────────────┘                  │┌─────────────────┴─────────────────────────────┐│            第二层:JS代码防护                    ││  · 虚拟机架构(自定义字节码解释器)               ││  · 字符串+2 Caesar编码 + Fisher-Yates洗牌       ││  · eval动态生成二层VM代码                       ││  · 所有关键参数动态化(每次请求不同)             │└─────────────────┬─────────────────────────────┘                  │┌─────────────────┴─────────────────────────────┐│          第三层:环境检测防护                    ││  · 30+种自动化工具检测                          ││  · Node.js环境检测(__filename/__dirname)       ││  · jsdom/SpiderMonkey Shell检测                 ││  · window instanceof Window检测                ││  · WebDriver事件陷阱                           │└─────────────────┬─────────────────────────────┘                  │┌─────────────────┴─────────────────────────────┐│          第四层:指纹采集防护                    ││  · Canvas 2D指纹                               ││  · WebGL指纹(着色器渲染+参数+28个扩展)         ││  · Screen/Window尺寸指纹                       ││  · Navigator属性组合指纹                       ││  · matchMedia查询                              ││  · Storage/IndexedDB持久化                     │└─────────────────┬─────────────────────────────┘                  │┌─────────────────┴─────────────────────────────┐│          第五层:行为检测                        ││  · 鼠标移动轨迹(screenX/screenY)              ││  · 键盘按键事件                                ││  · 触摸事件                                   ││  · Cookie周期性刷新(1秒/5秒定时器)             ││  · 页面可见性检测(visibilityState)             │└───────────────────────────────────────────────┘

八、纯Node.js补环境实现

8.1 环境模拟清单

根据DOMtrace中记录的每一个API调用,需要模拟的完整环境如下:

对象
关键属性/方法
模拟值
window
top, name, innerWidth/Height, devicePixelRatio
window自身, “”, 1281/832, 1.5
document
cookie (get/set), createElement, getElementsByTagName
完整读写
navigator
userAgent, platform, language, webdriver, maxTouchPoints
Firefox 146, Win32, en-US, false, 10
screen
width/height, colorDepth, availWidth/Height
1707/1067, 24, 1707/1019
location
href, protocol, host, pathname
目标URL各部分
localStorage
getItem, setItem, removeItem
内存Map实现
sessionStorage
getItem, setItem, removeItem
内存Map实现
indexedDB
open
最小模拟
Canvas 2D
getContext(“2d”), toDataURL
固定base64值
WebGL
getContext(“webgl”), 着色器操作, 扩展列表
完整模拟
matchMedia
查询匹配
“(any-pointer: fine)” → true
performance
now(), timing
递增毫秒数
Event系统
addEventListener, removeEventListener
空操作收集器

8.2 关键技术难点与解法

难点1:Cookie存储的getter/setter

瑞数的Cookie生成和验证都依赖 document.cookie 的正确行为——包括写入、读回、过期删除:

const cookieJar = {};// document.cookie getter - 返回所有cookie拼接function getCookieString() {  return Object.entries(cookieJar)    .map(([k, v]) => `${k}=${v}`).join('; ');}// document.cookie setter - 解析并存储单个cookiefunction setCookieString(str) {  const parts = str.split(';');  const kv = parts[0].trim();  const eqIdx = kv.indexOf('=');  if (eqIdx > 0) {    const name = kv.substring(0, eqIdx).trim();    const val = kv.substring(eqIdx + 1).trim();    const lower = str.toLowerCase();    if (lower.includes('expires=') && 过期日期 < 当前时间) {      delete cookieJar[name]; // 删除过期cookie    } else {      cookieJar[name] = val;  // 存储cookie    }  }}

瑞数会先写入一个测试Cookie enable_4hP44ZykCTt5=true,然后立刻读回验证。如果读不回来,直接判定环境异常。

难点2:window instanceof Window 检测绕过

这是瑞数6代最精巧的Node.js环境检测之一:

// 瑞数的检测代码new Function("try{return (window instanceof Window);}catch(e){}")// 在Node.js中,没有Window构造函数,会报错返回undefined// 必须返回true

解法——手动构造原型链:

function Window() {}Window.prototype = Object.getPrototypeOf(env);Object.setPrototypeOf(env, Window.prototype);env.Window = Window;// 现在 env instanceof Window === true

难点3:VM沙箱执行

必须用 vm.createContext() 而不是直接执行,这样可以自动隔离Node.js全局变量(__filename__dirnamerequire等),让瑞数的检测代码无法发现Node.js环境:

const vm = require('vm');const ctx = vm.createContext(env);  // env是构建好的浏览器环境// 1. 执行内联script(设置$_ts)vm.runInContext(inlineScript, ctx);// 2. 执行核心JS文件vm.runInContext(jsFileContent, ctx);// 3. 执行触发函数vm.runInContext('_$j4();', ctx);// 4. 从cookieJar中提取生成的cookieconst cookieP = cookieJar['4hP44ZykCTt5P'];

难点4:定时器同步刷新

瑞数JS使用 setTimeout(fn, 0) 异步执行部分代码。在Node.js VM中需要收集这些回调并同步刷新:

const pendingTimers = [];function mySetTimeout(fn, delay, ...args) {  const id = timerIdCounter++;  pendingTimers.push({ id, fn, args, delay, type'timeout' });  return id;}function flushTimers(maxRounds) {  for (let round = 0; round < maxRounds; round++) {    const timeouts = pendingTimers.filter(t => t.type === 'timeout');    if (timeouts.length === 0break;    for (const t of timeouts) {      pendingTimers.splice(pendingTimers.indexOf(t), 1);      t.fn(...t.args);    }  }}

难点5:Canvas/WebGL返回固定值

指纹不需要真实渲染,返回固定的base64即可——瑞数只关心指纹是否”像一个真实浏览器”,不会验证渲染结果的正确性:

if (tag === 'canvas') {  el.getContext = function(type) {    if (type === '2d'return make2dContext(el);    if (type === 'webgl'return makeWebGLContext(el);    return null;  };  el.toDataURL = function() {    return webglUsed ? CANVAS_WEBGL_DATA : CANVAS_2D_DATA;  };}

8.3 WebGL完整模拟

WebGL模拟是最复杂的部分,需要覆盖渲染管线的每一个API:

function makeWebGLContext(canvas) {  const exts = [    'ANGLE_instanced_arrays','EXT_blend_minmax',    'EXT_color_buffer_half_float','EXT_float_blend',    // ... 28个扩展    'WEBGL_lose_context','WEBGL_provoking_vertex'  ];  return {    canvas,    drawingBufferWidth300drawingBufferHeight150,    createBuffer() { return {}; },    bindBuffer() {}, bufferData() {},    createProgram() { return {}; },    createShader() { return {}; },    shaderSource() {}, compileShader() {},    getShaderParameter() { return true; },    attachShader() {}, linkProgram() {}, useProgram() {},    getProgramParameter() { return true; },    getAttribLocation() { return 0; },    enableVertexAttribArray() {},    vertexAttribPointer() {},    getUniformLocation() { return {}; },    uniform2f() {},    drawArrays() {},    getParameter(pname) {      if (pname === 37445return 'Mozilla';     // UNMASKED_VENDOR      if (pname === 37446return 'Mozilla';     // UNMASKED_RENDERER      if (pname === 7938)  return 'WebGL 1.0';   // VERSION      if (pname === 3379)  return 16384;         // MAX_TEXTURE_SIZE      // ... 更多参数      return 0;    },    getSupportedExtensions() { return exts; },    getExtension(name) {      if (name === 'WEBGL_debug_renderer_info')        return { UNMASKED_VENDOR_WEBGL37445UNMASKED_RENDERER_WEBGL37446 };      return exts.includes(name) ? {} : null;    },    getShaderPrecisionFormat(shaderType, precisionType) {      if ([363383633736336].includes(precisionType))        return { rangeMin127rangeMax127precision23 };      return { rangeMin31rangeMax30precision0 };    },  };}

8.4 完整执行架构

rs6_crack.js (~600行)├── httpGet()           ← HTTP GET封装├── parse412()          ← 解析412响应提取动态参数├── parseCookieO()      ← 提取Set-Cookie中的O值├── buildEnv()          ← 构建完整浏览器环境(核心,~400行)│   ├── cookieJar       ← Cookie存储(get/set)│   ├── makeStorage()   ← localStorage/sessionStorage模拟│   ├── makeElement()   ← DOM元素模拟│   ├── make2dContext() ← Canvas 2D上下文模拟│   ├── makeWebGLContext() ← WebGL上下文模拟(28个扩展)│   ├── document        ← Document对象│   ├── navigator       ← Navigator对象│   ├── screen          ← Screen对象│   ├── location        ← Location对象│   ├── performance     ← Performance对象│   ├── indexedDB       ← IndexedDB模拟│   ├── matchMedia()    ← MediaQuery模拟│   └── Window构造函数   ← instanceof检测绕过└── main()              ← 主流程    ├── [1] httpGet → 获取412 + Cookie O    ├── [2] httpGet → 下载核心JS(177KB)    ├── [3] vm.createContext + runInContext → 执行JS    ├── [4] 提取Cookie P    ├── [5] httpGet(带双Cookie) → 获取200页面    └── [6] 正则提取页面数据

8.5 主流程代码

async function main() {  // [1] 首次请求获取412响应  const resp1 = await httpGet(TARGET_URL);  const cookieOStr = parseCookieO(resp1.headers);  const params = parse412(resp1.body);  // [2] 下载核心JS文件  const jsResp = await httpGet(JS_URL, cookieOStr);  // [3] 构建环境 + 执行JS  const { env, cookieJar, flushTimers } = buildEnv(    params.metaContent, params.nsd, params.cd  );  const ctx = vm.createContext(env);  vm.runInContext(inlineScript, ctx);     // 设置$_ts  vm.runInContext(jsResp.body, ctx);       // 核心JS  vm.runInContext(params.triggerCall, ctx); // 触发函数  flushTimers(10);  // [4] 提取Cookie  const cookieP = cookieJar['4hP44ZykCTt5P'];  // [5] 带双Cookie请求正式页面  const fullCookie = `${cookieOStr}; 4hP44ZykCTt5P=${cookieP}`;  const resp2 = await httpGet(TARGET_URL, fullCookie);  // resp2.status === 200 → 成功!}

九、运行结果

9.1 完整输出

[1] 获取412响应...    状态: 412    Cookie O: 4hP44ZykCTt5O=60qP6vtiRskHXjVhUypRaBRwbfROXaO.Rfsw...    Meta content: Kr.iZgkDy2aTPzHQqFd9fF2W9TC27fWQLRWoP2MVLdF...    nsd: 45613    cd长度: 5646    触发调用: _$j4();[2] 下载核心JS文件...    JS文件大小: 177434 字节[3] 构建浏览器环境并执行JS...    内联script执行完成    核心JS执行完成    触发函数错误: Cannot read properties of null (reading '_$bw')[4] Cookie生成结果:    O: 4hP44ZykCTt5O=60qP6vtiRskHXjVhUypRaBRwbfROXaO.Rfsw...    P: 0r5mGxYBwzgLJr_1P84CbKXXIX22KdsF7N_0DapcfwuYGgXsglr4...    所有Cookie: enable_4hP44ZykCTt5, 4hP44ZykCTt5P[5] 带Cookie请求正式页面...    状态: 200[6] 解析页面数据:    找到 107 条通知:    1. 政府信息公开    2. 甘肃省人民政府关于推进全省铁路高质量发展的意见    3. 甘肃省能源局关于拟推荐申报国家第六批能源领域首台(套)...    4. 关于印发《省级零碳园区建设名单(第一批)》的通知    5. 甘肃省人民政府办公厅关于培育建设零碳园区的意见    6. 甘肃省人民政府办公厅关于印发加快场景培育和开放推动新场景...    7. 关于遴选年产6万吨锂电池石墨化负极材料等项目节能报告...    ... (共107条)

9.2 结果验证

步骤
预期
实际
状态
获取412响应
HTTP 412 + Set-Cookie
412 + Cookie O
通过
解析动态参数
meta/nsd/cd/trigger
全部成功提取
通过
下载核心JS
~177KB文件
177,434 bytes
通过
执行内联script
$_ts设置成功
成功
通过
执行核心JS
VM执行完成
成功
通过
触发函数
报错但cookie已生成
_$bw错误(不影响)
已知
Cookie P生成
有值
0r5mGxYBwz...
通过
正式页面请求
HTTP 200
200
通过
数据提取
包含目标文字
107条通知
通过

说明: 触发函数的 _$bw 错误是因为触发函数尝试执行一些页面跳转/meta操作时找不到对应的DOM节点,但此时Cookie已经在之前的核心JS执行阶段生成完毕,不影响结果。


十、绕过要点总结

技术点
具体做法
原因
VM沙箱执行 vm.createContext()

 创建隔离环境
自动隐藏Node.js全局变量
原型链伪造 Object.setPrototypeOf

 + Window构造函数
绕过 instanceof Window 检测
不用Proxy
直接用普通对象模拟
瑞数可能检测Proxy
Canvas/WebGL固定值
返回预制的base64字符串
指纹不需要真实渲染
Cookie读写
getter/setter + cookieJar
测试cookie必须能读回
定时器刷新
收集回调同步执行
setTimeout(fn,0)的异步代码
WebGL 28个扩展
完整列表 + getExtension模拟
扩展数量是指纹特征
着色器精度
getShaderPrecisionFormat × 8
rangeMin/rangeMax/precision
DOM树结构
head/body/meta/script元素
getElementsByTagName需要返回正确结构

十一、方法论的通用性

“全量DOMtrace + AI补环境”不只适用于瑞数。 这套方法论可以直接推广到:

  • 极验/数美/顶象
    等验证码系统的环境检测分析
  • 阿里系mtop签名
    的浏览器环境收集
  • 字节系/美团系
    的反爬JS分析
  • 任何依赖浏览器环境指纹的反爬系统

核心思路不变:不逆JS内部逻辑,在API调用层面全量记录,然后按图施工补环境。

关键优势:

  1. 完备性
     — 不会遗漏任何检测点,因为所有API调用都被记录了
  2. 效率
     — 不需要读懂VM字节码,只需要知道它访问了什么
  3. 可复用
     — 同一套环境可以复用到同类型的多个站点
  4. AI友好
     — 36,310行的结构化日志非常适合AI分析和自动生成代码

十二、相关文件清单

文件
说明
domtrace.txt
DOM调用跟踪日志(36,310行,分析环境需求的核心数据源)
rs6_crack.js
纯Node.js实现的Cookie生成 + 页面数据获取脚本(~600行)
rs6_execution_output.txt rs6_crack.js

 的完整执行输出
rs6_412_headers.txt
412响应头样本
rs6_412_body.html
412响应体HTML样本
ruishu_main.js
核心JS文件本地副本(177,434字节)
analyze_rs.js
辅助分析脚本(搜索cookie编码形式等)

一句话总结

通过36,310行全量DOMtrace日志,彻底摸清了瑞数6代的五重防护体系——从30+种自动化工具检测到Canvas/WebGL双指纹采集再到Node.js环境探测——然后AI根据日志按图施工,用600行Node.js代码构建精准的浏览器模拟环境,在VM沙箱中直接执行瑞数原始JS生成Cookie,不用浏览器、不用Playwright,纯算力从412到200,107条数据到手。


下边是广告环节(群满了加我)
小肩膀教育安全逆向教学
小肩膀教育作为国内十年逆向老机构,数十年如一日录制教程。
涵盖网络爬虫、JS逆向、安卓逆向、IOS逆向、小程序逆向,AI逆向和指纹浏览器开发等多个版块,完全从零基础开始教学。
官网:https://0xshoulderlab.site/
加入小肩膀,是加入了逆向技术圈子,互相学习、资源共享,欢迎加入小肩膀教育。

海外IP代理
海外IP代理做的人很多,有贵的也有便宜的,那为什么和如意合作呢?
因为和我们合作我们就有了联系,来找如意合作购买海外动态、静态住宅和包月不限量IP的,价格绝对优惠。(不能国内直接连接,需要海外环境)
https://0xshoulderlab.site/proxy

海外数据采集
AI时代,必须有高质量的数据采集服务搭配才可以。
有海外的数据采集需求的:虾皮、亚马逊、谷歌、ebay、雅虎、领英、X、youtube、TIKTOK等等,都可以来和如意合作。以下现成接口:
平台
API 数量
覆盖场景
Google
20
搜索、购物、新闻、学术、专利、航班、酒店、本地服务、财经、应用商店、视频
Walmart
8
商品、评论、分类、商家、搜索
LinkedIn
7
公司、职位、个人、帖子、人才列表
YouTube
6
搜索、视频详情、视频下载、音频/字幕下载
Lazada
5
商品详情、品牌/卖家/类目/关键词搜索
Amazon
4
搜索、商家、评论、商品详情
Yahoo
4
搜索、图片、视频、购物
DuckDuckGo
3
搜索、新闻、地图
X/Twitter
3
帖子详情、帖子列表、用户信息
Etsy
2
产品详情、商店信息
Wayfair
2
产品详情、关键词搜索
Crunchbase
2
搜索、详情
eBay
1
搜索
Yelp
1
评论
Trustpilot
1
公司评论
OOCL
1
货物跟踪
各类海外数据采集等等
而且有海外数据采集脚本的,都可以来联系如意,如意带你进cafe官方群,为你推广脚本,只要被调用就有钱拿,合作共赢!

会写海外数据采集的兄弟都可以入群弄个兼职,加如意微信入官方群:
IP数据检测
有代理IP,就有IP数据的检测服务。
IP归属地、IP风险画像、IP真人识别、IP代理识别、IP宿主识别,以及各类网络安全数据、用户资料风险画像和广告流量检测等,都可以来找如意谈合作。
在线IP纯净度检测网站:
https://www.iping.cc/
购买联系如意优惠价格:
邀请链接:https://www.ipdatacloud.com/?utm-source=wushuo&utm-keyword=?4271

图像识别合作
可解决一切图像识别问题,纯图片提交识别,识别任何图片,速度只比本地部署慢一点:

如意本人联系方式