阿里V2验证码 逆向分析报告
阅读须知
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号联系作者立即删除
一、概述
阿里V2验证码 是一套基于设备指纹 + 行为分析的风控验证系统。本报告详细分析了其前端验证流程、加密机制和数据结构。
二、核心组件
2.1 加载的脚本
AliyunCaptcha.js | |
sg.xxx.js | |
feilin.xxx.js |
2.2 关键全局对象
window.AliyunCaptchaConfig// 配置 (region, prefix)window.__ALIYUN_CRYPT// 加密库 (AES, MD5, SHA1, HMAC等)window.__ALIYUN_CAPTCHA_UTILS// 工具函数window.FEILIN// 指纹收集window.captcha// 验证码实例三、完整请求流程
3.1 初始化阶段 (页面加载)
┌──────────────────────────────────────────────────────────────────┐│ 1. Log1 请求 ││ URL: cloudauth-device-dualstack.cn-shanghai.aliyuncs.com ││ Action: Log1 ││ Data: 初始设备指纹 (加密) ││ 响应: DeviceConfig (含加密密钥) │├──────────────────────────────────────────────────────────────────┤│ 2. InitCaptchaV3 请求 ││ URL: {prefix}.captcha-open.aliyuncs.com ││ Action: InitCaptchaV3 ││ 参数: SceneId, Language, Mode, DeviceData ││ 响应: CertifyId, CaptchaType, DeviceConfig, StaticPath │├──────────────────────────────────────────────────────────────────┤│ 3. UploadLog 请求 ││ Action: UploadLog ││ 上传初始化日志 │├──────────────────────────────────────────────────────────────────┤│ 4. Log2 请求 ││ Action: Log2 ││ Data: 详细设备指纹 (加密) │└──────────────────────────────────────────────────────────────────┘3.2 验证阶段 (用户交互)
┌──────────────────────────────────────────────────────────────────┐│ 1. 用户点击登录按钮 ││ → 弹出滑块验证码 ││ → 开始收集鼠标移动轨迹 │├──────────────────────────────────────────────────────────────────┤│ 2. 用户完成滑动 ││ → 停止轨迹收集 ││ → AES加密轨迹数据 ││ → AES加密环境数据 ││ → 生成 deviceToken │├──────────────────────────────────────────────────────────────────┤│ 3. Log3 请求 ││ URL: cloudauth-device-dualstack.cn-shanghai.aliyuncs.com ││ Action: Log3 ││ Data: 完整设备指纹 + 行为数据 (加密) │├──────────────────────────────────────────────────────────────────┤│ 4. VerifyCaptchaV3 请求 ││ URL: {prefix}.captcha-open.aliyuncs.com ││ Action: VerifyCaptchaV3 ││ 参数: SceneId, CertifyId, CaptchaVerifyParam ││ 响应: securityToken, VerifyCode, VerifyResult │└──────────────────────────────────────────────────────────────────┘四、加密机制
4.1 AES 加密参数
基本参数:
• 算法: AES-128-CBC • IV: 012***DEF(固定值, Hex:303***546)• Padding: Pkcs7
密钥列表:
c17***2e2 | ||
45f***397 | ||
FqJ***pwb | ||
a69***919 | ||
a54***aa0 |
4.2 密钥解密体系
主解密密钥:
ACCESS_SEC = "FqJ***pwb"IV = "012***DEF"算法 = AES-128-CBCSDK内置密钥解密表:
k+1RW0***6Gas= | c17***2e2 | ||
8KmHIQs***xss0= | 45f***397 | ||
9NhnQQ***CgrM= | 87f***da7 | ||
+fR9tYzlKFr***bJiMfA= | a54***aa0 | ||
xLLw/***3ds42B0= | 75a***802 | ||
tBwmiXX***mDCbo= | LTAI***w72p | ||
9U6Kg***MVBg= | fpOK***IRcX | ||
NLAoq***zA== | daye,raolewoba! | ||
X1y5Vstb***a0A== | 8449449787 |
解密流程:
from Crypto.Cipher import AESimport base64defdecrypt_config(encrypted_b64): key = b'FqJ***pwb' iv = b'012***DEF' cipher = AES.new(key, AES.MODE_CBC, iv) data = base64.b64decode(encrypted_b64) decrypted = cipher.decrypt(data)# 去除 PKCS7 填充 padding_len = decrypted[-1]return decrypted[:-padding_len].decode('utf-8')4.3 DeviceConfig 解密
解密密钥: WEB_AES_SECRET_KEY.RES = 87f***da7
解密后格式 (# 分隔):
Base64(key)#Base64(switch)#sessionId#version#plugin1#plugin2#plugin3#timestamp#ip示例:
解密后: YTY5OGQ1NmU4ZjIyMzkxOQ==#MQ==#ab0***ae1-h-...#1.3.3/feilin077...####1768709118474#113.83.154.89字段解析:- key: Base64解码后 = a69***919 (后续 AES 加密密钥)- switch: 1- sessionId: ab0***ae1-h-timestamp-fingerprint- version: SDK版本- timestamp: 时间戳- ip: 客户端 IP4.3 请求签名
签名算法: HMAC-SHA1 (Base64编码输出)
签名字符串构建:
HTTPMethod + "&" + urlencode("/") + "&" + urlencode(sortedParams)sortedParams 构建规则:
1. 将所有请求参数 (除 Signature 外) 按参数名 ASCII 升序排列 2. 将每个参数的 key=value 进行 URL 编码 3. 用 & 连接所有参数
4.4 API 密钥
LTAI***xmvTd | YSKfs***F9r89koz | |
LTAI5***GQw72p | fpOKzILEa***gIRcX |
Captcha API ({prefix}.captcha-open.aliyuncs.com):
• 用于: InitCaptchaV3, UploadLog, VerifyCaptchaV3
Device API (cloudauth-device-dualstack.cn-shanghai.aliyuncs.com):
• 用于: Log1, Log2, Log3
重要: 签名时密钥末尾需要加 &,即 AccessKeySecret + "&"
五、数据结构
5.1 CaptchaVerifyParam
{"sceneId":"1wpbfo15","certifyId":"2xUrMCZFaL","deviceToken":"V0VCI2FiMDM0ZWMw...Base64...","data":"JRMnX3A3XiIh...AES加密的轨迹数据..."}5.2 deviceToken 结构
Base64解码后格式:WEB#[sessionID]#[AES加密的设备数据]#[版本号]#[MD5校验]示例:WEB#ab0***ae1-h-1768707464440-8e6***052#OsKhPZ+K63...#163#ad1***12c字段说明:- WEB: 客户端类型标识- sessionID: appKey-h-timestamp-fingerprint- 加密数据: AES加密的设备指纹- 版本号: 数据版本- MD5校验: 数据完整性校验5.3 鼠标轨迹数据 (data 字段解密后)
{"mousemove":[{"x":131,"y":596,"t":82},{"x":149,"y":534,"t":133},{"x":179,"y":494,"t":163}, ...]}5.4 环境数据格式 (99个字段,#分隔)
W.10050#####Win32#Chrome#143.0.0.0#...#fingerprint#...#Windows#10#...#IP#...#screen#...关键字段索引:
5.5 检测标志解码
Base64: MCMwIzAjMCMwIzAjMCMwIzAjMSMwIzAjMCMwIzAjMCMwIzAjMCMwIzEjMCMxMTExMTExMDExMTExMTExMTExMTExMTExMQ==解码后: 0#0#0#0#0#0#0#0#0#1#0#0#0#0#0#0#0#0#0#0#1#0#11111110111111111111111111每个标志位表示一种环境检测结果 (0=正常, 1=异常)六、验证响应
6.1 成功响应
{"RequestId":"7D44D70E-97A5-409A-879C-B103CE10A56A","Message":"success","HttpStatusCode":200,"Code":"Success","Success":true,"Result":{"securityToken":"6oOo7e72nA61uVLiZVKiLfBvot4lJ8pCirs9CNqYOlcDrTP1+FXxhlBexe0GIX8N...","VerifyCode":"T001","VerifyResult":true,"certifyId":"2xUrMCZFaL"}}6.2 success 回调数据
{"certifyId":"2xUrMCZFaL","sceneId":"1wpbfo15","isSign":true,"securityToken":"6oOo7e72nA61uVLiZVKiLfBvot4lJ8pCirs9CNqYOlcDrTP1..."}七、请求参数生成
7.1 Log1 Data 生成
Step 1: 加密初始化信息 明文: "W.10001.c#saf-captcha##captcha-front#coooqf#cn" 格式: version#appName#sceneId#mode#prefix#region 密钥: FLAG (c17***2e2)Step 2: 组装并加密 明文: "appKey#W#Step1密文#W20220202#CLOUD#" 密钥: REQ (45f***397)7.2 InitCaptchaV3 DeviceData 生成
与 Log1 相同格式,但 sceneId 有值,mode 为 "captcha-normal"
7.3 SessionId 生成
格式: appKey-h-timestamp-fingerprint示例: ab0***ae1-h-1768709118471-93f***625fingerprint: UUID v4 (去除连字符)Python: uuid.uuid4().hex7.4 Log2/Log3 Data 生成
外层 (用 UPLOAD 密钥 a54***aa0 加密): appKey#W#encEnvData#W20220202#CLOUD#num1#num2#base64Database64Data 解码后: sessionId#encryptedEnvData#field3#field4#field5#field6encryptedEnvData (用动态密钥加密): 54字段环境数据,#分隔7.5 deviceToken 生成
Base64编码前格式: WEB#sessionId#encryptedData#version#md5Checksum示例: WEB#ab034ec...#2qqVQzPgNt/58kND...#209#40d***ca27.6 环境数据字段 (54个)
八、VerifyCaptchaV3 data 字段完整分析 (✅ 已解决)
8.1 data 字段生成流程
data 字段使用 VM 虚拟机加密,不是标准 AES。完整流程如下:
步骤1: 生成 TrackList(鼠标轨迹数据) │ ▼步骤2: encryptTrackData(TrackListJSON) → trackId (32位hex) │ 使用 VM 函数: j(0, [], F, G, {r:1}, [json, "0000"]) ▼步骤3: 构建 trackData = {TrackList, TrackStartTime, VerifyTime, arg} │ ▼步骤4: originalData = trackId + JSON.stringify(trackData) │ ▼步骤5: compressed = pako.deflate(originalData) │ ▼步骤6: o0 = Base64.encode(compressed) │ ▼步骤7: data = vmEncrypt(o0, encryptKey) │ 使用 VM 函数: j(0, [], L, D, {r:1}, [o0, key]) ▼最终: data 字段 (Base64编码, ~1580字符)8.2 TrackList 结构
const trackList = {"mc": "x,y,time, ,flag|...", // 鼠标点击 (mouseclick)"tc": "", // 触摸点击 (touchclick)"mu": "x,y,time, ,flag|...", // 鼠标释放 (mouseup)"te": "", // 触摸结束 (touchend)"mp": "x,y,time,flag|...", // 鼠标移动 (mousemove) - 主要轨迹"tmv": "", // 触摸移动 (touchmove)"mm": "x,y,time,flag|...", // 鼠标滑动阶段轨迹"ks": "", // 键盘事件"fi": "time,type,flag|...", // 焦点事件"startTime": 1768715528000, // 轨迹开始时间戳"si": "1960,3440,1083,..."// 屏幕信息}8.3 trackId 生成
const trackListForId = {"TrackList": trackList,"TrackStartTime": trackStartTime,"VerifyTime": verifyTime};// VM 函数 j 使用 F/G 字节码生成 32 位 hex 字符串const trackId = j(0, [], F, G, {r:1}, [JSON.stringify(trackListForId), "0000"]);// 返回示例: "a1b***f01"8.4 VM 字节码位置 (sg.js)
8.5 VM 加密密钥
const vmEncryptKey = '3e6***913'; // 固定 16 字符密钥8.6 VM 操作码 (部分)
h[l] | ||
h + l | ||
h % l | ||
++x | ||
h & l | ||
h | l |
8.7 Canvas 指纹 MD5 生成逻辑
环境数据第 19 字段,示例值:b8c***7c9
生成流程:
// 1. 创建 Canvas (240x60)var canvas = document.createElement('canvas');canvas.width = 240;canvas.height = 60;var ctx = canvas.getContext('2d');// 2. 绘制文本1 - Times New Romanctx.font = '14.6667px "Times New Roman"';ctx.fillStyle = '#006699';ctx.fillText('Cwm fjordbank gly ?', 2, 15);// 3. 绘制文本2 - Arial 半透明ctx.font = '24px Arial';ctx.fillStyle = 'rgba(102, 204, 0, 0.2)';ctx.fillText('Cwm fjordbank gly ?', 4, 45);// 4. 绘制矩形ctx.fillStyle = '#ff6600';ctx.fillRect(100, 1, 62, 20);// 5. 获取 dataURL 并计算 MD5var dataUrl = canvas.toDataURL();var canvasMd5 = MD5(dataUrl); // 实际输入是 RGB 颜色数组 JSON实际输入:系统 CSS 颜色值数组的 JSON(约 799 字符)
["rgb(0, 0, 0)","rgb(0, 0, 0)","rgb(0, 102, 204)",...]8.8 设备指纹 MD5 生成逻辑
环境数据第 32 字段,示例值:862***472
生成流程:
// 1. 创建 WebGL Canvas (122x110)var canvas = document.createElement('canvas');canvas.width = 122;canvas.height = 110;var gl = canvas.getContext('webgl');// 2. 绘制 WebGL 图形并检测 winding 规则var winding = ctx.isPointInPath(6, 6, 'evenodd') === false;// 3. 获取 Canvas 图像数据var geometry = canvas.toDataURL();// 4. 构建 JSON 对象并计算 MD5var deviceData = {"winding": true,"geometry": "data:image/png;base64,iVBORw0KGgo..."};var deviceMd5 = MD5(JSON.stringify(deviceData)); // 约 20188 字符8.9 deviceToken 校验 MD5
输入格式:
WEB#sessionId#encryptedData#version#salt示例:
WEB#ab0***ae1-h-1768717322760-54b***829#Fuijq6okqtLAZhIpSySrZnGKXyZ/lBeZ7BUApHhyv4w...#169#daye,raolewoba!固定盐值:daye,raolewoba!
九、关键发现
9.1 安全机制
1. 密钥动态下发 - AES密钥由服务器返回,不是固定值 2. 设备指纹 - 收集大量浏览器/系统特征 3. 行为分析 - 记录完整鼠标移动轨迹 4. 时间戳 - 多个时间点校验 5. 环境检测 - 检测自动化工具/调试器
9.2 潜在弱点
1. IV 固定 - 012***DEF是固定值2. 加密算法已知 - AES-128-CBC 3. 数据格式清晰 - 字段结构可解析
十、调试方法
10.1 调试工具
• browser-connect MCP: 浏览器连接、网络监控、控制台日志 • mcp-js-debugger: 断点调试、表达式求值
10.2 Hook 代码示例
// Hook AES 加密var origAES = window.__ALIYUN_CRYPT.AES.encrypt;window.__ALIYUN_CRYPT.AES.encrypt = function(msg, key, cfg) {console.log('[AES]', {msg: msg?.toString?.().substring(0, 200),key: window.__ALIYUN_CRYPT.enc.Hex.stringify(key),iv: cfg?.iv ? window.__ALIYUN_CRYPT.enc.Hex.stringify(cfg.iv) : null });return origAES.apply(this, arguments);};// Hook HmacSHA1 - 捕获 AccessKeySecretvar origHmac = window.__ALIYUN_CRYPT.HmacSHA1;window.__ALIYUN_CRYPT.HmacSHA1 = function(msg, key) {var keyStr = '';try {if (typeof key === 'string') keyStr = key;elseif (key?.words) keyStr = window.__ALIYUN_CRYPT.enc.Utf8.stringify(key); } catch(e) {}console.log('[HMAC] Key:', keyStr); // AccessKeySecret + "&"return origHmac.apply(this, arguments);};10.3 签名生成示例 (Python)
import hmacimport hashlibimport base64import urllib.parsedefcompute_signature(params, access_key_secret):"""计算 API 签名"""# 1. 移除 Signature 参数并排序 sorted_params = sorted([(k, v) for k, v in params.items() if k != 'Signature'])# 2. 构建规范化查询字符串 query_string = '&'.join([f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(str(v), safe='')}"for k, v in sorted_params])# 3. 构建待签名字符串 string_to_sign = f"POST&{urllib.parse.quote('/', safe='')}&{urllib.parse.quote(query_string, safe='')}"# 4. 计算签名 (密钥 = AccessKeySecret + "&") sign_key = (access_key_secret + '&').encode('utf-8') signature = hmac.new(sign_key, string_to_sign.encode('utf-8'), hashlib.sha1).digest()return base64.b64encode(signature).decode('utf-8')十一、免责声明
本报告仅供安全研究和学习目的,请勿用于非法用途。本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号联系作者立即删除


