声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责。
若有侵权,请在评论区联系作者立即删除!
一、分析加密数据
案例网站,更多关于瑞数的解释,可以查看文章 常见反爬策略整理,这里只讲述如何通过手动补环境的方式解决瑞数 3 反爬,通过这个案例得以对补环境这种逆向手段有一个基本理解。
1.1 请求分析
这里选择 Charles 作为数据抓包工具。
- 抓包分析,看到经典
三次请求 - 首次请求响应状态
202 - Cookie_T 的值首字母是
3
1.2 定位 Cookie_T 生成位置
-
勾选
Script 断点:进入网页,打开浏览器的开发者工具,切换sources面板,右侧Event Listner Breakpoints->Script勾选。 -
清除旧的数据:切换
Application面板,点击左侧菜单列Storage,点击Clear site data,然后刷新网页。 -
插入 hook:此时程序会在执行到外链接 js 内容首行时会断住,这时在
Console执行 hook 代码,之后放开Script断点。继续执行。 -
定位
Cookie生成位置首先会碰到第一次断点,设置 cookie 的值为一个
true,非目标值,直接跳过。之后程序会断在真正的 Cookie 设置位置,分析右侧调用堆栈
Call Stack。在堆栈中找到 Cookie 的值第一次出现的位置,那里就是 Cookie 的生成方法。至此我们已经定位到关键参数
Cookie_T的生成位置。暂时先停在断点这里,后续步骤都需要基于该断点。
二、调试前的准备工作
补环境的核心就是,让浏览器和本地 Node 环境执行同一套代码,直到两边的执行流程和生成结果一致,才能证明环境补完了,因此有两个关键步骤:
- 有一份基准代码能让浏览器和本地环境执行
- 让浏览器一直执行固定不变的代码
2.1 文件和数据保存
基于上面的 Cookie_T 断点位置,保存以下内容:
-
保存
202的响应 html 文件为first.html打开浏览器控制台
Network面板,选择202的请求,点击Response,复制内容保存,需要注意的是,千万要保存未格式化的内容,否则会导致环境检测出错。 -
复制
first.html中的关键代码保存为core_code.js在
first.html中,第二个script且r = m的标签的内容,一大段函数定义,这里则是核心加密代码,复制未格式化的内容,保存到新的文件中。 -
保存第二次请求的外链接 js 响应内容为
link.js同样的要保存未格式化的内容。
-
保存此时的 Cookie
-
断点位置的
val即是Cookie_T,复制出来用作后续比对结果; -
在
Application面板中的 Cookie 中可以看到Cookie_S,后续调试时,不要把它清掉,不然会导致程序走入错的流程,如果不小心清掉了,重新发起请求获取新的Cookie_S即可。
-
2.2 覆盖浏览器请求
这里使用 Charles 作为调试请求的工具,通过覆盖浏览器请求该 URL 时永远返回的文件内容,让浏览器执行内容静态化。
-
选择响应状态为
202的请求,右键点击,选择Map Local -
选择上面保存的
first.html作为响应文件 -
后续对浏览器进行正常刷新,会发现执行的 html 是本地文件,这样就能保证浏览器访问目标
url执行的代码是不变的。
2.3 关键代码位置
基于上面的 Cookie_T的断点,为了便于后续的调试,需要记录下几个关键代码的位置。
-
Cookie_T的生成位置发现有
(5);关键标识,为了后面方便调试,我们可以根据这个关键表标识,对 VM 中的代码插桩。由于瑞数的代码虽然是动态变化的,但是基本都是命名变化,一些数值或者关键字是不变的,因此可以根据关键字和数值快速定位到不同的 VN 代码中,Cookie_T 的设置位置。
-
Cookie_T生成的最外层入口 -
VM的入口位置有
.push =关键字。
2.4 本地执行文件
-
新建文件
main.js,补充基本的环境,减少后续调试步骤。 -
将保存的
link.js放入和main.js的同一目录下。 -
执行外链接 js 的代码
-
执行
core_code.js中的加密代码 -
本地调用浏览器调试工具
执行
node --inspect-brk main.js命令,打开浏览器调试工具,然后就可以在浏览器调试本地代码了。
三、调试,补环境
补环境分为两个大的阶段:VM 入口前 和 VM 内部执行,而 VM 内部执行又分为两个小阶段:进入 Cookie_t 生成入口前 和 进入 Cookie_t 生成入口后。按照执行阶段补环境即可。
调试之前,把浏览器的异常捕获都打开。
3.1 VM 入口前
3.1.1 插入断点
-
编辑
first.html和core_code.js文件,在 VM 入口前插入debugger断点,先保证浏览器和本地都能执行到该位置。 -
两边都执行到 VM 入口处,则需要判断待执行的 VM 代码是否一致,如果不一致则说明此前补的环境有问题
3.1.2 调试过程
-
报错:
TypeError: document.getElementById is not a function。原因 document 缺少
getElementById方法,获取id为9DhefwqGPrzGxEp9hPaoag的对象。从浏览器执行结果比对,补充document.getElementById,分析代码得出,这里是获取first.html中 meta 标签的内容<meta id="9DhefwqGPrzGxEp9hPaoag" content="L)qO_fl......>中的content属性值。需要注意的是,content 内容直接从
first.html复制粘贴会出现编码问题,这里改成将复制的 content 放到文件中,然后程序从文件读取内容。如下: -
进入到
VM 入口的断点在待执行代码中,搜索上述的
Cookie_T的生成位置的代码发现能找到,且对比和浏览器的待执行代码是一致的,那么VM 入口前的环境补充完毕。
3.2 进入 Cookie_t 生成入口前
3.2.1 插入断点
在上面保存的 Cookie_T 生成的最外层入口位置插入断点。例如:入口代码为 _$iG();,在 VM 入口这里通过正则来进行一个替换,
编辑 first.html 和 core_code.js,在 VM 入口前插入代码:result=_$EJ.match(/_\$iG\(\);/);_$EJ=_$EJ.replace(result[0],'debugger;'+result[0]);,效果如图:
3.2.2 调试过程
-
报错:
TypeError: Cannot read properties of undefined (reading 'location')window缺少top -
报错:
TypeError: _$Hy[_$gD(...)] is not a functiondocument缺少createElement对比浏览器执行情况,补充:
-
报错:
TypeError: _$HB[_$mb(...)] is not a constructor和浏览器一致拥有的正常的报错,跳过即可。
-
报错:
TypeError: _$Dj[_$nH(...)] is not a function逻辑走错了,浏览器不会走到这里,分析这段代码块,发现是缺少
addEventListener环境,补充:window.addEventListener = function addEventListener(type, listener) {};。 -
报错:
TypeError: Cannot read properties of undefined (reading 'style')缺失
document.documentElement属性,补充document.documentElement = { style: {} }; -
报错:
TypeError: _$HB[_$mb(...)] is not a constructor此处报错是正常的,进入
catch块中,对navigator的mimeTypes进行了检测。补充window.navigator.mimeTypes = [];。 -
报错:
TypeError: _$Dj[_$nH(...)] is not a function缺少
document.addEventListener导致进入else逻辑块中。 -
报错:
TypeError: _$HB[_$uQ(...)][_$oW(...)] is not a function补充 location 的
reload方法。 -
报错:
TypeError: _$Hy[_$iU(...)] is not a function补充
document.getElementsByTagName。根据这段代码,比对浏览器,继续补充
getElementsByTagName返回的对象的属性,如图: -
报错:
TypeError: String.prototype.indexOf called on null or undefined结合
_$Dj的作用域,跟进堆栈,定位到问题,_$Dj是window.name的值,补充该属性值即可。 -
走到了一开始插入的
Cookie_T 的生成入口断点这里,证明前面的环境基本补充完成
3.3 进入 Cookie_T 生成入口内部
3.3.1 插入断点
编辑 first.html 和 core_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]);,效果如图:
3.3.2 调试过程
-
报错:
TypeError: Cannot read properties of undefined (reading 'toString')对比浏览器,发现正常逻辑不会走到这里,从
Cookie_T最外层入口开始,着重对分支进行断点调试。调试分析发现,是缺少了条件判断值
_$G4导致未进入正常 cookie_T 生成逻辑中,查看浏览器右侧的Scope发现_$G4是个全局变量,可以直接全局搜索_$G4的定义获取_$G4的值。全局搜索后定位到
_$G4的初始化位置,发现_$G4是localStorage属性,比照浏览器补充window.localStorage = { removeItem: function(key) {},};。 -
报错:
TypeError: _$sN[_$mv(...)] is not a function连续两个报错都是对
localStorage的检测。补充完毕后的
localStorage如下: -
补环境基本完成,进入预先插入的
Cookie_T生成的断点 -
善后工作
core_code.js程序执行还没结束,但是 Cookie_T 的值已经生成,有两种方案处理:- 在 VM 入口利用正则对代码进行处理,在获得 Cookie_T 的值时直接中断程序,可以略过后续的环境检测。
- 继续补环境,直到程序流程走完,本文章选择这种方式继续讲解。
-
报错:
_$Hy[_$nV(...)][_$wF(...)] is not a functiondocumentElement.getAttribute缺失。 -
报错:
TypeError: Cannot read properties of undefined (reading 'width')缺少
screen属性,按照代码补充数据。 -
运行本地程序,得到预期结果
core_code.js的代码流程走完,本地也生成了正确结果,此时补环境正式完成。
四、验证 Cookie
和浏览器生成的 Cookie_T 对比,发现值是完全一致的,接下来就可以编写请求程序验证生成的 Cookie_T 是否有效。
4.1 编写主程序入口
-
删除写死的代码
js_code和content需要根据每次请求动态变更,因此把main.js中的相关内容删除。注意:
link.js由于同一页面的 js 文件内容是相同的,因此本地保存一份可以一直使用,无需变更,除非网站改版。 -
编写请求流程
4.2 程序解析动态文件
利用 cheerio 实现对 html 文档的标签元素提取。
-
提取 HTML 的 content
-
提取 HTML 的 script
4.3 验证
完整代码如下,请求验证成功,至此补环境逆向网站的流程完成。