共计 8251 个字符,预计需要花费 21 分钟才能阅读完成。
申明
本文章中所有内容仅供学习交换应用,不用于其余任何目标,不提供残缺代码,抓包内容、敏感网址、数据接口等均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关!
本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【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:
接下来是生成 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 和正确的后缀,就能胜利拜访: