腾讯滑块验证码 · 技术逆向与验证逻辑文档范围限定本协议逆向分析与验证生命周期的技术实现。不含业务背景与非技术性描述。验证码类型腾讯TCaptcha拼图缺口型滑块接入标识aid固定值。一、协议总体生命周期┌─ 客户端生成 ───────────────────────────────────────────────┐ │ prehandle(GET/JSONP) → 下发 sess / pow_cfg / dyn_show_info │ │ getcapbysig(GET) → 下发背景板(1) / 拼图块(0) 字节 │ │ 本地PoW 爆破 → 缺口检测 → 轨迹合成 → TDC 加密 │ └───────────────────────────┬────────────────────────────────┘ │ cap_union_new_verify (POST, querybody) ┌─ 加密传输 ────────────────┴────────────────────────────────┐ │ 7 参数collect / tlg / eks / sess / ans / pow_answer / │ │ pow_calc_time collect 为 TEA 密文eks 为密钥材料│ └───────────────────────────┬────────────────────────────────┘ │ ┌─ 服务端校验 ──────────────┴────────────────────────────────┐ │ 会话(sess)信任 → PoW 重算 → 答案(缺口位置) → TEA 解密轨迹 │ │ 判定 errorCode0 通过 / 9 失败 │ └───────────────────────────────────────────────────────────┘请求链路按协议顺序cap_union_prehandle (GET) → 初始化会话回传 sess cap_union_new_getcapbysig (GET, img_index0/1, image令牌, sess) → 图片字节 [cap_union_new_getsig (POST)] → 条件触发 cap_union_new_verify (POST) → 提交滑动结果目标接口 cap_monitor (GET) → 遥测不参与校验二、客户端生成阶段逆向解析与复现2.1 prehandle 会话初始化请求HTTP GET返回 JSONPcallback_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 字符透传至 verifydata.pow_cfg.prefixPoW noncePoW 输入data.pow_cfg.md5PoW target隐藏答案的 md5PoW 校验目标data.dyn_show_info.bg_elem_cfg.img_url背景图直链含 sess下载缺口图data.dyn_show_info.fg_elem_list[id1].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 图片挑战下发两种下载路径均无需 cookiesess 已在 URL 中# A显式 getcapbysig cap_union_new_getcapbysig?img_index1image图片令牌sesssess # 背景板带缺口 cap_union_new_getcapbysig?img_index0image图片令牌sesssess # 拼图块 # B实际采用dyn_show_info 直链 data.dyn_show_info.bg_elem_cfg.img_url # 已含 sessimg_index1背景板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,timeoutMs30000){constt0Date.now();letu0;while(md5(prefixu)!target){u1;if(Date.now()-t0timeoutMs)break;}return{answer:u,duration:Date.now()-t0};}md5为标准 MD5RFC1321小写 hex与 Nodecrypto实现一致。实测u量级数万单次耗时 80~150ms。产物pow_answer prefix u如a1b2c373219pow_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, pieceW35]能量和最大者 → 缺口左右缘 5. 退化无成对峰时以最高峰为右缘左缘右缘-pieceWpieceW必须取拼图块真实宽fg_elem_list[id1].size_2d[0]不能用背景 canvas 宽早期逆向误用背景宽导致定位错误。输出GAP {left, right, width, center}拖动目标X 缺口左缘非中心。实测标定bg_live.png检测 left471 ≈ 已知答案 470误差 1pxbg_yiche2.png检测 left458。2.5 轨迹合成明文格式喂给 TDC 的轨迹点[ [4, 0, 0, t0, 0], [1, x, y, t, 0], ... ] type 4 起始标记type 1 移动点(x, y, t) 为坐标与累计毫秒坐标空间图像像素空间实测捕获真实点x559超过显示宽 340证明 SDK 以图像像素为单位。因此targetX 缺口左缘图像空间轨迹末点 x 必须等于targetX与ans.x一致。拟人化生成缓动曲线easeease-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 进。算法TEATiny Encryption Algorithm32 轮 Feisteldelta0x9e3779b9sum终值 ≈0xc6ef372064-bit / 8 字节分组密钥非静态常量由会话材料eks动态派生用静态TDC_KEY解密 collect 得乱码printable ratio0.39证实密钥非固定。调用链复现TDC.setData({ trackData, clientSize }) // 喂入合成轨迹 TDC.getData(true) → collect // TEA 加密轨迹输出 url-encoded base64 TDC.getInfo().info → eks // 动态密钥材料≈352 字符 base64 tlg collect.length // 密文长度eks自包含服务端收到eks后自行keyderive(eks)再解密collect故在独立 Node VM 内生成collecteks即构成合法 verify 请求体无需手工反推密钥派生函数。collect 结构TEA 加密后的字节经 url-encode 的 base64解码后为 8 字节整数倍的 TEA 密文实测 2256 字节对齐。三、加密传输阶段3.1 verify 报文结构cap_union_new_verify为 POST。query与body内容完全相同u f协议强制7 个参数同时出现在 URL 查询与请求体。参数编码方式来源collect已由getData(true)url-encode原样拼接不二次 encodeTDC 密文tlgencodeURIComponentcollect.lengtheksencodeURIComponentTDC.getInfo().infosessencodeURIComponentprehandlej.sess透传ansencodeURIComponentJSON.stringify([{elem_id:1,type:DynAnswerType_POS,data:X,100}])pow_answerencodeURIComponentprefix upow_calc_time数字原样duration3.2 参数编码与解密机制collect由getData(true)内部完成 url-encodebuildVerify中原样拼接服务端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.data的X与服务器侧真实缺口左缘允许像素级误差实测 ±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]代数签名(v4)(v5)、窗口模式 mode ratio≈1.0不稳定解释器可能将移位折叠进加法故放弃手工提密钥改由忠实 VM 代劳加密。最小 DOM 桩为让tdc.js在无浏览器环境运行对document/navigator/screen/location等做最小桩createElement返回elementStub、getContext/getBoundingClientRect返回固定值、定时函数为空操作仅满足 VM 初始化与加密所需的最小 API 面。5.3 绕过验证逻辑的底层实现信任链绕过唯一需浏览器处用 CDP 拉一次浏览器触发滑块并抓取真实 prehandle含可信sess随后关闭浏览器。此步骤等价于借用浏览器完成环境信任激活是整条纯 Node 求解链成立的前提。全链纯 Node 复现prehandle GET → PoW 爆破 → 图片下载 → 缺口检测 → 轨迹合成 → TDC VM 加密 → POST verify全程无浏览器依赖。关键构造点collect/eks自包含由 Node VM 内tdc.js生成服务端用回传eks派生密钥解密无需逆向密钥派生函数query body协议强制 7 参数双份同值构造时直接复用同一拼接串ans坐标对齐轨迹末点与ans.data的X均取缺口左缘图像像素空间保证答案与轨迹一致pieceW真实来源取fg_elem_list[id1].size_2d[0]避免早期误用背景 canvas 宽导致的定位偏差。绕过结果在浏览器出生 prehandle 下纯 Node 求解稳定返回errorCode:0ticket/randstr完成验证闭环。附核心参数与算法速查项值 / 形式加密算法TEA32 轮 Feisteldelta0x9e3779b9sum终值0xc6ef3720密钥由eks会话级动态派生非静态PoWmd5(prefixu)md5(target)暴力枚举轨迹明文[[4,0,0,t,0],[1,x,y,t,0],...]图像像素空间verify 参数collect/tlg/eks/sess/ans/pow_answer/pow_calc_timequerybody缺口输出X 缺口左缘图像像素ans.dataX,100图片真实尺寸背景 672×390size_2d 为含 padding 画布