声明本文所有内容仅供学习交流,不作任何其他用途,不提供完整代码,抓包内容,敏感网址,数据接口等。严禁脱敏用于商业和非法用途,否则由此产生的一切后果与作者无关!本文未经许可禁止转载,修改后禁止二次传播。对于因未经授权使用本文所解释的技术而导致的任何事故,作者概不负责。如有侵权,请第一时间联系作者公众号【K爬虫哥】删除!前言睿数动态安全Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态token等技术,不断动态改变服务器网页的底层代码,增加服务器行为的“不可预测性”。实现从客户端到服务端的全方位“主动防护”,为各类Web和HTML5提供强大的安全防护。在K哥之前的文章《人均瑞数系列,瑞数 4 代 JS 逆向分析》中,详细介绍了锐书的特点,如何区分不同的版本,锐书的代码结构以及各自的作用。见上一篇文章。Cookie入口定位本例中睿数5代网站为:aHR0cHM6Ly93d3cubm1wYS5nb3YuY24vZGF0YXNlYXJjaC9ob21lLWluZGV4Lmh0bWw=定位cookies,首选Hook,通过Fiddler插件、油猴脚本、浏览器插件等方式注入如下Hook代码。:(function(){//严格模式检查所有错误'usestrict';//document是要被hook的对象这里是hook的cookievarcookieTemp="";Object.defineProperty(document,'cookie',{//hookset方法也是赋值方法set:function(val){//这样可以在下面这行代码快速设置断点//快速定位到设置cookie控制台的代码.log('Hookcapturesthecookiesetting->',val);debugger;cookieTemp=val;returnval;},//hookget方法也是获取值的方法get:function(){returncookieTemp;}});})();赋值给document.cookie的代码类似于下面的结构:继续跟栈,类似于第4代瑞士数。(772,1)的位置就是入口。4代有一个生成fakecookies的过程,5代没有现在,如下图:顺着堆栈往前走,来到首页代码,这里是我们熟悉的调用位置,在图中_$ug其实就是eval方法,传入的第一个参数_$Cs是Window对象,第二个对象_$Dm是我们前面看到的VM虚拟机中的IIFE自执行代码。VM代码和$_ts变量获取第一步获取VM代码和$_ts变量,和4代类似,复制外链JS的代码(如fjtvkgf7LVI2.a670748.js)和自执行代码412页面到文件中,直接在本地运行即可,需要稍微补环境,缺的补,大致补窗口,位置,文档,具体内容可以直接复制进去浏览器控制台使用copy()命令,然后我们的VM代码可以通过Hookeval直接获取。这里$_ts变量的获取和4代有点不一样。我们4代的做法是运行代码后直接取window.$_ts。5代运行代码后,会有一个清零$_ts的操作,可以自己用栈查看逻辑,要么删除清零逻辑,要么定义一个全局变量,然后直接导出$的值_ts在调用的地方:一般补充环境代码如下:{"ancestorOrigins":{},"href":"https://offDesensitization/datasearch/home-index.html","origin":"https://Desensitization","protocol":"https:","host":"www.Desensitization.cn","hostname":"www.desensitization.cn","port":"","pathname":"/datasearch/home-index.html","search":"","hash":""}document={"scripts":["script","script"]}获取VM代码和$_ts变量:利用好Watch跟踪功能在分析栈之前,需要了解浏览器开发阅读器工具的watch功能,可以持续跟踪某个变量的值。对于Ruishu等很多控制流的情况,设置对应的变量tracking可以让你知道你现在处于哪个控制流,生成数组的值如下图,_$S8表示当前是279号大控制流中,_$5x表示大控制流下的哪个分支,_$mz表示128位大数组。同样的方法跟进栈分析,在本地替换一套412页面的代码,修复,然后开始跟进栈分析。直接从(772,1)开始(文中提到的控制流数和步骤数均为作者本人姓名,步骤数不代表实际步骤,只代表关键步骤):单步来吧,_$qh是传入的参数1,即将进入742号控制流:进入742号控制流,step1通过一个方法获取时间戳,进入这个方法,计算两者的差值时间戳,你会发现_$tb和_$t1两个变量产生了值:这两个值也是时间戳,怎么来的?直接搜索这两个变量,在几个搜索结果上设置断点。刷新break后,顺着栈往前走,会发现是firststepthroughNo.703的controlflow:firststepthroughNo.703,703ThefirststepthroughthecontrolflowNo.703,703ThefirststepthroughthecontrolflowNo.703699是进入699号控制流,返回一个数组。没什么特别的,直接扣代码即可:703号控制流的第2、3步分别取数组的值:703号控制流的第4、5步,第6步生成两个时间戳并赋值它们到上面提到的_$tb和_$t1变量。涉及的方法没有什么特别的,找找找找看:703号控制流的第7步,这里修改了$_ts的值(在VM代码中,$_ts赋值给另一个变量,_$iw在下图),_$iw._$uq的原值为_$ou,修改后为181,也是后面重点的4位数组之一,具体逻辑后面再说。703号控制流程结束,我们继续之前的742号控制流程,742号控制流程的第2步,将之前生成的时间戳赋值给另一个变量。742号控制流程第3步,进入279号控制流程,279号控制流程是生成128位数组的关键。进入279号控制流,第一步定义一个变量:279号控制流,第2步,进入157号控制流,157号控制流主要用??于自动检测279号控制流,第3、4步,5,做一些计算后,一些全局变量的值会发生变化,在后面的数组中会用到。279号控制流程,第6步,初始化一个128位的空数组,后面的操作就是给数组填充值。第279号控制流程的第7步进入第695号控制流程以生成一个20位数组。进入695号控制流看一看。第一步,取$_ts的值生成一个16位的数组。695号控制流程,第2步,取$_ts中的四个值,与前面的16位数组组成一个20位的数组。这里,注意一下这四个值是怎么来的。以第二个值_$iw._$KI为例,查找发现有语句_$iw._$KI=_$iw[_$iw._$KI](_$bl,_$n2);,先取等号右边的_$iw._$KI的值为_$Mo,那么_$iw["_$Mo"]其实就是_$iw._$Mo,前面的定义_$iw._$Mo=_$1D,_$1D是一个方法,所以原来的语句等价于_$iw._$KI=_$1D(_$bl,_$n2),其他三个值的来源也类似。695号控制流程结束,返回279号控制流程,第8步,将之前的时间戳转换为8位数组。控制流程279,步骤9,将值添加到128位数组。_$ae的值是怎么来的呢?查找断点顺着栈走,发现是走一开始的178号控制流得到的,顺着走就行了。279号控制流,第10步,给128位数组加一个值,这个值是从279号控制流开始的。279号控制流,第11、12、13、14步,时间戳相关计算,然后生成两个2位数组。注意里面的两个变量_$ll和_$ed在刷新cookie和生成后缀的时候可能会有值,如果你只访问首页没有值也没关系。在279号控制流中,在第15步,在128位数组中增加了一个4位数组_$bl,查找也可以发现是通过723号控制流得到的。这里的723号控制流其实就是取$_ts的某个值进行运算,生成一个16位数组,然后截取第一个4位数组返回。279号控制流程的第16步将8位数组_$Yb添加到128位数组。8位数组_$Yb也是找断点,可以在赋值语句中断:可以看到_$EJ的值为_$Yb,顺着栈往前走,会发现已经通过了657号、10号、777号控制流程777号控制流程是入口:如果按照777号控制流程一步一步来,会发现步骤很多,中间还有一些语句处理难度大,容易理解,这里直接关注657号控制流程。就这样,777号控制流直接进入10号控制流,再进入657号控制流,中间的一些流程暂且忽略,有漏的再说(后面的赋值等很多操作都是由777号流程控制的,大家可以关注一下),这个逻辑在局部表达式中的代码如下图所示:这里按照657号流程中的控制流程单步,在第1步和第2步中增加了一个新的方法。这里需要注意,容易走神,先进入_$bH方法并设置断点,然后下一个断点会进入里面,然后在单步调试时,会进入另一个小控制流程,如下图所示:用96号小控制流程开始单步,第1步定义一个变量。在96号小控制流程中,第二步将_$PI的值赋给了_$fT,而_$PI的值其实就是window.localStorage.$_YWTU。window.localStorage中有很多值。这是我们文章的最后一部分,另外还有一些值是和浏览器指纹有关的,知道是一个值就可以了。96号小控制流,第3步,进入94号小控制流,最后生成一个8位数组,其实就是我们之前想要的_$Yb的值。后面没什么特别的,中间的步骤我就省略了,直接按照推导代码,然后是96号小控制流程,第4步,把_$EJ的值赋给_$Yb。到这里先别着急结束,后面还有几个关键步骤,96号的小控制流程,第5步,遇到了和前面类似的写法。同样,先行_$pu断点,然后单步跟随。再来一个小控制流程,如下图所示:10号小控制流程第一步,取window.localStorage.$_cDro的值,转为int类型,赋值给_$5s,这_$5s也会把它加入到128位的大数组中。小控制流程No.10有几个步骤可以遵循,如果没用可以省略。最后一步返回96号小控制流。然后96号小控制流后面什么都没有,返回657号控制流。至此,我们已经得到了_$Yb,所以我们先忽略777号控制流。还有一些代码需要忽略或者不扣,等到用到的时候再说。返回279号控制流,然后进行第一步在第17步,变量_$5s经过264号控制流后产生一个值加入到128位的大数组中,则_$5s的值是我们之前和_$Yb对话时通过777号控制流得到的。其实就是取window.localStorage.$_cDro的值,转成int类型。279号控制流程,第18、19、20步,在128位数组中添加两个固定值和一个8位数组。279号控制流,第21步,向128位数组添加一个未定义的占位符,后续操作将用值填充它。279号控制流,第22步,进入58号控制流,58号控制流与window.localStorage.$_fb的值有关,如果有这个值,就会生成一个20位的数组,否则它将是不确定的。58号控制流程只有一个步骤,返回一个变量,即本文中的_$0g。这个_$0g从哪里来?同样直接查找,设置断点,发现是通过112号控制流得到的,往前进栈,也是先通过777号控制流,和前面的情况类似。不看中间过程,直接看这个112号控制流,本文中112号控制流的参数是_$bd[279],即$_fb,第一步112号控制流程进入247号控制流程。247号控制流程只有3步,首先将window.localStorage赋值给一个变量,然后取$_fb的值并返回。在第112号控制流的第2步和第3步中,一个try-catch语句使用window.localStorage.$_fb来计算一个25位数组,然后取前20位并返回它。这是我们之前需要的_$0g的值。279号控制流程,第23步,将前面window.localStorage.$_fb计算出来的20位数组添加到128位大数组中,注意如果这一步没有window.localStorage.$_fb值,它不会被添加。控制流程279号,第24步,对一个变量进行位运算,然后取window.localStorage.$_f0进行运算。如果$_f0为空,则不会向128位大数组添加任何值。控制流程279号,第25步,对一个变量进行位运算,然后取window.localStorage.$_fh0进行运算。如果$_fh0为空,则不会向128位大数组添加任何值。控制流程279号,第26步,对一个变量进行位运算,然后取window.localStorage.$_f1进行运算。如果$_f1为空,则不会向128位大数组添加任何值。279号控制流程,第27步,进入611号控制流程,611号控制流程主要是检测window.navigator.connection.type,即NetworkInformation网络相关信息,判断类型是否为蓝牙,cellular,ethernet,wifi,Wimax,正常的话应该返回0。279号控制流程,后面几步类似,这里直接参考28步,先对一个变量进行位运算,然后然后分别取window.localStorage.$_fr,window.localStorage.$_fpn1,window.localStorage.$_vvCI,window.localStorage.$_JQnh进行计算,如果这些变量为空,则128不加值位大数组。279号控制流程,第29步,给128位大数组增加一个固定值4,本文中的变量名是_$kW。_$kW这个变量是怎么来的?跟之前的套路差不多。直接搜索下一个中断。也是通过一开始的777号控制流得到的,如下图:继续控制279号流,中间有一些变量位操作等省略。在步骤30和31中,取长度为https:443进行计算,在128位大数组中依次加入一个固定值和一个9位数组。在279号控制流中,后面的几个步骤都是取值,而且都差不多,所以统称为32步。279号控制流,33步,前面128位的第12位large数组未定义,这里第12位用4位数组填充,还有一个变量_$8L,我们在上一步中有一个变量一直在做位运算,这就是_$8L的来源.279号控制流程,最后两步,原来128位的大数组,只取前21位有值,一共有多少位与window.localStorage的某些值有关,如果有值,长一点,不长一点,短一点,然后把数组的每个元素组合成一个最终的大数组返回,279号的控制流程就结束了。回到文章开头的逻辑,279号控制流程结束,回到742号控制流程。在第2步中,定义了一个变量,生成了一个32位的数组。742号控制流,第3步,从$_ts中获取一个值并将其分配给一个变量。742号控制流,第4步,将前面279号控制流得到的大数组与上一步$_ts中的某个值合并,合并后计算一个值。742号控制流程,第4步,将上一步得到的值进一步计算得到一个4位数组,然后与大数组合并。742号控制流,接下来的几步是对时间戳进行各种操作,这里统称为第5步。742号控制流程,第6步,计算上一步得到的4个时间戳,得到一个16位的数组。742号控制流程,第7步,对上一步得到的16位数组进行异或运算。742号控制流程,第8步,计算上一步的16位数组,得到一个字符串。742号控制流程,第9步,正式生成cookie值,其中_$bd[274]为固定值,一般认为是版本号,通过计算上一步得到的字符串计算得到,之前得到的大数组和一个32位数组,结合起来得到最后的结果。742号控制流结束,返回772号控制流,用一个方法拼装cookie,然后赋值给document.cookie,整个流程结束。代码中使用的$_ts的值需要自己匹配,动态替换。这些步骤与第4代类似。本文不再赘述。可以参考K哥4代的文章进行处理。后缀生成本例中,请求头中有一个sign参数,QueryStringParameters有两个后缀参数。这两个后缀和4代差不多,都是瑞书出的。和4代的处理方式一样,我们打下XHR断点,让网页先加载,然后打开开发者工具,无限调试通过后,点击搜索就断了,如下图:顺着栈到hasTokenGet是sojson下的jsjiamiv6混淆,不值一提。重点是jsonMD5ToStr方法。它首先对传入的参数进行编码,最后返回hex_md5,与在线MD5加密结果相同。它是标准的MD5。重点介绍瑞书的两种后缀生成方式。与第4代一样,重写了XMLHttpRequest.send和XMLHttpRequest.open。如下图,在XMLHttpRequest.open上设置断点,也就是图中_$RQ方法中,arguments[1]为原始URL,经过_$tB处理后可以得到后缀方法如图。跟进图中的_$tB方法,在_$tB方法中嵌套了一些其他的方法,再走一遍逻辑,在图中的_$5j方法中,前面的部分都是处理传入的URL。接下来生成一个16位数组:然后这个16位数组经过一个方法生成第一个后缀,如下图,这篇文章中的这个方法就是_$ZO。继_$ZO方法之后,主要有以下5个步骤:第一步:生成一个32位数组;第二步:拼接前面的16位数组和两个变量,生成50位数组;第三步:进入744控制流程,这里你会发现和我们之前使用cookie时的742控制??流程是一样的,重复一遍,这里不再赘述;第四步:执行第一次生成后缀值过程,得到一个两位数的字符串,在获取第二个后缀时会用到;第五步:拼接第一个后缀名和值并返回,此时,第一个后缀hKHnQfLv就生成了。紧接着前面的_$5j方法,图中的_$5j步骤是获取第二个后缀8X7Yi61c的值:主要看图中的_$UM方法,先将前面生成的两位数字符串与拼接URL参数,然后通过一个_$Nr方法可以得到第二个后缀的值。让我们再看一下_$Nr方法。先生成一个类似53924的值,再生成一个try语句。注意这里有一个方法。图中的_$Js方法使用了$_ts中的一个值,稍后生成。由数字组成的字符串,组合并再次计算得到最终值。回到之前的_$UM方法,前缀8X7Yi61c与值结合。至此,两个后缀都得到了:fingerprintgeneration我们之前分析过。在给128位数组加值的时候,会有window.localStorage里面计算某些值的步骤。这些值是由浏览器画布等指纹生成的。指纹可以同时随机生成。通常访问单个html页面时不验证指纹,生成的shortcookie可以通过。但是有些查询数据接口会校验指纹,通过触发load事件将指纹添加到cookie中,使得cookie的长度变长。如何找出指纹生成的位置。这里推荐直接看视频。已经解释的很清楚了。篇幅太长,本文不再赘述。资料链接:https://mp.weixin.qq.com/s/DE...
