Files
杨豪 cae37465d2 feat: 尺度精修上线,首次机器 PASS(live_160548 s=1.0375)
- method_l_shape: OK 候选尺度 ±0.0375 精修(步长 0.0125 + ±6px 窗口),
  GT live_165204 Δ 179→176(真人 177),离线 9/10 保持
- 插桩 JSON.stringify 捕获 SDK 日志明文;指针 probe 排除 coalesced/pressure
- 拖动不跟手指标作废:post_pos 晚于 SDK 重置滑块,鼠标事件流始终完整
- 剩余 VerifyErr = 候选整体选错(视觉确证),方向:多候选质量评估
- docs: experiments/solution/retrospective 同步收尾
2026-09-15 16:20:05 +08:00

7.8 KiB
Raw Permalink Blame History

CDP 拖动与行为风控实验记录(force/pressure 发现)

时间:2026-05-15(连线实验,Chrome CDP 9222 + douyin.com 登录触发验证码)

实验环境

  • 验证码 iframe:rmc.bytedance.com/verifycenter/captcha/v2,380x384,视口坐标 (770,305)
  • 滑块按钮初始中心:iframe 坐标 (54,307);刷新按钮 (30,355)
  • 大图 UI:.captcha-verify-image (20,69) 340x212(对应原图 552x344,SCALE=340/552=0.616)
  • mark 预览:.captcha-verify-image-slide 68x68(= 110x110 × 0.618)

核心发现 1:CDP 缺省 pressure=0 是硬指纹

CDP Input.dispatchMouseEvent 不带 force 参数时,产生的 MouseEvent 均无 pressure(DOM 事件里 event.pressure === 0)。按 W3C UI Events 规范, 真实鼠标按下/拖动时 pressure 应为 0.5(鼠标无压感设备时恒 0.5)。

SDK(captcha.js / webmssdk)采集的事件流含 pressure,可据此判自动化。

修复:mousePressed / mouseMoved / mouseReleased 全部加 force: 0.5, 实测 DOM event.pressure 变为 0.5 ✓(主页与 iframe 内均验证)。

核心发现 2:错误码语义分层(多次连线实验归纳)

错误码 message 语义
500 VerifyErr 验证失败,请根据提示重新操作 行为闸门已过,位置判定失败(或轨迹分不足)
502 [5014] 操作过快,请慢一点 行为判定拒(轨迹过快/异常),位置未判
502 [5009] 验证过于频繁,请稍后重试 频控:短时间连环尝试后触发,冷却数分钟恢复
502 NotFoundChallengeId 验证超时 challenge 过期(采题后拖延太久)

关键实验:

  1. force 缺省(=0)时全部 5014;
  2. force=0.5 后出现过 VerifyErr(行为关过了、位置判了)——说明 pressure 至少是 5014 的成因之一;
  3. 连环快速尝试(数十次/小时)后一律 5009 频控——5014 与 5009 会互相掩盖, 判定"位置对错"必须在频控冷却后做单次干净尝试;
  4. AMBIGUOUS 候选(solve 置信度低)的题拖了必 VerifyErr;OK 候选的题 (当时还没判到位置层)也被 5014/5009 拦住,位置正确性尚未闭环确认。

核心发现 3:拖动映射与 mark 预览行为

  • 按钮 1:1 带动 mark 预览(按钮 +60px → mark UI +60px,实测);
  • mark 初始相对大图 rel=(0, tip_y2)(原图坐标)——每题垂直位置自动对齐 (mark 预览 img 的 y 每题不同,实测 rel_y×(552/340) ≈ tip_y2);
  • 拖动距离 = solve.x × 340/552(UI px);
  • 拖动失败/失败重开后 mark 预览复位到 rel x=0;
  • 验证成功后 iframe 自毁(/json/list 里 rmc iframe 消失)。

核心发现 4:iframe 内 XHR hook 采题(不依赖 netcap)

netcap(Network 域抓包)attach 时机不稳。更可靠的做法:attach iframe 后在 页面上下文注入 XHR hook,把 captcha/get、captcha/verify 的响应存到 window.__lastGet / __lastVerify,CDP 轮询读取:

XMLHttpRequest.prototype.open = function(m,u){ this.__u=u; return oOpen.apply(this,arguments); };
XMLHttpRequest.prototype.send = function(){
  this.addEventListener('load', () => {
    if (/captcha\/get/.test(this.__u||'')) window.__lastGet = JSON.parse(this.responseText);
    if (/captcha\/verify/.test(this.__u||'')) window.__lastVerify = this.responseText;
  });
  return oSend.apply(this, arguments);
};

题目图片可直接 urllib 下载 question.url1/url2(无需解码密文)。 注意:hook 必须在拖动前注入;点击"刷新"产生新 get 不重建 iframe,hook 仍有效。

线上题 vs 离线样本的差异(solve 风险)

  • 离线 17 组样本(captchas/):洞为暗色覆盖物,方法 L 分数 1-3,全部命中;
  • 线上 URL 下载的题:覆盖物为亮白半透明花瓣贴片,方法 L 分数 3.6-7.7, 明显变差;部分题最佳候选 scale≠1.0(0.85-1.075),会出现 AMBIGUOUS;
  • tip_y*2(原图缺口顶 y)与 solve.y 交叉验证:OK 且分数低的题两者一致 (如 solve y=190 vs tip_y*2=192 ✓);solve 跑飞时两者差得远(可当否决器)。

后续验证结果(同日)

  1. 位置判定已确认可达:频控冷却后单次 force=0.5 拖动稳定得到 VerifyErr (位置层判定被执行),5014 不再出现 —— pressure 修复对行为闸门有效。
  2. hover 序列确认毒化拖动:按下前发 button:"left"+buttons:0 的 mouseMoved 会导致 verify 根本不发出。修正:无按键时用 button:"none"。
  3. solve 已增强:solve(..., tip_y=...) 用服务端缺口顶 y 真值锚定候选, 离线 10/10 不回归,线上 8 题 7 中(详见 solution.md「线上增强」节)。
  4. 频控仍是拦路虎:单 IP 连环尝试后 5009 窗口 >20 分钟,仅靠冷却 很难完成多次位置闭环判定。后续应换环境(新 IP/无痕 profile)做 「冷启动 → 单次拖动」实验,验证 tip_y 锚定后的 solve 能否一次通过。
  5. try/solo_attempt.py:单次干净尝试脚本(冷却→采题→solve→拖→读 verify), 供冷启动实验复用。

决定性对照实验(2026-05-15 补充,推翻旧归因)

实验 账号 操作者 结果
多次 CDP 拖动 老账号(已被限制 1467) 脚本 5014 / VerifyErr / 5009 混杂
真人手拖 ×3 同上老账号 人 超时 / 拖动过快 / 网络环境差,全失败
真人手拖 ×1 新账号 人 验证通过,iframe 自毁

结论修正:

  1. 老账号被风控标记后连真人都过不了 —— 之前「真人可过、CDP 必败」 的对照不成立,5014/VerifyErr 的主因是账号级标记(可能叠加 IP 维度), CDP pressure=0 只是叠加因素之一;
  2. 「网络环境差」「操作过快」「验证超时」是账号/环境被标记后的三种表象, 与拖动质量无关(真人正常手拖也被拒);
  3. 后续所有行为/位置判定实验必须在「干净账号 + 未标记环境」上做, 否则结果无法归因。此前在本机老账号上积累的 5014 样本全部存疑;
  4. CDP 自动化在干净账号上的真实通过率尚未测得(下一步实验)。

拖动时长实验(2026-05-15 下午)

把 gen_human_path 缺省总时长从 0.6-1.4s 拉到 5-10s 随机(用户要求,真人拖动 观测 ~4-8s)后连续测试:

# 场景 拖动时长 结果
3 干净账号第 2 连拖 ~6.5s 5014 操作过快
4 reload 后第 3 连拖 ~6s 5014 操作过快

结论:

  1. 5014「操作过快」与拖动速度无关 —— 6s 的慢拖同样被判过快;该文案是 行为分累积到阈值后的固定话术;
  2. 行为分按 账号+设备指纹 累积(跨 challenge、跨页面 reload 不清零), 连续 CDP 拖动 2-3 次即触发;真人首次通过 = 行为分从 0 开始;
  3. 第 1 拖(干净行为分)拿到的是 VerifyErr 而非 5014 —— 此时位置判定真正 执行了,但位置仍被判错(solve 经 IoU 亚像素验证 ±1px、SDK 实际位移与 期望一致)→ 位置无误仍 VerifyErr 指向 verify 通道的其它指纹 (captchaBody 密文内的轨迹编码 / detail 参数),而非坐标映射;
  4. 接口限流:随机号触发验证码的 get 接口也有频控(「系统繁忙」),触发 同样要冷却。

后续方向(按性价比排序):

  • 在干净环境(新 profile/IP,行为分 0)做单次 CDP 拖动:若 VerifyErr 消失 → 之前所有 VerifyErr 均为行为分污染,位置判定本来就对;
  • 对比真人通过样本与 CDP 样本的 captchaBody 长度/结构(轨迹编码密度);
  • hook SDK 轨迹采集函数,直接注入受控轨迹(绕开 CDP 事件指纹)。