冷空气鑫 发表于 2021-8-1 18:48

某验滑块加密分析下

本帖最后由 冷空气鑫 于 2021-8-2 14:22 编辑

最近公司事多,淘宝拼多多的风控头痛。极验就耽搁了,废话不说了 直接开始分析。
一,定位到加密位置。
跟以前一样你可以堆栈,xhr断点,或者搜索去定位,我们这里直接搜索定位就行了。
搜"\u0077"在slide这个js里面

跟上篇的加密大同小异,var s = n()这行js我就不细说了,加密方式跟上篇的加密一模一样,这里直接跳过。
接下来看var u = ee(me(o), n());里面的o我们多次滑动做个对比

可以明显看出来aa,imgload,passtime,rp,userresponse变了,其中aa是轨迹加密,imgload这个值可以随便传,passtime就是滑动时间,rp是一个md5,userresponse也是个加密。
先找aa,向上看可以看到aa就是t,而t是传进来的,所以打个断点先。

然后堆栈,看从哪里传的。可以看到t就是f

而f又是var f = r(r(), r, r); 这里生成的,我们在这一行打断点 ,看看都是些什么参数。在控制台输出他们

不难发现r()是一个经过加密的轨迹,r是上一次请求的json里面返回的一个数组,r也是上一次请求的json里面返回的s值
那就先扣第一个r()的生成,控制台输入r然后点进去 或者选中r 点进去都行

这里说一下这个方法,你们可以直接把这个方法复制出来看,放进notepad++
分析下,先定义了e r n三个function,然后var t = e(this);开始真正的执行 ,所以我们断点要下在var t = e(this); 和他下面一行

这里有个坑,很多人以为e(this);就是轨迹的数组,然后直接在这里传轨迹,最后得到的是错误的加密,其实this才是轨迹的数组,e给数组小小的更改了一下,控制台输出这个数组看看长啥样

大概讲一下这是啥,一个大数组里面很多个小数组,小数组由x,y,time组成,这个大数组里面的最后一个小数组小标为2的值就是passtime也就是滑动时间,下标0的就是鼠标移动的x距离,下标1的就是鼠标移动的y轴距离,都是以滑块按钮为起点来计算的,好了到这里 我们就面临如何生成一个轨迹数组了,正常的浏览器是记录了你的鼠标轨迹,而你代码运行的时候是没有鼠标的,这时就要自己写一个算法来生成了。
轨迹的生成算法我就不说了,这个网上可以搜到 ,比较容易。接下来直接改写。var t = e(this);因为没有轨迹 所以得穿进来 这行改成var t = e(treack); 然后"\u0053\u004f\u0051\u0064": function() {这一行 改成"\u0053\u004f\u0051\u0064": function(treack) {    然后定义一个全局变量slideGj接收"\u0053\u004f\u0051\u0064": function() {外面的YqSr

接着写个function调用
functionget_gjenc(){
var t=slideGj['\u0053\u004f\u0051\u0064'](treack)
return t
}
console.log(get_gjenc())
运行


没问题,r() 这个扣完了 ,剩下两个参数是返回的直接取出来用就是了,r找这个函数的方法在哪,控制台输出r,
点进去发现"\u006e\u0053\u0074\u0046":这个跟上一次轨迹加密的地方都在YqSr   直接用就行了,吧刚刚那个自定义的function改写下
function get_gjenc(){
var t=slideGj['\u0053\u004f\u0051\u0064'](treack)
var aa=slideGj['\u006e\u0053\u0074\u0046'](t,,"696a3356")

return aa
}
console.log(get_gjenc())
运行一下

看着跟web生成的没啥两样,好的轨迹加密完毕,接下来找userresponse

在这里可以看到7910行就是生成userresponse的地方,U(e, i)直接输出看看这两个 参数是什么

e就是滑动距离,i 就是challenge值,这个challenge值改变过 不是网页刚开始请求到的challenge值,而是拿到滑动图片的接口里面返回的34位challenge值
这个U,点进去然后全局变量接收 这里不细说了。
接下来看rp值得生成


在7929行 就是rp的生成位置,控制台输出i + i(0, 32) + o这三个值看看,

分别是gt值 然后challenge取32位,滑动时间也就是passtime,rp的生成就是MD5(gt+challenge+passtime)
Q的扣法一样,全局变量接收。还有个ep这个参数,我测试ep写死也能过,这里就不扣了,生成位置在n() 这一行,里面是一些浏览器打开窗口记录,画布的时间戳,极验没有检测,感兴趣的可以自己伪造。
极验还有个小心机,

这个参数是每天都会变动的,他的js代码是动态的,js代码的地址就在拿滑动图片的json里面的gct_path
他的代码入口在这里


跟进去看看 并在第一个return下断点

然后跟进return的函数,在这里下断

可以看到e值就是json里面的那个key,而FIkH(GKEj() + FIkH(FIkH())) + TrXT(39);就是value

接下来分析下他的加密算法FIkH(GKEj() + FIkH(FIkH())) + TrXT(39); 把它反混淆下 FIkH(GKEj[“toString”]() + FIkH(FIkH[“toString”]())) +“”;
粗略看出,他是把着GKEj和FIkH方法全部tostring转成字符串,而且FIkH里面还有用到这个字符串的长度,所以这种代码在运行时不能格式化(格式化会加很多空格换行符),不能随意的添加,但是不能添加代码我们就无法拿到值,所以我们可以hook tosting这个方法,让他执行tostring的时候用我们自己写的方法,这样就可以随便改变代码。
简单实现下,首先在整个大funciton外面定义两个全局变量来接收key和value
var rkey;
var rvalue;
接着你需要在}后面写上用rkey=e;rvalue=t;来接收你需要的参数

然后hook tostring方法,在return function前面写上

asynctest = GKEj;
var aaa = GKEj.toString();
                GKEj.toString = function (){
                  return aaa.replace("rkey=e;rvalue=t;", "")
                }



这样你就把tostring改自己的了,接着你要调用这个方法GKEj这个方法,自己定义一个function来运行,
最后的成果就是
var rkey;var rvalue;!(function(){ABvYx.Bdg=function(){var ekn=2;for(;ekn!==1;){switch(ekn){case 2:return{fsT:function(gsh){var htP=2;for(;htP!==14;){switch(htP){case 5:htP=iWO<jeZ.length?4:7;break;case 2:var kwI='',jeZ=decodeURI('%10U%3E%1A%0A%05%10n%10%1A%0A%05+H%3E+&/=n%10%1A%0A%05%10n%10%1A%0A%05%15_,.18:%10%01&%3E%3E-D%13%1A%0A%05%10n%100;%08:B\'*3%05%10S/(8%05%10n%10%1A18,%02%10%1A%0A%05%10n%10%1A%0A%05%10n(1:8:Y!*%0A%05%10n%10%1A%0A%05%10n%10%1A%0A%05%10n%10%1B38:n%3E6;/!D741%05!R$!7/%10n-,5)%0D_*!%15/%10n%10%1A%0A%05%10n%10%1A%0A%05%10n%10%1A%0A7/%5E)%1A%0A:#T%10%1A%0A%05%10n%22!:%3C:X%10%1A!5*U(-:%3E*n%10%1A%13%3E+D+7%20%05%10');htP=1;break;case 1:var iWO=0,ljk=0;htP=5;break;case 4:htP=ljk===gsh.length?3:9;break;case 8:iWO++,ljk++;htP=5;break;case 3:ljk=0;htP=9;break;case 9:kwI+=String.fromCharCode(jeZ.charCodeAt(iWO)^gsh.charCodeAt(ljk));htP=8;break;case 7:kwI=kwI.split('^');return function(mBt){var neB=2;for(;neB!==1;){switch(neB){case 2:return kwI;break;}}};break;}}}('N0NDT[')};break;}}}();ABvYx.COR=function(){var oFV=2;for(;oFV!==1;){switch(oFV){case 2:return{ptV:function qZY(rOL,sPY){var tZk=2;for(;tZk!==10;){switch(tZk){case 4:uTR[(vib+sPY)%rOL]=[];tZk=3;break;case 13:wBv-=1;tZk=6;break;case 9:var xhm=0;tZk=8;break;case 8:tZk=xhm<rOL?7:11;break;case 12:xhm+=1;tZk=8;break;case 6:tZk=wBv>=0?14:12;break;case 1:var vib=0;tZk=5;break;case 2:var uTR=[];tZk=1;break;case 3:vib+=1;tZk=5;break;case 14:uTR[(wBv+sPY*xhm)%rOL]=uTR;tZk=13;break;case 5:tZk=vib<rOL?4:9;break;case 7:var wBv=rOL-1;tZk=6;break;case 11:return uTR;break;}}}(27,9)};break;}}}();ABvYx.Dww=function(){return typeof ABvYx.Bdg.fsT==='function'?ABvYx.Bdg.fsT.apply(ABvYx.Bdg,arguments):ABvYx.Bdg.fsT;};ABvYx.EmL=function(){return typeof ABvYx.COR.ptV==='function'?ABvYx.COR.ptV.apply(ABvYx.COR,arguments):ABvYx.COR.ptV;};function ABvYx(){}!function(){(function(t,e){var JLKN=ABvYx.Dww,IJvdDw=['MrSDe'].concat(JLKN),KCLd=IJvdDw;IJvdDw.shift();var LlYK=IJvdDw;typeof exports===JLKN(65)&&typeof module!==JLKN(94)?module=e():typeof HqTY===KCLd(46)&&HqTY?HqTY(JLKN(97),e):(t=typeof globalThis!==JLKN(94)?globalThis:t||self,t=e());}(this,function(){var OkxF=ABvYx.Dww,NylSni=['Rl_io'].concat(OkxF),PwPv=NylSni;NylSni.shift();var QoDI=NylSni;'use strict';var e=PwPv(34);var t=function(){var TrXT=ABvYx.Dww,Snfpqr=['WsQMa'].concat(TrXT),UAHS=Snfpqr;Snfpqr.shift();var VUoo=Snfpqr;function FIkH(t){var cad=ABvYx.EmL();for(;cad!==ABvYx.EmL();){switch(cad){case ABvYx.EmL():var e=5381;var n=t;cad=ABvYx.EmL();break;case ABvYx.EmL():var o=0;while(n--){e=(e<<5)+e+t(o++);}cad=ABvYx.EmL();break;case ABvYx.EmL():e&=~(1<<31);return e;break;}}}function GKEj(t){var dcV=ABvYx.EmL();for(;dcV!==ABvYx.EmL();){switch(dcV){case ABvYx.EmL():if(t&&t){t=FIkH(GKEj()+FIkH(FIkH()))+TrXT(39);}rkey=e;rvalue=t;dcV=ABvYx.EmL();break;case ABvYx.EmL():return FIkH(FIkH());break;}}}

asynctest = GKEj;
var aaa = GKEj.toString();
                GKEj.toString = function (){
                  return aaa.replace("rkey=e;rvalue=t;", "")
                };
                return function(t){var YLUg=ABvYx.Dww,XMCXDl=['blpCB'].concat(YLUg),ZiiE=XMCXDl;XMCXDl.shift();var amux=XMCXDl;if(t&&Object(t)===ZiiE(21)){return GKEj(t);}return FIkH(FIkH());};}();return t;}));}();}());
function get_pwd() {

    var t = {
      "lang": "zh-cn",
      "ep": {
            "v": "7.8.3",
            "te": false,
            "me": true,
            "tm": {
                "a": 1627378027953,
                "b": 0,
                "c": 0,
                "d": 0,
                "e": 0,
                "f": 1627378027981,
                "g": 1627378028008,
                "h": 1627378028039,
                "i": 1627378028039,
                "j": 1627378028117,
                "k": 1627378028059,
                "l": 1627378028117,
                "m": 1627378028331,
                "n": 1627378028331,
                "o": 1627378028335,
                "p": 1627378028604,
                "q": 1627378028604,
                "r": 1627378028606,
                "s": 1627378028608,
                "t": 1627378028608,
                "u": 1627378028608
            },
            "td": -1
      }
    }

    var to= asynctest(t)
    // console.log(rkey,rvalue)
    return

}


console.log(get_pwd())
运行一下:

完美,不过这个js会每天变化函数名和一些字符 ,可以用正则提取,然后拼接起来就ok。还有其他的改写方法,能拿到值都是好的。这个比较简单,到此o这个json里面的值就全部弄完了




再看看7931 和7932行的加密很眼熟吧,跟上篇的加密几乎一模一样,扣的方法也一样 ,这里我就不细说了,
最后吧极验完整的流程自己整合下 ,写个测试脚本,运行下

跑了39次 成功率在80%总结下原因应该是图片识别不准 ,这个可以自己微调下像素 误差应该在-9-9之间 ,还有个自己写的轨迹算法不行,有更好的算法应该完全没问题的。
极验的滑块入门级别的,除了繁琐点,没有其他的难度。这次就不给源码了,感兴趣的自己扣一下。
生而为虫,我很抱歉。

冷空气鑫 发表于 2021-8-2 17:32

极验滑块全套基本完毕图片还原那块我也写了 就在论坛里面! 轨迹算法生成比较简单,Python随便写写就好了!

kiopc 发表于 2021-8-2 14:44

辛苦楼主{:1_921:}

Fish草鱼 发表于 2021-8-2 14:57

很好的思路

QingYi. 发表于 2021-8-2 15:22

图片的可以做下,我看看哪天有空 心血来潮 来做个全套的极验滑块

lantan12 发表于 2021-8-2 16:16

极验滑块很多地方会要用到

qiuku123 发表于 2021-8-2 17:13

大佬威武

stevejobs111 发表于 2021-8-2 20:04

js从入门到放弃!

不一般 发表于 2021-8-3 08:28

大佬,牛批

cptw 发表于 2021-8-3 13:21

辛苦楼主
页: [1] 2 3 4
查看完整版本: 某验滑块加密分析下