Commit 01a2cf55f20bdd20c0ddafded6fdc70307da0da8

Authored by yibo
1 parent 6d30843c

docs(xhs_lsposed_a16_fix): 更新 STATUS.md + 修复 update_xhshook_db.sh inode 陷阱

- STATUS.md:更新至 2026-06-30 00:33 验证通过状态,新增捕获到的额外
  签名 header(x-mini-sig/mua/s1/gid、x-legacy-sid、shield),
  补充 curl HTTP 200 复现验证结果,记录 lspd crash 恢复流程
- update_xhshook_db.sh:改用 Termux Python 原地 UPDATE + wal_checkpoint
  代替 cp/adb push(后者改变 inode,lspd FileObserver 监听旧 inode 不刷缓存)
- STATUS.md "已知不行的路线" 新增 adb push DB 和手动重启 lspd 两条陷阱

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xhs_lsposed_a16_fix/STATUS.md
1   -# XHS MITM 进度备忘 — 2026-06-29(Hermes 接手版)
  1 +# XHS MITM 进度备忘 — 2026-06-30(Hermes 接手版)
2 2  
3 3 写给设备上 Hermes Agent 的完整上下文。**直接可用**,无需 Mac 介入的操作都写了命令。
4 4  
... ... @@ -13,7 +13,7 @@
13 13  
14 14 ---
15 15  
16   -## 当前设备状态(2026-06-29 17:22 验证通过)
  16 +## 当前设备状态(2026-06-30 00:33 验证通过)
17 17  
18 18 ```
19 19 设备:Pixel 10 Pro Fold
... ... @@ -22,6 +22,7 @@ Magisk:30.7
22 22 LSPosed (Vector fork):正常运行,lspd 进程存活
23 23 xhshook:注入 com.xingin.xhs 成功
24 24 xy-common-params:已捕获(见下方样本)
  25 +curl HTTP 200:已用捕获参数成功复现 API 调用
25 26 ```
26 27  
27 28 ### 模块布局
... ... @@ -34,9 +35,9 @@ xy-common-params:已捕获(见下方样本)
34 35 │ ├── framework/lspd.dex # 注入 DEX(182840 字节)
35 36 │ ├── daemon # 启动脚本(已修复入口类名)
36 37 │ └── service.sh # Magisk 钩子(已去掉不兼容 flag)
37   -├── xhshook/ # Xposed 模块
  38 +├── xhshook/ # Xposed 模块(备用 APK,随 Magisk 入库)
38 39 │ ├── module.prop
39   -│ └── xhshook.apk # 已更新为 v2(无捆绑 XposedBridge)
  40 +│ └── xhshook.apk # v2(无捆绑 XposedBridge,classes.dex=3400 字节)
40 41 └── xhs_ca/ # MITM 代理 CA 证书
41 42 └── system/etc/security/cacerts/36522bbf.0
42 43 ```
... ... @@ -44,27 +45,30 @@ xy-common-params:已捕获(见下方样本)
44 45 ### xhshook 配置
45 46  
46 47 ```
47   -已安装:com.hermes.xhshook(streamed install,非 incremental)
  48 +已安装:com.hermes.xhshook(streamed install --no-incremental)
48 49 APK 含:assets/xposed_init → com.hermes.xhshook.Hook
49   -modules_config.db:mid=3, enabled=1, scope=com.xingin.xhs
  50 +modules_config.db:mid=1, enabled=1, scope=com.xingin.xhs
  51 +APK 路径:/data/app/~~1V3kNj_Xru_4IO7Ixo56Wg==/com.hermes.xhshook-5c7zNdRgh3RtSyoqISuL_A==/base.apk
50 52 ```
51 53  
52 54 ---
53 55  
54   -## 捕获到的 xy-common-params 样本
  56 +## 捕获到的完整 Header 集合
55 57  
56 58 打开 XHS 后,用 logcat 实时抓:
57 59  
58 60 ```bash
59 61 # 在 Termux 里(需要 root)
60   -su -c "logcat -b all | grep -F 'xy-common-params:'"
  62 +su -c "logcat -b all | grep -E 'xy-common-params:|x-mini-|x-legacy-sid|shield:|xy-platform-info'"
61 63 ```
62 64  
  65 +### xy-common-params(主要签名参数)
  66 +
63 67 示例值(session 级字段每次启动刷新):
64 68  
65 69 ```
66 70 fid=
67   -gid=7c9eca1fb5ac54aae84944d1bb4d85a3591f09d9473599ae77728621
  71 +gid=7c9ea3f0b4d054aae8494006bb4d85a3591f472947359203774768ef
68 72 device_model=phone
69 73 tz=Asia/Shanghai
70 74 channel=YingYongBao
... ... @@ -77,14 +81,33 @@ cpu_abi=arm64-v8a
77 81 nqe_score=89
78 82 project_id=ECFAAF
79 83 app_id=ECFAAF01
80   -id_token=VjEAAN3Pjix... ← 每次启动变
81   -t=1782724968 ← Unix 时间戳,每次变
  84 +id_token=VjEAANwAP8KY... ← 每次启动变
  85 +t=1782750823 ← Unix 时间戳,每次变
82 86 build=9351803
83 87 did=058844e5598e4d001b42a122d1a9d1c2 ← 设备固定
  88 +launch_id=1782750821 ← 每次启动变
84 89 ```
85 90  
86   -**固定字段**(可硬编码复现请求):`deviceId`、`did`、`gid`、`platform`、`versionName`、`build`
87   -**Session 字段**(需要 hook 实时拿):`id_token`、`sid`、`t`
  91 +**固定字段**(可硬编码复现请求):`deviceId`、`did`、`gid`、`platform`、`versionName`、`build`、`channel`
  92 +**Session 字段**(需要 hook 实时拿):`id_token`、`sid`、`t`、`launch_id`
  93 +
  94 +### 额外签名 Header(2026-06-30 新发现)
  95 +
  96 +edith 请求还带以下 header,复现 API 时可能需要:
  97 +
  98 +```
  99 +x-legacy-sid: session.1782715007780944836188 ← 同 sid
  100 +x-mini-gid: 7c9ea3f0b4d054aae8494006bb4d85a3591f... ← 同 gid
  101 +x-mini-s1: AAsAAAABA8AXUVb7YluNAgap... ← 每次变,base64
  102 +x-mini-sig: de47f603a5d8c1894b08a71008c1c9da... ← 每次变,hex
  103 +x-mini-mua: eyJhIjoiRUNGQUFGMDEi... ← JWT 格式,每次变
  104 +xy-direction: 90
  105 +xy-platform-info: platform=android&build=9351803&deviceId=...
  106 +xy-scene: fs=1&point=-1
  107 +shield: XYAAQABAAAAAEAAABTAAAAUzUWEe0x... ← 每次变
  108 +User-Agent: Dalvik/2.1.0 (Linux; U; Android 16; Pixel 10 Pro Fold Build/CP1A.260505.005)
  109 + Resolution/1080*2364 Version/9.35.1 Build/9351803
  110 +```
88 111  
89 112 ---
90 113  
... ... @@ -101,7 +124,12 @@ hook 点:okhttp3.Request$Builder.build()
101 124 输出:logcat tag=VectorLegacyBridge
102 125 ```
103 126  
104   -### 改 hook 的完整流程(需 Mac 操作)
  127 +### 改 hook 的完整流程(**必须在 Mac 上执行,设备端无法独立完成**)
  128 +
  129 +> ⚠️ **Hermes 不要在设备端跑这段流程。**
  130 +> 步骤 2-4 都需要 Mac adb + Mac 上的构建工具。如果只在设备端跑 `pm uninstall`
  131 +> 而不走 Mac 端 `update_xhshook_db.sh`,APK 重装后路径变了,lspd 会在下次启动时
  132 +> 因路径失效而崩溃(modules_config.db 里旧路径 → SQLiteCantOpenDatabaseException)。
105 133  
106 134 ```bash
107 135 # 1. 编辑 Hook.java
... ... @@ -110,12 +138,12 @@ hook 点:okhttp3.Request$Builder.build()
110 138 # 2. 重新编译 + 打包(Mac 上运行)
111 139 bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/rebuild_xhshook.sh
112 140  
113   -# 3. 推到设备
  141 +# 3. 推到设备(Mac adb)
114 142 ADB=~/Documents/LemonAI/pixelphone/platform-tools/adb
115 143 $ADB shell 'pm uninstall com.hermes.xhshook' && \
116 144 $ADB install --no-incremental ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/xhshook_v2_signed.apk
117 145  
118   -# 4. 更新 modules_config.db(脚本自动做)
  146 +# 4. 更新 modules_config.db(Mac 上运行脚本,通过 adb 操作设备)
119 147 bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/update_xhshook_db.sh
120 148  
121 149 # 5. 杀掉 XHS 进程重启(不需要重启整机!)
... ... @@ -126,6 +154,35 @@ $ADB shell &#39;am force-stop com.xingin.xhs&#39;
126 154 $ADB shell 'logcat -d | grep "\[XHSHook\]"'
127 155 ```
128 156  
  157 +### `update_xhshook_db.sh` 的 inode 陷阱(2026-06-30 教训)
  158 +
  159 +`cp` / `adb push` 替换文件会改变 inode,lspd 的 FileObserver 监听的是旧 inode,
  160 +改完后 lspd 内存缓存**不会自动刷新**,GET_BINDER 调用不触发注入。
  161 +
  162 +`update_xhshook_db.sh` 已修复为用 Termux Python **原地 UPDATE**(保留 inode),
  163 +同时做 `wal_checkpoint(TRUNCATE)` 确保写入主文件。**不需要重启 lspd 或重启整机**。
  164 +
  165 +### lspd crash 恢复流程(如果注入彻底失效)
  166 +
  167 +```bash
  168 +# 1. 检查 lspd 日志
  169 +adb shell su -c "tail -50 /data/adb/lspd/log/verbose_$(ls /data/adb/lspd/log/ | tail -1)"
  170 +
  171 +# 2. 检查 modules_config.db 里的 apk_path 是否还匹配已安装的包
  172 +INSTALLED=$(adb shell pm path com.hermes.xhshook | tr -d '\r' | sed 's/package://')
  173 +# 对比 DB 里的路径是否一致
  174 +
  175 +# 3. 如果路径不对,用 update_xhshook_db.sh 修复(Mac 端跑)
  176 +bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/update_xhshook_db.sh
  177 +
  178 +# 4. 如果 lspd 进程本身挂了(pidof lspd 无输出),直接重启设备
  179 +adb reboot
  180 +# 重启后 Magisk 会通过 service.sh 重新拉起 lspd(unshare -m 方式,无法手动模拟)
  181 +
  182 +# 5. 重启后需要 PIN 解锁(FBE CE 存储)才能启动 App
  183 +# 屏幕上输入 PIN → 解锁后手动点 XHS 图标
  184 +```
  185 +
129 186 ### 可以加的下一个 hook:CertificatePinner bypass
130 187  
131 188 xhs_ca 模块已经把 MITM 代理 CA 证书装进系统了。补上 CertificatePinner bypass 后可以直接用 mitmproxy 抓 OkHttp 流量(edith 仍然走 Cronet,但其他接口会走 OkHttp):
... ... @@ -153,7 +210,7 @@ try {
153 210  
154 211 ## 下一步可以做的事(Hermes 接手)
155 212  
156   -### A. 用捕获到的参数复现 XHS API 请求
  213 +### A. 用捕获到的参数复现 XHS API 请求 ✅ curl HTTP 200 已验证
157 214  
158 215 `xy-common-params` 里的固定字段可以直接复用,session 字段需要先打开 XHS 触发 hook 实时拿。
159 216  
... ... @@ -164,27 +221,31 @@ su -c &quot;logcat -c &amp;&amp; am force-stop com.xingin.xhs&quot;
164 221 su -c "logcat -d | grep 'xy-common-params:' | head -1"
165 222 ```
166 223  
167   -拿到后,用 curl 复现请求:
  224 +拿到后,用 curl 复现请求(已验证 HTTP 200):
168 225  
169 226 ```bash
170   -XY_PARAMS="fid=&gid=7c9eca...(完整值)"
  227 +XY_PARAMS="fid=&gid=7c9ea3f0b4d054...(完整值)"
171 228 curl -H "xy-common-params: $XY_PARAMS" \
172   - "https://edith.xiaohongshu.com/api/sns/v1/homefeed?..."
  229 + -H "xy-direction: 90" \
  230 + -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" \
  231 + "https://edith.xiaohongshu.com/api/sns/v3/system_service/flag_exp?fetchSceneTag=&deviceLevel=8"
173 232 ```
174 233  
175   -### B. Hook 更多字段(如果 curl 复现失败)
  234 +### B. 研究 x-mini-sig / shield 签名结构
176 235  
177   -edith 除 `xy-common-params` 外可能还检查其他 header(如 `xy-sign`、cookie)。修改 xhshook 打印所有 edith 请求的全部 header,观察缺什么。
  236 +edith 除 `xy-common-params` 外还带 `x-mini-sig`(hex)、`x-mini-mua`(JWT)、`shield`(自定义 base64)。
  237 +这些字段每次启动都变——研究是否由设备固定字段派生,还是需要服务端签名。
178 238  
179   -Hook.java 里已经打印了 `Headers.toString()` 的完整内容——重新抓一次 logcat 即可看到所有 header(logcat 多行输出,每个 header 一行)。
  239 +Hook.java 里已经打印了 `Headers.toString()` 的完整内容——重新抓一次 logcat 即可看到所有 header:
180 240  
181 241 ```bash
182   -su -c "logcat -d -b all | grep 'VectorLegacyBridge' | grep -v '\[XHSHook\]'" | head -30
  242 +su -c "logcat -d -b all | grep 'VectorLegacyBridge'" | grep -v '\[XHSHook\]' | head -30
183 243 ```
184 244  
185   -### C. 修改 xhshook 做实时转发(高级)
  245 +### C. 修改 xhshook 做实时转发(高级,需要 Mac 改 Hook.java)
186 246  
187   -改 hook 在 `beforeHookedMethod` 里把参数通过 HTTP POST 转到 Hermes 的本地 server(如 `localhost:8765`),让 Hermes 实时处理每条请求。
  247 +改 hook 在 `beforeHookedMethod` 里把完整 header 通过 HTTP POST 转到 Hermes 的本地 server
  248 +(如 `localhost:8765`),让 Hermes 实时处理每条请求的签名参数。
188 249  
189 250 ---
190 251  
... ... @@ -199,7 +260,7 @@ su -c &quot;logcat -d -b all | grep &#39;VectorLegacyBridge&#39; | grep -v &#39;\[XHSHook\]&#39;&quot; | h
199 260 ├── ipc_bridge.cpp ← 已打补丁的源文件
200 261 ├── xhshook_src/
201 262 │ └── Hook.java ← xhshook v2 源码
202   -└── xhshook_v2_signed.apk ← 预构建的 APK(当前已安装版本)
  263 +└── xhshook_v2_signed.apk ← 预构建的 APK(当前已安装版本,classes.dex=3400 字节)
203 264 ```
204 265  
205 266 ---
... ... @@ -211,3 +272,5 @@ su -c &quot;logcat -d -b all | grep &#39;VectorLegacyBridge&#39; | grep -v &#39;\[XHSHook\]&#39;&quot; | h
211 272 - **常驻 Frida daemon 注入 XHS**:XHS 检测到反调试后崩溃(wechat_plain 里有记录,XHS 同理)。
212 273 - **把 Xposed API 类打进 xhshook DEX**:LSPosed 会检查 `XposedBridge.class.getClassLoader() == initLoader`,打进去的话直接 `Cannot load module`。必须用 `compileOnly`。
213 274 - **incremental install(`adb install` 默认)**:Incremental Install 的 marker 会让 `java.util.zip.ZipFile` 解析失败,LSPosed 找不到 `assets/xposed_init`。必须用 `adb install --no-incremental`。
  275 +- **adb push 替换 modules_config.db**:改变文件 inode,lspd FileObserver 监听旧 inode,缓存不刷新,注入会静默失败(有 GET_BINDER 但无 "Loading Vector" 日志)。必须用 Termux Python 原地 UPDATE。
  276 +- **kill lspd 后手动重启**:lspd 是 init 直接子进程,由 Magisk `service.sh` 用 `unshare -m` 拉起,手动 `app_process` 启动缺少必要的 mount namespace,拉不起来。唯一办法是 `adb reboot`。
... ...
xhs_lsposed_a16_fix/update_xhshook_db.sh
... ... @@ -12,6 +12,16 @@ echo &quot;New APK path: $NEW_PATH&quot;
12 12 $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"'
13 13 $ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db
14 14 sqlite3 /tmp/mc_upd.db "UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook';"
15   -$ADB push /tmp/mc_upd.db /data/local/tmp/mc_upd.db
16   -$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"'
17   -echo "DB updated. Now: am force-stop com.xingin.xhs → open XHS manually."
  15 +# Use Python in-place UPDATE to preserve inode → triggers lspd FileObserver.
  16 +# (adb push / cp replaces the file, changing inode, which FileObserver misses.)
  17 +TERMUX_PY=/data/data/com.termux/files/usr/bin/python3
  18 +$ADB shell "su -c \"$TERMUX_PY -c \\\"
  19 +import sqlite3
  20 +db = sqlite3.connect('/data/adb/lspd/config/modules_config.db')
  21 +db.execute(\\\\\\\"UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook'\\\\\\\")
  22 +db.execute('PRAGMA wal_checkpoint(TRUNCATE)')
  23 +db.commit()
  24 +db.close()
  25 +print('done')
  26 +\\\"\"" 2>&1
  27 +echo "DB updated in-place. Now: am force-stop com.xingin.xhs → open XHS manually."
... ...