# region_ticket 静态逆向结论 ## 结论 `region_ticket` 不是 `sig`、`__NS_sig3` 或 DFP 一类的客户端计算结果。 APK 将它建模为服务端响应顶层 `region.ticket` 中的不透明票据,按用户保存到 `DefaultPreferenceHelper` 的 `_Region`,后续请求再把它放入 Cookie。 因此,纯 Python 的正确实现是“接收、持久化、复用”,不是本地生成: 1. 解析每个普通 API JSON 响应的顶层 `region`。 2. 有 `ticket` 时按 `region.uid` 保存;`uid` 为空时使用当前用户 ID。 3. 后续请求注入 `Cookie: region_ticket=; __NSWJ=`。 4. 账号切换时按 UID 隔离,不能把一个账号的票据当作设备级常量。 ## 静态调用链 ```text 任意普通 API JSON 响应 -> ResponseDeserializer.deserialize() -> 读取顶层 region.uid / region.name / region.ticket -> ylm.d.mRegion -> u0a.f.buildObservableBeforeRetry() -> doOnNext(com.kwai.framework.network.regions.c) -> regions.c.accept() -> o2a.c.c(region, "New region received") -> DefaultPreferenceHelper[_Region] = Region JSON 下一次请求 -> v0a.c.c(sceneName) -> q01.g.l0() -> NetworkAccessParams.e.l0() -> o2a.c.b(Region.class).ticket -> Cookie: region_ticket=; __NSWJ= ``` ## 关键证据 ### 1. IOC 实现绑定 - `pnm.b.b(-1479227965)` 在生成的 `IOCProviderImpl` 中绑定到 `com.kwai.framework.network.access.params.e`。 - `q01.g.l0()` 的实际实现读取 `o2a.c.b(Region.class)`,返回 `Region.b()`, 即 `mTicket`;不存在加密、哈希或 native 调用。 - Region scheduler 的 IOC ID `1013182224` 绑定到 `o2a.e`。 证据文件: - `out/region_ticket_jadx/IOCProviderImpl.java` - `out/region_ticket_jadx/NetworkAccessParamsE.java` - `out/region_ticket_jadx/RegionSchedulerProviderE.java` ### 2. 响应下发与统一持久化 `ResponseDeserializer` 只读取响应 JSON 的顶层字段: ```json { "region": { "uid": "...", "name": "...", "ticket": "RT_..." } } ``` `u0a.f` 把 `regions.c` 注册为普通 API 的全局 `doOnNext` 处理器。 `regions.c` 检查 `ylm.d.k()`,非空时直接调用: ```java o2a.c.c(response.k(), "New region received"); ``` 这说明没有唯一的 `region_ticket` 申请接口。任何使用统一响应包的普通 API 都可以顺带下发或轮换 Region。 ### 3. 本地存储 `o2a.c` 使用 `DefaultPreferenceHelper`: ```text 读取键: _Region 写入键: _Region 回退键: _Region 值格式: {"uid":"...","name":"...","ticket":"RT_..."} ``` 未登录时当前 UID 回退为 `0`。如果响应明确带 `region.uid`,写入时优先使用 该 UID,所以账号票据不会天然复制到另一个 UID。 ### 4. Cookie 注入 `v0a.c` 先组合 `region_ticket`、`kuaishou.api_st`、`__NSWJ`,再序列化为 `Cookie` 请求头。`region_ticket` 为空时该项被省略,不会生成占位值。 ### 5. RegionInfo 不是票据来源 `o2a.e` 会从 SharedPreferences 的 `RegionInfo` 或 raw 资源加载 API 区域路由。 APK 资源 `raw/0x7f100082` 只有 `api_group_host_list` 和 `api_mapping`,没有 `ticket`。它负责 follow/nearby 的区域 host 选择,不负责签发 Region 票据。 ## HAR 审计 新增 `tools/analyze_region_ticket.py`,结构化审计以下三类事件: - 响应 JSON 出现顶层或嵌套 `region.ticket`; - 响应头出现 `Set-Cookie: region_ticket=...`; - 请求 Cookie 携带 `region_ticket`。 对仓库全部 7 个 HAR、8334 个 entry 的结果: ```text 响应 region.ticket: 0 响应 Set-Cookie: 0 请求 region_ticket: 362 唯一票据: 12 票据长度: 全部 76 票据形态: RT_ + 73 个十六进制字符 ``` 12 个样本的 73 个十六进制位置均有变化,没有固定版本位或可见字段边界。 这与“服务端不透明票据”一致,但仅凭格式不能反推出生成算法。 现有 HAR 的抓包窗口开始时票据已经存在:例如历史窗口的第一批 startup、spot、 reddot 请求已经携带同一票据。因此“首个携带票据的请求”不是签发接口,这批 HAR 不能确定首次下发发生在哪个 endpoint。 脱敏审计产物位于: - `out/region_ticket_har_small.json` - `out/region_ticket_har_20260708_1416.json` - `out/region_ticket_har_20260710.json` - `out/region_ticket_har_20260726.json` - `out/region_ticket_har_ip_20260726.json` - `out/region_ticket_har_cdn_20260724.json` ## 对短信登录 CLI 的影响 当前 CLI 已支持 `--region-ticket`、`KS_REGION_TICKET` 和 app-fields 注入, 但当前 `sms_device_latest.json` 不包含该状态。静态链给出的实现边界是: - DFP 注册不会本地生成 `region_ticket`。 - `passport_account_image`、account_security 和请求签名也不会生成它。 - CLI 应在 checker、发码、登录及后续普通 API 响应中检查顶层 `region`,收到后 更新当前 Session Cookie 并按 UID 保存。 - 在首次收到服务端 Region 之前,只能不带该 Cookie;不能构造一个等价票据。 缺少 `region_ticket` 会造成 APP/CLI 请求上下文差异,可能增加风控评分,但静态证据 不足以把 `705` 单独归因于它。要验证因果,应在同一设备画像、同一手机号、同一网络 下只改变该 Cookie,比较 checker 和 mobileVerifyCode 的结果。 ## 复现命令 ```powershell uv run python -m tools.analyze_region_ticket ` ks.har ` nebula.kuaishou.com_2026_07_10_17_15_37.har ` --out out/region_ticket_audit.json uv run python -m unittest tests.test_analyze_region_ticket ```