feat(验证): 纯算链路在真实 aid 上端到端出 ticket —— 归因 1ms 的 12 为 aid 级风控

做了什么
- 新增 scripts/check_real_tencent_aid.py: 用**可反复测**的真实 aid 回归纯算链路
  (默认 aid=190758559 / mliev.login.wang 短信登录; 也可 --aid 195672344 对照)。
- 账本 har/1ms_dianxuan/PROGRESS.md: 已知 #26~#28、已排除新增三行、下一步重写为"已闭合结论 + 可复现靶子"。

为什么 (AGENTS §2.1 第 0 问 / §5.7)
- 用户提供可重复的真机登录抓包 har/原始抓包/登录抓包-点选.har, 得以把"12 到底是不是我们的问题"
  用一个能成功的目标做单变量反证。

怎么验证 (2026-10-11 实测)
- aid 190758559: 本客户端 solve() => errorCode=0 + 真 ticket, 连续 4 次:
  tr03lxdviLWtn4uXGjWjmrLQ / tr03A5zTSQ9bCog51sDm / tr039KeeQxXX-OdduJoY / tr03fGUOpcJTZWXWmZG7Uioc0Y8GlHPn
  其中 **httpx + 无 cookie + 无任何头部保真** 也拿到 0 —— 证明纯算链路本就完好。
- aid 195672344 (1ms): 在 cookie / curl_cffi Chrome 指纹 / 逐 endpoint 头保真 / collect 内容
  逐字节同构 / 带签名 identity_context 先行注册 —— 全部仍 12。
- 结论: 1ms 的 12 是该 aid 自身风控不放行"非真机页面"会话; 请求侧已无自变量,
  纯协议侧无解, 只能真机桥接或换入口。
This commit is contained in:
yml2213
2026-10-11 23:11:36 +08:00
parent 06c4348163
commit bc8cc5e124
2 changed files with 96 additions and 10 deletions
+29 -10
View File
@@ -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.<b64>.<sig>`) | 确证 |
| 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:<br>`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.<b64>.<sig>`) —— 但它只发给 **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 大幅下发滑块,见抓包)。
---
+67
View File
@@ -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())