声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责。
若有侵权,请在评论区联系作者立即删除!
一、分析加密数据
案例网站,更多关于瑞数的解释,可以查看文章 常见反爬策略整理,这里只讲述如何通过手动补环境
的方式解决瑞数 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 function
document
缺少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 function
documentElement.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 验证
完整代码如下,请求验证成功,至此补环境逆向网站的流程完成。