1. VAPTCHA手势验证码逆向分析概述
手势验证码作为一种新型的人机验证机制,通过要求用户按照特定轨迹滑动或点击来区分真实用户与自动化程序。VAPTCHA作为国内主流验证码服务提供商之一,其手势验证码方案在电商、社交等多个领域得到广泛应用。本次分析将深入剖析VAPTCHA手势验证码的三个核心接口交互流程,重点解密其中关键参数en的生成逻辑。
在逆向工程实践中,我们通常需要关注以下几个核心环节:首先是验证码初始化阶段获取的基础参数,这些参数往往作为后续加密的种子值;其次是验证图片的获取与还原机制,这直接关系到验证码的识别难度;最后是验证请求的加密逻辑,这是整个验证流程的最后防线。通过对这些环节的逐层拆解,我们可以建立起完整的验证码行为模型。
特别说明:本文所有分析均基于公开可访问的演示页面,旨在研究验证码技术原理。实际应用中请严格遵守服务提供商的使用条款,禁止用于任何非法用途。
2. 验证码接口交互流程解析
2.1 初始化接口分析
第一个接口(示例URL经Base64编码)返回的JSON数据结构如下所示:
json复制{
"code": 200,
"data": {
"knock": "a1b2c3d4e5",
"secretC": "f6g7h8i9j0",
"globalMd5": "k1l2m3n4o5p6q7r8s9t0"
}
}
这些返回参数在后续加密中扮演重要角色:
knock:用于生成canvas指纹的盐值,通常截取最后5位使用secretC:参与UA加密的固定值,通常截取前10位使用globalMd5:全局MD5值,用于最终校验,通常截取前5位使用
该接口的特点是无需携带复杂参数,但返回的数据却为后续所有加密操作提供了基础素材。在实际逆向过程中,需要特别注意这些参数的传递路径和使用方式。
2.2 验证图片获取接口
第二个接口返回的数据包含验证码图片和关键加密参数en:
json复制{
"img_url": "https://example.com/captcha.jpg",
"img_order": "0123456789",
"en": "abcdef1234567890"
}
en参数的生成是该接口的核心难点,其加密逻辑主要包含以下组件:
- Canvas指纹(带盐和不带盐两个版本)
- 浏览器环境哈希值(带盐和不带盐两个版本)
- 多级MD5校验值
- localStorage相关标记
- UA与主机名组合哈希
这些组件通过特定顺序拼接后,再经过自定义加密函数处理最终生成en参数。在逆向工程中,需要特别注意各组件的生成顺序和依赖关系。
2.3 验证接口分析
第三个验证接口的核心参数仍然是en,但其生成方式在前者基础上增加了用户轨迹数据:
json复制{
"dt": 123456789,
"ch": 50,
"cw": 30,
"v": "x1y1,x2y2,x3y3",
"en": "ghijk1234567890"
}
用户轨迹v参数记录了手势操作的坐标序列,经过标准化处理后与其他参数拼接。验证接口的en参数生成流程与图片获取接口相似,但增加了时间戳、轨迹哈希等动态因素,使得每次请求的加密结果都具有唯一性。
3. 关键参数逆向工程
3.1 en参数生成逻辑拆解
以图片获取接口的en参数为例,其核心生成代码如下(经反混淆处理):
javascript复制function generateEn() {
// 组件1:基础canvas指纹
const fp1 = generateCanvasFP();
// 组件2:带盐canvas指纹
const fp2 = generateCanvasFP(knock.substr(-5));
// 组件3:基础环境哈希
const env1 = generateEnvHash();
// 组件4:带盐环境哈希
const env2 = generateEnvHash(knock.substr(-5));
// 组件5:组合MD5
const md5_1 = hex_md5(fp2 + env2 + fixedValue).substr(0,5);
// 组件6:localStorage标记
const lsFlag = localStorage['ha'] || 'defaultValue';
// 组件7:UA哈希
const uaHash = hex_md5(cleanUA + location.host + secretC.substr(0,10)).substr(0,5);
// 最终拼接
const raw = fp1 + fp2 + env1 + env2 + md5_1 + lsFlag + uaHash + globalMd5.substr(0,5);
return encryFunc(selectFrom(3,15), raw);
}
各组件生成要点说明:
| 组件 | 生成方式 | 依赖项 | 长度 |
|---|---|---|---|
| fp1 | canvas基础指纹 | 无 | 8 |
| fp2 | canvas带盐指纹 | knock后5位 | 8 |
| env1 | 基础环境哈希 | 无 | 8 |
| env2 | 带盐环境哈希 | knock后5位 | 8 |
| md5_1 | 组合MD5 | fp2+env2+固定值 | 5 |
| lsFlag | localStorage标记 | 'ha'键值 | 变长 |
| uaHash | UA组合哈希 | UA+host+secretC | 5 |
| globalMd5 | 全局校验值 | 初始接口返回 | 5 |
3.2 Canvas指纹生成原理
Canvas指纹通过以下步骤生成:
- 创建隐藏的canvas元素
- 绘制特定图形和文本
- 调用toDataURL获取Base64编码
- 对结果进行CRC32校验
关键实现代码:
javascript复制function generateCanvasFP(salt = '') {
const canvas = document.createElement('canvas');
canvas.width = 200; canvas.height = 50;
const ctx = canvas.getContext('2d');
// 绘制基础图形
ctx.fillStyle = 'rgb(128,128,128)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 添加干扰因素
ctx.font = '18px Arial';
ctx.fillStyle = 'white';
ctx.fillText('Canvas FP' + salt, 10, 30);
// 获取指纹
const dataURL = canvas.toDataURL();
return crc32(dataURL).toString(16);
}
在实际逆向中,canvas指纹的复现有两种方案:
- 本地模拟:使用node-canvas库实现,但需要注意字体渲染差异
- 自动化获取:通过Puppeteer等工具实际执行页面脚本
经验提示:不同操作系统和浏览器版本的canvas渲染结果可能存在差异,建议在实际运行环境中获取指纹值。
3.3 环境哈希生成机制
环境哈希通过收集以下浏览器特征生成:
- UserAgent
- 屏幕分辨率
- 时区
- 语言设置
- 插件列表
- WebGL渲染器
- AudioContext指纹
生成流程:
javascript复制function generateEnvHash(salt = '') {
const components = [
navigator.userAgent,
screen.width + 'x' + screen.height,
Intl.DateTimeFormat().resolvedOptions().timeZone,
navigator.languages.join(','),
// 其他环境参数...
salt
];
return hashComponents(components);
}
环境哈希的特点在于:
- 具有设备唯一性
- 对浏览器配置变化敏感
- 带盐版本增加了knock参数作为干扰项
4. 验证码图片还原技术
4.1 图片解密算法分析
图片还原的核心是Decrypt函数,其参数构造如下:
javascript复制const decryptKey = work2Value + canvasFP + parseInt(secretC) + fixedValue;
const imgOrder = Decrypt(originalOrder, decryptKey);
Decrypt函数的典型实现包含以下步骤:
- 将key转换为32位整数数组
- 初始化S-box
- 多轮异或和置换操作
- 生成最终排列顺序
4.2 Python实现图片还原
以下是完整的图片处理实现:
python复制def restore_image(order_str, img_url, output_path):
"""还原验证码图片
:param order_str: 10位数字的图片顺序字符串
:param img_url: 原始图片URL
:param output_path: 输出图片路径
:return: 是否成功
"""
if len(order_str) != 10 or not order_str.isdigit():
raise ValueError("排序字符串必须为10位数字")
# 下载原始图片
resp = requests.get(img_url, headers={'User-Agent': 'Mozilla/5.0'})
src_img = Image.open(BytesIO(resp.content))
# 初始化目标画布
canvas = Image.new('RGB', (290, 167), (255, 255, 255))
# 计算切片尺寸
block_w, block_h = 290//5, 167//2
src_block_w, src_block_h = 400//5, 230//2
for i in range(10):
# 源图切片位置
src_x = (i % 5) * src_block_w
src_y = (i // 5) * src_block_h
# 目标位置
pos = int(order_str[i])
dst_x = (pos % 5) * block_w
dst_y = (pos // 5) * block_h
# 裁剪并缩放
block = src_img.crop((
src_x, src_y,
src_x + src_block_w,
src_y + src_block_h
))
block = block.resize((block_w, block_h), Image.LANCZOS)
# 粘贴到目标位置
canvas.paste(block, (dst_x, dst_y))
canvas.save(output_path)
return True
关键参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
| 原始图片尺寸 | 400×230 | 包含10个切片 |
| 目标画布尺寸 | 290×167 | 最终验证码显示大小 |
| 切片排列 | 5×2 | 横向5块,纵向2块 |
| 插值方法 | LANCZOS | 高质量缩放算法 |
4.3 还原效果优化技巧
在实际操作中,可能会遇到以下问题及解决方案:
-
图片下载失败:
- 添加合理的User-Agent头
- 设置请求超时和重试机制
- 使用代理IP轮询
-
还原顺序错误:
- 检查decryptKey的生成是否准确
- 验证Decrypt函数的实现细节
- 确认原始order_str的获取方式
-
图片质量损失:
- 使用PNG格式保存
- 调整缩放算法参数
- 保持原始宽高比
5. 验证请求构造要点
5.1 轨迹参数生成
验证接口的轨迹参数v需要经过标准化处理:
- 记录原始坐标序列:
[x1,y1, x2,y2, ..., xn,yn] - 转换为相对坐标:
[dx1,dy1, dx2,dy2, ...] - 添加时间戳信息
- 进行归一化处理
典型轨迹加密代码:
javascript复制function normalizeTrajectory(points) {
const normalized = [];
let prev = points[0];
for (let i = 1; i < points.length; i++) {
const curr = points[i];
normalized.push({
dx: curr.x - prev.x,
dy: curr.y - prev.y,
dt: curr.t - prev.t
});
prev = curr;
}
return btoa(JSON.stringify(normalized));
}
5.2 最终en参数构造
验证接口的en参数在前述基础上增加了:
- 时间戳(dt)
- 轨迹校验值(v)
- 验证码版本号
- 其他动态参数
生成逻辑对比:
| 组件 | 图片接口en | 验证接口en |
|---|---|---|
| 基础指纹 | 包含 | 包含 |
| 环境哈希 | 包含 | 包含 |
| MD5校验 | 包含 | 包含 |
| UA哈希 | 包含 | 包含 |
| 轨迹数据 | 无 | 必须包含 |
| 时间戳 | 无 | 必须包含 |
| 动态盐值 | 固定 | 随机生成 |
5.3 自动化验证方案
实现自动化验证需要考虑:
-
环境一致性:
- 保持相同的User-Agent
- 固定Canvas指纹
- 维护环境参数
-
轨迹模拟:
- 使用贝塞尔曲线生成自然轨迹
- 添加合理的时间间隔
- 模拟人类操作误差
-
容错处理:
- 错误重试机制
- 参数动态调整
- 验证结果反馈
6. 常见问题与解决方案
6.1 加密参数不匹配
问题现象:请求被拒绝,返回"参数错误"
排查步骤:
- 检查基础参数(knock/secretC)是否与初始接口一致
- 验证canvas指纹生成逻辑
- 确认环境哈希的组件完整性
- 检查MD5计算的输入顺序
典型错误:
- 漏掉某个加密组件
- 参数拼接顺序错误
- 字符串编码不一致
6.2 图片还原异常
问题现象:还原后的图片错乱或无法识别
解决方案:
- 确认img_order解密正确
- 检查图片切片尺寸计算
- 验证缩放算法参数
- 确保原始图片下载完整
调试技巧:
- 输出中间解密结果
- 保存每个切片位置
- 对比官方示例图片
6.3 验证请求失败
问题现象:轨迹验证不通过
优化方向:
- 轨迹采样率(建议50-100ms间隔)
- 移动速度变化(加入加速度变化)
- 轨迹平滑度(使用曲线拟合)
- 点击精度控制(±3px随机偏移)
实测建议:
- 录制真实用户操作样本
- 分析官方验证策略
- 建立反馈调节机制
7. 技术防护建议
从防御角度考虑,验证码系统可以加强以下方面:
-
动态混淆:
- 每次加载变换JS结构
- 核心函数名动态生成
- 加密逻辑定期轮换
-
行为验证:
- 增加鼠标移动特征分析
- 检测操作时间分布
- 验证轨迹自然度
-
环境检测:
- 强化Canvas指纹变异
- 检测自动化工具特征
- 验证参数时序合理性
在实际开发中,建议采用多层防御策略,将手势验证与其他验证方式(如行为分析、设备指纹等)结合使用,构建更加可靠的人机识别系统。