参考文章:https://mp.weixin.qq.com/s/wpVCVKmEpcXoIi1L4UpsXw

目前对如何绕过滑块绕过的逆向工程进行了学习,并由此对其他的滑块的绕过方式提供了一些思路。测试的网址为网x的滑块demo网站:

https://dun.xxx.com/trial/jigsaw

做滑块认证首先就需要先确定流程链,这里我总结的流程为:

逆向获取dom结构和传输协议 ---> 根据协议获取信息 ---> 识别滑块坐标 ---> 根据坐标移动

当然这套流程有一定的局限性,后面会解释:

协议结构逆向

关于其中的结构,通过逆向js内容,可以获得协议内容是应该三段请求:getconf → get → check,响应都是 __JSONP_xxx({"data":{...},"error":0,"msg":"ok"}); 的包裹格式。

首先是拿配置

{"data":{  
"dt":"/XfN****",// 设备 token 派生相关,脱敏  
"ac":{"enable":1,"bid":"07e2****","pn":"YD2****","token":"9ca1****"},  
"imageServer":["<图片服务器>","<图片服务器-备>"],  
"zoneId":"CN**",  
"resources":["/2.28.5/core-optimi.v2.28.5.min.js"],
"apiServer":["<api服务器>","<api服务器-备>"],  
"theme":"light","audio":true,"customStyles":true,"smart":false  
},"error":0,"msg":"ok"}

getconf返回的apiServer决定后两段的域名,resources决定加载哪份 core,dt初始化设备 token。这个过程后再进行 get

{"data":{
"bg":["https://<图片服务器>/4273****.jpg","https://<图片服务器-备>/4273****.jpg"],
"front":["https://<图片服务器>/2354****.png","https://<图片服务器-备>/2354****.png"],
"token":"5322****631f",
"type":2,"waitTime":300,"zoneId":"CN**"
},"error":0,"msg":"ok"}

通过json我们可以判断type=2 即滑块(1 是智能无感、3 是点选等);bg是带缺口的背景图,front是拼图块;token贯穿 get/check 两段,是本次验证会话的凭证。

然后第三段验证就是再滑动完成后提交轨迹:

{"data":{
"result":true,"zoneId":"CN**","token":"5322****631f",
"validate":"8gxM****(共 172 字符,base64)"
},"error":0,"msg":"ok"}

validata就是前端计算其中的滑动事件来计算完成。

采集点逆向

按下之后才把移动/抬起绑到 document.body,按能力选事件源:

  • 支持 Pointer:pointerdown(滑块) +document.body上的pointermove/pointerup
  • 移动端:touchstart/touchmove/touchend
  • 回退:mouse 系列

2. 每个采集点存什么 移动处理函数里,每来一个 move 就 push 一个点(traceData.push):

[ Math.round(dragX), Math.round(dragY), now() - beginTime, isTrusted ? 1 : 2 ]

立刻用 token 做 XOR:

point = xorEncode(token, "x,y,t,trusted")
traceData.push(point)
  • dragX/dragY = clientX/Y - startX/Y(相对拖动起点),有的组件用 clientX - bgRect.left(相对背景图),不是页面绝对坐标。
  • t 是相对 beginTime 的毫秒数,beginTime 在首次交互时记。
  • isTrusted 是核心反自动化信号:真人/ CDP 注入的鼠标 = 1,JS dispatchEvent = 2。

3. 避障类还多一份轨迹 ballTraceData 额外记录小球圆心坐标 [cx, cy, t, trusted],和 traceData 并行。

4. 提交时怎么打包(onMouseUp)

m   = aes( sample(traceData, 上限).join(':') )
p   = aes( xorEncode(token, 最后一个点) )
ext = aes( xorEncode(token, mouseDownCounts + ',' + traceData.length) )
data = JSON.stringify({ d:'', m, p, ext })

然后这个 data 放进 check 请求。上限就是前面说的:非 IE 100、IE 50、IE<8 30。

DOM 结构 dump

用 page.evaluate 把验证码容器里每个元素的 getBoundingClientRect 打出来,得到几个关键几何量,用于计算:

slider:  w=40  h=38# 可拖的滑块按钮,初始 left=0px
control: w=320 h=40# 轨道
bgimg:   w=320 h=160# 带缺口的背景图(原始尺寸就是 320×160,无缩放)
jigsaw:  w≈61  h=160# 拼图块(transform 初始平移 0)

这一部分用于计算图的内容

OCR计算位置

计算位置有两个办法,一个是传统Pillow来计算边缘,然后通过边缘来匹配,虽然廉价,但最大的问题是精度完全不可用。如果使用第三方 OCR 平台的话

payload = {"base64Image": <b64>, "modelName": "普通模型",  
"keyCode": "D2W2****",          # 脱敏  
"question": "框出正确位置", "system": ""}  
r = requests.post(OCR_URL, json=payload, timeout=60).json()

返回msg计算json自负床,然后计算拖动长度即可。当然同时我们也需要手动拖一次,拿到 ground truth,来看一下人是怎么做的

局限性

还记的之前我们提到的那个问题吗,我们的所有计算都是建立于正常的速度,我们可以通过最初计算的距离来设置滑动长度,如果移动的速度并不是1:1,或者是变速的,再或者是中间转向等等,破局的办法目前只有训练实时的AI滑动模型才有是最优解(不同速度其实也可以传统解,在未来写一下吧)。

拖动

对于拖动,复用网x前端自己的算法是最好的,随意这里使用playwright来进行拖动即可。但此时,我们需要的对抗才刚刚开始。

机器人和人类在滑动验证码的时候有这样的几个区别,人的手是抖动的,拖动的速率是快启动、匀速、慢对齐的过程,在最后还有一定的偏差,所以难点就是去模仿这个东西。

根据我的尝试,总结这几个方法可以完成绕过

  • 最后滑块坐标和标准坐标有>10px的坐标长度,对称"拟人"轨迹(加速-减速 + 3px 回弹 + y±1.5 大抖动)
  • 快启动、中部匀速,慢微调
  • 根据人滑动的流程改造

既然"拟人"是编出来的,把真人 64 个 move 点的位移序列完整采下来,逐点分析。

完整位移序列

我们根据我们之前的自己在playwright的捕获,来获得一下内容

dx: [1,1,2,2,4,4,4,4,4,5,5,5,5,7,5,5,6,4,5,4, 3,2,2,1,1,1,1,1,1,1,2,1,3,1,2,2,1,2,0,1,2,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,0,1,0,0,1,0,1]dt: [46,7,9,8,9,8,8,9,8,8,9,8,8,8,9,8,9,8,8,8,9,8,9,8,8,9,8,8,8,9,8,9,8,8,8,9,8,8,9,8,8,9,8,8,9,8,8,9,8,9,8,8,8,9,9,15,74,8,10,12,5,50,16,178]

然后根据这个进行微改造即可