乐趣区

关于python:人均瑞数系列瑞数-4-代-JS-逆向分析

申明

本文章中所有内容仅供学习交换应用,不用于其余任何目标,不提供残缺代码,抓包内容、敏感网址、数据接口等均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关!

本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【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,有关键字 retcall 是明文;

  • 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 和正确的后缀,就能胜利拜访:

退出移动版