人均瑞数系列,瑞数 4 代 JS 逆向分析
![](https://s2.loli.net/2022/03/10/8jxX4wDZiR5HfuP.png)## 声明
**本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!**
**本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!**
## 前言
!(https://s2.loli.net/2022/06/25/1LE4dTn6MtNafwY.png)
瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。
瑞数 Botgate 多用于政企、金融、运营商行业,曾一度被视为反爬天花板,随着近年来逆向大佬越来越多,相关的逆向文章也层出不穷,真正到了人均瑞数的时代了,这里也感谢诸如 Nanda、懒神等逆向大佬,揭开了瑞数神秘的面纱,总结的经验让后来人少走了不少弯路。
过瑞数的方法基本上有以下几种:自动化工具(要隐藏特征值)、RPC 远程调用、JS 逆向(硬扣代码和补环境),本文介绍的是 JS 逆向硬扣代码,尽可能多的介绍各种细节。
## 瑞数特征以及不同版本的区别
对于绝大多数使用了瑞数的网站来说,有以下几点特征(可能有特殊版本不一样,先仅看主流的):
1、打开开发者工具(F12)会依次出现两个典型的无限 debugger:
!(https://s2.loli.net/2022/06/25/gVAjGrczmIKQw13.png)
!(https://s2.loli.net/2022/06/25/xocINyknMGVKCdU.png)
2、瑞数的 JS 混淆代码中,变量、方法名大多类似于 `_$xx`,有众多的 `if-else` 控制流,新版瑞数还可能会有 jsvmp 以及众多三目表达式的情况:
!(https://s2.loli.net/2022/06/27/CkGUsfSa52tDlKP.png)
3、看请求,会有典型的三次请求,首次请求响应码是 202(瑞数3、4代)或者 412(瑞数5代),接着单独请求一个 JS 文件,然后再重新请求页面,后续的其他 XHR 请求中,都带有一个后缀,这个后缀的值是由 JS 生成的,每次都会变化,后缀的值第一个数字为瑞数的版本,比如 `MmEwMD=4xxxxx` 就是4代瑞数,`bX3Xf9nD=5xxxxx` 就是5代瑞数:
!(https://s2.loli.net/2022/06/25/4phou197Ujk3raA.png)
!(https://s2.loli.net/2022/06/25/vLtr3V6RawlPfnN.png)
!(https://s2.loli.net/2022/06/25/klX3bwRjaIzKMiW.png)
!(https://s2.loli.net/2022/06/25/nLHtQY5eGlW8FJV.png)
4、看 Cookie,瑞数 3、4 代有以 T 和 S 结尾的两个 Cookie,其中以 S 开头的 Cookie 是第一次的 201 那个请求返回的,以 T 开头的 Cookie 是由 JS 生成的,动态变化的,T 和 S 前面一般会跟 80 或 443 的数字,Cookie 值第一个数字为瑞数的版本(为什么可以通过第一个数字来判断版本?难道相同版本第一个数字不会变吗?这些问题我们在分析 JS 的时候可以找到答案),比如:
- `FSSBBIl1UgzbN7N80T=37Na97B.nWX3....`:数字 80 是 http 协议的默认端口号,对应 http 请求,其值第一位为 3,表示 3 代瑞数;
- `FSSBBIl1UgzbN7N443T=4a.tr1kEXk.....`:数字 443 是 https 协议的默认端口号,对应 https 请求,其值第一位为 4,表示 4 代瑞数。
!(https://s2.loli.net/2022/06/27/Gt1dsXVlBQe7MF5.png)
瑞数 5 代也有以 T 和 S 结尾的两个 Cookie,但有些特殊的 5 代瑞数也有以 O 和 P 结尾的,同样的,以 O 开头的是第一次的 412 那个请求返回的,以 P 开头的是由 JS 生成的,Cookie 值第一个数字同样为瑞数的版本,和 3、4 代不同的是,5 代没有加端口号了,比如:
- `vsKWUwn3HsfIO=57C6DwDUXS.....`:以 O 结尾,其值第一位为 5,表示 5 代瑞数;
- `WvY7XhIMu0fGT=53.9fybty......`:以 T 结尾,其值第一位为 5,表示 5 代瑞数。
!(https://s2.loli.net/2022/06/27/c5hJZ6wQo8DI29N.png)
!(https://s2.loli.net/2022/06/27/C2J1BRpTvPmatde.png)
5、看入口,瑞数有个流程是在虚拟机 VM 中加载 1w+ 行的代码,加载此代码的入口,不同版本也不一样(这个入口具体在哪里?怎么定位?在后续逆向分析中再详细介绍),示例如下:
- 3 代:`_$aW = _$c6(_$wc, _$mo);`,`_$c6` 实际上是 `eval`,`_$l6()` 实际上是 `call`;
!(https://s2.loli.net/2022/06/28/OA4fiPCyxDdsNw3.png)
- 4 代:`ret = _$DG.call(_$6a, _$YK);`,`_$DG` 实际上是 `eval`,有关键字 `ret`,`call` 是明文;
!(https://s2.loli.net/2022/06/28/yfibHmY9PT3nqJK.png)
- 5 代:5 代种类比较多了,最初和 4 代的类似,比如 `ret = _$Yg.call(_$kc, _$mH);`,有关键字 ret,call 是明文,也有没有 ret 关键字的版本,比如 `_$ap = _$j5.call(_$_T, _$gp);`,也有像 3 代那样全部混淆了的,比如:`_$x8 = _$mP](_$z3, _$Ec);`,`_$mP` 实际上是 `eval`,`_$nU` 实际上是 `call`,混淆的 `call` 与 3 代的区别就是 5 代是在一个数组里取值得到的;
!(https://s2.loli.net/2022/06/28/CeNv1OQsjrcfwpD.png)
!(https://s2.loli.net/2022/06/28/BGHLeu2XUNv9Mho.png)
!(https://s2.loli.net/2022/06/28/r1HPoqDZSpe694l.png)
当然要想精准区分不同版本,得各个条件结合起来看,最主要的还是得看看内部的实现逻辑,以及页面的代码结构,比如 4 代有一个生成假 Cookie 的步骤,而 5 代没有,有的特殊版本虽然看起来是 5 代,但是加了 jsvmp 和三目表达式,和传统的 5 代又有区别,偶尔愚人节啥的突然来个新版本,也会不一样,各版本在分析一遍之后,就很容易区分了。
## Cookie 入口定位
本文案例中瑞数 4 代网站为:`aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL25ld19ob3VzZS9uZXdfaG91c2VfZGV0YWlsLmh0bWw=`
首先过掉无限 debugger(过不过其实无所谓,后面的分析其实这个基本上没影响),直接右键 `Never pause here` 永不在此处断下即可:
!(https://s2.loli.net/2022/06/28/dGk57ai6XbpJyPl.png)
定位 Cookie,首选 Hook 来的最快,通过 Fiddler 等抓包工具、油猴脚本、浏览器插件等方式注入以下 Hook 代码:
```javascript
(function() {
// 严谨模式 检查所有错误
'use strict';
// document 为要hook的对象 这里是hook的cookie
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
// hook set方法也就是赋值的方法
set: function(val) {
// 这样就可以快速给下面这个代码行下断点
// 从而快速定位设置cookie的代码
console.log('Hook捕获到cookie设置->', val);
debugger;
cookieTemp = val;
return val;
},
// hook get 方法也就是取值的方法
get: function()
{
return cookieTemp;
}
});
})();
```
Hook 发现会有生成两次 Cookie 的情况,断下之后往上跟栈,可以看到组装 Cookie 的代码,类似如下结构:
!(https://s2.loli.net/2022/06/28/524tBWAPgHYmNdc.png)
仔细观察这两次 Cookie 生成的地方,分别往上跟栈,你就会发现两个 Cookie 分别是经过了两个不同方法得到的,如下图所示:
!(https://s2.loli.net/2022/06/28/jdqkVcfDU9oCpn1.png)
!(https://s2.loli.net/2022/06/28/bevGhxN7Rj2dUtn.png)
这里的代码存在于 VM 虚拟机中,且是 IIFE 自执行代码,我们还得往前跟栈看看这些 VM 代码是从哪里加载出来的,跟栈来到首页(202页面)带有 call 的位置:
!(https://s2.loli.net/2022/06/28/x59TgbvsDEzQJNU.png)
我们在文章开头介绍的这个位置就是这么分析得来的,这个位置通常在分析瑞数的时候作为入口,图中 `_$te` 实际上是 eval 方法,传入的第一个参数 `_$fY` 是 Window 对象,第二个对象 `_$F8` 是我们前面看到的 VM 虚拟机中的 IIFE 自执行代码。
在知道了瑞数大致的入口之后,我们也可以使用事件监听中的 Script 断点,一直下一个断点(F8)就可以走到 202 页面,然后搜索 call 关键字就能快速定位到入口,Script 断点中的两个选项,第一个表示运行 JS 脚本的第一条语句时断下,第二个表示 JS 因为内容安全政策而被屏蔽时断下,一般选择第一个就可以了,如下图所示:
!(https://s2.loli.net/2022/06/28/G8U9jnmqdAtTBcs.png)
## 文件结构与逻辑
想要后续分析 Cookie 的生成,我们不得不要观察一下 202 页面的代码,meta 标签有个 content 内容,引用了一个类似于 `c.FxJzG50F.dfe1675.js` 的 JS 文件,接着跟一个自执行的 JS,如下图所示:
!(https://s2.loli.net/2022/06/30/bRusC68x9opOSlI.png)
第1部分 meta 标签的 content 内容,每次都是变化的,第2部分引用的这个外部 JS 在不同页面也有所差别,但是同一个网站同一个页面 JS 里的内容一般是固定不会变的,第3部分自执行代码每次变化的只是变量名,整体逻辑不变,后续我们在扣代码的时候,也会用到这里的部分方法。自执行代码里同样也是有很多 `if-else` 控制流,开头的那个数组,比如上图中的 `_$Dk` 就是用来控制后续的控制流的。
引用的 `c.FxJzG50F.dfe1675.js` 直接打开看是乱码的,而自执行 JS 的主要作用是将这 JS 乱码还原成 VM 里的 1w+ 行的正常代码,并且定义了一个全局变量 `window.$_ts` 并赋了许多值,这个变量在后续 VM 中作用非常大,meta 标签的 content 内容同样也会在 VM 里用到。
由于很多值、变量都是动态变化的,肯定不利于我们的分析,所以我们需要固定一套代码到本地,打断点、跟栈都会更加方便,随便保存一份 202 页面的代码,以及该页面对应的外链 JS 文件,如 `c.FxJzG50F.dfe1675.js` 到本地,使用浏览器自带的 overrides 重写功能、或者浏览器插件 ReRes、或者抓包工具的响应替换功能(如 Fiddler 的 AutoResponder)进行替换。
!(https://s2.loli.net/2022/06/29/1MN74jTEUs3zgLQ.png)
VM 里面的代码是生成 Cookie 的主要代码,包含众多的 `if-else` 控制流,无疑增加了我们分析代码的成本,这里就可以使用 AST 技术做一下反混淆,比如 Nanda 就将 `if-else` 控制流转换成了 `switch-case` 的,同一个控制流下的代码放在了同一个 `case` 下,然后在 `call` 入口那个地方,将 VM 代码做一下本地替换,具体可以参考 Nanda 的文章:[《某数4代逻辑分析》](https://mp.weixin.qq.com/s/r3FXjvT5Mm9Ikg_bDEadcw),感兴趣的可以试试,不了解 AST 的可以看看以前的文章[《逆向进阶,利用 AST 技术还原 JavaScript 混淆代码》](https://mp.weixin.qq.com/s/fIbPuNMs5FRADJE5MOZXgA),后续有时间 K 哥再写写 AST 还原瑞数代码的实战,本文咱们选择硬刚!
!(https://s2.loli.net/2022/06/29/E2NivagLVdIpCbZ.jpg)
## VM 代码以及 $_ts 变量获取
前面我们了解了 VM 代码和 `$_ts` 的重要性,所以我们第一步是要想办法拿到他们,至于在什么时候有用到,文章后续再说,复制外链 JS,即`c.FxJzG50F.dfe1675.js` 的代码和 202 页面的自执行代码到文件,本地直接运行即可,需要轻度补一下环境,缺啥补啥,大致补一下 window、location、document 就行了,补的具体内容可以直接在浏览器控制台使用 `copy()` 命令复制过来,然后 VM 代码我们就可以直接 Hook eval 的方式得到,大致的补环境代码如下:
```javascript
var eval_js = ""
window = {
$_ts:{},
eval:function (data) {
eval_js = data
}
}
location = {
"ancestorOrigins": {},
"href": "http://www.脱敏处理.com.cn/new_house/new_house_detail.html",
"origin": "http://www.脱敏处理.com.cn",
"protocol": "http:",
"host": "www.脱敏处理.com.cn",
"hostname": "www.脱敏处理.com.cn",
"port": "",
"pathname": "/new_house/new_house_detail.html",
"search": "",
"hash": ""
}
document = {
"scripts": ["script", "script"]
}
```
!(https://s2.loli.net/2022/06/30/BeRF1rYuKGhqXLz.png)
观察 `$_ts` 的 key 和 value,和浏览器中得到的是一样的:
!(https://s2.loli.net/2022/06/30/Uz8eSuy27JMIHVc.png)
注意事项:`c.FxJzG50F.dfe1675.js` 外链 JS 如果你直接下载下来用编辑器打开可能会被自动编码,和原始数据有出入,导致运行报错,这里建议直接在浏览器在线访问这个文件,手动复制过来,或者在抓包软件里将响应内容复制过来,观察以下两种情况,第一种情况就可能会导致运行出错,第二种是正常的:
!(https://s2.loli.net/2022/06/30/4sWMyD3kZJE1rS6.png)
## 扣代码
前面说了这么多,现在终于可以进入主题了,那就是扣代码,找个好椅子,准备把屁股坐穿,此时你的键盘只有 F11 有用,不断单步调试,只需要亿点点细节,就完事儿了!
扣代码步骤太多,不可能每一步都截图写出来,只写一下比较重要的,如有遗漏的地方,那也没办法,首先先在我们替换的 202 页面里,自执行代码开始的地方手动加个 debugger,一进入页面就断下,方便后续的分析:
!(https://s2.loli.net/2022/06/30/h4RWubIqy6nB9JG.png)
通过前面我们的分析,已经知道了入口在 call 的地方,快速搜索并下断点:
!(https://s2.loli.net/2022/06/30/VpSCie6b4D5LGkw.png)
通过前面我们的分析,我们也知道了有两次生成 Cookie 的地方,快速搜索 `(5)`,搜索结果第二个即为入口:
!(https://s2.loli.net/2022/06/30/egxvfLmXQohK3iT.png)
### 假 Cookie 生成逻辑
首先单步跟假 Cookie,虽然是假的,但是后续生成真 Cookie 中会用到,在跟的时候你会走到这个逻辑里面:
!(https://s2.loli.net/2022/06/30/9Mfd2hgTRApY7ct.png)
有一步会调用 `_$8e()` 方法,而 `_$8e = _$Q9`,`_$Q9` 又嵌套在 `_$d0` 里的,搜索一下哪里调用了 `_$d0`,发现是代码开头:
!(https://s2.loli.net/2022/06/30/PR3JOmejlXprqEU.png)
那么传入的参数 `_$Wn` 是啥呢?单步跟入,是一个方法,作用就是取 202 页面的 content 内容,那么我们在本地就直接删掉这个 `_$Wn` 方法,直接传入 content 的值即可,如下图所示:
!(https://s2.loli.net/2022/06/30/pMSh49Z5m7CoOjH.png)
另外,我们发现,代码有非常多的在数组里面按索引取值的情况,比如上图中的 `_$PV` 的值,实际上就是字符串 content,很显然我们要把这个数组的来源找到,直接搜索 `_$PV = `,可以找到疑似定义和赋值的地方:
!(https://s2.loli.net/2022/06/30/M3x5LR6e8XdwWFm.png)
!(https://s2.loli.net/2022/06/30/qWjcGn45xrwLaiV.png)
所以我们得看看这个 `_$iL` 方法,传入了一个非常长的字符串,打断点进去看看,果然生成了 `_$PV`,是一个 725 位的数组:
!(https://s2.loli.net/2022/06/30/F9flpiCVXKcHOjJ.png)
接下来在扣代码的过程中,你会经常遇到一个变量,在本文中是 `_$sX`:
!(https://s2.loli.net/2022/06/30/9VpsxQRHyTkYPn6.png)
有没有很熟悉?这个值就是我们前面拿到的 `$_ts` 变量,在开头就可以看到是将 `window.$_ts` 赋值给了 `_$sX`:
!(https://s2.loli.net/2022/06/30/mf8tD9bWBQPJXpc.png)
继续走,会走到以下逻辑中:
!(https://s2.loli.net/2022/06/30/v5XogiWEnwmx31q.png)
这里会遇到六个数组,他们都已经有值了,所以我们得找到他们是咋来的,任意搜索其中一个数组名称,会找到定义和赋值的地方:
!(https://s2.loli.net/2022/06/30/maJAnI7eld5sT3w.png)
!(https://s2.loli.net/2022/06/30/pBXaZPHe85yDxiK.png)
赋值明显是调用了 `_$rv` 方法,再搜 `_$rv` 方法,发现是开头就调用了:
!(https://s2.loli.net/2022/06/30/7qAMQsSBGJ4K9mL.png)
后续没有什么特别的,一直单步,最后有个 `join('')` 操作,就生成了假 Cookie:
!(https://s2.loli.net/2022/06/30/tDb1kliY63CZXSH.png)
接下来是生成 Cookie 的名字 `FSSBBIl1UgzbN7N80T`,然后将 Cookie 赋值给 `document.cookie`,然后又向 `localStorage` 里面的 `$_ck` 赋了个值,`localStorage` 的内容可以直接复制下来,没有太大影响。
!(https://s2.loli.net/2022/06/30/ngkaizOL6MqKUXY.png)
### 真 Cookie 生成逻辑
单步跟真 Cookie,在本文中也就是 `_$ZN(768, 1);`,可以看到开始进入了无穷无尽的 `if-else` 控制流:
!(https://s2.loli.net/2022/06/30/Y9KruCtVHFoBx8l.png)
这里本地应该怎样处理呢?我的做法是以 `_$Hn` 和其值命名函数,`function _$Hn768(){}` 就表示所有走 768 号控制流的方法,继续跟,生成真 Cookie 的方法基本上在 747 号控制流,后续我们主要以 747 号控制流的各个步骤来看,747 号控制流扣出来的代码大致如下:
!(https://s2.loli.net/2022/06/30/fV1Hum7tgGFKPMb.png)
#### 取假 Cookie
单步跟 747 号控制流,会有个进入第 709 号控制流的步骤,会取先前生成的假 Cookie,经过一系列操作之后返回一个数组:
!(https://s2.loli.net/2022/06/30/EFfQYHNDIKci7go.png)
!(https://s2.loli.net/2022/06/30/bOtLNQJHjgWmU5n.png)
至此我们在本地同步扣的代码,如果正常的话,返回的数组也应该是一样的(后续的数据就不一样了,有一些时间戳之类的参数参与运算):
!(https://s2.loli.net/2022/06/30/SluzHAydLeaVW6C.png)
#### 自动化工具检测
继续跟 747 号控制流,会进入 268 号控制流,接着进入 154 号控制流,这里面会针对自动化工具做一些检测,如下图所示:
!(https://s2.loli.net/2022/07/01/aiSEHAsfcqNvj7Q.png)
!(https://s2.loli.net/2022/07/01/8bBTeaFQWvGdnxM.png)
这里定义了一个变量 `_$iL`,检测不通过就是1,后续又把这个变量赋值给了 `_$aW`,所以我们本地保持一致,也为 false 即可(其实我们不用自动化工具的话,这一段检测就不用管直接返回 false 就行):
!(https://s2.loli.net/2022/07/01/m45PrkF39LwBG7O.png)
#### 20 位核心数组
继续跟 268 号控制流,会进入 668 号控制流,668 号控制流就两个操作,一是生成一个 16 位数组,二是取 `$_ts` 里面的 4 个变量,加到前面的 16 位后面,组成一个 20 位数组,这 20 位数组的最后 4 位是瑞数核心,其中的映射关系搞错了请求是通不过的,在五代中这部分的处理逻辑会更加复杂。
!(https://s2.loli.net/2022/07/01/qmJMSR9ZBEWK6Hc.png)
!(https://s2.loli.net/2022/07/01/4XOFlf8JeBtixVq.png)
这里不是单纯的取 `$_ts` 里的键值对,你在扣代码的时候,你也许会发现怎么本地到这里取值的时候,取出来的不是数字,而是字符串呢?就像下面这种情况:
!(https://s2.loli.net/2022/07/01/q3hLJSIyzmGM4wd.png)
实际上我们最开始得到的 `$_ts` 值,是经过了二次处理的,我们以第一个 `_$sX._$Xb` 为例,直接搜索 `_$sX._$Xb`,可以发现这么一个地方:
!(https://s2.loli.net/2022/07/01/A5sGR6iwa2JCZ3t.png)
很明显这里给`_$sX._$Xb` 重新赋值了一遍,我们可以看到等号右边,先取了一次 `_$sX._$Xb`,其值为 `_$Rm`,这和我们初始 `$_ts` 里面对应的值是一样的,然后我们就得再看看 `_$sX["_$Rm"]` 又是何方神圣,直接搜索发现是开头赋值了一个方法,通过调用这个方法来生成新的值:
!(https://s2.loli.net/2022/07/01/1uLAdx5fgMyKzb7.png)
另外其他三个值也是同样的套路,赋值的代码分别为:
```javascript
_$sX._$Xb = _$sX(_$BH, _$DP);
_$sX._$oI = _$sX(_$ZJ, _$DS)
_$sX._$EN = _$sX();
_$sX._$D9 = _$sX(_$iL);
```
实际上应该是:
```javascript
_$sX._$Xb = _$sX["_$Rm"](_$BH, _$DP);
_$sX._$oI = _$sX["_$Nw"](_$ZJ, _$DS)
_$sX._$EN = _$sX["_$Uh"]();
_$sX._$D9 = _$sX["_$ci"](_$iL);
```
进一步来说,实际上是:
```javascript
_$sX._$Xb = _$1k(_$BH, _$DP);
_$sX._$oI = _$jH(_$ZJ, _$DS)
_$sX._$EN = _$9M();
_$sX._$D9 = _$oL(_$iL);
```
静态分析没问题,我们可以先固定下来,但是实际应用当中这些值都是动态的,那我们应该怎么处理呢?先来多看几个对比一下找找规律:
!(https://s2.loli.net/2022/07/01/3HWaiShXfZ1mI4P.png)
!(https://s2.loli.net/2022/07/01/8pLnG93rUey7JSI.png)
可以发现每次对应的位次都不一样,但是实际上相同位置的方法点进去都是一样的,也就是说,变的只有方法名和变量名,实现的逻辑是不变的,所以我们只要知道了这四个值分别对应的位置,就能够拿到正确的值,在本地,我们就可以这样做:
1、先利用正则匹配出这四个值,如:``;
!(https://s2.loli.net/2022/07/01/DqM4xPGiLKoFYe9.png)
2、再匹配出 VM 代码开头的 20 个赋值的语句,如:`_$sX._$RH = _$wI; _$sX._$i5 = _$n5;` 等;
!(https://s2.loli.net/2022/07/01/GOXnUKul2C5m9Ik.png)
3、然后通过 `$_ts` 取这四个值对应的值,相当于:`_$sX._$Xb = _$ts._$Xb = _$Rm`;然后再找这四个值所定义的方法在 20 个赋值语句中的位置,相当于:查找 `_$sX._$Rm = _$1k; ` 在 20 个赋值语句中的位置为 7(索引从 0 开始)
!(https://s2.loli.net/2022/07/01/uMWteX1CZ2YKAhS.png)
4、我们知道了这四个方法在 20 个赋值语句中的位置,那么我们直接匹配本地对应位置的名称,进行动态替换即可,当然前提是咱们本地已经扣了一套代码出来了:
!(https://s2.loli.net/2022/07/01/ta8HCN2VbsXpi3c.png)
!(https://s2.loli.net/2022/07/01/6EFZ85o3GYctPkS.png)
经过这样处理后,就能够保证这四个值的准确性了。
#### 其他用到 $_ts 值的地方
除了上面说的 20 位数组里用到了 4 个 `$_ts` 的值以外,还有其他地方有 7 个值也用到了,直接搜索就能定位,这 7 个值相对较简单,每次都是固定取 `$_ts` 里面的第 2、3、4、15、16、17、19 位的值,同样的,找到对应位置,进行动态替换即可:
!(https://s2.loli.net/2022/07/01/lTzHNMZecYFr3Kq.png)
### 注意事项
特别注意 VM 代码开头,会直接调用执行一些方法,某些变量的值就是通过这些方法生成的,当你一步一步跟的时候发现某些参数不对,或者没有,那么就得注意开头这些方法了,可能一开始就已经生成了。
!(https://s2.loli.net/2022/07/01/fMXko7cYtsQeSxR.png)
### 后缀 MmEwMD 生成逻辑
后续的其他 XHR 请求中,都带有一个后缀,这个后缀的值同样是由 JS 生成的,每次都会变化,当然不同网站,后缀名不一定都是一样的,本例中是 `MmEwMD`,先下一个 XHR 断点,当 XHR 请求中包含了 `MmEwMD=` 时就断下,然后刷新网页:
!(https://s2.loli.net/2022/07/01/OmKyNM7D1UJWACi.png)
可以看到后传入 `l.open()` 的 URL 还是正常的,断下后到 `l.send()` 就带有后缀了,再看 `l.open()` 其实就是 `xhr.open()`,明显和正常的有区别,同样这个方法也在 VM 代码里,应该是重写了方法,可以和正常的做对比:
!(https://s2.loli.net/2022/07/01/5a9TnEvsc2JbI8M.png)
跟到 VM 代码里去看看,经过了 `_$sd(arguments)` 方法就变成了带有后缀的完整链接了:
!(https://s2.loli.net/2022/07/01/uReo9BEOqsQrpMY.png)
跟进 `_$sd` 方法,前面都是对 url 做一些处理,后面有个进入第 779 号控制流的流程,实际上就是原来我们生成 Cookie 的步骤,跟一下就行了。
!(https://s2.loli.net/2022/07/01/ewQMIyKaG2JoNnC.png)
## 善用 Watch 跟踪功能
!(https://s2.loli.net/2022/07/01/z5nkOXENYcpCVr4.png)
开发者工具的 Watch 功能能够持续跟踪某个变量的值,对于这种控制流很多的情况,设置相应的变量跟踪,能够让你知道你现在处于哪个控制流中,以及生成的数组的变化,不至于跟着跟着不知道到哪一步了。
## 结果验证
如果整个流程没问题,代码也扣得正确,携带正确的 Cookie 和正确的后缀,就能成功访问:
!(https://s2.loli.net/2022/07/01/ypLR4X5sq87DucT.png)
!(https://s2.loli.net/2022/07/01/l2RzbIPnBU94GA1.png) xzdatm 发表于 2023-2-9 18:08
大佬 请问刚从js入门咋检验自己的js到啥水平了
你好,想学习JS,有视频课程推荐吗 ?
短期目标方向是:
1、物流开单小程序、
2、物流开单APP(安卓)
3、爬数据到自己的软件上 我在同一个网站也是跟着步骤来的,为啥第一个得到的伪cookie是空,有没有遇到同样问题的哥儿们。 大佬 请问刚从js入门咋检验自己的js到啥水平了{:1_908:} xzdatm 发表于 2023-2-9 18:08
大佬 请问刚从js入门咋检验自己的js到啥水平了
刚发现 原来是我k佬啊{:1_918:} xzdatm 发表于 2023-2-9 18:08
刚发现 原来是我k佬啊
还是回去好好琢磨琢磨你的公众号了 学习,膜拜大佬。目前看到瑞数和指纹就投降 大佬66666 给K哥捧场! 刚刚在爬虫视频那看到瑞数,大佬的帖子就来了 谢谢楼主,学习下:lol 我都看完了 但都看不懂 但我大为震惊