目录
一、某盾难点
- 本次测试网址: 点击链接
明明打了断点为何再次刷新定位不到?
那是因为刷新后js文件名变了,可重新打断点,再次刷新试试;或者直接把源网页的js文件copy下来,通过fiddler每次都替换成相同的js文件- validate为空原因主要原因did参数无效,did无效会导致actoken无效,和fp、轨迹没有任何关系,b请求可做可不做,但未批量测试,只是简单研究;但如果你把actoken写死,仍然没通过,说明你的代码流程有问题,先放开d请求js研究看代码流程问题
- 我理解的无感是actoken为空值参考官网,但实际上的无感知是指真正的人为操作,不会触发高级的验证码(如滑块、点选),只需要点下按钮就通过了,而如果是可疑用户则可能触发(滑块、点选等)
- 点选验证码和滑块是同一个分析逻辑,过了滑块基本点选也没啥问题,可以先研究点选,滑块的did有时候会出现第一次不通过,但后面会一直通过,多测试几次
- 注意一些js加密参数时间戳的问题,如d请求的加密参数d有加密时间戳,而响应也有时间戳,中间的速度过快是否也会影响校验失败也需要考虑
1、请求包流程
- "d请求"响应参数是后续actoken加密参数之一,js涉及检查浏览器的指纹、请求头、屏幕宽高等
- “get?id"获取验证码并返回验证码对应的token参数
- "b请求”响应参数是d请求的响应参数之一,js涉及检验d请求的响应参数以及鼠标的移动随机值,目前来说b请求可以不做
- "check?id"验证滑动是否通过并返回validate参数
- 分析调试方法:本文主要通过调用栈回溯解析介绍
- 通过调用栈往前回溯找目标参数,打断点调试
- 直接搜索目标参数打断点调试
二、“check?id”获取验证通过后的validate(cb、data、actoken)
check?id
用于验证滑动是否通过,如果通过滑动通过,则validate返回值;请求参数涉及token、cb、data、actoken
这4个可变参数,其它参数可写死;其中token参数是“get?id"响应返回的
cb
参数随机生成的,无所谓,扣出js即可data
参数和轨迹相关,涉及轨迹的加密actoken
参数和d请求返回的参数相关,最终会生成actoken,actoken只要js口德没问题,就受did参数影响,did无效则actoken无效,则validate为空
1、调试入口
- (1)先从调用栈i先打断点往前回溯
- (2)然后滑动验证码,就会停在你打的断点处,此时发现cb、actoken、data参数已生成
- (3)追溯到anonymous这个栈,发现cb参数就是这个s()方法生成的,actoken参数是I,data参数是k;我们在这里打个断点,重新滑动验证码来逐个看这几个参数是怎么生成的;
2、cb参数解析
- (1)滑动验证码,断点停在cb参数生成的位置,即s()方法,扣出js按缺啥补啥即可生成
function s() {
var e = $.uuid(32);
return A(e)
}
(2)s()方法涉及的就是$.uuid(32)和A(e);
- ① 把源代码里面关于uuid的方法function(e, t)换成成右侧的 _B()方法,并添加return i ;
- ② 把源代码里面关于 A(e)的方法function(e, t) 替换成右侧的_A()方法,并添加var t = {};return t;
- ① 把源代码里面关于uuid的方法function(e, t)换成成右侧的 _B()方法,并添加return i ;
(3)最终补全生成cb参数的s()方法如下,如图可生成
3、data参数解析
(1)接着前面的继续往前回溯,我们发现verifyCaptcha这里传进来的e参数的data就是我们的目标参数,
(2)往前回溯到onMouseUp,这里便是data参数的生成位置,我们重点研究这里,并在此处打上断点,重新滑动验证码在这里断住
this.traceData
:这个是鼠标滑动的xyt加密生成的轨迹数组- this.$store.state.token:这个是滑动验证码对应的token,即get?id响应返回的
this.$jigsaw.style.left
:滑块滑动的距离- this.width:该案例固定值为300
- this.mouseDownCounts:固定值1
(3)data参数生成的关键参数是
traceData
- ① a.sample(this.traceData,50)意思是只取最多50个坐标轨迹,比如你传78个,该方法会取其中的50个;如果传的小于50个,就直接返回
- ② 可以全局搜索this.traceData,并在可疑处打断点,最后会发现这么一个方法p()方法对鼠标的位移点(x,y,t)进行了加密然后再存储到this.traceData;
也就是说要想生成traceData参数,首先得传入鼠标点的x,y,t值
- ① a.sample(this.traceData,50)意思是只取最多50个坐标轨迹,比如你传78个,该方法会取其中的50个;如果传的小于50个,就直接返回
(4)data参数的traceData参数的关键点
xyt
生成观察- ① traceData的轨迹xyt和缺口位移的生成,这里建议
用fiddler替换core.js
,首先复制原网页的core.js,添加并修改如下js内容如下
- ② 打开fiddler替换网页core.js方法如下,将EXACT:和v=后面的数字去掉即可模糊匹配core.js文件,替换为本地前面已修改过的core.js文件,然后save,如下图
- ③ 滑动滑块的多次观察下输出轨迹坐标点,看缺口位移距离offset和输出坐标轨迹之间xyt的关系,我们在这个案例中发现生成的offset与最终停止的x关系是
end_x = offset + 8
,平时多滑几次观察下,确定最终关系;
- ④
注意点
:不同网站end_x和offset的运算之差是不一致的,需多次测试,本网址的offset不是缺口的距离,实际是this.$jigsaw.style.left
- ⑤ 该案例实际上识别图片的缺口的距离记gap_offset,如下一个案例计算关系是
end_x=offset+8=gap_offset+3(可能存在误差)
; 这个在不同网址也是不一样的 - ⑥ 总之就是要多次测试得出
你识别的距离offset
和js中要传的left,end_x之间的关系,以及你的图片大小和网页图片大小是否一致(可通过参数width来判断图片大小,本案例图片进行缩放300×150, 57×150);否则可能导致滑动识别,验证失败率低
- ① traceData的轨迹xyt和缺口位移的生成,这里建议
(5)最终补全生成data参数的get_data()方法如下,如图可生成
4、actoken参数解析
- (1)用通用的方法往前面回溯,在
e(t._arg)
这里我们发现,t._arg其实就是我们要的actoken参数,并在这里打上断点
- (2)重新滑动验证码,断点会停在该处,继续往前回溯,会发现actoken参数其实由watchman.js传给core.js的;于是在k这个栈上继续打上断点,清除缓存以及前面所有的断点,只保留这个断点,重新滑动滑块;
- (3)此时断在了watchman.js的k这个栈上,我们直接运行这个方法发现,这就是生成actoken的方法呀,目前我们还不知道这个q是怎么生成的,我们直接旋转该
cc()
函数进入函数里面打断点
- (4)重新滑动滑块,此时停在了断点处,我们发现
Fa()
方法其实就是生成actoken的真正方法,那Fa需要的参数是b字典
{ “r”: 1, “d”:“ADTGW385YRdBBFAREFIvOlx3e6okxI85”, “b”: “JLq8pv+ucZoAQVFQFQJrexligIfflAKv”}
- ① b字典涉及三个key,分别是r ,b,d;其中key_r固定值为1,key_d是d请求响应传进来的,key_b函数随机生成的;
- ② key_b随机生成的,其实就是我们一开始k栈的cc方法q参数传进来的,而这个q参数,我们通过
搜索var q = 发现bc()
这个函数生成的值就是我们的key_b随机值,注意:这里的key_b随机值和我们后面要讲解的b请求js里面的bc()方法生成的值是一致的;
- ③ 接下来要求actoken参数,就是把前面提到的d字典
key_d
,key_b及bc()方法
,Fa()方法
按啥缺啥去补js就可以生成actoken的参数;
- ① b字典涉及三个key,分别是r ,b,d;其中key_r固定值为1,key_d是d请求响应传进来的,key_b函数随机生成的;
- (5)补全Fa()方法和bc()方法生成actoken如下
三、b请求和d请求之请求参数(d)
- 其实只关注d请求即可,每次清楚浏览器缓存可以看到d请求,b请求可适当研究其内部逻辑,和鼠标的第一个坐标点的移动轨迹有一定的关联
1、寻找d参数入口
(1)d请求和b请求的d参数生成的方法基本一致,但是方法一致,所传的参数还是有所区别的
(2)先以b请求为例子,调试入口打断点调试,如图 e里面有我们要的d参数,往前回溯到e.ga这个栈并重新打上断点调试;(d请求必须得清除cookie等所有缓存后才会首次出现,其余与b请求调试方法一致)
(3)b请求在e.ga这个栈断住,我们发现b请求的d参数是它生成的
ha(hb, b[675], void 0)(d.concat(w, u))
; 同样d请求的d参数是它生成的ha(hb, b[675], void 0)(c.concat(e, f))
;也就是生成d参数是同一个方法ha()
方法,但传参不一致
2、d参数关键方法的解析
(1)ha()方法的js扣一下就可以分别生成d请求和b请求的d参数了;其中d请求的d参数
(c、e、f)变量
ha(hb, b[675], void 0)(c.concat(e, f));b请求的d参数(d, w, u)变量
ha(hb, b[675], void 0)(d.concat(w, u))(2) d请求d参数的
c变量
《==》b请求的d参数的d变量
,都调用了Wa()
这个方法,我们分别看下Wa()方法
- ① b请求
d变量
的Wa()方法有2个参数来自d请求的响应,还有一个随机生成的值是我们前面介绍的bc()方法生成的。验证了d请求的2个响应参数;同时bc()方法这个生成的参数与前面actoken里面js的key_b也是bc()方法,这2个值是一致的;如下看图:
- ② d请求
c变量
的Wa()方法,除了一个固定的值,其余都是空值
- ③ Wa()方法修改如下,其他缺的js方法补全即可
- ① b请求
(3) d请求d参数的
f变量
《==》b请求的d参数的w变量
,都调用了Fb()
这个方法,我们分别看下Fb()方法
- ① Fb()方法 主要涉及一些缺失的函数,和两个对象Gb和Ec对象,共同特征每个key里面都有一个f的方法和变量类型;Gb对象的各个key我已写死
- ② Fb()方法修改如下,其他缺的方法js补全即可,其中Ec对象里面的key( Db、Zb、hb、nb)相关的f方法返回值不可写死,得扣js生成,或者Ec对象完全扣js即可
- 其中Db涉及时间戳之差:[d[“navigation”].type, c[“responseStart”] - c[“requestStart”], c[“responseEnd”] - c[“fetchStart”]]
- 其中Zb涉及:return B[“state”][“options”].Jc
- 其中hb涉及: B[“state”].W.slice(1)
- 其中nb涉及document的html长度:v[“documentElement”][“innerHTML”].length
- ① Fb()方法 主要涉及一些缺失的函数,和两个对象Gb和Ec对象,共同特征每个key里面都有一个f的方法和变量类型;Gb对象的各个key我已写死
(4)剩余最后一个变量: d请求d参数的e变量《==》b请求的d参数的u变量
① d请求的e变量:用于检查浏览器请求头user_agent, win32,"zh-CN"等参数,其中js当中有个Ra变量这里我写死了,不同网站可能不一样;扣下js最终生成e变量如下
② b请求u变量:关键点与鼠标的几个关键点click,down, move,up相关,把如图中js扣下来,即可生成u变量;
(5)最终js生成的d参数如下
四、“get?id”获取滑动验证码(fp、cb)
get?id
请求用于获取滑块验证码,请求参数当中涉及fp
参数和cb
参数,返回图片的token参数和对应图片
fp
的重要性:fp加密也涉及到浏览器的指纹等参数,按照逻辑扣基本没问题,但是把fp直接替换到网页是行不通的会一直失败(测试过,主要原因不明),代码没问题的cb
参数随机生成的,无所谓,扣出js即可
1、fp参数解析
- (1)先从调用栈i先打断点往前回溯
- (2)然后刷新网页,会停在你的断点处,此时fp参数已生成,通过调用栈往前追溯
- (3)追溯到anonymous这个栈,发现fp就是 e.state里面的fingerprint,继续往回追溯
- (4)追溯到dispatch这个栈,发现fp就是 this.state里面的fingerprint,在this.state这里打个断点,重新刷新页面
- (5)此时断点定位到刚打的断点处,往回继续追溯,追溯i这个栈里面有fingerprint,发现fingerprint就是
window.gdxidpyhxde
这个全局对象;接下来的难点是如何找到window.gdxidpyhxde
赋值的地方,那我们要借助油猴来hook了
- (6)通过油猴写一个hook脚本来定位
window.gdxidpyhxde
这个生成位置,油猴脚本如下,点击链接查看油猴使用方法
// ==UserScript==
// @name hookgdxidpyhxde
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
var _fp =window.gdxidpyhxde;
Object.defineProperty(window,'gdxidpyhxde',{
get:function(){
console.log("调用fp",_fp)
return _fp;
},
set:function(val){
console.log("生成fp",val)
debugger;
this._value = val;
return val;
}
})
// Your code here...
})();
- (7)添加油猴脚本后,刷新页面点击登录,然后就debugger住了,一直点击往下调试,直到发现了一个window[be] = null,我们可以看出 window[be] 其实
window.gdxidpyhxde
参数生成的位置,也就是W()这个方法生成的
- (8)继续调试回发现,其实就是W()这个函数最终返回的 h = h + u[7] + p,参数就是我们要的
fp
参数;而我们扣js就从这个W()函数入手,按缺啥补啥的方法将所有函数补齐即可生成fp
的js
- (9)简单介绍一下W()函数的内部的关键,这个W函数检测了许多有关window属性即浏览器指纹;
- 这里面的一个fp是navigator和canvas等浏览器属性生成的(
根据你浏览器版本不同生成的值也不同,可以写死,也可以多生成几个不同的随机切换
),另一个h是网站地址; - 接下来只要单独把W函数拿出来,去补全其他缺的函数、参数即可
- W()函数前半部分、后半部分按如下方式修改即可;其他的运行后,按缺啥函数就补啥函数的方式补齐即可;ps:W()函数里面的try…catch,可以直接把try里面的拿出来,catch的注释掉,没啥作用
- 补全W()函数后,console.log(W())即可生成fp参数
- 这里面的一个fp是navigator和canvas等浏览器属性生成的(
2、cb参数解析
- 和前面check?id里面cb参数的是一致的扣法,不再重复说
五、代码文件
- 点击链接获取代码;该文件已隐藏,是非常早期的js,如果需要可私信我,文件不建议直接运行,只是为了记录下思路,随意找了个网站,粗略的扣了js,有些参数并不是很准确,建议按照逻辑自己扣
注意
:validate为空原因主要原因did参数无效
,did无效会导致actoken无效,和fp、轨迹关系不大,或许我没感觉到,b请求可做可不做,未批量测试,只是简单研究友善提醒
:有人扣的js检查很多次觉得没问题,可是怎么运行都不成功,可以关注下你扣的watchman.js的版本即buildVersion
,和d请求的参数v版本
是否一致,不确定是不是这个原因,会导致生成响应的did是无效的,比如watchman.js里面的C.state.options.buildVersion = “2.7.1_cb1402a2”,则d请求参数v值限制是cb1402a2;其次有点人headers可能缺参数也会导致验证失败;还有复制actoken写死或者did写死流程也是失败(大概率原因代码逻辑有问题,先放开js把流程调通了,再去研究js)其它了解
:本次只是讲解了滑块的逻辑思路,图标点选、文字点选思路也是一样的,基本上是98%+成功的