前两篇写完之后,来找我聊滑块的人里,十个有九个栽在同一个地方:缺口识别得很准,轨迹却一眼假。很多人把注意力全放在“图里有什么”上,忘了极验第四代真正在意的,是“你怎么把它拖过去”。观察极验这些年的更新路线就能发现,第四代已经把对抗重心从图像识别转移到了行为建模——位置答对了不一定及格,动作不规范大概率直接出局。这篇就顺着“滑块轨迹构造”这个主题,把我在研究和复现过程中的思路、数学工具和踩坑经验展开聊聊。老规矩,所有分析仅用于安全研究与学习,请勿用于绕过线上验证码。
1. 为什么“轨迹”成了验证码对抗的核心战场
1.1 从“图里有什么”到“你是怎么动的”
最早的滑块验证码,服务端只关心两件事:你有没有点到滑块、你有没有把滑块拖到目标位置。这种方案最大的缺点是“没有过程信息”:只要脚本最终把距离算出来,直接平移过去,验证码就形同虚设。于是验证码厂商开始在图像本身上做文章:加缺口、加干扰线、加旋转、加各种背景干扰,试图提高图像识别的难度。
但这里有一个无法回避的问题:图像难度越高,正常用户的识别成本也越高。到了极验第三代前后,缺口识别技术已经非常成熟,一个训练好的模型在几百毫秒内就能定位缺口,图像层面的博弈接近天花板。极验第四代的思路转变就在这里——与其继续在图像识别上跟你拼算力,不如回到“操作过程”本身。图片缺口变简单了,但对“这段拖动行为到底是不是人产生的”的判断,反而变得复杂得多。
1.2 极验第四代到底采集了哪些数据
要理解轨迹为什么重要,先得知道验证码服务端在拖动前后能看到什么。从公开的分析资料和抓包数据来看,极验第四代采集的信息大致可以分为下面几类。
| 数据维度 | 具体内容 | 用于判断什么 |
|---|---|---|
| 事件序列 | mousedown、mousemove、mouseup 的完整生命周期 | 操作流程是否完整、顺序是否自然 |
| 坐标数据 | 每个移动事件对应的 x、y 坐标 | 轨迹形状、起点偏移、终点容差 |
| 时间戳 | 每两个事件之间的时间间隔 | 是否存在均匀的机器节律 |
| 派生特征 | 速度、加速度、曲率变化 | 是否符合人类肌肉运动规律 |
| 环境特征 | 浏览器指纹、Canvas、WebGL、UA 等 | 是否来自真实浏览器和可信设备 |
注意,坐标和时间戳本身只是“原料”,服务端真正计算的是从这些原料中推导出的二阶甚至更高阶特征。比如轨迹的总长度与直线位移之比、加速度方差、速度曲线的形状、事件间隔的信息熵等。这些特征在生物识别和人机对抗领域都有对应的研究基础,单独看每一个都不致命,合在一起就形成了一个相当高的判断维度。
1.3 服务端打分的三个维度
从行为验证的整体逻辑来看,极验这类服务端通常不会靠单一维度做硬性拦截,而是打一个综合分。我总结为三个维度:
- 结果是否正确:滑块最终停的位置是否落在缺口区域内,允许一定像素的误差。
- 过程是否自然:整段轨迹的时长、速度形态、抖动幅度、端点偏差是否在人类操作的可信区间内。
- 环境是否可信:浏览器指纹、设备参数、网络特征是否像一台真实设备发出的请求。
这三个维度是并行评估的。结果正确只是门槛,过程不自然或环境可疑,同样会被打低分。这也是为什么很多人明明缺口位置算得很准,却总被弹“再试一次”的原因。
2. 先看懂人类真实轨迹,再谈构造
2.1 人类轨迹的三大典型特征
构造轨迹之前,我建议你先做一件事:自己用鼠标真实拖十次滑块验证码,每次都用浏览器开发者工具把 mousemove 事件打印出来。只看数据,你会立刻发现人类轨迹有几个共性。
第一,时间尺度。一次完整的滑块拖动通常在 300ms 到 1500ms 之间。很短的距离也不会低于 300ms,因为人有“按下-瞄准-移动-松开”的完整动作链;距离很长也不会拖到 2 秒以上,因为那会显得反应迟钝。这个时间区间和距离不是严格线性关系,距离每增加一倍,时间可能只增加 0.3~0.5 倍。
第二,速度形态。人类拖动的速度曲线不是一条直线,也不是完美对称的钟形曲线,而是“快速上升-峰值波动-快速下降”的形态。接近目标时,速度会明显下降,并且在终点附近常有一次微小的反向修正。这个“末端定位”过程是肌肉控制和视觉反馈共同作用的结果,也是最难用简单算法模仿的部分。
第三,空间抖动。人手在移动鼠标时会有生理性微颤,反映在轨迹上就是 1~3 像素的上下波动。轨迹在 y 轴方向几乎不可能保持一条严格水平的直线,它更像一条带有随机噪声的平滑曲线。此外,鼠标按下时指针不一定在滑块正中心,往往偏了几个像素;松手时滑块也不一定完美停在缺口中心,甚至可能出现“过冲一点再拉回来”的细微动作。
还有一个容易被忽略的细节:真人按下鼠标之后,不会立刻以最大速度开始拖动。手指刚使劲的时候,滑块往往会先有一个极其微小的位移,像是“试了试手感”,然后才进入快速移动阶段。松手之前也是一样,滑块停在终点附近时,常常会有一次 1~3 像素的往复。这个“按下-试探-快移-减速-微调-松开”的生命周期,比简单的直线运动复杂得多,但也正是这些细节构成了人类操作的特征。
2.2 为什么纯算法生成的轨迹会一眼穿帮
很多人最初写轨迹生成,思路非常简单:先算出起点到目标缺口的横向距离,然后把距离平均切分成 N 份,每隔固定时间移动一份。这种轨迹的问题,用一个词概括就是“太干净”。
| 特征维度 | 真人轨迹 | 简单脚本轨迹 |
|---|---|---|
| 速度变化 | 明显加速、减速、末端微调 | 匀速或线性变化 |
| 帧间位移 | 不均匀,存在随机波动 | 固定值或固定增量 |
| y 轴抖动 | 1~3 像素随机波动 | 几乎为 0 |
| 端点位置 | 存在偏差和轻微回拉 | 精确对准 |
| 事件时间间隔 | 8~20ms 不规则波动 | 完全等间隔 |
把两者放在同一张坐标图里,肉眼都能分辨。验证码服务端的判定器更不用说了,它算出的统计特征会直接落在两个完全不同的分布区间。我之前做过最原始的“等分平移”方式验证缺口识别结果,十次里有八次被弹回;换成带速度变化和抖动的轨迹后,通过率才明显上升。
2.3 从识别器视角看:它在找什么
理解构造的另一个角度,是站在验证码服务端识别器的位置上,看它到底在找什么。学术界和工业界对人机行为判定的研究已经很成熟,核心思想可以简化成:判断“这段轨迹是由生物运动产生的,还是由程序计算产生的”。
识别器会计算大量统计特征,举几个典型的例子。轨迹曲折程度,即轨迹实际长度与首尾直线距离的比值,人类手指的轨迹通常有一定冗余,比值会明显大于 1,而纯直线逼近则接近 1。加速度方差,人类运动的加速度波动大,方差高,匀速或匀加速模型的方差极低。时间间隔熵,人类事件间隔不均匀,信息熵较高,机器定时器的间隔则低得可疑。还有速度曲线的峰度和偏度,人类速度曲线通常有右偏和尖峰,均匀曲线的峰度偏度则会落在不同区间。
这些特征没有一个能单独作为“最后一击”,但组合起来,统计模型就能以极高的置信度把程序生成的轨迹挑出来。所以,光“看起来差不多”是不够的,要让关键统计量都落在人类分布的可信区间内。
3. 轨迹构造背后的数学与物理模型
3.1 用贝塞尔曲线打底:路径的优雅表达
现在进入构造环节。第一步是生成一条合理的路径。直线插值当然不行,这里我习惯用贝塞尔曲线作为路径骨架。贝塞尔曲线的核心思想是:用少量控制点定义一条平滑曲线。二次贝塞尔曲线只需要 3 个点,公式是:
B(t) = (1-t)²P₀ + 2t(1-t)P₁ + t²P₂,t ∈ [0, 1]
三次贝塞尔曲线需要 4 个点,公式是:
B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
其中 P0 是起点,也就是鼠标按下时滑块所在的位置;P3 是终点,也就是目标缺口附近的位置;P1、P2 是控制点。控制点越偏离直线,曲线弯曲程度越明显。比如,把 P1 略微向上偏、P2 略微向下偏,轨迹就会呈现一个自然的纵向起伏,而不是一把尺子。
一个最小可用的实现是这样的。
import numpy as np def cubic_bezier(p0, p1, p2, p3, n=60): t = np.linspace(0, 1, n) return ( (1 - t)**3 * p0 + 3 * (1 - t)**2 * t * p1 + 3 * (1 - t) * t**2 * p2 + t**3 * p3 )要注意,这里返回的是从起点到终点的几何路径,还没有时间概念。下一步的关键,是决定“什么时候”走到哪个点。
3.2 多段速度模型:让“什么时候到哪”符合直觉
如果直接把贝塞尔曲线函数里的 t 均匀取 60 个点,再按顺序输出坐标,得到的仍然是一条匀速运动轨迹。因此,必须把“时间”和“路径”分开处理。
我的做法是显式构造一个速度剖面。把整个拖动过程分为三段:启动段,速度从 0 快速上升,加速度较大;巡航段,速度在峰值附近小范围波动,模拟手指或鼠标的不稳定控制;减速段,速度明显下降,接近终点时可能出现一次轻微回拉。
假设总时长是 T,在每一小段里先生成瞬时速度,再对速度做积分,得到位移序列,最后把位移叠加到起点上,就得到了每个时刻的位置。下面是一个概念性的伪代码框架。
def velocity_sequence(duration, peak_velocity): # 三段式速度剖面 # 启动段:线性上升 # 巡航段:峰值 + 随机波动 # 减速段:指数下降 pass def track_from_velocity(v_list): # 对速度积分得到位移 # 位移累加 + 起点 pass为什么用积分而不是直接分段?因为位移序列的每一阶连续性都会影响速度曲线、加速度曲线,进而影响识别器提取的二阶特征。用积分构造,可以保证速度、加速度的统计特性是自洽的。真人手部运动有一个特点:位置是平滑的,速度是波动的,加速度是“粗糙”的。如果你生成的位置序列很平滑但速度序列“假滑”,就很容易被二阶特征识别出来。
使用梯形速度曲线时,启动段和减速段各占多少比例,需要根据距离动态调整。距离短(50px 以内),启动段占比可以高一些,因为人的“启动响应”时间相对固定;距离长(200px 以上),巡航段占比会明显增加,因为中段人基本处于匀速拖动状态。峰值速度则大致与距离成正比,但不会超过一个上限——没有人能用 50px 和 500px 的拖动达到同样的峰值速度。
3.3 噪声注入:去“完美化”是关键
纯贝塞尔曲线加理想速度模型生成的轨迹,最大的问题是“太顺滑”。我做过一个对比实验:一条精心生成的无噪声轨迹,和一条带适度噪声的轨迹,后者在自建分类器里的“人类概率”反而更高。原因不复杂,人体运动系统本身就有大量噪声,完全没有抖动本身就是一种机器特征。
噪声注入主要在三个层面做。空间噪声:在 x、y 坐标上加入高斯噪声,模拟手部微颤,幅度通常控制在 0.5~3px。时间噪声:给事件时间戳加上随机抖动,而不是精确等间隔。速度噪声:在巡航段的峰值速度上叠加小幅随机波动,模拟肌肉控制的不稳定。
# 空间噪声示例:幅度可视距离调整 noise_x = np.random.normal(0, 1.2, n) noise_y = np.random.normal(0, 1.0, n) track[:, 0] += noise_x track[:, 1] += noise_y这里有个经验值:如果总距离在 200px 以上,x 方向噪声幅度可以给到 1.5~2px,y 方向 1px 左右;如果总距离只有几十像素,噪声幅度要相应减小,否则轨迹会显得“太碎”。
3.4 一条完整轨迹的组装思路
把前面的模块串起来,一个完整的轨迹生成流程大致是这样:
- 根据起点(滑块按下位置)和终点(缺口位置),选择一组控制点,生成三次贝塞尔路径。
- 确定总时长,并按“启动-巡航-减速”三段生成速度序列。
- 对速度序列积分,得到每个时刻对应的路径参数,再从贝塞尔路径上重采样坐标。
- 对坐标和时间戳分别注入随机噪声。
- 在末端追加一个可选的“微调段”,模拟人类过冲后回拉的动作。
这个流程指向的是通用的轨迹生成思想,不是某个验证码的专用对抗脚本。参数怎么定、要不要加回拉、噪声幅度给多大,都要根据你实际测试的环境反复调整——这也是它无法被“一键复制”的原因。
4. 实操中的参数细节与常见翻车现场
4.1 参数要符合“人的分布”,不要均匀随机
这是我在实际操作中最想强调的一点:随机性不等于均匀分布。
很多初学者会用 random.uniform 生成各种参数,比如“每次拖动时长在 600~1000ms 之间随机选”。这在数学上恰恰是最不像人的分布之一。真实人的反应时间、动作时长更多接近正态分布或对数正态分布:存在一个中心值,大量样本集中在中心附近,两侧是逐渐衰减的长尾。而且,同一个人的多次操作之间存在一定相关性,比如连续操作几次之后,速度会略微降低,终点偏差会略微变大——这是疲劳和习惯导致的。
所以,参数设计阶段就要有“分布意识”。时长、峰值速度、噪声幅度都尽量用正态分布采样,而不是均匀分布。不同参数之间的相关性也值得模拟,比如距离越远,总时长越长,但并非线性;距离越短,末端微调出现的概率越高。
# 用正态分布采样时长,而非均匀分布 duration = np.random.normal(loc=800, scale=120) # 均值800ms duration = np.clip(duration, 400, 1600)4.2 容易被忽略的三个细节:端点、抖动、时间戳
第一,按下点的位置。真实的鼠标按下位置通常在滑块区域的左半部分,而且每次会有一点偏移,不会每次都精确落在滑块正中心。如果每次都从同一个坐标按下,识别器一眼就能发现规律。
第二,终点容差。人类松手的位置很少精确等于缺口中心,通常有 1~3 像素的偏差。这个偏差是正常误差,不需要修正。反而你每次都能“正中靶心”,就成了异常特征。比较合理的做法是生成一个以缺口中心为均值、标准差约 1 像素的正态偏差。
第三,时间戳间隔。浏览器 mousemove 事件的触发频率受设备和浏览器影响,通常是 8~20ms 一个事件。简单脚本用 setTimeout 固定 10ms 生成事件,间隔过于均匀。更好做法是在 8~20ms 之间按正态分布加入抖动,同时避免出现过于极端的时间间隔。触摸屏设备的事件间隔可能更稀疏,会到 16ms 或 33ms,和屏幕刷新率有关。
4.3 如何快速自查轨迹质量
构造完轨迹后,不要急着拿去做验证,先做几件不需要对方服务器配合的自查。
把轨迹画出来。用 matplotlib 或任何绘图库画出 (x, y) 路径,看它是否出现“人工感”很强的平滑长弧。真人的轨迹在放大后应该是“光滑但带微抖”,而不是一条没有任何毛刺的完美曲线。
画出速度-时间图。正常人类拖动的速度曲线应该呈现“快速上升-波动-快速下降”的形态。如果速度是一条平滑抛物线或一条直线,说明时间模型还有问题。
对比真实拖动。把你自己手动拖十次的轨迹数据和生成的轨迹放在同一坐标系里对比,重点看速度分布、端点偏差、时长区间。这种直观对比能帮你快速找出参数不合理的地方。如果生成轨迹的时长、抖动、偏差都在你自己真实操作分布范围之内,质量基本就过关了。
5. 攻防演化与研究的边界
5.1 从缺口识别到行为模型:极验的演进逻辑
回头看一下验证码攻防的演化,其实是一个不断升维的过程。最早是“图像难度”对抗,破解方用模板匹配、深度学习识别缺口,厂商不断增加图像复杂度;图像层面拼到一定程度后,厂商发现单纯增加难度会让正常用户体验严重下降,于是转向行为特征;破解方跟着转向轨迹模拟,从最早的匀速拖动,到贝塞尔曲线,再到带噪声的多段速度模型。
这个循环还在继续。极验第四代的环境指纹、设备可信度、时序异常检测,说明对抗已经不只是“轨迹像不像人”这么简单,而是整个请求环境是否可信的问题。单靠一段仿真轨迹远远不够,这也是为什么我始终不建议把它当成一个纯“本地算法题”来研究。
5.2 研究安全性的正确姿势
最后说点实际的边界问题。研究这类机制和写一个“可用脚本”之间,有一条不能越过的线。
只对你有权测试的系统做验证码机制研究,比如你自己的网站、公司的测试环境、或拿到授权的渗透测试项目。不要用这类技术去绕过公开网站的验证码;不要用批量注册、批量数据采集、刷单、抢优惠券等目的,这不仅违反平台服务条款,还可能触及法律红线。圈子里因为写脚本爬数据、绕过验证码栽跟头的案例,一只手数不过来。厂商的设计缺陷,正确的处理方式是走漏洞报告渠道,而不是公开“破解教程”。
我在实际研究中的体会是,把重点放在“理解验证码如何判断人和机器”这件事上,收获远比“跑通一个脚本”大得多。理解了行为特征的原理,你也能反过来帮自己的网站设计更合理的风控策略,这才是更持久的能力。