- 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 同步收尾
7.8 KiB
7.8 KiB
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-slide68x68(= 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 过期(采题后拖延太久) |
关键实验:
force缺省(=0)时全部 5014;force=0.5后出现过 VerifyErr(行为关过了、位置判了)——说明 pressure 至少是 5014 的成因之一;- 连环快速尝试(数十次/小时)后一律 5009 频控——5014 与 5009 会互相掩盖, 判定"位置对错"必须在频控冷却后做单次干净尝试;
- 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 跑飞时两者差得远(可当否决器)。
后续验证结果(同日)
- 位置判定已确认可达:频控冷却后单次 force=0.5 拖动稳定得到 VerifyErr (位置层判定被执行),5014 不再出现 —— pressure 修复对行为闸门有效。
- hover 序列确认毒化拖动:按下前发
button:"left"+buttons:0的 mouseMoved 会导致 verify 根本不发出。修正:无按键时用button:"none"。 - solve 已增强:
solve(..., tip_y=...)用服务端缺口顶 y 真值锚定候选, 离线 10/10 不回归,线上 8 题 7 中(详见 solution.md「线上增强」节)。 - 频控仍是拦路虎:单 IP 连环尝试后 5009 窗口 >20 分钟,仅靠冷却 很难完成多次位置闭环判定。后续应换环境(新 IP/无痕 profile)做 「冷启动 → 单次拖动」实验,验证 tip_y 锚定后的 solve 能否一次通过。
try/solo_attempt.py:单次干净尝试脚本(冷却→采题→solve→拖→读 verify), 供冷启动实验复用。
决定性对照实验(2026-05-15 补充,推翻旧归因)
| 实验 | 账号 | 操作者 | 结果 |
|---|---|---|---|
| 多次 CDP 拖动 | 老账号(已被限制 1467) | 脚本 | 5014 / VerifyErr / 5009 混杂 |
| 真人手拖 ×3 | 同上老账号 | 人 | 超时 / 拖动过快 / 网络环境差,全失败 |
| 真人手拖 ×1 | 新账号 | 人 | 验证通过,iframe 自毁 |
结论修正:
- 老账号被风控标记后连真人都过不了 —— 之前「真人可过、CDP 必败」 的对照不成立,5014/VerifyErr 的主因是账号级标记(可能叠加 IP 维度), CDP pressure=0 只是叠加因素之一;
- 「网络环境差」「操作过快」「验证超时」是账号/环境被标记后的三种表象, 与拖动质量无关(真人正常手拖也被拒);
- 后续所有行为/位置判定实验必须在「干净账号 + 未标记环境」上做, 否则结果无法归因。此前在本机老账号上积累的 5014 样本全部存疑;
- 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 操作过快 |
结论:
- 5014「操作过快」与拖动速度无关 —— 6s 的慢拖同样被判过快;该文案是 行为分累积到阈值后的固定话术;
- 行为分按 账号+设备指纹 累积(跨 challenge、跨页面 reload 不清零), 连续 CDP 拖动 2-3 次即触发;真人首次通过 = 行为分从 0 开始;
- 第 1 拖(干净行为分)拿到的是 VerifyErr 而非 5014 —— 此时位置判定真正 执行了,但位置仍被判错(solve 经 IoU 亚像素验证 ±1px、SDK 实际位移与 期望一致)→ 位置无误仍 VerifyErr 指向 verify 通道的其它指纹 (captchaBody 密文内的轨迹编码 / detail 参数),而非坐标映射;
- 接口限流:随机号触发验证码的 get 接口也有频控(「系统繁忙」),触发 同样要冷却。
后续方向(按性价比排序):
- 在干净环境(新 profile/IP,行为分 0)做单次 CDP 拖动:若 VerifyErr 消失 → 之前所有 VerifyErr 均为行为分污染,位置判定本来就对;
- 对比真人通过样本与 CDP 样本的 captchaBody 长度/结构(轨迹编码密度);
- hook SDK 轨迹采集函数,直接注入受控轨迹(绕开 CDP 事件指纹)。