瑞数 3 逆向之补环境详细步骤

388 阅读7分钟

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责。

若有侵权,请在评论区联系作者立即删除!

一、分析加密数据

案例网站,更多关于瑞数的解释,可以查看文章 常见反爬策略整理,这里只讲述如何通过手动补环境的方式解决瑞数 3 反爬,通过这个案例得以对补环境这种逆向手段有一个基本理解。

1.1 请求分析

这里选择 Charles 作为数据抓包工具。

  • 抓包分析,看到经典三次请求
  • 首次请求响应状态 202
  • Cookie_T 的值首字母是 3

image.png

1.2 定位 Cookie_T 生成位置

  • 勾选 Script 断点:进入网页,打开浏览器的开发者工具,切换 sources 面板,右侧 Event Listner Breakpoints -> Script 勾选。

    image.png

  • 清除旧的数据:切换 Application 面板,点击左侧菜单列 Storage,点击 Clear site data,然后刷新网页。

    image.png

  • 插入 hook:此时程序会在执行到外链接 js 内容首行时会断住,这时在 Console 执行 hook 代码,之后放开 Script 断点。继续执行。

    image.png

  • 定位 Cookie 生成位置

    首先会碰到第一次断点,设置 cookie 的值为一个 true,非目标值,直接跳过。

    image.png

    之后程序会断在真正的 Cookie 设置位置,分析右侧调用堆栈 Call Stack

    image.png

    在堆栈中找到 Cookie 的值第一次出现的位置,那里就是 Cookie 的生成方法。至此我们已经定位到关键参数 Cookie_T的生成位置。

    暂时先停在断点这里,后续步骤都需要基于该断点。

    image.png

二、调试前的准备工作

补环境的核心就是,让浏览器和本地 Node 环境执行同一套代码,直到两边的执行流程和生成结果一致,才能证明环境补完了,因此有两个关键步骤:

  • 有一份基准代码能让浏览器和本地环境执行
  • 让浏览器一直执行固定不变的代码

2.1 文件和数据保存

基于上面的 Cookie_T 断点位置,保存以下内容:

  • 保存 202 的响应 html 文件为 first.html

    打开浏览器控制台 Network 面板,选择 202 的请求,点击 Response,复制内容保存,需要注意的是,千万要保存未格式化的内容,否则会导致环境检测出错。

    image.png

  • 复制 first.html 中的关键代码保存为 core_code.js

    first.html 中,第二个 scriptr = m 的标签的内容,一大段函数定义,这里则是核心加密代码,复制未格式化的内容,保存到新的文件中。

    image.png

  • 保存第二次请求的外链接 js 响应内容为 link.js

    同样的要保存未格式化的内容。

    image.png

  • 保存此时的 Cookie

    • 断点位置的 val 即是 Cookie_T,复制出来用作后续比对结果;

    • Application 面板中的 Cookie 中可以看到 Cookie_S,后续调试时,不要把它清掉,不然会导致程序走入错的流程,如果不小心清掉了,重新发起请求获取新的 Cookie_S 即可。

    image.png

2.2 覆盖浏览器请求

这里使用 Charles 作为调试请求的工具,通过覆盖浏览器请求该 URL 时永远返回的文件内容,让浏览器执行内容静态化。

  • 选择响应状态为 202 的请求,右键点击,选择 Map Local

    image.png

  • 选择上面保存的 first.html 作为响应文件

    image.png

  • 后续对浏览器进行正常刷新,会发现执行的 html 是本地文件,这样就能保证浏览器访问目标 url 执行的代码是不变的。

2.3 关键代码位置

基于上面的 Cookie_T的断点,为了便于后续的调试,需要记录下几个关键代码的位置。

  • Cookie_T 的生成位置

    发现有 (5);关键标识,为了后面方便调试,我们可以根据这个关键表标识,对 VM 中的代码插桩。

    由于瑞数的代码虽然是动态变化的,但是基本都是命名变化,一些数值或者关键字是不变的,因此可以根据关键字和数值快速定位到不同的 VN 代码中,Cookie_T 的设置位置。

    image.png

  • Cookie_T 生成的最外层入口

    image.png

  • VM 的入口位置

    .push = 关键字。

    image.png

2.4 本地执行文件

  • 新建文件 main.js,补充基本的环境,减少后续调试步骤。

    image.png

  • 将保存的 link.js 放入和 main.js的同一目录下。

    image.png

  • 执行外链接 js 的代码

    image.png

  • 执行 core_code.js 中的加密代码

    image.png

  • 本地调用浏览器调试工具

    执行 node --inspect-brk main.js命令,打开浏览器调试工具,然后就可以在浏览器调试本地代码了。

    image.png

    image.png

三、调试,补环境

补环境分为两个大的阶段:VM 入口前VM 内部执行,而 VM 内部执行又分为两个小阶段:进入 Cookie_t 生成入口前进入 Cookie_t 生成入口后。按照执行阶段补环境即可。

调试之前,把浏览器的异常捕获都打开。

image.png

3.1 VM 入口前

3.1.1 插入断点
  • 编辑 first.htmlcore_code.js 文件,在 VM 入口前插入 debugger 断点,先保证浏览器和本地都能执行到该位置。

  • 两边都执行到 VM 入口处,则需要判断待执行的 VM 代码是否一致,如果不一致则说明此前补的环境有问题

    image.png

3.1.2 调试过程
  • 报错:TypeError: document.getElementById is not a function

    原因 document 缺少 getElementById 方法,获取 id9DhefwqGPrzGxEp9hPaoag 的对象。从浏览器执行结果比对,补充 document.getElementById,分析代码得出,这里是获取 first.html 中 meta 标签的内容 <meta id="9DhefwqGPrzGxEp9hPaoag" content="L)qO_fl......> 中的 content 属性值。 image.png

    需要注意的是,content 内容直接从 first.html 复制粘贴会出现编码问题,这里改成将复制的 content 放到文件中,然后程序从文件读取内容。如下:

    image.png

  • 进入到 VM 入口 的断点

    在待执行代码中,搜索上述的 Cookie_T 的生成位置的代码发现能找到,且对比和浏览器的待执行代码是一致的,那么 VM 入口前 的环境补充完毕。

    image.png

3.2 进入 Cookie_t 生成入口前

3.2.1 插入断点

在上面保存的 Cookie_T 生成的最外层入口位置插入断点。例如:入口代码为 _$iG();,在 VM 入口这里通过正则来进行一个替换,

编辑 first.htmlcore_code.js,在 VM 入口前插入代码:result=_$EJ.match(/_\$iG\(\);/);_$EJ=_$EJ.replace(result[0],'debugger;'+result[0]);,效果如图:

image.png

3.2.2 调试过程
  • 报错:TypeError: Cannot read properties of undefined (reading 'location')

    window 缺少 top image.png

  • 报错:TypeError: _$Hy[_$gD(...)] is not a function

    document 缺少 createElement

    image.png

    对比浏览器执行情况,补充:

    image.png

  • 报错:TypeError: _$HB[_$mb(...)] is not a constructor

    和浏览器一致拥有的正常的报错,跳过即可。

    image.png

  • 报错:TypeError: _$Dj[_$nH(...)] is not a function

    逻辑走错了,浏览器不会走到这里,分析这段代码块,发现是缺少 addEventListener 环境,补充:window.addEventListener = function addEventListener(type, listener) {};

    image.png

  • 报错:TypeError: Cannot read properties of undefined (reading 'style')

    缺失 document.documentElement 属性,补充 document.documentElement = { style: {} }; image.png

  • 报错:TypeError: _$HB[_$mb(...)] is not a constructor

    此处报错是正常的,进入 catch 块中,对 navigatormimeTypes 进行了检测。补充 window.navigator.mimeTypes = [];

    image.png

  • 报错:TypeError: _$Dj[_$nH(...)] is not a function

    缺少 document.addEventListener 导致进入 else 逻辑块中。

    image.png

  • 报错:TypeError: _$HB[_$uQ(...)][_$oW(...)] is not a function

    补充 location 的 reload 方法。

    image.png

  • 报错:TypeError: _$Hy[_$iU(...)] is not a function

    补充 document.getElementsByTagName

    image.png

    根据这段代码,比对浏览器,继续补充 getElementsByTagName 返回的对象的属性,如图:

    image.png

  • 报错:TypeError: String.prototype.indexOf called on null or undefined

    image.png

    结合 _$Dj 的作用域,跟进堆栈,定位到问题,_$Djwindow.name 的值,补充该属性值即可。

    image.png

  • 走到了一开始插入的 Cookie_T 的生成入口 断点这里,证明前面的环境基本补充完成

    image.png

3.3 进入 Cookie_T 生成入口内部

3.3.1 插入断点

编辑 first.htmlcore_code.js 在之前 hook 到的 Cookie_T 的生成位置插入断点。依旧是在 VM 入口这里,替换待执行字符串以达到设置断点的目的。

例如:生成 Cookie_T 的代码是 var _$EJ = _$jI(5);,插入替换字符串:result=_$EJ.match(/var _\$EJ=_\$jI\(5\);/);_$EJ=_$EJ.replace(result[0],'debugger;'+result[0]);,效果如图:

image.png

3.3.2 调试过程
  • 报错:TypeError: Cannot read properties of undefined (reading 'toString')

    对比浏览器,发现正常逻辑不会走到这里,从 Cookie_T 最外层入口开始,着重对分支进行断点调试。

    image.png

    调试分析发现,是缺少了条件判断值 _$G4 导致未进入正常 cookie_T 生成逻辑中,查看浏览器右侧的 Scope 发现 _$G4 是个全局变量,可以直接全局搜索 _$G4 的定义获取 _$G4的值。

    image.png

    全局搜索后定位到 _$G4 的初始化位置,发现 _$G4localStorage 属性,比照浏览器补充 window.localStorage = { removeItem: function(key) {},};

    image.png

  • 报错:TypeError: _$sN[_$mv(...)] is not a function

    连续两个报错都是对 localStorage 的检测。

    image.png

    补充完毕后的 localStorage 如下:

    image.png

  • 补环境基本完成,进入预先插入的 Cookie_T 生成的断点

    image.png

  • 善后工作

    core_code.js 程序执行还没结束,但是 Cookie_T 的值已经生成,有两种方案处理:

    • 在 VM 入口利用正则对代码进行处理,在获得 Cookie_T 的值时直接中断程序,可以略过后续的环境检测。
    • 继续补环境,直到程序流程走完,本文章选择这种方式继续讲解。
  • 报错:_$Hy[_$nV(...)][_$wF(...)] is not a function

    documentElement.getAttribute 缺失。

    image.png

  • 报错:TypeError: Cannot read properties of undefined (reading 'width')

    缺少 screen 属性,按照代码补充数据。

    image.png

  • 运行本地程序,得到预期结果

    core_code.js 的代码流程走完,本地也生成了正确结果,此时补环境正式完成。

    image.png

四、验证 Cookie

和浏览器生成的 Cookie_T 对比,发现值是完全一致的,接下来就可以编写请求程序验证生成的 Cookie_T 是否有效。

4.1 编写主程序入口

  • 删除写死的代码

    js_codecontent 需要根据每次请求动态变更,因此把 main.js 中的相关内容删除。

    注意:link.js 由于同一页面的 js 文件内容是相同的,因此本地保存一份可以一直使用,无需变更,除非网站改版。

  • 编写请求流程

    image.png

4.2 程序解析动态文件

利用 cheerio 实现对 html 文档的标签元素提取。

  • 提取 HTML 的 content

    image.png

  • 提取 HTML 的 script

    image.png

4.3 验证

完整代码如下,请求验证成功,至此补环境逆向网站的流程完成。

image.png