diff --git a/har/1ms_dianxuan/PROGRESS.md b/har/1ms_dianxuan/PROGRESS.md index 4b909c9..964c888 100644 --- a/har/1ms_dianxuan/PROGRESS.md +++ b/har/1ms_dianxuan/PROGRESS.md @@ -56,6 +56,9 @@ | 23 | **纯 Python 密钥能解密 1ms 真实成功样本的 collect** | 用 `har/签到成功-1.har` 里那次 ec=0 之前的 tdc.js 提密钥 → 解出该次 collect 明文 `{"cd":[2560,"MacIntel",...` 1353B;且与 `models/success_collect_template.b64` **逐字节同构**(仅轨迹/时间戳/盐被回填)| 确证 | | 24 | **1ms 真实成功链路已完整解析** | prehandle(带腾讯云登录 cookie) → tdc.js → getcapbysig → verify **ec=0**(ticket `tr03…`) → `POST /api/v1/mall/checkin`,body = `{captchaTicket:{provider,data:{ticket,randstr}}, sessionUuid, identity_context}`;`/api/v1/captcha/params` 的 body 带 `identity_context`(`emctx1..`) | 确证 | | 25 | **真实浏览器对 verify 从不吃 12** | `点选.har`(同一 cookie/同一 aid) 三次 verify = 50/50/9;`签到成功-1.har` = 0。而我们**任何**客户端形态都是 12 → 12 是"请求方身份"判定,不是答案/载荷 | 确证 | +| 26 | **纯算链路在真实 aid 上端到端成功出 ticket**(2026-10-11 实测)| 用新抓包 `har/原始抓包/登录抓包-点选.har` 的 aid=**190758559**(mliev.login.wang 短信登录),本客户端 `solve()` 返回 `errorCode=0` + 真 ticket:
`tr03lxdviLWtn4uXGjWjmrLQ` / `tr03A5zTSQ9bCog51sDm` / `tr039KeeQxXX-OdduJoY`。**关键:最原始的 httpx + 无 cookie 也 0** | 确证 | +| 27 | **1ms 的 12 是该 aid 自身的风控策略,不是我们的请求侧差异** | 同一天同一台机器:aid 190758559 用 httpx/无 cookie/无头保真 → 0;aid 195672344 用 curl_cffi(Chrome 指纹)+真机 cookie+逐 endpoint 头保真 → 仍 12。→ 请求侧已无自变量(§2.1 第 0 问:服务端就是不放行这个 aid 来自非真机页面的会话)| 确证 | +| 28 | 新抓包暴露另一套业务接入(可复现靶子)| `har/原始抓包/登录抓包-点选.har`:`POST /api/captcha/params`(scene=sendSms + fingerprint/userAgent/platform/language/timezone) → 腾讯点选 → `POST /api/oidc/send-register-verification-code`(`{type,receiver,captchaTicket:{provider,data:{ticket,randstr,fingerprint}}, sessionUuid}`) | 确证 | --- @@ -90,6 +93,8 @@ | **HTTP/TLS 传输指纹是 12 的自变量** | `curl_cffi`(impersonate=chrome: JA3/JA4 + HTTP/2 + Chrome 头序) + 真实 cookie,仍 12(`scripts/experiment_transport_ab.py --only b`)→ 传输层排除 | | **请求头保真度是 12 的自变量** | 按真实抓包逐 endpoint 保真:资源 GET 用 `Sec-Fetch-Dest=script/image` + `Mode=no-cors` + `Storage-Access=active` + cookie,POST 用 `empty/cors` + Origin 且**不带** cookie;仍 12 | | **collect 明文结构/取值是 12 的自变量** | 我们的 collect 明文与 1ms 真实成功样本**逐字节同构**(同 1353B、同字段序、同取值,仅回填轨迹/时间戳/盐),仍 12 | +| **H5:1ms 后端用 `identity_context` 把会话标记为可信** | 拿抓包里**签名过的** `identity_context`(599B `emctx1.*`) 先调 `POST /api/v1/captcha/params`(响应与不带时**完全相同**,无额外 token)再解题,仍 12 → H5 排除 | +| **"请求侧保真度不够"这一整类** | 直接反证:aid 190758559 上 **未做任何保真的 httpx + 无 cookie** 也拿到 ticket(0);即请求侧细节本就足够,12 与保真度无关 | --- @@ -122,16 +127,30 @@ TencentCaptchaClient(telemetry_mode="python_keyed") # 默认 残留增强项(非阻塞):① 若服务端改了 handler 语义,`tdc_opcodes.derive_opcode_table` 会抛"指纹未识别",此时应更新参考指纹而非改定位逻辑;② 抗"多套诱饵数组 >2 层捕获"的 兜底(当前两层已覆盖实测全部 6 份)。 -1. **拿下 1ms 的 12**:本轮(2026-10-11)已把已知自变量逐条单变量排除 —— 见"已排除"表末四行 - (真实 cookie / 传输指纹 / 请求头保真 / collect 内容)。**剩余两条可执行路径**: - (a) **保底(建议先做)**:账本 §2.4 的 ③ 桥接 —— 在**真实浏览器页面**里跑一次拿 ticket - (不是 CDP/headless;CDP 后端历史实测同样 12); - (b) **仍要纯协议**:唯一尚未覆盖的输入是 `/api/v1/captcha/params` 的 `identity_context` - (`emctx1..`) —— 但它只发给 **1ms 后端**、不进 Tencent 的 verify, - 因此**不能单独解释** 12;除非 1ms 后端用它把业务会话与 Tencent 侧绑定(需一次对照实验验证)。 -2. **补齐新版明文字段语义**:对照 10+ 份实时模板做差分。 -3. **实现 2408 图标点选**:按 1ms 图标类别重训 `yolo_detector` + `icon_classifier`。 -4. **实现滑块 (2401/2402)**:模板匹配找缺口 + 生成滑动轨迹。 +1. **拿下 1ms 的 12 —— 结论已闭合:不是我们能修的请求侧问题**(2026-10-11) + * 证据链:① 同一份代码在 aid **190758559** 上 `errorCode=0` 出真 ticket(且 **httpx+无 cookie** 也 0); + ② 1ms aid **195672344** 在 cookie / 传输指纹 / 头保真 / collect 内容 / `identity_context` + 全部单变量排除后**恒为 12**;③ 真机同 aid 只出 50/9/0。 + * 即 **1ms 这个 aid 的风控策略不放行"非真机页面"的会话**(§2.1 第 0 问:服务端就是判这个)。 + * 因此只剩两条路,且都不在"协议纯算"范围内: + (a) **桥接保底**(§2.4 ③):在**真机页面**里跑一次拿 ticket,再走纯算业务提交; + (b) 换一个风控更宽的入口/aid(如 190758559 那种),或与 1ms 沟通白名单。 +2. **可复现靶子(新增资产)**:aid 190758559 / `mliev.login.wang` 短信登录 —— **可反复测**, + 用来回归"纯算链路是否完好"(1ms 每天只能试一次,不适合做回归): + ```bash + .venv/bin/python - <<'PY' + from tcaptcha.client import TencentCaptchaClient, KIND_TEXT_CLICK + c=TencentCaptchaClient(aid="190758559", + entry_url="https://mliev.login.wang/auth/#/oidc/authorize", + referer="https://mliev.login.wang/", wb="1", accept_kinds=(KIND_TEXT_CLICK,)) + print(c.solve()["raw_response"].get("errorCode")) # 期望 0 + c.close() + PY + ``` +3. **补齐新版明文字段语义**:对照 10+ 份实时模板做差分。 +4. **实现 2408 图标点选**:按 1ms 图标类别重训 `yolo_detector` + `icon_classifier`。 +5. **实现滑块 (2401/2402)**:模板匹配找缺口 + 生成滑动轨迹(1ms 大幅下发滑块,见抓包)。 + --- diff --git a/scripts/check_real_tencent_aid.py b/scripts/check_real_tencent_aid.py new file mode 100644 index 0000000..0d452ea --- /dev/null +++ b/scripts/check_real_tencent_aid.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +真机 aid 冒烟测试 —— 用**可反复测**的真实 aid 回归"纯算链路是否完好"。 + +背景 (har/1ms_dianxuan/PROGRESS.md 已知 #26/#27): + aid 195672344 (1ms.run) 对本客户端恒返回 errorCode=12 (aid 级风控, 与请求侧无关), + 且 1ms 签到每天只能试一次 —— 不适合做回归。 + 而抓包 `har/原始抓包/登录抓包-点选.har` 里的 aid **190758559** (mliev.login.wang 短信登录) + 可反复测: 本客户端 (纯算, 零 Node) 稳定拿到 errorCode=0 + 真 ticket。 + +用法: + .venv/bin/python scripts/check_real_tencent_aid.py # 默认 190758559 + .venv/bin/python scripts/check_real_tencent_aid.py --aid 195672344 # 对照: 1ms 会得到 12 + +注意: 只调用腾讯天御的验证码接口 (prehandle/tdc.js/getcapbysig/verify), + **不会**触发任何短信/签到等业务副作用。 +""" +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from tcaptcha.client import KIND_TEXT_CLICK, TencentCaptchaClient # noqa: E402 + +# (aid, entry_url, referer, wb) +TARGETS = { + "190758559": ("https://mliev.login.wang/auth/#/oidc/authorize", "https://mliev.login.wang/", "1"), + "195672344": ("https://1ms.run/console/personal/benefits/check-in", "https://1ms.run/", "1"), +} + + +def main() -> int: + ap = argparse.ArgumentParser(description="真机 aid 冒烟: 纯算链路端到端") + ap.add_argument("--aid", default="190758559", choices=sorted(TARGETS)) + ap.add_argument("--transport", default="httpx", choices=["httpx", "curl"]) + ap.add_argument("--refreshes", type=int, default=25) + args = ap.parse_args() + + entry_url, referer, wb = TARGETS[args.aid] + client = TencentCaptchaClient(aid=args.aid, entry_url=entry_url, referer=referer, wb=wb, + accept_kinds=(KIND_TEXT_CLICK,), telemetry_mode="python_keyed", + transport=args.transport) + try: + result = client.solve(max_retries=2, max_refreshes=args.refreshes) + finally: + client.close() + + raw = result.get("raw_response") or {} + print("\n=== 结果 ===") + print("aid :", args.aid) + print("transport :", args.transport) + print("success :", result.get("success")) + print("errorCode :", raw.get("errorCode")) + print("ticket :", (raw.get("ticket") or "")[:32]) + print("randstr :", raw.get("randstr")) + print("telemetry :", result.get("raw_response", {}).get("sess") and "python_keyed(纯算)") + return 0 if result.get("success") else 1 + + +if __name__ == "__main__": + raise SystemExit(main())