189 lines
9.7 KiB
Markdown
189 lines
9.7 KiB
Markdown
# 快手极速版 短信登录链路(纯 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 + 设备)门槛低得多。
|
||
|
||
## 登录 API(Retrofit 接口 `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 body,URL 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"`,无需额外参数。
|