162 lines
5.6 KiB
Markdown
162 lines
5.6 KiB
Markdown
# 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
|
||
```
|