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

308 lines
18 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.

# 发现与决策
## 需求
- 用户要求先了解一个黑盒测试比赛。
- 当前工作区:`ksjsb`
- 工具目录:`D:\decode-tools`
## 研究发现
- 工作区初始可见文件:
- `ksjsb.apk`:约 108 MB核心 Android 目标。
- `log-sdk.ksapisrv.com_2026_07_08_12_16_09.har`:约 11 MB。
- `nebula.kuaishou.com_2026_07_08_14_16_43.har`:约 164 MB。
- `2026-07-09-065602-ddecode-toolsfrida.txt`:约 340 KB疑似 Frida 输出。
- `sign_layers_all.log`:约 1.7 KB疑似签名链路日志。
- `1.txt`:约 11 KB内容待确认。
- `D:\decode-tools` 已存在 Android/逆向相关工具:
- `apktool`
- `dex-tools-v2.4`
- `jadx-1.5.5`
- `jadx-gui-1.5.5-with-jre-win`
- `GDA4.04`
- `ghidra_10.4_PUBLIC`
- `ghidra_11.3.2_PUBLIC`
- `IDA_Pro_7.7_Portable`
- `IDA_Pro_8.3`
- `ida93sp2`
- `jeb-pro-3.19.1.202005071620`
- `x64dbg`
- `dnSpy-net-win64`
- `ILSpy_selfcontained_8.2.0.7535-x64`
- `out` 目录并非空目录,已有大量前序分析成果:
- `CURRENT_STATE.md`
- `FINDINGS.md`
- `FINDINGS_VM.md`
- `ks_sign.py`
- 多个 `probe_*.js`、`kste_*.log`、`xfalcon_*.log`
- `jadx`、`jadx16`、`manifest`、`so`、`dex` 子目录
- `1.txt` 明确给出目标:逆向快手极速版看广告接口的 9 个签名/加密参数,
包括 `sig`、`__NS_sig3`、`__NS_xfalcon`、`__NStokensig`、
`encData`、`sign`,以及 `egid`、`oDid`、`did` 设备指纹,
最终转换成 Python 纯算。
- `1.txt` 中的关键链路:
- `sig``fmm/{a,b,c,e,i}.getSig(明文)`,后端疑似 `doCommandNative`
- `__NS_sig3``fmm/g: k.b(sig1, path)``doCommandNative(10418)` / `libkwsgmain`
- `__NS_xfalcon``fmm/{a,b,c,e}: k.a(path, str)`,涉及 `kste VM` / `libksxgs`
- `__NStokensig``fmm/g: k.d(sig2, token)`,记录中判断为 `sha256(sig2+token)`
- `encData/sign`:疑似 `libpfl_crypto` 的 AES/HMAC 类逻辑
- `sign_layers_all.log` 显示已在设备 `PJZ110` 上对
`com.kuaishou.nebula` 及多个子进程进行了 Frida attach
但该文件本身只有 attach/detach 记录,未见具体签名结果。
- 当前核心样本 SHA256
- `ksjsb.apk``EBA577EF45CBA1715CC8C5562022AD5D4B10C1C442578F1C41BD3F05CCC33183`
- `log-sdk...har``7FBCA534370DA5D63694BBC75E341D91A9E12ED97229C04DFB58FA7AEF64754C`
- `nebula...har``57F5A07645C6758761E43102567AB8DE45423C364744351BE695D8A7A098FADE`
- APK 初筛结论:
- 包名:`com.kuaishou.nebula`
- 版本:`14.5.50.11631``versionCode=11631`
- 入口:`com.yxcorp.gifshow.HomeActivity`
- Application`com.yxcorp.gifshow.App`
- SDK`minSdk=21``targetSdk=30`
- 19 个 dex约 100 个 arm64 native so混淆强。
- 不是传统单一壳入口;更像大型业务包 + 自研安全/插件/热修复体系。
- 高关注 so`libkwsgmain.so`、`libksxgs.so`、`libksse.so`、
`libw.so`、`libpfl_crypto.so`、`libsnow.so`、`libshadowhook.so`。
- Manifest 高关注:`allowBackup=false`,未显式 `debuggable`
网络安全配置允许明文流量debug-overrides 信任用户证书。
- HAR 初筛结论:
- `log-sdk...har`681 entries方法分布 `CONNECT=395`、`POST=146`、`GET=139`。
- `nebula...har`1156 entries方法分布 `CONNECT=567`、`GET=511`、`POST=76`。
- 高频业务 endpoint
- `POST adlog.e.kuaishou.com/rest/nebula/log/ad/photo/action`
- `POST log-sdk.ksapisrv.com/rest/wd/common/log/collect/misc2`
- `GET nebula.kuaishou.com/rest/n/nebula/activity/earn/...`
- `POST /rest/e/reward/mixed/ad` 各 HAR 中各 1 条。
- 未发现 `Authorization` header认证/会话主要在 `Cookie` 与 query/body。
- 高频签名/状态字段:`sig`、`__NS_sig3`、`__NS_xfalcon`、
`__NStokensig`、`egid`、`did`、`oDid`、`rdid`、`kuaishou.api_st`、
`client_key`、`cold_launch_time_ms`。
- 原样短期重放可能可行;改参稳定重放必须重算签名。
- 已验证的本地算法/脚本:
- `out/ks_sign.py` 可验证 `sig`、`__NStokensig`、`10400 encData`、
`10418 reward sign`、`10418 __NS_sig3` 的多组样本。
- `out/analyze_reward_samples.py` 共解析 3 条 `/rest/e/reward/mixed/ad`
POST 样本,`sig_reward_form` 全部匹配。
- `out/test_live_reward_log.py`、`out/test_reward_sig.py`、
`out/test_build_reward_request.py` 等单测均通过。
- 当前算法完成度:
- `sig = MD5(sorted(query + formBody, skip sig/sig2/__NS*) + "772867c19925")`
- `__NStokensig = SHA256(sig + clientSalt)`
- `encData = Base64(kwsg 10400 raw)``strE -> 0x266fc block -> inner ZT -> outer ZT`
- `body.sign = kwsg 10418 true-mode(strE, sdk=95147564-...)`
- `__NS_sig3 = kwsg 10418 false-mode(path + sig)`
- `__NS_xfalcon` reward/API 输入链路已接入本地 core 计算。
- reward 广告拉取 strE 已能按当前账号/设备动态生成,并接入
本地 `encData/sign/sig/sig3/tokensig/xfalcon`
- 轻量在线验证中,普通广告/签到广告/宝箱广告拉取均返回
`HTTP=200 result=1 msg=OK`,说明动态 reward body 已解决
换设备后的 `ANTISPAM_REQUEST/INVALID_REQUEST`
- `did/oDid/rdid` 已可本地生成;`egid/cloud did` 已可通过 DFP
bootstrap 在线注册并覆盖;仍需继续处理 H5 `__NS_sig3/kww`
动态生成。
## 技术决策
| 决策 | 理由 |
|------|------|
| 优先做被动侦察 | 当前已有 APK、HAR、Frida 日志,足以先建立证据图 |
| 并行拆分子任务 | APK、HAR、日志、工具链互不依赖可独立读取分析 |
| 主线从 `out/CURRENT_STATE.md` 等已有成果恢复 | 避免重复劳动,当前证据显示已有较深入的 xfalcon/kste 分析 |
| 暂不主动请求生产接口 | 当前阶段目标是了解和复核;主动重放可能消耗账号/服务端状态 |
## 验证结果
- `python out\ks_sign.py`:通过,输出多项 `[OK]`
- `python out\test_reward_sig.py`:退出码 0。
- `python out\test_live_reward_log.py`4 tests OK。
- `python out\test_analyze_xfalcon_te.py`3 tests OK。
- `python out\test_build_reward_request.py`1 test OK。
- `python out\test_extract_kste_vmobj_log.py`1 test OK。
- `python out\analyze_reward_samples.py`:解析 3 条 reward 样本,
`sig_reward_form ok=True``__NS_sig3_calc ok=True`;其中一条
`tokensig` 不匹配当前 `CLIENT_SALT`,判断为不同账号/会话上下文。
## 遇到的问题
| 问题 | 解决方案 |
|------|---------|
| 当前目录不是 Git 仓库 | 不使用 Git 作为变更依据 |
| `.codex\skills\.system\using-superpowers` 不存在 | 改读 `.agents\skills\using-superpowers` |
## 2026-07-27 - passport_account_image 纯算结论
- `passport_account_image` 由客户端 `Engine.pr(99999, 0, mf.a().toString().length()*2, json)`
确定性生成,不是 `/f/a/p` 服务端签发或回写票据。
- 基础层是 `VIMG_` + Base64固定头、Modified UTF-8 长度与正文经 `0x55` XOR
再进入自定义状态的 ChaCha20-IETF。
- `$AI_` 由基础层文本经 16-word XOR 折叠、自定义 IV BLAKE2s 压缩和固定 H2
混合生成;跨 64-word 块使用累计 word counter。
- 21 组不同长度输入与 arm64 `libweapon.so` Unicorn oracle 完全一致。
- CLI 默认按当前 `DeviceProfile` 和现场时间纯算,不读取 app-fields也不依赖
Frida、抓包、APK、ELF 或 Unicorn 运行时。
## 资源
- `D:\decode-tools`
- `ksjsb.apk`
- 两个 HAR 文件
- 已有 Frida/签名日志
## capture/ 登录链路 + APP 初始化实测2026-07-23
- 抓包:`capture/` Reqable body-only 导出1738 文件,**无 URL/header/method**
仅 req/res body请求体多为 kwsg 加密)。
- 抓的是**全新装机**`did=ANDROID_f05497e9cef09a7f`(与 `.env` 账号
`e8dfd2f16b618053` 不同06:32 冷启动,启动时 `uid=0`(未登录)。
- 混入少量酷狗(`com.kugou.android`flow 466/467噪声主体快手 nebula。
- APP 初始化(已定位):
- DFP 设备注册 flow 189`productName=NEBULA&ts&deviceInfo=<urlenc>`
= `gdfp_report`/`unified_fetch`,已还原于 `core/dfp_forms.py`)。
- 配置拉取 flow 171`public_param{uid:0,...}+request_info[{config}]`。
- 域名-IP 路由表 flow 272/274`domains[]{domain,iplist}`(含
`api.e.kuaishou.com`、`id.kuaishou.com`**非登录**)。
- **登录方式 = 运营商一键登录**(非手机号短信):
- flow 221登录提交请求体明文 + 已还原签名):
`provider=11 & provider_token=<protobuf> & session_id & sig & __NS_sig3
& __NS_xfalcon & client_key=2ac2a76d & os=android`
- `provider_token` base64 解码 = protobuf `{kpn="NEBULA",
did="ANDROID_f05497e9cef09a7f", ts, nonce}`,由运营商一键登录 SDK
签发,绑定设备 did。
- `sig/__NS_sig3/__NS_xfalcon` 全部可用已还原算法复算。✓
- 响应:`{"result":1,"bind_interval_ms":604800000}`(设备绑定 7 天);
会话在 flow 222 `{"dataRsp":"<576B加密>","result":1}` 内。
- **会话票据 = libpfl_crypto 层,未纯 Python 还原**
- `dataRsp`576B`head8=7a1c41a6...`**不属于**已还原的 kwsg 10400
ZT envelope`head8=5a54eecd...`,见 `core/enc_data.py:ZT_OUTER_CONFIGS`)。
- 历史 `Pfl.decryptBinaryNative` 用的是 native 桥,纯 Python 解密未还原。
- `api_st`/`h5_st` 也可能下发在响应 headerbody-only 抓包不可见)。
- 交叉验证 `out/FINDINGS.md:2476-2504`:直接换 H5 did/egid ->
`signIn/report`、`treasureBox/report` 返回 `result=50 签名验证失败`
H5 真正缺口是 `kwssectoken/kwscode/kwfv1/kww`Yoda/KsGuard/WebView
运行时票据),**不在 HTTP body 抓包**`kww` 实为请求 header
- "新设备纯 Python 登录"三阻塞点:
① 运营商 `provider_token`(外部黑盒,需 SIM+设备);
`dataRsp` libpfl 解密(未还原纯 Python
`kwssectoken/kwscode/kwfv1/kww`WebView 运行时票据H5 换设备必 result=50
- 详见 `docs/capture_login_chain.md`
## 视觉/浏览器发现
- 暂无。
## 2026-07-27 - region_ticket 静态逆向
- `pnm.b.b(-1479227965)` 绑定到
`com.kwai.framework.network.access.params.e`;其 `l0()` 仅从
`DefaultPreferenceHelper[<uid>_Region]` 读取 `Region.ticket`
- `ResponseDeserializer` 从所有统一响应的顶层 `region` 读取
`uid/name/ticket``u0a.f` 注册的全局 `regions.c` 随后调用
`o2a.c.c(region, "New region received")` 持久化。
- `v0a.c` 把非空 ticket 序列化为请求 Cookie 的 `region_ticket`;没有
客户端生成、签名或 native 计算链。
- `RegionInfo` 的 APK 预置资源只有 API group/host 路由映射,没有 ticket。
- 全部 7 个 HAR 共 8334 entries响应 `region.ticket=0`、响应
`Set-Cookie=0`、请求 Cookie 命中 362共 12 个唯一票据,全部为
`RT_ + 73 hex`、总长 76。抓包开始时状态已经存在未覆盖首次签发。
- 结论:纯 Python 应实现服务端 Region 的接收、按 UID 保存和复用;没有
本地等价生成算法。缺少该 Cookie 是 APP/CLI 差异,但不能据此单独解释 705。
- 详见 `docs/region_ticket_static_chain.md`
## 2026-07-28 - keyconfig 与登录 query 必须分开建模
- APP 最终登录请求的 query 不包含 `client_key/os`;这两个字段由登录接口的
form body 承载,因此 `login_api_params()` 已正确排除它们。
- `system/keyconfig` 与登录接口只共享设备参数子集。APP 抓包显示 keyconfig
query 必须包含 `client_key=2ac2a76d``os=android`
- 此前 `refresh_region_ticket()` 直接复用 `login_api_params()`,登录参数对齐后
意外导致 keyconfig 丢失上述两个字段,服务端 HTTP 200 但响应不含 Region。
- 已新增独立 `keyconfig_api_params()`,仅为 keyconfig 补回 `client_key/os`
同时保持 `oaid/countryCode/sid/deviceName` 不进入该 query。
- APP 成功样本没有调用 `/rest/zt/pass/refresh/anonymousToken`,该接口不是当前
短信登录链路的前置条件。
- 修复只解释最新日志中的 `region_ticket present=False`。历史实测在已有有效
Region、KAW/KAS、VIMG 时仍可能返回 `705`,该部分仍属于服务端设备风险判定,
不能用本次 keyconfig 修复宣称已经解决。
## 2026-07-28 - 705 验证完成后的 captcha_token 重放
- `jlm.a.execute()` 是 APP 的通用 Retrofit Call 包装器;其字段
`f176918d` 非空时会把值以 `captcha_token` 追加到 FormBody/MultipartBody
然后执行被包装的原请求。
- 浏览器链路先由 `kSecretApiVerify` 返回 `captchaToken`,再用该 token 调用
`/rest/wd/captcha/verify` 完成 challenge 绑定。APP 随后不是只同步 Cookie
而是把同一个 token 注入原 API 表单并重新经过签名拦截器。
- CLI 旧实现已经捕获 token`_complete_captcha_handoff()` 只返回布尔值,
token 在 checker/login 重试前被丢弃;因此重试请求仍没有 `captcha_token`
服务端返回新的 705/key。
- `mobile_checker()`、`login_by_code()` 及其路径包装器现已支持
`captcha_token`,该字段参与 `sig/__NS_sig3/__NS_xfalcon/KAS` 的重新计算。
- `system` 浏览器模式改为启动独立系统 Edge/Chrome 并通过本地 CDP 观察响应,
保留可见人工滑动窗口,同时能回传 token 和 Cookie。
## 2026-07-28 - CAPTCHA WebView 设备身份绑定
- 用户实测已经证明 `captcha_token` 被捕获并注入原接口,但每次重放仍得到新的
705/key因此“缺少 captcha_token”只是先前缺口不是当前剩余根因。
- APP 静态链路在 WebView 导航前通过 `CookieInjectManager` 注入公共参数。
`com/yxcorp/gifshow/webview/cookie/f.smali` 明确把 `sys/appver/did` 设为高优先级,
`und/b.smali` 的公共列表还包含 `kpn/kpf/userId/c/ver/language/countryCode/mod/net`
- CLI 的临时 Edge/Chrome 画像此前没有预注入 Cookie验证码页会建立 `web_*` DID
`/rest/wd/captcha/verify result=1` 只证明 Web 挑战成功,不能证明返回 token 与
原 API 的 `ANDROID_*` DID 属于同一身份。
- 验证浏览器现会在首次导航前注入 APP 同形态匿名身份 Cookie并在成功响应后
校验浏览器 DID 必须仍等于设备画像 DID。页面若改写为 `web_*` 或删除 DID
CLI 会停止重放并报告身份不一致,避免继续生成无效的新 challenge key。
- 当前没有证据表明还缺某个普通 HTTP 请求头APP 每次重试本来就会重新生成
`X-REQUESTID`。后续实测应以 `device_identity=matched` 和原接口不再返回新 705
作为联合成功条件。
## 2026-07-28 - 10418 进程状态连续性
- 最新实测已经达到 `/rest/wd/captcha/verify result=1`、`captchaToken` 回传、
39 个 Cookie 同步和 `device_identity=matched`,但重放仍生成新的 705 key
因此普通 header、滑块求解和 Web/Android DID 不一致均不再是首要假设。
- 代码审计发现每个短信端点都会调用 `_sig3_state(session_seed)` 创建新对象,
导致 checker、验证码重放、发码和登录的 10418 counter 全部重复从 1 开始。
- APP 捕获证明 10418 是进程级全局状态:新进程首次可见 counter 约为 `0x60`
后续请求严格递增seed 也不是固定 `0x5d7e742b`
- 捕获的 `session_seed=0x6f5d0faa` 可由进程启动秒 `1785049292` 经 Android
bionic/BSD `srand(time); rand()+1` 精确复现,确认了动态 seed 的来源。
- CLI 现为每次运行创建一份新鲜 `Kwsg10418State`,默认从启动后基线 `0x5f`
开始,并在整条短信链共享;`--session-seed` 和 `--sig3-counter` 可用于样本回放。
## 2026-07-28 - 705 重放结构与实际出站诊断
- 最新实测中动态 sig3 seed/counter 已启用,浏览器绑定仍为 `result=1`,但 API
重放继续返回新 705因此 sig3 状态重置不是当前剩余根因。
- APP 的 705 链通过 `retryWhen` 重新订阅,并在每次订阅时克隆 Retrofit Call
`NetworkSequenceIdInterceptor` 还会重新生成 `X-REQUESTID`。CLI 每次重放生成
新请求 ID 与 APP 一致,不能复用首次请求 ID。
- 705 Activity 回调只把 `RETURN_RESULT` 写入 `jlm.a` 的 token 字段;随后
`jlm.a.execute()` 只向原 FormBody 追加 `captcha_token`。没有 CAPTCHA 专用
query、header 或 cookie因此继续猜测额外验证码字段缺少静态依据。
- `_do_post()` 现从 `requests.Response.request` 读取实际出站请求,脱敏记录:
header 名、body 字段顺序、Cookie 名、captcha token 长度/摘要、请求 ID、
sig3 seed/counter 和 HTTP 版本。不会记录手机号、Cookie 值或 token 明文。
- 浏览器绑定成功日志也打印同算法的 token SHA-256 短摘要;它应与随后
PreparedRequest 中的摘要一致,从而直接验证 token 交接未被替换或损坏。
- 当前 CLI 使用 `requests`,通常通过 HTTP/1.1 和 Python/OpenSSL 指纹访问APP
使用 OkHttp/BoringSSL 且可协商 HTTP/2。若下一次诊断确认 token/body/Cookie
均正确但仍为 HTTP/1.1,传输指纹将成为优先假设,但尚未用同请求 A/B 实测证明。
---
## 2026-07-28 - 705 传输层 A/B
- 用户最新日志证明浏览器绑定 token 与实际 POST 中的 `captcha_token` 摘要完全
一致Region、KAW/KAS 和动态 sig3 counter 也都存在;应用层交接已闭环。
- 静态确认 APP 的 OkHttp 默认协议顺序是 HTTP/2、HTTP/1.1,且 Aegon 的
`CronetInterceptor` 可能接管请求并动态选择 QUIC/H2/TCP所以 APP 不是简单的
“固定 OkHttp HTTP/2”。
- 登录通用链不会添加 `Accept`。原 `requests.Session` 自动携带的
`Accept: */*` 是已确认的出站差异。
- 新增显式 `--transport okhttp4-android10` A/B 路径,使用 curl_cffi 官方文档的
OkHttp 4 Android 10 JA3/Akamai 参考配置,并优先协商 HTTP/2默认仍保留
`requests`,便于同一业务输入做单变量比较。
- HTTP/2 适配层移除 framing 层禁止的 `Connection`,关闭 curl_cffi 浏览器默认
headers保留现有表单原始字节、CookieJar、签名和验证码 token。
- 该参考配置不等于 APP 的精确 Aegon/Cronet 指纹。若 A/B 仍返回 705应采集
APP 实际 ALPN/JA3/HTTP2 SETTINGS 后再替换 profile不能据此倒推缺业务字段。
---
*每执行2次查看/浏览器/搜索操作后更新此文件*
*防止视觉信息丢失*