- 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 同步收尾
83 lines
3.4 KiB
Python
83 lines
3.4 KiB
Python
"""探测验证码 iframe 内 SDK 结构:script 源、addEventListener 采集点、轨迹缓冲。"""
|
||
import asyncio, json, urllib.request, sys
|
||
import websockets
|
||
|
||
CDP_HTTP = "http://127.0.0.1:9222"
|
||
|
||
def _json(path):
|
||
try:
|
||
with urllib.request.urlopen(f"{CDP_HTTP}{path}", timeout=5) as r:
|
||
return json.load(r)
|
||
except (OSError, ValueError):
|
||
return None
|
||
|
||
async def main():
|
||
ver = _json("/json/version")
|
||
targets = _json("/json/list") or []
|
||
if not ver or "webSocketDebuggerUrl" not in ver:
|
||
print("CDP 9222 不可用", file=sys.stderr); return
|
||
cand = [t for t in targets if "rmc" in t.get("url","") or "captcha" in t.get("url","").lower()]
|
||
if not cand:
|
||
print("未找到 captcha iframe,targets:")
|
||
for t in targets: print(" ", t["type"], t["url"][:80])
|
||
return
|
||
tgt = cand[0]
|
||
print("探测目标:", tgt["url"][:100])
|
||
async with websockets.connect(ver["webSocketDebuggerUrl"], max_size=64*1024*1024) as ws:
|
||
mid = [0]
|
||
async def cdp(method, params=None, sid=None):
|
||
mid[0] += 1
|
||
m = {"id": mid[0], "method": method, "params": params or {}}
|
||
if sid: m["sessionId"] = sid
|
||
await ws.send(json.dumps(m))
|
||
while True:
|
||
try:
|
||
r = json.loads(await ws.recv())
|
||
except json.JSONDecodeError:
|
||
continue
|
||
if r.get("id") == mid[0]:
|
||
if "error" in r: raise RuntimeError(f"{method}: {r['error']}")
|
||
return r.get("result", {})
|
||
sid = (await cdp("Target.attachToTarget",
|
||
{"targetId": tgt["id"], "flatten": True}))["sessionId"]
|
||
await cdp("Runtime.enable", sid=sid)
|
||
|
||
async def js(expr):
|
||
r = await cdp("Runtime.evaluate",
|
||
{"expression": expr, "returnByValue": True,
|
||
"awaitPromise": True, "userGesture": True}, sid)
|
||
return r.get("result", {}).get("value")
|
||
|
||
# 1) 挂 listener 记录 hook(记录到 window 全局,跨 evaluate 可读)
|
||
await js("""
|
||
(() => {
|
||
if (window.__lh) return 'already';
|
||
const orig = EventTarget.prototype.addEventListener;
|
||
window.__lh = [];
|
||
EventTarget.prototype.addEventListener = function(t, f, o){
|
||
try { window.__lh.push([t, String(f && f.name || 'anon'),
|
||
String(f).slice(0,120).replace(/\\n/g,' ')]); } catch(e){}
|
||
return orig.call(this, t, f, o);
|
||
};
|
||
return 'hook installed';
|
||
})()
|
||
""")
|
||
# 2) 已有 listener(getEventListeners 仅 DevTools 可用,改用枚举 on* + 重放脚本已知点)
|
||
scripts = await js("[...document.scripts].map(s=>s.src||('inline:'+s.textContent.length)).join('\\n')")
|
||
print("== scripts ==\\n" + str(scripts))
|
||
globals_ = await js("""
|
||
Object.getOwnPropertyNames(window).filter(k=>/captcha|verify|secsdk|byted|rms|rmc|track|slid|nocaptcha/i.test(k)).join('\\n')
|
||
""")
|
||
print("== 全局候选 ==\\n" + str(globals_))
|
||
await asyncio.sleep(15)
|
||
log = await js("JSON.stringify(window.__lh||[])")
|
||
try:
|
||
rows = json.loads(log or "[]")
|
||
except (TypeError, ValueError) as e:
|
||
print(f"__lh 解析失败: {e}", file=sys.stderr)
|
||
return
|
||
print(f"== 15s 内 addEventListener 调用: {len(rows)} ==")
|
||
for t, name, src in rows:
|
||
print(f" [{t}] {name} :: {src[:110]}")
|
||
asyncio.run(main())
|