
声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
前言
近期 vaptcha 验证码迎来了一次重大迭代,正式推出全新的 V4 版本。相较于迭代稳定、逻辑固化的旧版,V4 彻底重构了整套验证架构与算法流程,其将行为轨迹、生物特征、设备指纹、POW 与 AI 风控模型整合到统一验证链路。V4 版本由 AI 动态生成手势背景图,叠加点状、线状噪声(水彩溅点、墨点、火花、星点等等)、诱导线,轨迹同源伪装、软边发光、羽化,进一步提升了识别路径的复杂度。本文将对新版本进行相关分析:

逆向目标
- 目标:Vaptcha V4 手势验证码
- 网址:
aHR0cHM6Ly93d3cudmFwdGNoYS5jb20v
抓包分析
本文分析官网 demo,已经有站点用上 V4 版本了,与官网的流程略有差别,仅供参考,只作学习研究。
之前 K 哥也写过 vaptcha 的分析文章,感兴趣的小伙伴可以去回顾下,对比看看有哪些改动:
V4 版本的侧重点更多的放在了图像识别的复杂度上,不像之前重风控,算法中各种环境参数、IP 相关联。当然,算法风控层面的难度是降低了,但是想要训练出一个成功率高的识别模型,也是较为困难的。
言归正传,现在来具体分析下该验证码。
老方案,先打开开发者工具抓包(避免漏了关键接口),再进入网页,点击右侧的 体验 VAPTCHA 即可触发验证码,未触发就是 IP 被风控了。
需要先从 /api/v1/config 接口拿到响应 cookies,该接口的请求参数和响应内容都经过 base64 编码:

可以去 K 哥的工具站,解码看看都是些什么:

Payload 中的几个非空参数,根据该接口上的 /v4j 接口可知,分别对应:
- vid:盾 id,固定值,各站点不同;
- sdkv:版本号;
- tag:动态值,后文分析;
- _t:13 位时间戳。
/api/v1/knock 接口返回了一些关键参数,后续会用到,请求参数 d 中则是一大堆环境相关的值:

其中 trajectory 为验证码背景图片的链接,和之前一样,还是经过乱序处理,需要还原,后文分析:

/api/v1/validate 即校验接口,请求中包含轨迹及一些环境指纹参数,和之前一样,若校验通过,会响应返回 token 值:

逆向分析
先来看看 /api/v1/config 接口的请求参数,从堆栈处跟到 v4.js 文件中,ctrl + f 搜索 "4.0.0",定位到了唯一的位置:

几个非空参数依次为 vid、sdkv、tag 和 _t,除了时间戳外,tag 参数也是动态变化,搜索定位,下图红框处即生成算法,生成了一个 32 位、小写的随机十六进制字符串,可直接用 python 复现:

python
import secrets
def random_hex():
return secrets.token_hex(16)
print(random_hex()) # 输出示例 -> d517feb8d8a8456a043197a5ce353d54
/api/v1/knock 接口主要就是一堆环境参数,跟栈下断后,即可跟到下图位置,可以自行逐一分析下,tag 和 knock 值需要与上一个接口一致,否则会报错 {"code":12,"msg":"config_required"}:

该接口响应返回的参数都与验证码背景图片相关,直接拿到的背景图链接 trajectory 是乱序的,需要还原。
根据以往的经验,验证码图片大概率是通过 canvas 绘制的,下个 canvas 断点尝试下,重新触发图片,发现断到了 verify.html 文件中:

经过分析,背景图片被分成了 2 行 5 列,主要的还原逻辑如下:
JavaScript
preparePow()
// 1. 准备计算 PoW。
// 只有当前还没有 orderStr,并且 boot 里有 result / salt 时才继续。
// orderStr 是图片还原顺序,有了它才能拼回 trajectory 图片。
-> solvePow(...)
// 2. 调用 window.VaptchaPow.solve(data)。
// 核心算法在 pow.js 文件中。
// 输入 pow_start、result、salt。
// 它会计算出 PoW 结果,其中包含 powInfo.order。
// order 就是乱序图片的还原顺序。
-> 得到 powInfo.order
// 3. powInfo.order 一般是 0-9 的排列。
// 例如 "3142098765"。
// 因为图片被切成 5 x 2,一共 10 块。
-> orderStr = String(powInfo.order)
// 4. 把 order 转成字符串保存到全局变量 orderStr。
// 后面还原图片和提交验证都会用到它。
-> applySolvedOrder()
// 5. 开始应用已经算出来的顺序。
// 停止加载阶段的随机打乱动画。
// 然后调用 renderRestoredBackground() 还原背景图。
-> renderRestoredBackground(currentLoadUrl, orderStr)
// 6. 根据 currentLoadUrl 和 orderStr 创建 10 个 div 切片。
// 每个 div 都用同一张 trajectory 图片作为 background-image。
// 通过 background-position 显示不同切片区域。
-> getRestorePositions(orderStr)
// 7. 把 orderStr 转成每个原始块对应的乱序位置。
// 例如 originalIdx=0 时,查 order.indexOf(0),找到第 0 块现在在哪。
// 返回形如 [{col, row}, ...] 的位置数组。
-> 显示还原后的图片
// 8. renderRestoredBackground 按原始顺序 append 10 个 div。
// div 内容来自乱序图中的对应位置。
// 最终 CSS grid 显示成完整还原图。

Python 复现如下,完整算法已分享到知识星球中:
python
def restore_trajectory_image(image, order_str):
cols, rows = 5, 2
order = [int(x) for x in str(order_str)]
w, h = image.size
piece_w = w // cols
piece_h = h // rows
restored = Image.new("RGB", (w, h))
for original_idx in range(cols * rows):
shuffled_idx = order.index(original_idx)
src_col = shuffled_idx % cols
src_row = shuffled_idx // cols
dst_col = original_idx % cols
dst_row = original_idx // cols
box = (
src_col * piece_w,
src_row * piece_h,
(src_col + 1) * piece_w,
(src_row + 1) * piece_h,
)
piece = image.crop(box)
restored.paste(piece, (dst_col * piece_w, dst_row * piece_h))
return restored

最终的验证接口 /api/v1/validate 和前文提到的 /api/v1/knock 一样,也是一些环境参数(上量存在风控、一致性校验,参数不对,轨迹正确也会验证失败),主要多了加密后的轨迹和乱序还原顺序:

环境及轨迹加密位置,下断后(可以先通过 canvas 断点断进去)简单跟栈即可定位到:

JavaScript
function encodeTrajectory(pts) {
if (!pts || pts.length < 2)
return '';
var result = '';
for (var i = 0; i < pts.length; i++) {
var p = pts[i];
result += Math.max(0, Math.min(65535, Math.round(p.x))).toString(16).padStart(4, '0').toUpperCase();
result += Math.max(0, Math.min(65535, Math.round(p.y))).toString(16).padStart(4, '0').toUpperCase();
if (i === 0)
result += '00000';
else
result += Math.max(0, Math.min(1048575, Math.round(p.t - pts[i - 1].t))).toString(16).padStart(5, '0').toUpperCase()
}
return result
}
相关算法已分享到知识星球中,仅供学习交流。
结果验证
