当前位置:网站首页>人均瑞数系列,瑞数 4 代 JS 逆向分析
人均瑞数系列,瑞数 4 代 JS 逆向分析
2022-07-06 15:42:00 【VIP_CQCRE】
这是「进击的Coder」的第 656 篇技术分享
作者:K 小哥
来源:K 哥爬虫
“
阅读本文大概需要 13 分钟。
”声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
前言
瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。
瑞数 Botgate 多用于政企、金融、运营商行业,曾一度被视为反爬天花板,随着近年来逆向大佬越来越多,相关的逆向文章也层出不穷,真正到了人均瑞数的时代了,这里也感谢诸如 Nanda、懒神等逆向大佬,揭开了瑞数神秘的面纱,总结的经验让后来人少走了不少弯路。
过瑞数的方法基本上有以下几种:自动化工具(要隐藏特征值)、RPC 远程调用、JS 逆向(硬扣代码和补环境),本文介绍的是 JS 逆向硬扣代码,尽可能多的介绍各种细节。
瑞数特征以及不同版本的区别
对于绝大多数使用了瑞数的网站来说,有以下几点特征(可能有特殊版本不一样,先仅看主流的):
1、打开开发者工具(F12)会依次出现两个典型的无限 debugger:
2、瑞数的 JS 混淆代码中,变量、方法名大多类似于 _$xx
,有众多的 if-else
控制流,新版瑞数还可能会有 jsvmp 以及众多三目表达式的情况:
3、看请求,会有典型的三次请求,首次请求响应码是 202(瑞数3、4代)或者 412(瑞数5代),接着单独请求一个 JS 文件,然后再重新请求页面,后续的其他 XHR 请求中,都带有一个后缀,这个后缀的值是由 JS 生成的,每次都会变化,后缀的值第一个数字为瑞数的版本,比如 MmEwMD=4xxxxx
就是4代瑞数,bX3Xf9nD=5xxxxx
就是5代瑞数:
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 代瑞数。
瑞数 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 代瑞数。
5、看入口,瑞数有个流程是在虚拟机 VM 中加载 1w+ 行的代码,加载此代码的入口,不同版本也不一样(这个入口具体在哪里?怎么定位?在后续逆向分析中再详细介绍),示例如下:
3 代:
_$aW = _$c6[_$l6()](_$wc, _$mo);
,_$c6
实际上是eval
,_$l6()
实际上是call
;
4 代:
ret = _$DG.call(_$6a, _$YK);
,_$DG
实际上是eval
,有关键字ret
,call
是明文;
5 代:5 代种类比较多了,最初和 4 代的类似,比如
ret = _$Yg.call(_$kc, _$mH);
,有关键字 ret,call 是明文,也有没有 ret 关键字的版本,比如_$ap = _$j5.call(_$_T, _$gp);
,也有像 3 代那样全部混淆了的,比如:_$x8 = _$mP[_$nU[15]](_$z3, _$Ec);
,_$mP
实际上是eval
,_$nU[15]
实际上是call
,混淆的call
与 3 代的区别就是 5 代是在一个数组里取值得到的;
当然要想精准区分不同版本,得各个条件结合起来看,最主要的还是得看看内部的实现逻辑,以及页面的代码结构,比如 4 代有一个生成假 Cookie 的步骤,而 5 代没有,有的特殊版本虽然看起来是 5 代,但是加了 jsvmp 和三目表达式,和传统的 5 代又有区别,偶尔愚人节啥的突然来个新版本,也会不一样,各版本在分析一遍之后,就很容易区分了。
Cookie 入口定位
本文案例中瑞数 4 代网站为:aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL25ld19ob3VzZS9uZXdfaG91c2VfZGV0YWlsLmh0bWw=
首先过掉无限 debugger(过不过其实无所谓,后面的分析其实这个基本上没影响),直接右键 Never pause here
永不在此处断下即可:
定位 Cookie,首选 Hook 来的最快,通过 Fiddler 等抓包工具、油猴脚本、浏览器插件等方式注入以下 Hook 代码:
(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 的代码,类似如下结构:
仔细观察这两次 Cookie 生成的地方,分别往上跟栈,你就会发现两个 Cookie 分别是经过了两个不同方法得到的,如下图所示:
这里的代码存在于 VM 虚拟机中,且是 IIFE 自执行代码,我们还得往前跟栈看看这些 VM 代码是从哪里加载出来的,跟栈来到首页(202页面)带有 call 的位置:
我们在文章开头介绍的这个位置就是这么分析得来的,这个位置通常在分析瑞数的时候作为入口,图中 _$te
实际上是 eval 方法,传入的第一个参数 _$fY
是 Window 对象,第二个对象 _$F8
是我们前面看到的 VM 虚拟机中的 IIFE 自执行代码。
在知道了瑞数大致的入口之后,我们也可以使用事件监听中的 Script 断点,一直下一个断点(F8)就可以走到 202 页面,然后搜索 call 关键字就能快速定位到入口,Script 断点中的两个选项,第一个表示运行 JS 脚本的第一条语句时断下,第二个表示 JS 因为内容安全政策而被屏蔽时断下,一般选择第一个就可以了,如下图所示:
文件结构与逻辑
想要后续分析 Cookie 的生成,我们不得不要观察一下 202 页面的代码,meta 标签有个 content 内容,引用了一个类似于 c.FxJzG50F.dfe1675.js
的 JS 文件,接着跟一个自执行的 JS,如下图所示:
第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)进行替换。
VM 里面的代码是生成 Cookie 的主要代码,包含众多的 if-else
控制流,无疑增加了我们分析代码的成本,这里就可以使用 AST 技术做一下反混淆,比如 Nanda 就将 if-else
控制流转换成了 switch-case
的,同一个控制流下的代码放在了同一个 case
下,然后在 call
入口那个地方,将 VM 代码做一下本地替换,具体可以参考 Nanda 的文章:《某数4代逻辑分析》,感兴趣的可以试试,不了解 AST 的可以看看以前的文章《逆向进阶,利用 AST 技术还原 JavaScript 混淆代码》,后续有时间 K 哥再写写 AST 还原瑞数代码的实战,本文咱们选择硬刚!
VM 代码以及 $_ts 变量获取
前面我们了解了 VM 代码和 $_ts
的重要性,所以我们第一步是要想办法拿到他们,至于在什么时候有用到,文章后续再说,复制外链 JS,即 c.FxJzG50F.dfe1675.js
的代码和 202 页面的自执行代码到文件,本地直接运行即可,需要轻度补一下环境,缺啥补啥,大致补一下 window、location、document 就行了,补的具体内容可以直接在浏览器控制台使用 copy()
命令复制过来,然后 VM 代码我们就可以直接 Hook eval 的方式得到,大致的补环境代码如下:
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"]
}
观察 $_ts
的 key 和 value,和浏览器中得到的是一样的:
注意事项:c.FxJzG50F.dfe1675.js
外链 JS 如果你直接下载下来用编辑器打开可能会被自动编码,和原始数据有出入,导致运行报错,这里建议直接在浏览器在线访问这个文件,手动复制过来,或者在抓包软件里将响应内容复制过来,观察以下两种情况,第一种情况就可能会导致运行出错,第二种是正常的:
扣代码
前面说了这么多,现在终于可以进入主题了,那就是扣代码,找个好椅子,准备把屁股坐穿,此时你的键盘只有 F11 有用,不断单步调试,只需要亿点点细节,就完事儿了!
扣代码步骤太多,不可能每一步都截图写出来,只写一下比较重要的,如有遗漏的地方,那也没办法,首先先在我们替换的 202 页面里,自执行代码开始的地方手动加个 debugger,一进入页面就断下,方便后续的分析:
通过前面我们的分析,已经知道了入口在 call 的地方,快速搜索并下断点:
通过前面我们的分析,我们也知道了有两次生成 Cookie 的地方,快速搜索 (5)
,搜索结果第二个即为入口:
假 Cookie 生成逻辑
首先单步跟假 Cookie,虽然是假的,但是后续生成真 Cookie 中会用到,在跟的时候你会走到这个逻辑里面:
有一步会调用 _$8e()
方法,而 _$8e = _$Q9
,_$Q9
又嵌套在 _$d0
里的,搜索一下哪里调用了 _$d0
,发现是代码开头:
那么传入的参数 _$Wn
是啥呢?单步跟入,是一个方法,作用就是取 202 页面的 content 内容,那么我们在本地就直接删掉这个 _$Wn
方法,直接传入 content 的值即可,如下图所示:
另外,我们发现,代码有非常多的在数组里面按索引取值的情况,比如上图中的 _$PV[68]
的值,实际上就是字符串 content,很显然我们要把这个数组的来源找到,直接搜索 _$PV =
,可以找到疑似定义和赋值的地方:
所以我们得看看这个 _$iL
方法,传入了一个非常长的字符串,打断点进去看看,果然生成了 _$PV
,是一个 725 位的数组:
接下来在扣代码的过程中,你会经常遇到一个变量,在本文中是 _$sX
:
有没有很熟悉?这个值就是我们前面拿到的 $_ts
变量,在开头就可以看到是将 window.$_ts
赋值给了 _$sX
:
继续走,会走到以下逻辑中:
这里会遇到六个数组,他们都已经有值了,所以我们得找到他们是咋来的,任意搜索其中一个数组名称,会找到定义和赋值的地方:
赋值明显是调用了 _$rv
方法,再搜 _$rv
方法,发现是开头就调用了:
后续没有什么特别的,一直单步,最后有个 join('')
操作,就生成了假 Cookie:
FSSBBIl1UgzbN7N80T
,然后将 Cookie 赋值给 document.cookie
,然后又向 localStorage
里面的 $_ck
赋了个值, localStorage
的内容可以直接复制下来,没有太大影响。 真 Cookie 生成逻辑
单步跟真 Cookie,在本文中也就是 _$ZN(768, 1);
,可以看到开始进入了无穷无尽的 if-else
控制流:
这里本地应该怎样处理呢?我的做法是以 _$Hn
和其值命名函数,function _$Hn768(){}
就表示所有走 768 号控制流的方法,继续跟,生成真 Cookie 的方法基本上在 747 号控制流,后续我们主要以 747 号控制流的各个步骤来看,747 号控制流扣出来的代码大致如下:
取假 Cookie
单步跟 747 号控制流,会有个进入第 709 号控制流的步骤,会取先前生成的假 Cookie,经过一系列操作之后返回一个数组:
至此我们在本地同步扣的代码,如果正常的话,返回的数组也应该是一样的(后续的数据就不一样了,有一些时间戳之类的参数参与运算):
自动化工具检测
继续跟 747 号控制流,会进入 268 号控制流,接着进入 154 号控制流,这里面会针对自动化工具做一些检测,如下图所示:
这里定义了一个变量 _$iL
,检测不通过就是1,后续又把这个变量赋值给了 _$aW
,所以我们本地保持一致,也为 false 即可(其实我们不用自动化工具的话,这一段检测就不用管直接返回 false 就行):
20 位核心数组
继续跟 268 号控制流,会进入 668 号控制流,668 号控制流就两个操作,一是生成一个 16 位数组,二是取 $_ts
里面的 4 个变量,加到前面的 16 位后面,组成一个 20 位数组,这 20 位数组的最后 4 位是瑞数核心,其中的映射关系搞错了请求是通不过的,在五代中这部分的处理逻辑会更加复杂。
这里不是单纯的取 $_ts
里的键值对,你在扣代码的时候,你也许会发现怎么本地到这里取值的时候,取出来的不是数字,而是字符串呢?就像下面这种情况:
实际上我们最开始得到的 $_ts
值,是经过了二次处理的,我们以第一个 _$sX._$Xb
为例,直接搜索 _$sX._$Xb
,可以发现这么一个地方:
很明显这里给 _$sX._$Xb
重新赋值了一遍,我们可以看到等号右边,先取了一次 _$sX._$Xb
,其值为 _$Rm
,这和我们初始 $_ts
里面对应的值是一样的,然后我们就得再看看 _$sX["_$Rm"]
又是何方神圣,直接搜索发现是开头赋值了一个方法,通过调用这个方法来生成新的值:
另外其他三个值也是同样的套路,赋值的代码分别为:
_$sX._$Xb = _$sX[_$sX._$Xb](_$BH, _$DP);
_$sX._$oI = _$sX[_$sX._$oI](_$ZJ, _$DS)
_$sX._$EN = _$sX[_$sX._$EN]();
_$sX._$D9 = _$sX[_$sX._$D9](_$iL);
实际上应该是:
_$sX._$Xb = _$sX["_$Rm"](_$BH, _$DP);
_$sX._$oI = _$sX["_$Nw"](_$ZJ, _$DS)
_$sX._$EN = _$sX["_$Uh"]();
_$sX._$D9 = _$sX["_$ci"](_$iL);
进一步来说,实际上是:
_$sX._$Xb = _$1k(_$BH, _$DP);
_$sX._$oI = _$jH(_$ZJ, _$DS)
_$sX._$EN = _$9M();
_$sX._$D9 = _$oL(_$iL);
静态分析没问题,我们可以先固定下来,但是实际应用当中这些值都是动态的,那我们应该怎么处理呢?先来多看几个对比一下找找规律:
可以发现每次对应的位次都不一样,但是实际上相同位置的方法点进去都是一样的,也就是说,变的只有方法名和变量名,实现的逻辑是不变的,所以我们只要知道了这四个值分别对应的位置,就能够拿到正确的值,在本地,我们就可以这样做:
1、先利用正则匹配出这四个值,如:[_$sX._$Xb, _$sX._$oI, _$sX._$EN, _$sX._$D9]
;
2、再匹配出 VM 代码开头的 20 个赋值的语句,如:_$sX._$RH = _$wI; _$sX._$i5 = _$n5;
等;
3、然后通过 $_ts
取这四个值对应的值,相当于:_$sX._$Xb = _$ts._$Xb = _$Rm
;然后再找这四个值所定义的方法在 20 个赋值语句中的位置,相当于:查找 _$sX._$Rm = _$1k;
在 20 个赋值语句中的位置为 7(索引从 0 开始)
4、我们知道了这四个方法在 20 个赋值语句中的位置,那么我们直接匹配本地对应位置的名称,进行动态替换即可,当然前提是咱们本地已经扣了一套代码出来了:
经过这样处理后,就能够保证这四个值的准确性了。
其他用到 $_ts 值的地方
除了上面说的 20 位数组里用到了 4 个 $_ts
的值以外,还有其他地方有 7 个值也用到了,直接搜索就能定位,这 7 个值相对较简单,每次都是固定取 $_ts
里面的第 2、3、4、15、16、17、19 位的值,同样的,找到对应位置,进行动态替换即可:
注意事项
特别注意 VM 代码开头,会直接调用执行一些方法,某些变量的值就是通过这些方法生成的,当你一步一步跟的时候发现某些参数不对,或者没有,那么就得注意开头这些方法了,可能一开始就已经生成了。
后缀 MmEwMD 生成逻辑
后续的其他 XHR 请求中,都带有一个后缀,这个后缀的值同样是由 JS 生成的,每次都会变化,当然不同网站,后缀名不一定都是一样的,本例中是 MmEwMD
,先下一个 XHR 断点,当 XHR 请求中包含了 MmEwMD=
时就断下,然后刷新网页:
可以看到后传入 l.open()
的 URL 还是正常的,断下后到 l.send()
就带有后缀了,再看 l.open()
其实就是 xhr.open()
,明显和正常的有区别,同样这个方法也在 VM 代码里,应该是重写了方法,可以和正常的做对比:
跟到 VM 代码里去看看,经过了 _$sd(arguments[1])
方法就变成了带有后缀的完整链接了:
跟进 _$sd
方法,前面都是对 url 做一些处理,后面有个进入第 779 号控制流的流程,实际上就是原来我们生成 Cookie 的步骤,跟一下就行了。
善用 Watch 跟踪功能
开发者工具的 Watch 功能能够持续跟踪某个变量的值,对于这种控制流很多的情况,设置相应的变量跟踪,能够让你知道你现在处于哪个控制流中,以及生成的数组的变化,不至于跟着跟着不知道到哪一步了。
结果验证
如果整个流程没问题,代码也扣得正确,携带正确的 Cookie 和正确的后缀,就能成功访问:
End
崔庆才的新书《Python3网络爬虫开发实战(第二版)》已经正式上市了!书中详细介绍了零基础用 Python 开发爬虫的各方面知识,同时相比第一版新增了 JavaScript 逆向、Android 逆向、异步爬虫、深度学习、Kubernetes 相关内容,同时本书已经获得 Python 之父 Guido 的推荐,目前本书正在七折促销中!
内容介绍:《Python3网络爬虫开发实战(第二版)》内容介绍
扫码购买
点个在看你最好看
边栏推荐
- (flutter2) as import old project error: inheritfromwidgetofexacttype
- Unified Focal loss: Generalising Dice and cross entropy-based losses to handle class imbalanced medi
- koa2对Json数组增删改查
- cuda 探索
- Unified Focal loss: Generalising Dice and cross entropy-based losses to handle class imbalanced medi
- Should the jar package of MySQL CDC be placed in different places in the Flink running mode?
- Motion capture for snake motion analysis and snake robot development
- How to choose the server system
- On file uploading of network security
- Can async i/o be implemented by UDF operator and then called by SQL API? At present, it seems that only datastre can be seen
猜你喜欢
专为决策树打造,新加坡国立大学&清华大学联合提出快速安全的联邦学习新系统
Flutter life cycle
儿童睡衣(澳大利亚)AS/NZS 1249:2014办理流程
How to choose indoor LED display? These five considerations must be taken into account
Designed for decision tree, the National University of Singapore and Tsinghua University jointly proposed a fast and safe federal learning system
UE4 blueprint learning chapter (IV) -- process control forloop and whileloop
Ajout, suppression et modification d'un tableau json par JS
Let's see through the network i/o model from beginning to end
Cover fake big empty talk in robot material sorting
Coscon'22 community convening order is coming! Open the world, invite all communities to embrace open source and open a new world~
随机推荐
#DAYU200体验官# 首页aito视频&Canvas绘制仪表盘(ets)
专为决策树打造,新加坡国立大学&清华大学联合提出快速安全的联邦学习新系统
浅谈网络安全之文件上传
dockermysql修改root账号密码并赋予权限
CUDA exploration
spark调优(二):UDF减少JOIN和判断
Huawei cloud gaussdb (for redis) unveils issue 21: using Gauss redis to achieve secondary indexing
Redis 持久化机制
OpenSSL: a full-featured toolkit for TLS and SSL protocols, and a general encryption library
How to achieve text animation effect
服务器的系统怎么选者
Should the jar package of MySQL CDC be placed in different places in the Flink running mode?
Detailed explanation of ThreadLocal
Graphite document: four countermeasures to solve the problem of enterprise document information security
(shuttle) navigation return interception: willpopscope
Balanced Multimodal Learning via On-the-fly Gradient Modulation(CVPR2022 oral)
Pytest unit test series [v1.0.0] [pytest execute unittest test case]
Children's pajamas (Australia) as/nzs 1249:2014 handling process
Flutter life cycle
前置机是什么意思?主要作用是什么?与堡垒机有什么区别?