diff --git a/xhs_lsposed_a16_fix/STATUS.md b/xhs_lsposed_a16_fix/STATUS.md index 7788d02..be99d56 100644 --- a/xhs_lsposed_a16_fix/STATUS.md +++ b/xhs_lsposed_a16_fix/STATUS.md @@ -1,4 +1,4 @@ -# XHS MITM 进度备忘 — 2026-06-29(Hermes 接手版) +# XHS MITM 进度备忘 — 2026-06-30(Hermes 接手版) 写给设备上 Hermes Agent 的完整上下文。**直接可用**,无需 Mac 介入的操作都写了命令。 @@ -13,7 +13,7 @@ --- -## 当前设备状态(2026-06-29 17:22 验证通过) +## 当前设备状态(2026-06-30 00:33 验证通过) ``` 设备:Pixel 10 Pro Fold @@ -22,6 +22,7 @@ Magisk:30.7 LSPosed (Vector fork):正常运行,lspd 进程存活 xhshook:注入 com.xingin.xhs 成功 xy-common-params:已捕获(见下方样本) +curl HTTP 200:已用捕获参数成功复现 API 调用 ``` ### 模块布局 @@ -34,9 +35,9 @@ xy-common-params:已捕获(见下方样本) │ ├── framework/lspd.dex # 注入 DEX(182840 字节) │ ├── daemon # 启动脚本(已修复入口类名) │ └── service.sh # Magisk 钩子(已去掉不兼容 flag) -├── xhshook/ # Xposed 模块 +├── xhshook/ # Xposed 模块(备用 APK,随 Magisk 入库) │ ├── module.prop -│ └── xhshook.apk # 已更新为 v2(无捆绑 XposedBridge) +│ └── xhshook.apk # v2(无捆绑 XposedBridge,classes.dex=3400 字节) └── xhs_ca/ # MITM 代理 CA 证书 └── system/etc/security/cacerts/36522bbf.0 ``` @@ -44,27 +45,30 @@ xy-common-params:已捕获(见下方样本) ### xhshook 配置 ``` -已安装:com.hermes.xhshook(streamed install,非 incremental) +已安装:com.hermes.xhshook(streamed install --no-incremental) APK 含:assets/xposed_init → com.hermes.xhshook.Hook -modules_config.db:mid=3, enabled=1, scope=com.xingin.xhs +modules_config.db:mid=1, enabled=1, scope=com.xingin.xhs +APK 路径:/data/app/~~1V3kNj_Xru_4IO7Ixo56Wg==/com.hermes.xhshook-5c7zNdRgh3RtSyoqISuL_A==/base.apk ``` --- -## 捕获到的 xy-common-params 样本 +## 捕获到的完整 Header 集合 打开 XHS 后,用 logcat 实时抓: ```bash # 在 Termux 里(需要 root) -su -c "logcat -b all | grep -F 'xy-common-params:'" +su -c "logcat -b all | grep -E 'xy-common-params:|x-mini-|x-legacy-sid|shield:|xy-platform-info'" ``` +### xy-common-params(主要签名参数) + 示例值(session 级字段每次启动刷新): ``` fid= -gid=7c9eca1fb5ac54aae84944d1bb4d85a3591f09d9473599ae77728621 +gid=7c9ea3f0b4d054aae8494006bb4d85a3591f472947359203774768ef device_model=phone tz=Asia/Shanghai channel=YingYongBao @@ -77,14 +81,33 @@ cpu_abi=arm64-v8a nqe_score=89 project_id=ECFAAF app_id=ECFAAF01 -id_token=VjEAAN3Pjix... ← 每次启动变 -t=1782724968 ← Unix 时间戳,每次变 +id_token=VjEAANwAP8KY... ← 每次启动变 +t=1782750823 ← Unix 时间戳,每次变 build=9351803 did=058844e5598e4d001b42a122d1a9d1c2 ← 设备固定 +launch_id=1782750821 ← 每次启动变 ``` -**固定字段**(可硬编码复现请求):`deviceId`、`did`、`gid`、`platform`、`versionName`、`build` -**Session 字段**(需要 hook 实时拿):`id_token`、`sid`、`t` +**固定字段**(可硬编码复现请求):`deviceId`、`did`、`gid`、`platform`、`versionName`、`build`、`channel` +**Session 字段**(需要 hook 实时拿):`id_token`、`sid`、`t`、`launch_id` + +### 额外签名 Header(2026-06-30 新发现) + +edith 请求还带以下 header,复现 API 时可能需要: + +``` +x-legacy-sid: session.1782715007780944836188 ← 同 sid +x-mini-gid: 7c9ea3f0b4d054aae8494006bb4d85a3591f... ← 同 gid +x-mini-s1: AAsAAAABA8AXUVb7YluNAgap... ← 每次变,base64 +x-mini-sig: de47f603a5d8c1894b08a71008c1c9da... ← 每次变,hex +x-mini-mua: eyJhIjoiRUNGQUFGMDEi... ← JWT 格式,每次变 +xy-direction: 90 +xy-platform-info: platform=android&build=9351803&deviceId=... +xy-scene: fs=1&point=-1 +shield: XYAAQABAAAAAEAAABTAAAAUzUWEe0x... ← 每次变 +User-Agent: Dalvik/2.1.0 (Linux; U; Android 16; Pixel 10 Pro Fold Build/CP1A.260505.005) + Resolution/1080*2364 Version/9.35.1 Build/9351803 +``` --- @@ -101,7 +124,12 @@ hook 点:okhttp3.Request$Builder.build() 输出:logcat tag=VectorLegacyBridge ``` -### 改 hook 的完整流程(需 Mac 操作) +### 改 hook 的完整流程(**必须在 Mac 上执行,设备端无法独立完成**) + +> ⚠️ **Hermes 不要在设备端跑这段流程。** +> 步骤 2-4 都需要 Mac adb + Mac 上的构建工具。如果只在设备端跑 `pm uninstall` +> 而不走 Mac 端 `update_xhshook_db.sh`,APK 重装后路径变了,lspd 会在下次启动时 +> 因路径失效而崩溃(modules_config.db 里旧路径 → SQLiteCantOpenDatabaseException)。 ```bash # 1. 编辑 Hook.java @@ -110,12 +138,12 @@ hook 点:okhttp3.Request$Builder.build() # 2. 重新编译 + 打包(Mac 上运行) bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/rebuild_xhshook.sh -# 3. 推到设备 +# 3. 推到设备(Mac adb) ADB=~/Documents/LemonAI/pixelphone/platform-tools/adb $ADB shell 'pm uninstall com.hermes.xhshook' && \ $ADB install --no-incremental ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/xhshook_v2_signed.apk -# 4. 更新 modules_config.db(脚本自动做) +# 4. 更新 modules_config.db(Mac 上运行脚本,通过 adb 操作设备) bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/update_xhshook_db.sh # 5. 杀掉 XHS 进程重启(不需要重启整机!) @@ -126,6 +154,35 @@ $ADB shell 'am force-stop com.xingin.xhs' $ADB shell 'logcat -d | grep "\[XHSHook\]"' ``` +### `update_xhshook_db.sh` 的 inode 陷阱(2026-06-30 教训) + +`cp` / `adb push` 替换文件会改变 inode,lspd 的 FileObserver 监听的是旧 inode, +改完后 lspd 内存缓存**不会自动刷新**,GET_BINDER 调用不触发注入。 + +`update_xhshook_db.sh` 已修复为用 Termux Python **原地 UPDATE**(保留 inode), +同时做 `wal_checkpoint(TRUNCATE)` 确保写入主文件。**不需要重启 lspd 或重启整机**。 + +### lspd crash 恢复流程(如果注入彻底失效) + +```bash +# 1. 检查 lspd 日志 +adb shell su -c "tail -50 /data/adb/lspd/log/verbose_$(ls /data/adb/lspd/log/ | tail -1)" + +# 2. 检查 modules_config.db 里的 apk_path 是否还匹配已安装的包 +INSTALLED=$(adb shell pm path com.hermes.xhshook | tr -d '\r' | sed 's/package://') +# 对比 DB 里的路径是否一致 + +# 3. 如果路径不对,用 update_xhshook_db.sh 修复(Mac 端跑) +bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/update_xhshook_db.sh + +# 4. 如果 lspd 进程本身挂了(pidof lspd 无输出),直接重启设备 +adb reboot +# 重启后 Magisk 会通过 service.sh 重新拉起 lspd(unshare -m 方式,无法手动模拟) + +# 5. 重启后需要 PIN 解锁(FBE CE 存储)才能启动 App +# 屏幕上输入 PIN → 解锁后手动点 XHS 图标 +``` + ### 可以加的下一个 hook:CertificatePinner bypass xhs_ca 模块已经把 MITM 代理 CA 证书装进系统了。补上 CertificatePinner bypass 后可以直接用 mitmproxy 抓 OkHttp 流量(edith 仍然走 Cronet,但其他接口会走 OkHttp): @@ -153,7 +210,7 @@ try { ## 下一步可以做的事(Hermes 接手) -### A. 用捕获到的参数复现 XHS API 请求 +### A. 用捕获到的参数复现 XHS API 请求 ✅ curl HTTP 200 已验证 `xy-common-params` 里的固定字段可以直接复用,session 字段需要先打开 XHS 触发 hook 实时拿。 @@ -164,27 +221,31 @@ su -c "logcat -c && am force-stop com.xingin.xhs" su -c "logcat -d | grep 'xy-common-params:' | head -1" ``` -拿到后,用 curl 复现请求: +拿到后,用 curl 复现请求(已验证 HTTP 200): ```bash -XY_PARAMS="fid=&gid=7c9eca...(完整值)" +XY_PARAMS="fid=&gid=7c9ea3f0b4d054...(完整值)" curl -H "xy-common-params: $XY_PARAMS" \ - "https://edith.xiaohongshu.com/api/sns/v1/homefeed?..." + -H "xy-direction: 90" \ + -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 16; Pixel 10 Pro Fold Build/CP1A.260505.005) Resolution/1080*2364 Version/9.35.1 Build/9351803 Device/(Google;Pixel 10 Pro Fold) discover/9.35.1 NetType/WiFi" \ + "https://edith.xiaohongshu.com/api/sns/v3/system_service/flag_exp?fetchSceneTag=&deviceLevel=8" ``` -### B. Hook 更多字段(如果 curl 复现失败) +### B. 研究 x-mini-sig / shield 签名结构 -edith 除 `xy-common-params` 外可能还检查其他 header(如 `xy-sign`、cookie)。修改 xhshook 打印所有 edith 请求的全部 header,观察缺什么。 +edith 除 `xy-common-params` 外还带 `x-mini-sig`(hex)、`x-mini-mua`(JWT)、`shield`(自定义 base64)。 +这些字段每次启动都变——研究是否由设备固定字段派生,还是需要服务端签名。 -Hook.java 里已经打印了 `Headers.toString()` 的完整内容——重新抓一次 logcat 即可看到所有 header(logcat 多行输出,每个 header 一行)。 +Hook.java 里已经打印了 `Headers.toString()` 的完整内容——重新抓一次 logcat 即可看到所有 header: ```bash -su -c "logcat -d -b all | grep 'VectorLegacyBridge' | grep -v '\[XHSHook\]'" | head -30 +su -c "logcat -d -b all | grep 'VectorLegacyBridge'" | grep -v '\[XHSHook\]' | head -30 ``` -### C. 修改 xhshook 做实时转发(高级) +### C. 修改 xhshook 做实时转发(高级,需要 Mac 改 Hook.java) -改 hook 在 `beforeHookedMethod` 里把参数通过 HTTP POST 转到 Hermes 的本地 server(如 `localhost:8765`),让 Hermes 实时处理每条请求。 +改 hook 在 `beforeHookedMethod` 里把完整 header 通过 HTTP POST 转到 Hermes 的本地 server +(如 `localhost:8765`),让 Hermes 实时处理每条请求的签名参数。 --- @@ -199,7 +260,7 @@ su -c "logcat -d -b all | grep 'VectorLegacyBridge' | grep -v '\[XHSHook\]'" | h ├── ipc_bridge.cpp ← 已打补丁的源文件 ├── xhshook_src/ │ └── Hook.java ← xhshook v2 源码 -└── xhshook_v2_signed.apk ← 预构建的 APK(当前已安装版本) +└── xhshook_v2_signed.apk ← 预构建的 APK(当前已安装版本,classes.dex=3400 字节) ``` --- @@ -211,3 +272,5 @@ su -c "logcat -d -b all | grep 'VectorLegacyBridge' | grep -v '\[XHSHook\]'" | h - **常驻 Frida daemon 注入 XHS**:XHS 检测到反调试后崩溃(wechat_plain 里有记录,XHS 同理)。 - **把 Xposed API 类打进 xhshook DEX**:LSPosed 会检查 `XposedBridge.class.getClassLoader() == initLoader`,打进去的话直接 `Cannot load module`。必须用 `compileOnly`。 - **incremental install(`adb install` 默认)**:Incremental Install 的 marker 会让 `java.util.zip.ZipFile` 解析失败,LSPosed 找不到 `assets/xposed_init`。必须用 `adb install --no-incremental`。 +- **adb push 替换 modules_config.db**:改变文件 inode,lspd FileObserver 监听旧 inode,缓存不刷新,注入会静默失败(有 GET_BINDER 但无 "Loading Vector" 日志)。必须用 Termux Python 原地 UPDATE。 +- **kill lspd 后手动重启**:lspd 是 init 直接子进程,由 Magisk `service.sh` 用 `unshare -m` 拉起,手动 `app_process` 启动缺少必要的 mount namespace,拉不起来。唯一办法是 `adb reboot`。 diff --git a/xhs_lsposed_a16_fix/update_xhshook_db.sh b/xhs_lsposed_a16_fix/update_xhshook_db.sh index 58fa6ff..cd544fe 100755 --- a/xhs_lsposed_a16_fix/update_xhshook_db.sh +++ b/xhs_lsposed_a16_fix/update_xhshook_db.sh @@ -12,6 +12,16 @@ echo "New APK path: $NEW_PATH" $ADB shell 'su -c "cp /data/adb/lspd/config/modules_config.db /data/local/tmp/mc_upd.db && chmod 644 /data/local/tmp/mc_upd.db"' $ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db sqlite3 /tmp/mc_upd.db "UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook';" -$ADB push /tmp/mc_upd.db /data/local/tmp/mc_upd.db -$ADB shell 'su -c "cp /data/local/tmp/mc_upd.db /data/adb/lspd/config/modules_config.db && rm -f /data/adb/lspd/config/modules_config.db-wal /data/adb/lspd/config/modules_config.db-shm && echo done"' -echo "DB updated. Now: am force-stop com.xingin.xhs → open XHS manually." +# Use Python in-place UPDATE to preserve inode → triggers lspd FileObserver. +# (adb push / cp replaces the file, changing inode, which FileObserver misses.) +TERMUX_PY=/data/data/com.termux/files/usr/bin/python3 +$ADB shell "su -c \"$TERMUX_PY -c \\\" +import sqlite3 +db = sqlite3.connect('/data/adb/lspd/config/modules_config.db') +db.execute(\\\\\\\"UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook'\\\\\\\") +db.execute('PRAGMA wal_checkpoint(TRUNCATE)') +db.commit() +db.close() +print('done') +\\\"\"" 2>&1 +echo "DB updated in-place. Now: am force-stop com.xingin.xhs → open XHS manually."