ksjsb/docs/region_ticket_static_chain.md
2026-07-30 20:25:56 +08:00

162 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# region_ticket 静态逆向结论
## 结论
`region_ticket` 不是 `sig`、`__NS_sig3` 或 DFP 一类的客户端计算结果。
APK 将它建模为服务端响应顶层 `region.ticket` 中的不透明票据,按用户保存到
`DefaultPreferenceHelper``<uid>_Region`,后续请求再把它放入 Cookie。
因此,纯 Python 的正确实现是“接收、持久化、复用”,不是本地生成:
1. 解析每个普通 API JSON 响应的顶层 `region`
2.`ticket` 时按 `region.uid` 保存;`uid` 为空时使用当前用户 ID。
3. 后续请求注入 `Cookie: region_ticket=<ticket>; __NSWJ=<value>`
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[<uid>_Region] = Region JSON
下一次请求
-> v0a.c.c(sceneName)
-> q01.g.l0()
-> NetworkAccessParams.e.l0()
-> o2a.c.b(Region.class).ticket
-> Cookie: region_ticket=<ticket>; __NSWJ=<value>
```
## 关键证据
### 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
读取键: <QCurrentUser.id>_Region
写入键: <region.uid>_Region
回退键: <QCurrentUser.id>_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
```