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

前言
瑞数信息(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 |
| 目标站点 |
|
| 反爬系统 |
|
| 目标Cookie | 4hP44ZykCTt5P
|
| 依赖Cookie | 4hP44ZykCTt5O
|
| 核心JS文件 | /tQrlMwxgEtCS/xsWaJeZftrRw.294cc83.js
|
| 最终目标 |
|
二、为什么要用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补环境的方法论:
-
DOMtrace记录所有API调用(what) -
AI分析每个调用的用途(why) -
AI构建精准的模拟环境(how) -
在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"]
|
Kr.iZgkDy2aTPzHQ... |
|
$_ts.nsd |
45613 |
|
$_ts.cd |
qtxqrrAloAEqDGVF... |
|
|
|
_$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-6: String.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(0, 0, 100, 30) // 画矩形ctx.fillStyle = "#17e" // 蓝色文字ctx.fillText("ActiveXObject", 3, 16) // 第一层文字ctx.fillStyle = "rgba(240,110,53,0.4)" // 半透明覆盖ctx.fillText("ActiveXObject", 5, 18) // 第二层文字(偏移)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 )") → matches: truematchMedia("(any-pointer: coarse )") → matches: falsematchMedia("(any-hover: hover )") → matches: truematchMedia("(any-hover: none )") → matches: false
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 在页面生命周期中被多次重新生成:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
七、瑞数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 |
|
|
| document |
|
|
| navigator |
|
|
| screen |
|
|
| location |
|
|
| localStorage |
|
|
| sessionStorage |
|
|
| indexedDB |
|
|
| Canvas 2D |
|
|
| WebGL |
|
|
| matchMedia |
|
|
| performance |
|
|
| Event系统 |
|
|
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、__dirname、require等),让瑞数的检测代码无法发现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 === 0) break;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,drawingBufferWidth: 300, drawingBufferHeight: 150,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 === 37445) return 'Mozilla'; // UNMASKED_VENDORif (pname === 37446) return 'Mozilla'; // UNMASKED_RENDERERif (pname === 7938) return 'WebGL 1.0'; // VERSIONif (pname === 3379) return 16384; // MAX_TEXTURE_SIZE// ... 更多参数return 0;},getSupportedExtensions() { return exts; },getExtension(name) {if (name === 'WEBGL_debug_renderer_info')return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };return exts.includes(name) ? {} : null;},getShaderPrecisionFormat(shaderType, precisionType) {if ([36338, 36337, 36336].includes(precisionType))return { rangeMin: 127, rangeMax: 127, precision: 23 };return { rangeMin: 31, rangeMax: 30, precision: 0 };},};}
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] 构建环境 + 执行JSconst { env, cookieJar, flushTimers } = buildEnv(params.metaContent, params.nsd, params.cd);const ctx = vm.createContext(env);vm.runInContext(inlineScript, ctx); // 设置$_tsvm.runInContext(jsResp.body, ctx); // 核心JSvm.runInContext(params.triggerCall, ctx); // 触发函数flushTimers(10);// [4] 提取Cookieconst cookieP = cookieJar['4hP44ZykCTt5P'];// [5] 带双Cookie请求正式页面const fullCookie = `${cookieOStr}; 4hP44ZykCTt5P=${cookieP}`;const resp2 = await httpGet(TARGET_URL, fullCookie);// resp2.status === 200 → 成功!}
九、运行结果
9.1 完整输出
[1] 获取412响应...状态: 412Cookie O: 4hP44ZykCTt5O=60qP6vtiRskHXjVhUypRaBRwbfROXaO.Rfsw...Meta content: Kr.iZgkDy2aTPzHQqFd9fF2W9TC27fWQLRWoP2MVLdF...nsd: 45613cd长度: 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 结果验证
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0r5mGxYBwz... |
|
|
|
|
|
|
|
|
|
|
|
说明: 触发函数的
_$bw错误是因为触发函数尝试执行一些页面跳转/meta操作时找不到对应的DOM节点,但此时Cookie已经在之前的核心JS执行阶段生成完毕,不影响结果。
十、绕过要点总结
|
|
|
|
|---|---|---|
| VM沙箱执行 | vm.createContext()
|
|
| 原型链伪造 | Object.setPrototypeOf
|
|
| 不用Proxy |
|
|
| Canvas/WebGL固定值 |
|
|
| Cookie读写 |
|
|
| 定时器刷新 |
|
|
| WebGL 28个扩展 |
|
|
| 着色器精度 |
|
|
| DOM树结构 |
|
|
十一、方法论的通用性
“全量DOMtrace + AI补环境”不只适用于瑞数。 这套方法论可以直接推广到:
- 极验/数美/顶象
等验证码系统的环境检测分析 - 阿里系mtop签名
的浏览器环境收集 - 字节系/美团系
的反爬JS分析 -
任何依赖浏览器环境指纹的反爬系统
核心思路不变:不逆JS内部逻辑,在API调用层面全量记录,然后按图施工补环境。
关键优势:
- 完备性
— 不会遗漏任何检测点,因为所有API调用都被记录了 - 效率
— 不需要读懂VM字节码,只需要知道它访问了什么 - 可复用
— 同一套环境可以复用到同类型的多个站点 - AI友好
— 36,310行的结构化日志非常适合AI分析和自动生成代码
十二、相关文件清单
|
|
|
|---|---|
domtrace.txt |
|
rs6_crack.js |
|
rs6_execution_output.txt |
rs6_crack.js
|
rs6_412_headers.txt |
|
rs6_412_body.html |
|
ruishu_main.js |
|
analyze_rs.js |
|
一句话总结
通过36,310行全量DOMtrace日志,彻底摸清了瑞数6代的五重防护体系——从30+种自动化工具检测到Canvas/WebGL双指纹采集再到Node.js环境探测——然后AI根据日志按图施工,用600行Node.js代码构建精准的浏览器模拟环境,在VM沙箱中直接执行瑞数原始JS生成Cookie,不用浏览器、不用Playwright,纯算力从412到200,107条数据到手。


官网:https://0xshoulderlab.site/
https://0xshoulderlab.site/proxy

|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

https://www.iping.cc/

邀请链接:https://www.ipdatacloud.com/?utm-source=wushuo&utm-keyword=?4271


夜雨聆风