腾讯滑块验证码逆向解析
2026/7/23 13:55:46 网站建设 项目流程

腾讯滑块验证码 · 技术逆向与验证逻辑文档

范围限定:本协议逆向分析与验证生命周期的技术实现。不含业务背景与非技术性描述。
验证码类型:腾讯TCaptcha拼图缺口型滑块,接入标识aid(固定值)。


一、协议总体生命周期

┌─ 客户端生成 ───────────────────────────────────────────────┐ │ prehandle(GET/JSONP) → 下发 sess / pow_cfg / dyn_show_info │ │ getcapbysig(GET) → 下发背景板(1) / 拼图块(0) 字节 │ │ 本地:PoW 爆破 → 缺口检测 → 轨迹合成 → TDC 加密 │ └───────────────────────────┬────────────────────────────────┘ │ cap_union_new_verify (POST, query==body) ┌─ 加密传输 ────────────────┴────────────────────────────────┐ │ 7 参数:collect / tlg / eks / sess / ans / pow_answer / │ │ pow_calc_time (collect 为 TEA 密文,eks 为密钥材料)│ └───────────────────────────┬────────────────────────────────┘ │ ┌─ 服务端校验 ──────────────┴────────────────────────────────┐ │ 会话(sess)信任 → PoW 重算 → 答案(缺口位置) → TEA 解密轨迹 │ │ 判定 errorCode:0 通过 / 9 失败 │ └───────────────────────────────────────────────────────────┘

请求链路(按协议顺序):

cap_union_prehandle (GET) → 初始化会话,回传 sess cap_union_new_getcapbysig (GET, img_index=0/1, image=<令牌>, sess) → 图片字节 [cap_union_new_getsig (POST)] → 条件触发 cap_union_new_verify (POST) → 提交滑动结果(目标接口) cap_monitor (GET) → 遥测,不参与校验

二、客户端生成阶段(逆向解析与复现)

2.1 prehandle 会话初始化

请求:HTTP GET,返回 JSONP(callback=_aq_<ts>包裹)。固定参数 28 个,核心:

aid, protocol, accver, showtype, ua, clientype, cap_cd, uid, lang, entry_url, js=/tcaptcha-frame.*.js, subsid, callback, sess=(空), ...

请求侧sess参数为空;真实会话令牌在JSONP 响应对象e.sess中。

响应关键字段解析

字段含义后续用途
state1=成功流程闸门
sess会话令牌(≈506 字符)透传至 verify
data.pow_cfg.prefixPoW noncePoW 输入
data.pow_cfg.md5PoW target(隐藏答案的 md5)PoW 校验目标
data.dyn_show_info.bg_elem_cfg.img_url背景图直链(含 sess)下载缺口图
data.dyn_show_info.fg_elem_list[id=1].size_2d[0]拼图块真实宽pieceW缺口检测参数

结构兼容:浏览器直出data.pow_cfg;某些链路嵌套在data.comm_captcha_cfg.pow_cfg,解析时双路径兜底。

关键约束(环境信任)sess与服务端"环境信任"绑定。只有由真实浏览器加载tcaptcha-frame.js后产生的sess才是可信的;纯 Node 自行拼装的 prehandle 虽能拿到完整响应,但其sess未被"激活",verify 必返回errorCode 9。这是整条逆向链唯一必须借助浏览器的环节。

2.2 图片挑战下发

两种下载路径(均无需 cookie,sess 已在 URL 中):

# A:显式 getcapbysig cap_union_new_getcapbysig?img_index=1&image=<图片令牌>&sess=<sess> # 背景板(带缺口) cap_union_new_getcapbysig?img_index=0&image=<图片令牌>&sess=<sess> # 拼图块 # B(实际采用):dyn_show_info 直链 data.dyn_show_info.bg_elem_cfg.img_url # 已含 sess

img_index:1=背景板,0=拼图块;image令牌由挑战加载时分配;sess与 verify 完全一致。

尺寸修正(逆向实测):背景图实际像素672×390dyn_show_info.bg_elem_cfg.size_2d=[672,480]是含 padding 的显示画布,非图片像素。拼图块 sprite682×620,不透明 bbox≈x[157…242] y[507…592],宽≈85。缺口检测必须以真实图片像素为基准。

2.3 工作量证明 PoW

算法:暴力枚举u,使md5(prefix + u) === md5(target)。服务端将隐藏答案的 md5 作为 target 下发,客户端从 0 递增找回原值。

functionrunPow(prefix,target,timeoutMs=30000){constt0=Date.now();letu=0;while(md5(""+prefix+u)!==target){u+=1;if(Date.now()-t0>timeoutMs)break;}return{answer:""+u,duration:Date.now()-t0};}

md5为标准 MD5(RFC1321),小写 hex,与 Nodecrypto实现一致。实测u量级数万,单次耗时 80~150ms。

产物

  • pow_answer = prefix + u(如a1b2c373219
  • pow_calc_time = duration(毫秒)

2.4 缺口定位(垂直边缘能量法)

背景图缺口为"被挖掉的洞",左右竖直边产生强垂直边缘能量。

1. 逐列计算垂直边缘能量 edge[x] = Σ|L-R|(RGB 三通道) 2. 窄窗口(3) 平滑,保留尖锐边缘,压单像素噪点 3. 主体区(x∈[40, w-40]) 局部极大值,能量 > avg*1.3 → top 峰 4. 在 top 峰中找成对峰:间距 ∈ [pieceW-35, pieceW+35],能量和最大者 → 缺口左右缘 5. 退化:无成对峰时,以最高峰为右缘,左缘=右缘-pieceW

pieceW必须取拼图块真实宽fg_elem_list[id=1].size_2d[0]),不能用背景 canvas 宽(早期逆向误用背景宽导致定位错误)。

输出GAP {left, right, width, center},拖动目标X = 缺口左缘(非中心)。实测标定:bg_live.png检测 left=471 ≈ 已知答案 470(误差 1px);bg_yiche2.png检测 left=458。

2.5 轨迹合成

明文格式(喂给 TDC 的轨迹点):

[ [4, 0, 0, t0, 0], [1, x, y, t, 0], ... ] type 4 = 起始标记;type 1 = 移动点;(x, y, t) 为坐标与累计毫秒

坐标空间:图像像素空间(实测捕获真实点x=559超过显示宽 340,证明 SDK 以图像像素为单位)。因此targetX= 缺口左缘(图像空间),轨迹末点 x 必须等于targetX(与ans.x一致)。

拟人化生成

  • 缓动曲线ease:ease-in-out cubic,末段叠加微小过冲(overshoot)后回稳;
  • 时间非均匀:起止慢、中段快;
  • 竖直方向 ±2px 抖动;
  • 末点强制对齐targetX

2.6 TDC 加密(tdc.js VM 字节码)

加密体tdc.js内含VM 字节码解释器window.TDC.setData/getData/getInfo/getKeyInfo均为while(!![]){switch(...)}形式的 VM 函数,源码层不可 step 进)。

算法:TEA(Tiny Encryption Algorithm)

  • 32 轮 Feistel,delta=0x9e3779b9sum终值 ≈0xc6ef3720
  • 64-bit / 8 字节分组;
  • 密钥非静态常量,由会话材料eks动态派生(用静态TDC_KEY解密 collect 得乱码,printable ratio=0.39,证实密钥非固定)。

调用链复现

TDC.setData({ trackData, clientSize }) // 喂入合成轨迹 TDC.getData(true) → collect // TEA 加密轨迹,输出 url-encoded base64 TDC.getInfo().info → eks // 动态密钥材料(≈352 字符 base64) tlg = collect.length // 密文长度

eks自包含:服务端收到eks后自行key=derive(eks)再解密collect,故在独立 Node VM 内生成collect+eks即构成合法 verify 请求体,无需手工反推密钥派生函数。

collect 结构:TEA 加密后的字节经 url-encode 的 base64;解码后为 8 字节整数倍的 TEA 密文(实测 2256 字节,对齐)。


三、加密传输阶段

3.1 verify 报文结构

cap_union_new_verify为 POST。querybody内容完全相同u == f,协议强制):7 个参数同时出现在 URL 查询与请求体。

参数编码方式来源
collect已由getData(true)url-encode,原样拼接(不二次 encode)TDC 密文
tlgencodeURIComponentcollect.length
eksencodeURIComponentTDC.getInfo().info
sessencodeURIComponentprehandlej.sess透传
ansencodeURIComponentJSON.stringify([{elem_id:1,type:"DynAnswerType_POS",data:"X,100"}])
pow_answerencodeURIComponentprefix + u
pow_calc_time数字原样duration

3.2 参数编码与解密机制

  • collectgetData(true)内部完成 url-encode;buildVerify原样拼接,服务端decodeURIComponent(collect)还原 TEA 字节后解密;其余参数正常encodeURIComponent
  • ans结构:[{"elem_id":1,"type":"DynAnswerType_POS","data":"<X>,100"}]X为缺口左缘像素(图像空间),y固定 100。
  • 传输层:Content-Type: application/x-www-form-urlencodedReferer/Origin指向t.captcha.qq.com

四、服务端校验逻辑(逆向推演)

以下为基于客户端协议与实测反馈(errorCode)反推的服务端判定逻辑。

4.1 会话信任校验

  • 校验sess是否由可信环境(浏览器框架)激活;
  • sess一次性、有时效:过期或被复用 → 拒绝(errorCode 9);
  • 纯 Node 自建sess缺信任链 → 拒绝。

4.2 PoW 校验

  • 重算md5(prefix + pow_answer_without_prefix) === md5(target)
  • 校验pow_calc_time合理性(与爆破耗时量级一致);
  • 不一致 → 拒绝。

4.3 答案校验

  • 比对ans.dataX与服务器侧真实缺口左缘;
  • 允许像素级误差(实测 ±1px 通过);
  • X偏差超阈值(缺口检测算偏)→ 拒绝。

4.4 加密轨迹校验

  • 用回传eks派生 TEA 密钥 → 解密collect→ 还原trackData
  • 对轨迹做行为/设备指纹评估(速度曲线、分布等);
  • 实测结论:轨迹的isTrusted(是否真实浏览器鼠标事件)不是校验决定因素——纯 Node 无任何浏览器事件生成的collect/eks可被服务端接受并返回0

4.5 errorCode 判定汇总

判定errorCode触发条件
通过0sess 可信 + PoW 正确 + 答案命中 + 轨迹可解密
失败9① prehandle 非浏览器出生(缺信任);② 缺口检测偏差致距离错;③ sess 过期/复用

五、逆向工程底层实现

5.1 抓包解析

  • prehandle 抓取:通过 CDP(--remote-debugging-port)拉起 Chrome,监听Network.requestWillBeSent,正则/cap_union_prehandle/匹配并捕获完整请求 URL(含 28 参数)。该 URL 即"浏览器出生"的可信 prehandle。
  • 请求链定位:在 DevTools Network 中按接口名过滤cap_union_prehandle/cap_union_new_getcapbysig/cap_union_new_verify/cap_monitor,确定参数传递顺序与字段。
  • JSONP 解析parseJsonp用正则/^\s*[_a-zA-Z][\w]*\(([\s\S]*)\)\s*;?\s*$/抽取回调包裹的 JSON 体。
  • iframe 隔离:验证码在跨域 iframe 内加载,主页面无法读其响应体;故pow_cfg通过算法复现(已知、自测 PASS),图片经dyn_show_info直链(Node 端下载,无需 cookie)绕开 CORS。

5.2 反混淆策略

  • tdc.js VM 字节码:加密逻辑以while(!![]){switch(...)}的 VM 解释器实现,源码不可直接读。逆向策略为忠实运行而非反编译——在 Nodevm沙箱中原样执行tdc.js,让其自身完成 TEA 加密,直接取collect/eks
  • 寄存器采样失败:试图用寄存器采样法从 VM 内存提取 128-bit 密钥(regs [16,25,34,62],代数签名(v<<4)&(v>>>5)、窗口模式 mode ratio≈1.0)不稳定(解释器可能将移位折叠进加法),故放弃手工提密钥,改由忠实 VM 代劳加密。
  • 最小 DOM 桩:为让tdc.js在无浏览器环境运行,对document/navigator/screen/location等做最小桩(createElement返回elementStubgetContext/getBoundingClientRect返回固定值、定时函数为空操作),仅满足 VM 初始化与加密所需的最小 API 面。

5.3 绕过验证逻辑的底层实现

  1. 信任链绕过(唯一需浏览器处):用 CDP 拉一次浏览器,触发滑块并抓取真实 prehandle(含可信sess),随后关闭浏览器。此步骤等价于"借用浏览器完成环境信任激活",是整条纯 Node 求解链成立的前提。
  2. 全链纯 Node 复现:prehandle GET → PoW 爆破 → 图片下载 → 缺口检测 → 轨迹合成 → TDC VM 加密 → POST verify,全程无浏览器依赖。
  3. 关键构造点
    • collect/eks自包含:由 Node VM 内tdc.js生成,服务端用回传eks派生密钥解密,无需逆向密钥派生函数;
    • query == body:协议强制 7 参数双份同值,构造时直接复用同一拼接串;
    • ans坐标对齐:轨迹末点与ans.dataX均取缺口左缘(图像像素空间),保证答案与轨迹一致;
    • pieceW真实来源:取fg_elem_list[id=1].size_2d[0],避免早期误用背景 canvas 宽导致的定位偏差。
  4. 绕过结果:在浏览器出生 prehandle 下,纯 Node 求解稳定返回errorCode:"0"+ticket/randstr,完成验证闭环。

附:核心参数与算法速查

值 / 形式
加密算法TEA,32 轮 Feistel,delta=0x9e3779b9sum终值0xc6ef3720
密钥eks会话级动态派生(非静态)
PoWmd5(prefix+u)===md5(target),暴力枚举
轨迹明文[[4,0,0,t,0],[1,x,y,t,0],...],图像像素空间
verify 参数collect/tlg/eks/sess/ans/pow_answer/pow_calc_time(query==body)
缺口输出X = 缺口左缘(图像像素),ans.data="X,100"
图片真实尺寸背景 672×390(size_2d 为含 padding 画布)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询