docs(huya): 新增总览去困惑地图 + 代码风险标记 + 真机 Frida 稳定注入通道
- docs/HUYA_APP_OVERVIEW.md: 整体流程/三形态hdid区分/登录入口盘点/随机化矩阵/风险清单/真机Frida现状 - core/huya 各模块 docstring 加风险标记与 hdid 名称混淆提示, 指向总览 - tools/app_login_flow 标注已过时(未接注册链), tools/huya_device_profile 标注与 core 策略关系 - scripts/phone_stable_capture.py + diag_phone_lifecycle.py: 真机 spawn+art_callsite 稳定抓帧/四组诊断 - evidence/diag_phone/: 真机基线/attach/稳定抓帧实验证据 (90s 存活无 EGL 崩)
This commit is contained in:
@@ -12,6 +12,11 @@
|
||||
当前继续使用已注册样本;新注册链动态更新的是 safedeviceid 和 device_id。
|
||||
注册链(``core/huya/dfp_register``)每次登录前执行,失败即抛错终止(``HuyaAppLoginError``),
|
||||
不读取画像里的旧固定值,也不静默回退旧链。
|
||||
|
||||
⚠ 风险与边界(详见 ``docs/HUYA_APP_OVERVIEW.md``):
|
||||
- hdid 全账号固定共享 -> 服务端可设备维度关联账号;
|
||||
- 滑块 UA / session / traceId 用金样本常量 -> 与随机机型画像不自洽;
|
||||
- qr_auth(扫码)/dx_auth(短信) 无自动闭环 -> 碰上直接失败(QR_AUTH_REQUIRED)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class HuyaSdidResult:
|
||||
"""sdid 获取结果。"""
|
||||
|
||||
sdid: str = ""
|
||||
hdid: str = "" # df/collect 同响应下发的 40hex 设备ID; 与 WUP 登录的 32hex hdid 非同一体系
|
||||
hdid: str = "" # df/collect 同响应下发的 40hex 设备ID; 与 WUP 登录的 32hex hdid 非同一体系(见 docs/HUYA_APP_OVERVIEW.md §二)
|
||||
source: str = ""
|
||||
message: str = ""
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
``safedeviceid``(RSA action 令牌)与登录帧 ``device_id`` 由
|
||||
:mod:`core.huya.dfp_register` 在每次 WUP 登录前经新设备注册链实时签发,
|
||||
不再写入画像,也不再允许任何固定金样本令牌被多账号复用。
|
||||
|
||||
⚠ 字段状态速览(详见 ``docs/HUYA_APP_OVERVIEW.md`` §四):
|
||||
- 随机: fingerprint / 机型 / 屏幕 / 宽高 / device_id(画像侧);
|
||||
- 固定: hdid(32hex 硬锚,全账号同一) / app_version / sdk_version;
|
||||
- 动态签发: safedeviceid、登录帧 device_id。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
错误语义: 链上任何一步失败(模板缺失/损坏、HTTP 异常、响应缺字段)都会抛
|
||||
``DfpRegistrationError``。调用方(core/huya/app_login)应把注册失败当作明确失败
|
||||
终止登录;禁止静默回退到画像里的旧固定 safedeviceid/device_id。
|
||||
|
||||
⚠ 名称混淆提示(详见 ``docs/HUYA_APP_OVERVIEW.md`` §二):
|
||||
- 这里签发的 t2 = 登录帧 safedeviceid、t5 = 登录帧 device_id(40hex);
|
||||
- 登录帧 t1.t0 的 32hex hdid 是另一体系(libhydeviceid.so 设备ID,服务端硬锚),
|
||||
本模块 ``_random_triple`` 里的 64hex "hdid" 只是随机 cw 的 JSON 占位,非真身份。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ def _build_wup_data(w: _Writer, uid_str: str, sha1_password: str,
|
||||
# -- t1: 设备信息 struct --
|
||||
di = device_info
|
||||
w.struct_begin(1)
|
||||
w.string(0, hdid)
|
||||
w.string(0, hdid) # t1.t0 = 32hex 设备ID (libhydeviceid.so 硬锚, 不可随机/换, 换→APP_SIGN_NOT_MATCH)
|
||||
w.string(1, di.get("app_version", "13.4.22"))
|
||||
w.string(2, di.get("sdk_version", "1.0.80138"))
|
||||
w.string(3, "")
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# 虎牙 App 协议链 总览(去困惑地图)
|
||||
|
||||
> 目的:一张图讲清“整体流程 / 几个 hdid 区分 / 几个登录入口 / 哪里随机 / 哪里有风险”,
|
||||
> 避免在代码和多个零散文档里来回翻。**先看本文,再按需深入具体文档。**
|
||||
>
|
||||
> 关联文档:`docs/dfpReport破解进度.md`(破解细节)、`docs/dfpReport生成流程.md`(零设备注册链)、
|
||||
> `docs/模拟器存活闪退诊断报告.md`(Frida 存活实验,模拟器)、`docs/虎牙纯Python全自动登录-总结与交接.md`。
|
||||
|
||||
---
|
||||
|
||||
## 一、生产登录整体流程(唯一推荐的入口)
|
||||
|
||||
入口:`core/huya/app_login.py` → `login_huya_app_password()` / `HuyaAppPasswordLogin`。
|
||||
Web UI 通过 `web/backend/routers/huya.py` 调用它。
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ 每次 WUP 登录前 │
|
||||
│ ① 新设备注册链 core/huya/dfp_register.register_device() │
|
||||
│ getDfpConfig → selectOperator → dfpReport(随机cw) │
|
||||
│ 返回 (t1, safedeviceid, device_id) ← 全新,非画像旧值 │
|
||||
│ 失败 → 抛 DfpRegistrationError → HuyaAppLoginError,终止,不回退 │
|
||||
│ │
|
||||
│ ② WUP 密码登录 wup_password_login_raw() │
|
||||
│ POST wup.huya.com hypasswordLogin(safedeviceid/device_id 用①的)│
|
||||
│ ├─ 正常 → 解出 114B cred + 真实 uid │
|
||||
│ └─ pt_auth 滑块 → solve_safe_auth() 自动过验 → 重发登录(≤3轮) │
|
||||
│ │
|
||||
│ ③ 本地铸证:nonce_forge(uid+k1) → cert_forge 铸证书 → envelope_forge │
|
||||
│ 补丁 WUP 信封(证书 + UID) │
|
||||
│ ④ 模拟扫码绑定四步流 QrRole(getQrId→scan→bind→tryQrLogin)→ biztoken│
|
||||
│ ⑤ POST /web/cookie/verify 兑换 → 全套网页 Cookie │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**结论**:这是“绕开 App UI、复现协议”的纯协议链。它绕过了滑块(pt_auth),
|
||||
但 **qr_auth(扫码)/ dx_auth(短信)没有自动闭环**——碰上直接失败,属已知边界。
|
||||
|
||||
---
|
||||
|
||||
## 二、几个 “hdid” 的区分(最容易混的点)
|
||||
|
||||
代码里出现三种都叫“hdid”的东西,**不是同一个**:
|
||||
|
||||
| # | 形态 | 示例 | 所在 | 是否登录硬锚 | 能否随机 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | **WUP 登录帧 t1.t0 的 32hex hdid** | `ed0db8334cadd236c00cadf7e11ab5a5` | `core/huya/wup_encoder.py`(`hdid` 参数);`device_profile.HDID` | ✅ **服务端硬锚**,`libhydeviceid.so` native 生成上报,不走 HTTP | ❌ 换掉 → `APP_SIGN_NOT_MATCH` |
|
||||
| 2 | **dfpReport 响应 t5 / device_id(40hex)** | `7c5387e0539c023c31c4ff0e807e7256117385ee` | 注册链 `dfp_register`;登录帧 `device_id` | ❌ 与 #1 非同一体系(`device_fingerprint.py` 有明示) | ✅ 随机 cw 时服务端签发随机值 |
|
||||
| 3 | **注册链随机 cw 里的 “hdid”(64hex sha256)** | — | `dfp_register._random_triple` / `tools/dfp_gen.py` | ❌ 仅凑 JSON 模板形态,服务端不校验 cw 内容 | ✅ 每次随机 |
|
||||
|
||||
**一句话**:#1 是真身份、不可换、全项目唯一;#2 是登录帧 `device_id` 的签发源,动态;
|
||||
#3 是诊断占位。混淆来源主要是 dfpReport JSON 明文里把 40hex 设备 ID 也叫 `hdid`。
|
||||
|
||||
---
|
||||
|
||||
## 三、登录入口盘点(哪个该用、哪些是历史)
|
||||
|
||||
### 生产链(core/huya,Web UI 调用)
|
||||
| 入口 | 功能 | 状态 |
|
||||
|---|---|---|
|
||||
| `core/huya/app_login.py` → `login_huya_app_password` | **App 协议全链路**(①~⑤) | ✅ **推荐**,已接注册链 |
|
||||
| `core/huya/login.py` → `login_huya_password` | Web 密码登录 | ✅ 独立 Web 渠道 |
|
||||
| `core/huya/sms_login.py` → `login_huya_sms` | 短信登录 | ✅ |
|
||||
| `core/huya/auto_register.py` | 手机号自动注册 | ✅ |
|
||||
| `web/backend/routers/huya.py` | Web UI 接口(单账号/批量) | ✅ |
|
||||
|
||||
### 工具 / 历史(tools,试验用,与生产分离,勿在生产调用)
|
||||
| 文件 | 用途 | 注意 |
|
||||
|---|---|---|
|
||||
| `tools/huya_device_register.py` | 注册链 CLI(`--gen` 零设备 / `--login`) | 与 `core/huya/dfp_register` 同逻辑的工具侧版 |
|
||||
| `tools/app_login_flow.py` | 旧 App 登录流 | ⚠ 仍读画像 `safedeviceid` 回退金样本,**未接注册链,已过时** |
|
||||
| `tools/huya_wup_encoder.py` | 旧 WUP 编码器副本 | 正式版在 `core/huya/wup_encoder.py` |
|
||||
| `tools/full_auto_safe.py` / `full_auto_test.py` / `full_web_cookie.py` | 一键试验脚本 | 依赖 `evidence/` 信封/证书模板,非生产 |
|
||||
| `tools/web_cookie_login.py` | 网页版密码登录 | Web 渠道独立 |
|
||||
| `tools/huya_device_profile.py` | 旧画像生成器 | ⚠ 仍含固定 `safedeviceid`,与 core 策略(动态签发)不一致 |
|
||||
|
||||
---
|
||||
|
||||
## 四、随机化矩阵(哪里随机 / 固定 / 动态签发)
|
||||
|
||||
| 字段 | 状态 | 来源 | 风险 |
|
||||
|---|---|---|---|
|
||||
| **hdid (32hex)** | **固定**(全账号同一) | `device_profile.HDID` 金样本 | ⚠ 多账号设备维度关联(主要风险) |
|
||||
| safedeviceid (t2) | **动态签发**(每次登录前) | 注册链 `dfp_register` | 低 |
|
||||
| device_id (t5) | **动态签发**(随机 cw 时为随机值) | 注册链 `dfp_register` | 低 |
|
||||
| fingerprint | **随机**(每账号) | `generate_profile` sha1 | 低 |
|
||||
| 机型 / 屏幕 / 宽高 | **随机**(每账号从 `REAL_MODELS` 选) | `generate_profile` | 低 |
|
||||
| app_version / sdk_version | **固定** | 常量 `13.4.22` / `1.0.80138` | 低(需与 hdid 配套) |
|
||||
| 滑块 UA | **固定** 小米 M2102J2SC | `app_login.APP_UA_MOBILE` | ⚠ 与随机机型画像不一致 |
|
||||
| session / traceId / 登录行为 JSON | **固定金样本** | `_golden_session_assets()` | ⚠ 多账号相同,异常画像 |
|
||||
| QrRole context / reqid / page_id | **随机** | `QrRole`(uuid/random) | 低 |
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与未解决项(诚实清单)
|
||||
|
||||
1. **hdid 固定共享** → 服务端可在设备维度把多个账号关联起来(见 §2 #1)。纯代码无法铸造新 hdid;换掉即 `APP_SIGN_NOT_MATCH`。
|
||||
2. **滑块 UA / session / traceId 固定** → 与随机机型画像不自洽,长期易形成异常画像。
|
||||
3. **qr_auth / dx_auth 无自动闭环** → 碰上直接失败(`QR_AUTH_REQUIRED`)。
|
||||
4. **无稳定性数据** → 没有 20/50/100 次连续成功率、多账号/IP/时段矩阵。
|
||||
5. **Frida attach 主进程必被 msaoaidsec 杀**(真机/模拟器均实测)→ 见 §六。
|
||||
|
||||
---
|
||||
|
||||
## 六、真机 Frida 现状(2026-08-27 实测)
|
||||
|
||||
- **裸 attach 主进程**:真机同样被 `libmsaoaidsec.so` 静默 `_exit`(KeepAlive 换 pid,UI 几乎无感)。模拟器版详见诊断报告。
|
||||
- **真机稳定注入通道(已打通)**:`spawn` 挂起中加载 `bypass_msaoaid_maps_art_callsite.js`(RE 库)再 `resume` → **长时存活 + 可 hook**(90s 无 detached,抓到 `libudbauthunify.so` 基址)。真机 GPU 正常,**不再触发模拟器的 EGL 崩溃**。
|
||||
- 脚本:`scripts/phone_stable_capture.py`(真机稳定抓帧)、`scripts/diag_phone_lifecycle.py`(真机四组对照诊断)。
|
||||
- 含义:真机上做长时 Frida 抓帧/取证是可行的(不再受模拟器 ~4s stale 窗口限制);但**登录/验证码流程仍不要带 frida**(msaoaidsec 会标记)。
|
||||
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"group": "A",
|
||||
"t_spawn": 25.5,
|
||||
"pid": 7289,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 0.4,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 1.2,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 2.0,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 2.7,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 3.4,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 4.0,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 4.8,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 5.5,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 6.2,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 6.9,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 7.6,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 8.3,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 9.0,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 9.7,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 10.4,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 11.1,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 11.7,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 12.4,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 13.1,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 13.9,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 14.5,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 15.3,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 16.0,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 16.7,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 17.4,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 18.1,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 18.8,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 19.5,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 20.2,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 20.9,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 21.7,
|
||||
"proc": 7289,
|
||||
"ui_ok": true,
|
||||
"focus": "902807b mode=0 rootTaskId=276 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
}
|
||||
],
|
||||
"crash_count": 3,
|
||||
"crash_tail": " 00000078345d85f0 x5 0000000000000000 x6 0000000000000000 x7 6a6e6e6aff636473\n08-27 04:55:13.862 31092 31092 F DEBUG : x8 00000000000009c0 x9 0000000000000000 x10 0000000000000000 x11 00000000000000a2\n08-27 04:55:13.862 31092 31092 F DEBUG : x12 0000007885d447f0 x13 0000000000000028 x14 0000007895b19050 x15 0000007885fb3ff0\n08-27 04:55:13.862 31092 31092 F DEBUG : x16 0000000000000000 x17 000000791a2759dc x18 000000791d49a000 x19 0000007885e94640\n08-27 04:55:13.862 31092 31092 F DEBUG : x20 0000007895b19010 x21 0000000000001000 x22 0000007885e87800 x23 0000007885e94630\n08-27 04:55:13.862 31092 31092 F DEBUG : x24 0000007895b19384 x25 000000000000000b x26 0000007885e864c0 x27 000000788d7873f8\n08-27 04:55:13.862 31092 31092 F DEBUG : x28 0000000000000001 x29 0000007fdf595000\n08-27 04:55:13.862 31092 31092 F DEBUG : lr 00000078345db630 sp 0000007fdf595000 pc 00000078345d9910 pst 0000000020001000\n08-27 04:55:13.862 31092 31092 F DEBUG : backtrace:\n08-27 04:55:13.862 31092 31092 F DEBUG : #00 pc 00000078345d9910 <unknown>\n08-27 04:55:13.862 31092 31092 F DEBUG : #01 pc 00000078345db630 <unknown>\n"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"group": "D",
|
||||
"pid": 9944,
|
||||
"attach_ms": 168.2,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 0.2,
|
||||
"proc": 9944,
|
||||
"ui_ok": true,
|
||||
"focus": "dbb4ed9 mode=0 rootTaskId=277 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 0.9,
|
||||
"proc": 9944,
|
||||
"ui_ok": true,
|
||||
"focus": "dbb4ed9 mode=0 rootTaskId=277 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
},
|
||||
{
|
||||
"t": 1.7,
|
||||
"proc": 11364,
|
||||
"ui_ok": true,
|
||||
"focus": "dbb4ed9 mode=0 rootTaskId=277 u0 com.duowan.kiwi/com.duowan.kiwi.homepage.Homepage"
|
||||
}
|
||||
],
|
||||
"detached": [
|
||||
{
|
||||
"reason": "application-requested",
|
||||
"detail": "None"
|
||||
}
|
||||
],
|
||||
"crash_count": 0,
|
||||
"crash_tail": ""
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"type": "mod-base",
|
||||
"tag": "mod-base",
|
||||
"val": "0x77ba455000",
|
||||
"ts": 1787884118264
|
||||
},
|
||||
{
|
||||
"type": "ready",
|
||||
"tag": "ready",
|
||||
"val": "all hooked",
|
||||
"ts": 1787884118265
|
||||
}
|
||||
],
|
||||
"timeline": [
|
||||
{
|
||||
"t": 0.1,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 3.2,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 6.4,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 9.5,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 12.7,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 15.8,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 19.0,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 22.1,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 25.3,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 28.4,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 31.6,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 34.7,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 37.9,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 41.0,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 44.2,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 47.4,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 50.5,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 53.7,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 56.8,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 60.0,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 63.1,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 66.3,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 69.4,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 72.6,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 75.7,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 78.9,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 82.0,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 85.2,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
},
|
||||
{
|
||||
"t": 88.3,
|
||||
"proc": 15977,
|
||||
"pid_changed": false
|
||||
}
|
||||
],
|
||||
"detached": []
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机版 存活/闪退四组对照诊断器 (M2102J2SC 5dd8c93f, fs152 15.2.2).
|
||||
|
||||
背景: 模拟器 GC3VE 上结论 = attach 主进程必死(msaoaidsec 静默 _exit) /
|
||||
spawn+脚本必 EGL 崩。换真机后两项都要重测 —— 真机 GPU 正常、无模拟器特征,
|
||||
msaoaidsec 在真机上的 frida 检测行为是未知数。
|
||||
|
||||
A. baseline 无 frida 正常启动 (应该活)
|
||||
B. spawn-zero spawn 立即 resume, 不加载任何脚本 (模拟器上稳定活)
|
||||
C. spawn-bypass spawn 挂起中加载 art_callsite 补丁后 resume (模拟器上 EGL 崩)
|
||||
D. attach 正常启动 7s 后 attach 主进程, 裸 attach (模拟器上静默 _exit)
|
||||
|
||||
指标(与模拟器版一致, 可交叉对比):
|
||||
proc_main /proc/<pid>/cmdline 精确等于 com.duowan.kiwi
|
||||
ui_focus dumpsys window mCurrentFocus 是否含包名
|
||||
crash logcat -b crash 的 Fatal/signal/Abort 计数
|
||||
detached frida session detached reason/detail
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/diag_phone_lifecycle.py [a|b|c|d] [serial] [remote]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import frida
|
||||
|
||||
SERIAL = sys.argv[2] if len(sys.argv) > 2 else "5dd8c93f"
|
||||
REMOTE = sys.argv[3] if len(sys.argv) > 3 else "127.0.0.1:31878"
|
||||
PACKAGE = "com.duowan.kiwi"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT_DIR = REPO / "evidence" / "diag_phone"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
DURATION = 22.0 # 单轮观察时长(s)
|
||||
|
||||
|
||||
def sh(*a):
|
||||
return subprocess.run(a, capture_output=True, text=True)
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return sh("adb", "-s", SERIAL, *a)
|
||||
|
||||
|
||||
def main_pid():
|
||||
r = adb("shell", "pidof", PACKAGE)
|
||||
for p in r.stdout.strip().split():
|
||||
rr = adb("shell", "cat", f"/proc/{p}/cmdline")
|
||||
if rr.stdout.strip("\x00").strip() == PACKAGE:
|
||||
return int(p)
|
||||
return None
|
||||
|
||||
|
||||
def focus_state():
|
||||
r = adb("shell", "dumpsys", "window")
|
||||
m = re.search(r"mCurrentFocus=Window\{([^}]+)\}", r.stdout)
|
||||
if m:
|
||||
f = m.group(1)
|
||||
return (PACKAGE in f), f
|
||||
return False, "?"
|
||||
|
||||
|
||||
def crash_tail():
|
||||
r = adb("shell", "logcat", "-d", "-b", "crash", "-t", "60")
|
||||
n = r.stdout.count("Fatal") + r.stdout.count("Abort message") + r.stdout.count("signal ")
|
||||
return n, r.stdout[-1200:]
|
||||
|
||||
|
||||
def snapshot(label):
|
||||
p = main_pid()
|
||||
ok, foc = focus_state()
|
||||
return {"label": label, "proc": p, "ui_ok": ok, "focus": foc}
|
||||
|
||||
|
||||
DETACHED = []
|
||||
|
||||
|
||||
def detach_cb(reason, detail):
|
||||
DETACHED.append({"reason": reason, "detail": str(detail)})
|
||||
print(f" [detached] reason={reason} detail={detail}", flush=True)
|
||||
|
||||
|
||||
def run_observe(d, pid, label, extra=None):
|
||||
"""观察进程到超时或主进程死亡, 每 0.5s 记一次存活. 返回 timeline."""
|
||||
t0 = time.time()
|
||||
timeline = []
|
||||
last_pid = pid
|
||||
while time.time() - t0 < DURATION:
|
||||
ok, foc = focus_state()
|
||||
cur = main_pid()
|
||||
timeline.append({"t": round(time.time() - t0, 1), "proc": cur, "ui_ok": ok, "focus": foc})
|
||||
if cur is None or (last_pid is not None and cur != last_pid):
|
||||
break
|
||||
if extra is not None and extra():
|
||||
break
|
||||
time.sleep(0.5)
|
||||
return timeline
|
||||
|
||||
|
||||
def case_a():
|
||||
adb("shell", "logcat", "-c")
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
adb("shell", "monkey", "-p", PACKAGE, "-c", "android.intent.category.LAUNCHER", "1")
|
||||
t0 = time.time()
|
||||
time.sleep(3)
|
||||
pid = main_pid()
|
||||
tl = run_observe(None, pid, "A")
|
||||
return {"group": "A", "t_spawn": round(time.time() - t0, 1), "pid": pid, "timeline": tl}
|
||||
|
||||
|
||||
def case_b():
|
||||
d = frida.get_device_manager().add_remote_device(REMOTE)
|
||||
adb("shell", "logcat", "-c")
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
t0 = time.time()
|
||||
pid = d.spawn([PACKAGE])
|
||||
d.resume(pid)
|
||||
tl = run_observe(d, pid, "B", extra=lambda: False)
|
||||
return {"group": "B", "pid": pid, "t_spawn": round(time.time() - t0, 1),
|
||||
"timeline": tl, "detached": DETACHED}
|
||||
|
||||
|
||||
def case_c():
|
||||
d = frida.get_device_manager().add_remote_device(REMOTE)
|
||||
adb("shell", "logcat", "-c")
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
t0 = time.time()
|
||||
pid = d.spawn([PACKAGE])
|
||||
suspend_start = time.time()
|
||||
session = d.attach(pid)
|
||||
session.on("detached", detach_cb)
|
||||
sc = session.create_script(ART_CALLSITE.read_text())
|
||||
sc.load()
|
||||
suspend_ms = (time.time() - suspend_start) * 1000
|
||||
d.resume(pid)
|
||||
print(f" [C] spawn pid={pid} 挂起{suspend_ms:.0f}ms(art_callsite) resumed", flush=True)
|
||||
tl = run_observe(d, pid, "C", extra=lambda: False)
|
||||
try:
|
||||
session.detach()
|
||||
except Exception:
|
||||
pass
|
||||
return {"group": "C", "pid": pid, "suspend_ms": round(suspend_ms, 1),
|
||||
"timeline": tl, "detached": DETACHED}
|
||||
|
||||
|
||||
def case_d():
|
||||
d = frida.get_device_manager().add_remote_device(REMOTE)
|
||||
adb("shell", "logcat", "-c")
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
adb("shell", "monkey", "-p", PACKAGE, "-c", "android.intent.category.LAUNCHER", "1")
|
||||
time.sleep(7)
|
||||
pid = main_pid()
|
||||
t_att = time.time()
|
||||
try:
|
||||
session = d.attach(pid)
|
||||
session.on("detached", detach_cb)
|
||||
att_ms = (time.time() - t_att) * 1000
|
||||
print(f" [D] attach pid={pid} 耗时{att_ms:.0f}ms", flush=True)
|
||||
except Exception as exc:
|
||||
return {"group": "D", "pid": pid, "error": f"attach失败: {exc}",
|
||||
"detached": DETACHED}
|
||||
tl = run_observe(d, pid, "D", extra=lambda: False)
|
||||
try:
|
||||
session.detach()
|
||||
except Exception:
|
||||
pass
|
||||
return {"group": "D", "pid": pid, "attach_ms": round(att_ms, 1),
|
||||
"timeline": tl, "detached": DETACHED}
|
||||
|
||||
|
||||
def main():
|
||||
which = (sys.argv[1] if len(sys.argv) > 1 else "a").lower()
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
cases = {"a": case_a, "b": case_b, "c": case_c, "d": case_d}
|
||||
result = cases[which]()
|
||||
n, tail = crash_tail()
|
||||
result["crash_count"] = n
|
||||
result["crash_tail"] = tail
|
||||
out = OUT_DIR / f"phone_{which}.json"
|
||||
out.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[{which.upper()}] -> {out}")
|
||||
print(" 主进程最终:", result["timeline"][-1] if result["timeline"] else None)
|
||||
print(" crash:", n)
|
||||
if DETACHED:
|
||||
print(" detached:", DETACHED)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机稳定抓帧: spawn + art_callsite 补丁 + 长时 hook.
|
||||
|
||||
模拟器结论: spawn+脚本必 EGL 崩(约4s), 只能抢 stale 窗口。
|
||||
真机 M2102J2SC 实测: spawn 挂起加载 art_callsite 补丁后 12s+ 稳定存活
|
||||
(无 detached, 无 EGL 崩) —— 本脚本验证两点:
|
||||
1) 更长存活 (默认 90s), 主进程不被换/不闪退 (watch 逻辑)
|
||||
2) hook 能力真实可用: 抓 libudbauthunify.so 的 setSafeDeviceId/createWupDeviceInfo
|
||||
(32hex hdid) + SSL_write (hypasswordLogin 帧) + 内存扫描 32hex
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_stable_capture.py [duration_s] [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/phone_stable_capture.json (事件 + 存活时间线)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import frida
|
||||
|
||||
SERIAL = sys.argv[2] if len(sys.argv) > 2 else "5dd8c93f"
|
||||
REMOTE = sys.argv[3] if len(sys.argv) > 3 else "127.0.0.1:31878"
|
||||
DURATION = float(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].replace(".", "").isdigit() else 90.0
|
||||
PACKAGE = "com.duowan.kiwi"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "phone_stable_capture.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
MAIN_JS = r"""
|
||||
'use strict';
|
||||
function readStdString(p){
|
||||
if (p.isNull()) return null;
|
||||
try{
|
||||
var first = p.readU8();
|
||||
if ((first & 1) === 0) {
|
||||
var len = first >> 1;
|
||||
return (len>0 && len<256) ? p.add(1).readUtf8String(len) : '';
|
||||
} else {
|
||||
var data = p.readPointer();
|
||||
var len = p.add(8).readU64();
|
||||
return (len>0 && len<1024) ? data.readUtf8String(len) : null;
|
||||
}
|
||||
}catch(e){ return null; }
|
||||
}
|
||||
function hexb(p,n){try{return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join('');}catch(e){return '';}}
|
||||
function head(p,n){try{return p.readCString(n);}catch(e){return '';}}
|
||||
function emit(tag,val){ send({type:tag, tag:tag, val:''+(val||''), ts:Date.now()}); }
|
||||
var hooked=false, scanned=false;
|
||||
|
||||
function hookUdb(){
|
||||
if (hooked) return;
|
||||
var md = Process.findModuleByName('libudbauthunify.so');
|
||||
if (!md) return;
|
||||
emit('mod-base', ''+md.base);
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26A2E0), {
|
||||
onEnter:function(a){ this.sd=readStdString(a[1]); this.hd=readStdString(a[2]); },
|
||||
onLeave:function(){
|
||||
var hd=(this.hd||''); emit('SETSD', hd+' | sd='+(this.sd||'').slice(0,24));
|
||||
if(/^[0-9a-f]{32}$/.test(hd)) emit('FOUND_HDID_32', hd);
|
||||
}
|
||||
});
|
||||
}catch(e){ emit('hook-err','setSd '+e); }
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x2746A0), {
|
||||
onLeave:function(ret){
|
||||
try{ var s=readStdString(ret.add(152)); if(s){ emit('WUPDEV_HDID',''+s); if(/^[0-9a-f]{32}$/.test(s)) emit('FOUND_HDID_32',''+s); } }catch(e){}
|
||||
}
|
||||
});
|
||||
}catch(e){ emit('hook-err','wupdev '+e); }
|
||||
emit('ready','all hooked');
|
||||
hooked = true;
|
||||
}
|
||||
|
||||
function hookSSL(){
|
||||
try{
|
||||
var r=new ApiResolver('module');
|
||||
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
|
||||
Interceptor.attach(m.address,{onEnter:function(a){
|
||||
var len=a[2].toInt32();
|
||||
if(len<50||len>50000)return;
|
||||
var h=head(a[1],Math.min(len,600));
|
||||
if(h.indexOf('hypasswordLogin')>=0){
|
||||
emit('WUP_LOGIN_FULL', len+'B '+hexb(a[1],len));
|
||||
}
|
||||
}});
|
||||
});
|
||||
send({type:'sslwrite-hooked', tag:'ssl'});
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
function scanMem(){
|
||||
var found = {};
|
||||
try{
|
||||
var ranges = Process.enumerateRanges({protection:'r--', coalesce:true});
|
||||
var total = ranges.length, scannedB = 0;
|
||||
ranges.forEach(function(r, ri){
|
||||
try{
|
||||
var size = r.size;
|
||||
if (size <= 0) return;
|
||||
var step = 256*1024;
|
||||
var off = 0;
|
||||
while (off < size){
|
||||
var n = Math.min(step, size - off);
|
||||
var buf = Memory.readByteArray(r.base.add(off), n);
|
||||
if (buf){
|
||||
var sbuf = new Uint8Array(buf);
|
||||
var s = '';
|
||||
for (var i=0;i<sbuf.length;i++) s += String.fromCharCode(sbuf[i]);
|
||||
var re = /[0-9a-f]{32,40}/gi;
|
||||
var m;
|
||||
while ((m = re.exec(s)) !== null){
|
||||
var v = m[0].toLowerCase();
|
||||
if (!/^[0-9a-f]{32}$/.test(v)) continue;
|
||||
found[v] = (found[v]||0) + 1;
|
||||
}
|
||||
scannedB += n;
|
||||
}
|
||||
off += step;
|
||||
}
|
||||
}catch(e){}
|
||||
});
|
||||
}catch(e){ emit('scan-err',''+e); }
|
||||
var keys = Object.keys(found);
|
||||
emit('scan-result', 'ranges='+total+' scannedB='+scannedB+' found32+='+keys.length);
|
||||
keys.sort().forEach(function(k, i){
|
||||
if (i < 60) emit('H32CAND', k+' x'+found[k]);
|
||||
});
|
||||
scanned = true;
|
||||
}
|
||||
|
||||
setInterval(hookUdb, 100);
|
||||
setInterval(hookSSL, 1000);
|
||||
var scanLaunched = false;
|
||||
setInterval(function(){
|
||||
if (!scanLaunched && hooked && !scanned){
|
||||
scanLaunched = true;
|
||||
setTimeout(scanMem, 2500);
|
||||
}
|
||||
}, 500);
|
||||
"""
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
|
||||
|
||||
|
||||
def main_pid():
|
||||
r = adb("shell", "pidof", PACKAGE)
|
||||
for p in r.stdout.strip().split():
|
||||
rr = adb("shell", "cat", f"/proc/{p}/cmdline")
|
||||
if rr.stdout.strip("\x00").strip() == PACKAGE:
|
||||
return int(p)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
adb("shell", "logcat", "-c")
|
||||
|
||||
d = frida.get_device_manager().add_remote_device(REMOTE)
|
||||
pid = d.spawn([PACKAGE])
|
||||
print(f"[*] spawn pid={pid}", flush=True)
|
||||
session = d.attach(pid)
|
||||
det = []
|
||||
session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True))
|
||||
|
||||
def load(js, name):
|
||||
s = session.create_script(js); s.load()
|
||||
print(f"[*] loaded {name}", flush=True)
|
||||
return s
|
||||
|
||||
try:
|
||||
load(ART_CALLSITE.read_text(), "art_callsite")
|
||||
except Exception as e:
|
||||
print("[*] bypass load err:", str(e)[:160], flush=True)
|
||||
d.resume(pid)
|
||||
print("[*] resumed, running", DURATION, "s", flush=True)
|
||||
|
||||
events = []
|
||||
def on_message(m, data):
|
||||
if m.get("type") == "error":
|
||||
print("[JS-ERR]", str(m)[:200], flush=True)
|
||||
return
|
||||
p = m.get("payload") or {}
|
||||
t = p.get("type")
|
||||
if t == "FOUND_HDID_32":
|
||||
print(f"\n*** FOUND_HDID_32 = {p.get('val')} ***", flush=True)
|
||||
elif t in ("mod-base", "ready", "scan-result", "sslwrite-hooked", "hook-err"):
|
||||
print(f"[{t}] {p.get('val')}", flush=True)
|
||||
elif t == "WUP_LOGIN_FULL":
|
||||
b = bytes.fromhex(p.get("val", "").split("B ")[-1]) if "B " in p.get("val", "") else b""
|
||||
h = re.search(rb"hdid.{0,100}", b)
|
||||
print(f"[WUP_LOGIN] len={p.get('val','').split('B')[0]} ctx={(h.group(0)[:110] if h else b[:60])}", flush=True)
|
||||
events.append(p)
|
||||
OUT.write_text(json.dumps({"events": events, "detached": det}, indent=1), encoding="utf-8")
|
||||
|
||||
main_sc = load(MAIN_JS, "main_hook")
|
||||
main_sc.on("message", on_message)
|
||||
|
||||
t0 = time.time()
|
||||
timeline = []
|
||||
start_pid = pid
|
||||
while time.time() - t0 < DURATION:
|
||||
cur = main_pid()
|
||||
timeline.append({"t": round(time.time() - t0, 1), "proc": cur, "pid_changed": cur != start_pid})
|
||||
if cur is None or cur != start_pid:
|
||||
print(f"[*] 主进程 pid 变化/消失: {start_pid} -> {cur}", flush=True)
|
||||
break
|
||||
time.sleep(3)
|
||||
print(f"[*] done, {len(events)} events, alive_pid={start_pid == main_pid()}", flush=True)
|
||||
OUT.write_text(json.dumps({"events": events, "timeline": timeline, "detached": det}, indent=1), encoding="utf-8")
|
||||
print(f"[*] saved {OUT}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""虎牙App渠道纯Python登录流 —— 含新账号 safe_auth 滑块自动过验。
|
||||
|
||||
⚠ 已过时:本工具仍读取画像里的固定 safedeviceid 并在缺失时回退金样本,**未接
|
||||
``core/huya/dfp_register`` 新设备注册链**。生产请用 ``core/huya/app_login.py``
|
||||
(每次登录前注册链动态签发 safedeviceid/device_id)。本文件仅作历史参考。
|
||||
|
||||
链路(全部实测/逆向自 pt_auth 页JS, 见 work/safe_auth/pt_auth.pretty.js):
|
||||
账号密码 -> WUP hypasswordLogin (POST wup.huya.com)
|
||||
|- 正常: 响应bean直出新鲜cred(114B)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""多账号设备画像生成器 —— 每账号一套独立设备身份 (2026-08-26 实测定论)。
|
||||
|
||||
⚠ 本文件是 tools 侧的独立试验版画像器。core 生产链用的是
|
||||
``core/huya/device_profile.py`` + ``core/huya/dfp_register``(safedeviceid/登录帧
|
||||
device_id 每次登录前动态签发,画像不再写入固定 safedeviceid)。
|
||||
|
||||
背景 (服务端校验矩阵, 全部对 wup.huya.com hypasswordLogin 实测):
|
||||
┌──────────────────┬───────────────────────────────────────────────┐
|
||||
│ 字段 │ 换值表现 │
|
||||
|
||||
Reference in New Issue
Block a user