某验滑块加密分析下
本帖最后由 冷空气鑫 于 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之间 ,还有个自己写的轨迹算法不行,有更好的算法应该完全没问题的。
极验的滑块入门级别的,除了繁琐点,没有其他的难度。这次就不给源码了,感兴趣的自己扣一下。
生而为虫,我很抱歉。 极验滑块全套基本完毕图片还原那块我也写了 就在论坛里面! 轨迹算法生成比较简单,Python随便写写就好了! 辛苦楼主{:1_921:} 很好的思路 图片的可以做下,我看看哪天有空 心血来潮 来做个全套的极验滑块 极验滑块很多地方会要用到 大佬威武 js从入门到放弃! 大佬,牛批 辛苦楼主