# XHS MITM 进度备忘 — 2026-06-30(Hermes 接手版) 写给设备上 Hermes Agent 的完整上下文。**直接可用**,无需 Mac 介入的操作都写了命令。 --- ## 战役目标:✅ 已完成 抓 `edith.xiaohongshu.com` 的 `xy-common-params`(ECFAAF/ECFAAF01 签名参数),用于复现 XHS 请求鉴权。 **核心障碍**:edith 用 Google Mainline Cronet + 静态链接 BoringSSL,Frida Interceptor 无法 hook 静态符号。 **解法**:LSPosed(JingMatrix/Vector fork)在 XHS 进程 fork 时注入,hook OkHttp Java 层,在签名传入 Cronet 前拦截。 --- ## 当前设备状态(2026-06-30 01:17 验证通过) ``` 设备:Pixel 10 Pro Fold Android:16 (API 36) Magisk:30.7 LSPosed (Vector fork):正常运行,lspd 进程存活 xhshook v3:注入 com.xingin.xhs 成功(mid=4,APK=16863 字节,DEX=6328 字节) xy-common-params:已捕获(见下方样本) curl HTTP 200:已用捕获参数成功复现 API 调用 xhshook HTTP 转发:POST /xhs_params 已实现,event_server.py 已更新 event_server.py:已推送到设备 ~/.hermes/event_bus/event_server.py ``` ### 模块布局 ``` /data/adb/modules/ ├── zygisk_vector/ # LSPosed Vector fork(含 A16 JNI size fix) │ ├── zygisk/arm64-v8a.so # 核心注入 so(1142464 字节) │ ├── daemon.apk # Java 守护进程(3199711 字节) │ ├── framework/lspd.dex # 注入 DEX(182840 字节) │ ├── daemon # 启动脚本(已修复入口类名) │ └── service.sh # Magisk 钩子(已去掉不兼容 flag) ├── xhshook/ # Xposed 模块(备用 APK,随 Magisk 入库) │ ├── module.prop │ └── xhshook.apk # v2(无捆绑 XposedBridge,classes.dex=3400 字节) └── xhs_ca/ # MITM 代理 CA 证书 └── system/etc/security/cacerts/36522bbf.0 ``` ### xhshook 配置 ``` 已安装:com.hermes.xhshook(streamed install --no-incremental) APK 含:assets/xposed_init → com.hermes.xhshook.Hook modules_config.db:mid=4, enabled=1, scope=(4, com.xingin.xhs, 0) APK 路径:/data/app/~~F2OUnKKEvaoaCC4wwrGOsg==/com.hermes.xhshook-NAAljEo_GRPgf6S_5pp7kw==/base.apk xhshook v3 新增:HTTP POST 转发到 127.0.0.1:8765/xhs_params(后台线程,1s 超时,静默失败) ``` --- ## 捕获到的完整 Header 集合 打开 XHS 后,用 logcat 实时抓: ```bash # 在 Termux 里(需要 root) 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=7c9ea3f0b4d054aae8494006bb4d85a3591f472947359203774768ef device_model=phone tz=Asia/Shanghai channel=YingYongBao versionName=9.35.1 deviceId=748327bc-6079-3b06-9351-1638281dbc6f ← 设备固定 platform=android sid=session.1782715007780944836188 ← 每次启动变 identifier_flag=4 cpu_abi=arm64-v8a nqe_score=89 project_id=ECFAAF app_id=ECFAAF01 id_token=VjEAANwAP8KY... ← 每次启动变 t=1782750823 ← Unix 时间戳,每次变 build=9351803 did=058844e5598e4d001b42a122d1a9d1c2 ← 设备固定 launch_id=1782750821 ← 每次启动变 ``` **固定字段**(可硬编码复现请求):`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 ``` --- ## xhshook 架构(Hermes 改 hook 用) ### Hook 逻辑(当前 v2) ```java // 源码位置(设备):/sdcard/Download/xhs_lsposed_a16_fix/xhshook_src/Hook.java // 源码位置(Mac):~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/xhshook_src/Hook.java hook 点:okhttp3.Request$Builder.build() 过滤:只打印 edith.xiaohongshu.com 的请求 输出:logcat tag=VectorLegacyBridge ``` ### 改 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 # ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/xhshook_src/Hook.java # 2. 重新编译 + 打包(Mac 上运行) bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/rebuild_xhshook.sh # 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(Mac 上运行脚本,通过 adb 操作设备) bash ~/Documents/LemonAI/pixelphone/xhs_lsposed_a16_fix/update_xhshook_db.sh # 5. 杀掉 XHS 进程重启(不需要重启整机!) $ADB shell 'am force-stop com.xingin.xhs' # 然后手动在屏幕上点 XHS 图标 # 6. 验证 $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): ```java // 在 handleLoadPackage 里加(XHS 包名判断之后): try { XposedHelpers.findAndHookMethod( "okhttp3.CertificatePinner", lpparam.classLoader, "check", String.class, List.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.setResult(null); } } ); XposedBridge.log("[XHSHook] CertificatePinner bypassed!"); } catch (Throwable t) { XposedBridge.log("[XHSHook] CertificatePinner bypass failed: " + t); } ``` --- ## 下一步可以做的事(Hermes 接手) ### A. 用捕获到的参数复现 XHS API 请求 ✅ curl HTTP 200 已验证 `xy-common-params` 里的固定字段可以直接复用,session 字段需要先打开 XHS 触发 hook 实时拿。 ```bash # Termux 里实时抓一次完整的 xy-common-params su -c "logcat -c && am force-stop com.xingin.xhs" # → 手动打开 XHS → su -c "logcat -d | grep 'xy-common-params:' | head -1" ``` 拿到后,用 curl 复现请求(已验证 HTTP 200): ```bash XY_PARAMS="fid=&gid=7c9ea3f0b4d054...(完整值)" curl -H "xy-common-params: $XY_PARAMS" \ -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. 研究 x-mini-sig / shield 签名结构 edith 除 `xy-common-params` 外还带 `x-mini-sig`(hex)、`x-mini-mua`(JWT)、`shield`(自定义 base64)。 这些字段每次启动都变——研究是否由设备固定字段派生,还是需要服务端签名。 Hook.java 里已经打印了 `Headers.toString()` 的完整内容——重新抓一次 logcat 即可看到所有 header: ```bash su -c "logcat -d -b all | grep 'VectorLegacyBridge'" | grep -v '\[XHSHook\]' | head -30 ``` ### C. 实时转发 → Hermes event_server ✅ 已完成(2026-06-30 01:17) xhshook v3(Hook.java)已在 `beforeHookedMethod` 里启动后台线程 HTTP POST 到 `127.0.0.1:8765/xhs_params`。 event_server.py 已新增两个端点(已推送到设备 `~/.hermes/event_bus/event_server.py`): ```bash # Hermes 启动 event_server 后(uvicorn event_server:app --host 127.0.0.1 --port 8765), # 打开 XHS,hook 自动转发 edith 请求,Hermes 可以查询: # 查最近捕获的 XHS 请求(含完整 headers) curl http://127.0.0.1:8765/xhs_params # 或通过通用 events 接口 curl "http://127.0.0.1:8765/events/latest?type=xhs_request&app=com.xingin.xhs" ``` 转发的 JSON 格式:`{"method":"GET","url":"https://edith.xiaohongshu.com/...","headers":{"xy-common-params":"...","x-mini-sig":"...",...}}` **启动 event_server 步骤(Termux 里):** ```bash cd ~/.hermes/event_bus uvicorn event_server:app --host 127.0.0.1 --port 8765 # 然后打开 XHS,xhshook 会自动 POST 每条 edith 请求过来 ``` --- ## 文件索引(设备上) ``` /sdcard/Download/xhs_lsposed_a16_fix/ ├── STATUS.md ← 本文件 ├── CLAUDE.md ← 项目说明(构建/部署命令) ├── apply_a16_fix.sh ← A16 补丁脚本(已应用,留作参考) ├── ipc_bridge.h ← 已打补丁的头文件 ├── ipc_bridge.cpp ← 已打补丁的源文件 ├── xhshook_src/ │ └── Hook.java ← xhshook v3 源码(含 HTTP 转发到 :8765/xhs_params) └── xhshook_v2_signed.apk ← 预构建的 APK(当前已安装版本,classes.dex=6328 字节,含 HTTP 转发) ~/.hermes/event_bus/ └── event_server.py ← FastAPI event bus,含 POST/GET /xhs_params 端点 ``` --- ## 已知不行的路线(别重新踩) - **Frida hook edith.xiaohongshu.com**:Cronet 静态链接 BoringSSL,Frida Interceptor 找不到符号。SSL_read/SSL_write 都 hook 不上。 - **Java TrustManager 替换**:时序问题,edith 在 app 启动很早就开始通信,Frida 注入晚了窗口就过了。LSPosed 没有这个问题。 - **常驻 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`。