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

189 lines
9.7 KiB
Markdown
Raw 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.

# 快手极速版 短信登录链路(纯 Python 可复刻路径)
> 来源:`login_gateway_plugin-master.apk`jadx+ 主 app jadx`zvl.a` Retrofit 接口)
> 日期2026-07-24
> 关联:`docs/capture_login_chain.md`(运营商一键登录 = 加密 dataRsp难复刻
## 核心结论
**短信登录是纯 Python 可复刻的登录路径**:请求为明文 form响应为明文 JSON直接返回
`api_st/h5_st/api_client_salt/userInfo`。**无需 pfl/kwsg 解密**(与运营商一键登录的
加密 `dataRsp` 不同)。
唯一非 Python 环节:**接收短信验证码**(需一台能收码的手机/接码服务)--但比运营商
一键登录(需 carrier SDK + SIM + 设备)门槛低得多。
## 登录 APIRetrofit 接口 `zvl.a`,全部 POST form
### 1. 发送短信验证码
```
POST n/user/requestMobileCode # 方法 M@FormUrlEncoded
form:
mobileCountryCode "+86"
mobile "<手机号>"
type 27 # 已存在手机号短信登录
useVoice false
needCheck true
prefetchPhoneNumber ""
requestSource "<来源>"
query:
+ sig / __NS_sig3 / __NS_xfalcon / client_key / os / did / egid / oDid / rdid / ...
-> RequestVerifyCodeResponse { result, isCheck, phone[] }
```
### 2. 验证码登录(拿会话)
```
POST /rest/n/user/login/mobileVerifyCode # 方法 r0@FormUrlEncoded@y0n.d Map
query:
did / egid / oDid / rdid / client_key / os / ...
form (动态 map / FieldMap):
code / mobile / mobileCountryCode / type=27 / isDegraded=false
deviceName / deviceMode / publicKey / raw / secret
+ sig / __NS_sig3 / __NS_xfalcon
-> LoginUserResponse (明文 JSON见下
```
签名细节2026-07-24 Frida + jadx 确认):
- `sig = CPU.getClock(sorted(query + body 非签名字段))`
- `__NS_sig3 = KSecurity.atlasSign(encodedPath + sig)`
- `__NS_xfalcon = KXGS((sig + __NS_sig3).getBytes(), 2096)`
- `encodedPath` 只用于 sig3 与跳过列表判断;`xfalcon` 原始 byte[] **不包含 path**
相关变体(同接口):
- `n/user/login/mobileVerifyCode`r0、`n/user/login/token`s0
`n/user/login/mobile`148、`n/user/login/mobileQuick`197一键
`n/user/login/quickLogin`140、`n/user/login/preCheck`205
`/rest/n/loginRegister/unified/verify`258
## 响应LoginUserResponse明文含完整会话
直接 JSON 字段(`@c` 注解 = JSON key
| 字段 | JSON key | 用途 |
|------|----------|------|
| mApiServiceToken | `kuaishou.api_st` | **API 会话 token** |
| mH5ServiceToken | `kuaishou.h5_st` | **H5 会话 token** |
| mNewTokenClientSalt | `kuaishou.api_client_salt` | **client_salt**__NStokensig 用) |
| mobile / mobileCountryCode | `mobile` / `mobileCountryCode` | 登录手机号 |
| mPassToken | `passToken` | pass token |
| quickloginToken | `quickloginToken` | 一键登录 token |
| userInfo / multiUserInfo | `userInfo` / `multiUserInfo` | 用户信息(含 user_id |
| codeKey / codeUri | `codeKey` / `codeUri` | 二维码登录用 |
| bindPhoneRequired / canLogin / canQuickLogin / isNewRegisterUser / loginType | 同名 | 登录态标志 |
> 响应包装:`Observable<ylm.d<LoginUserResponse>>` -> 标准 KS 信封
> `{result:1, error_msg:"", data:{ kuaishou.api_st, kuaishou.h5_st, kuaishou.api_client_salt, ... }}`
> **明文,非 dataRsp 加密**。
## 与运营商一键登录对比
| 维度 | 短信登录 `mobileVerifyCode` | 运营商一键 `quickLogin` |
|------|----------------------|----------------------|
| 请求 | 明文 form + sig/sig3/xfalcon | 明文 form `provider=N&provider_token=<carrier proto>` |
| 凭证来源 | 手机号 + 收到的短信码 | carrier SDK token需 SIM+设备+SDK |
| 响应 | **明文** LoginUserResponse | **加密** `dataRsp`libpfl未纯 Python 还原) |
| 纯 Python 可行 | **是**(仅缺收码) | 否carrier token + dataRsp 解密两道黑盒) |
## 复刻流程(纯 Python
```
新设备画像DFP bootstrap 在线注册 did/egid/oDid/rdid已还原
POST /rest/n/user/requestMobileCode (mobile=手机号, did=新设备, sig/sig3/xfalcon)
↓ 服务端发短信
[人工/接码] 收到验证码
POST /rest/n/user/login/mobileVerifyCode
(query=设备, body=code/type=27/publicKey/raw/secret/sig/sig3/xfalcon)
↓ 明文响应
LoginUserResponse -> 取 kuaishou.api_st / kuaishou.h5_st / api_client_salt / userInfo
写入 .env / cookie_dict后续任务链即可用新设备+新会话跑
```
## 待确认 / 风险
1. **host**`/rest/n/user/login/...` 走 aegon 网关,确切 host 需实测确认
(候选:`api2.kuaishou.com` / `apissl.ksapisrv.com` / `api.ksapisrv.com`)。
静态未见明文 base URL`zvl.a` 经 `pnm.b.b(1559932927)` DI 创建base URL 在网络模块)。
2. **签名**:高概率走已还原的 `sig/__NS_sig3/__NS_xfalcon`(运营商登录 flow 221 实测带这些,
同一网络层),验码 FieldMap 中已补 `publicKey/raw/secret` 账号保护字段。
3. **请求是否带 encData**`@FormUrlEncoded` + flow 221 明文先例 -> 判定明文 form无 encData。
待实测确认。
4. **风控**:新设备 + 短信登录可能触发设备校验/短信频控/异地登录提示,需小流量验证。
5. **收码**:必须能接收短信(自有手机或接码平台),这是 SMS 登录固有限制。
## 关键文件索引
- Retrofit 接口:`out/jadx/sources/zvl/a.java``M` 发码,`r0` mobileVerifyCode
- `LoginUserResponse`:含 `kuaishou.api_st`/`h5_st`/`api_client_salt`/`userInfo`
- `RequestVerifyCodeResponse``{result, isCheck, phone[]}`
- `login_gateway_plugin-master.apk`:仅 carrier 一键登录(联通/电信/移动 SDK + AuthModel
**无短信路径** -> 短信登录在主 app。
- 主 app 调用点:`com.yxcorp.login.bind.*`ChangePhoneFragment 等)
## 实现已完成2026-07-24
- `core/sms_login.py`
- `login_api_params(profile)` 设备+app 参数(静态协议字段 + `device_profile_cookie_fields` 覆盖)。
- `signed_login_url(path, params, body_pairs, state, base_url, t1, t2)` 签名 URL
`sig` / `__NS_sig3` / `__NS_xfalcon`**无 `__NStokensig`**,登录态未建立)。
- `request_mobile_code(...)` -> `RequestVerifyCodeResponse`(发码)。
- `login_by_code(...)` -> `LoginSession{api_st, h5_st, client_salt, user_id, mobile, pass_token}`
`login/*` 为 Retrofit `@FieldMap`,签名字段放在 form bodyURL query 只放设备参数。
- `parse_login_user_response(data)` 解析明文 `LoginUserResponse`
- sig3 使用进程级 `Kwsg10418State`seed 按 native
`srand(time) -> rand()+1` 现场生成,验证码重放、发码和登录共享同一递增
counter。
- `tools/sms_login_cli.py`CLI 闭环(全新设备画像 -> 在线 DFP 注册 -> 发码 ->
输入码 -> 会话)。每次执行都不读取或保存历史设备、抓包字段及区域票据。
- CLI 只提供 `--mobile`、`--code`、`--base-url`、`--transport`;短信类型、
超时、sig3、region 和验证码重试策略均由内部固定。
## 待实测确认(用 CLI 跑一次真机即可)
```bash
# 真实流程(在线注册全新设备 -> 发码 -> 输入码 -> 会话)
uv run python -m tools.sms_login_cli --mobile <手机号>
# 如果发码请求超时但手机已收到短信,可跳过发码继续验码:
uv run python -m tools.sms_login_cli --mobile <手机号> --code <收到的验证码>
# 可选覆盖登录 host
uv run python -m tools.sms_login_cli --mobile <手机号> --base-url https://api.ksapisrv.com
# 可选切换 HTTP 传输:
uv run python -m tools.sms_login_cli --mobile <手机号> --transport okhttp4-android10
```
`requestMobileCode``mobileVerifyCode` 同时返回 `result=705` 和 HTTPS
`error_url`CLI 自动尝试验证码求解,并在拿到 `captchaToken` 后重放原请求。
每个阶段内部最多重试 2 次。HTTP 求解未拿到 `captchaToken` 时直接停止,不启动
Playwright、Edge/Chrome 或系统浏览器。
当前 `14.5.50.11631` 匿名登录 query 已按 APP 样本对齐 `kcv=1630`,并补齐
`language/ud/bottom_navigation/is_background/icaver/darkMode/ftt`。这些字段参与签名和
服务端设备上下文判断,不能继续使用旧版本默认值。
登录阶段的 `raw/publicKey/secret`、设备画像、手机号密文和
`passport_account_image` 在当前 CLI 流程内固定,验证后重试时保持原请求正文一致。
当请求表现为 `status=0 body={}`CLI 会额外打印底层 `error` 字段。该状态属于
传输失败,不会误判为 705也不会进入验证重试。
实测需确认:
1. **host**:默认 `apissl.ksapisrv.com`;若 `result!=1`/网络错误,再换
`api2.kuaishou.com` / `api.ksapisrv.com` / `api.e.kuaishou.com`
2. **session_seed/counter**seed 是每进程动态值不是固定常量CLI 已纯 Python
复现 native PRNG并从启动后基线 `counter=0x5f` 开始让所有请求连续递增。
CLI 不提供固定 seed/counter 的复现入口。
3. **响应明文**`mobileVerifyCode` 响应应直接含 `kuaishou.api_st`/`h5_st`/`api_client_salt`。
4. **userInfo.user_id 字段名**:解析已兼容 `user_id/userId/eid/uid`
5. **验证码位数 / type 归属**:复核 `PhoneVerifyParams``zvl.a.B(map)` 后确认,
APP 6 位码对应“手机验证页”路径:`requestMobileCode(type=6)` ->
`n/user/verify/mobile`,返回 `ActionResponse`,不是直接登录拿会话。
用户实测 `type=1` 文案为“仅用于注册”不用于登录CLI 直接登录固定使用
`type=27`
登录成功后 CLI 会自动输出可直接写入 `.env`
`ksck="账号#完整 Cookie#client_salt"`,无需额外参数。