Commit b683af4a48f6545add98f5b1cea4554de8aaa6b2

Authored by yibo
1 parent 6b47a431

docs(wechat-spoof): 设备名泄露 Pixel 修复全记录(四层根因+排查弯路+根治步骤)

复盘 2026-07-11 从"以为一个 hook 就完事"到"挖了四层才根治"的完整过程:
Java Build.* / native __system_property_get / 系统 settings device_name /
微信自己首装存下的持久 MMKV 缓存(mmkv.default),缺一漏一处。
含无效弯路(Settings hook/分区 prop hook 零命中)、正确解(resetprop+
settings put+清 mmkv 缓存)、按账号不同现象的服务器端记录解释、维护坑。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wechat_pad_login/DEVICE_NAME_LEAK_FIX.md 0 → 100644
  1 +# 微信副设备登录「设备名泄露 Pixel」修复全记录(2026-07-11)
  2 +
  3 +> 一次从「以为一个 hook 就完事」到「挖了四层才根治」的完整复盘。
  4 +> 配套代码:`../xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java`(`hookDeviceSpoof`)、
  5 +> `../xhs_lsposed_a16_fix/wechat-model-spoof.sh`(resetprop 开机脚本)。
  6 +> 背景与平板登录逆向见同目录 `LESSONS.md`;memory `wechat-pad-login-device-spoof`。
  7 +
  8 +---
  9 +
  10 +## 1. 问题现象
  11 +
  12 +微信「登录设备管理 / 账号与安全」里,本机登录记录显示:
  13 +
  14 +```
  15 +你的微信号于 21:24 在 samsung-Pixel10 Pro Fold 设备上通过微信密码登录
  16 +```
  17 +
  18 +- **brand 改到了**(`samsung`,来自 LSPosed 对 `Build.MANUFACTURER` 的伪装)
  19 +- **机型没改**(还是真机 `Pixel 10 Pro Fold`)
  20 +
  21 +→ 伪装只盖住一半,每次登录都以「新设备 / 异常设备」触发安全预警。
  22 +
  23 +更诡异的后续现象:**同一台机、同样的系统属性,火星人账号显示 `SM-X700`(对),小火子账号显示 `Pixel`(错)** —— 按账号不同。这条线索最终揭穿了真正的根因(见 §4)。
  24 +
  25 +---
  26 +
  27 +## 2. 设备身份的「四层」结构(核心认知)
  28 +
  29 +微信读取「机型」不是一条路,而是四层,**任何一层没盖住都会从那层泄露**:
  30 +
  31 +| 层 | 读取源 | 谁能盖 | 本次是否泄露过 |
  32 +|---|---|---|---|
  33 +| ① Java | `android.os.Build.MODEL` / `Build.MANUFACTURER` | LSPosed Java hook | manufacturer 已盖,model 未从这读 |
  34 +| ② native | `__system_property_get("ro.product.model")` 及各分区变体 | **只有 Magisk `resetprop`**(Java hook 盖不到 native) | ✅ 泄露过 |
  35 +| ③ 系统设备名 | `Settings.Global "device_name"` / `Settings.Secure "bluetooth_name"` | `settings put`(改系统 settings 库) | ✅ 泄露过 |
  36 +| ④ **微信自己的持久缓存** | `files/mmkv/mmkv.default`(首装时算一次存下的机型种子)→ `HARDWARE_INFO_MMKV`(二级缓存) | **删缓存逼重采集** | ✅ 泄露过(最坑) |
  37 +
  38 +关键规律:**微信登录注册读的是第 ④ 层缓存,不是每次实时读系统。** 首次安装时把机型算一次存进 `mmkv.default`,之后一直复用。所以前三层全伪装对了,它照样发缓存里的 Pixel。
  39 +
  40 +---
  41 +
  42 +## 3. 排查过程:错误的弯路 vs 正确的落点
  43 +
  44 +### 弯路 A(无效):以为是 `Settings.Global device_name`
  45 +加了 hook `Settings.{Global,Secure,System}.getString` 对 `device_name`/`bluetooth_name` 返回伪装值。
  46 +→ **没用**。微信不走 Java 的 `getString` 包装。
  47 +
  48 +### 弯路 B(无效):以为是分区限定 prop 没盖全
  49 +把 `SystemProperties.get` 的 hook 从写死白名单改成模式匹配 `ro.product.*.model`,又加 `BluetoothAdapter.getName()` hook + 诊断日志。
  50 +→ **登录时诊断零命中**。证明微信**根本没调 Java 层的 `SystemProperties.get`**。
  51 +
  52 +> 💡 **诊断零命中 = 该立刻想到 native 读**。这是本次最大的时间浪费点:Java 层怎么 hook 都不命中,就该马上转向 native / 缓存,而不是继续加 Java hook。
  53 +
  54 +### 正确落点 ②(有效但不够):`resetprop`
  55 +`getprop ro.product.model` 全系统改成 `SM-X700`(native 读也拿到伪装值):
  56 +```sh
  57 +resetprop ro.product.model SM-X700 # + 全分区变体 + marketname
  58 +```
  59 +→ 机型(device type)字段变对了,但**设备名字段仍 Pixel**。
  60 +
  61 +### 正确落点 ③(有效但不够):改系统 settings
  62 +```sh
  63 +settings put global device_name "Galaxy Tab S8"
  64 +settings put secure bluetooth_name "Galaxy Tab S8"
  65 +```
  66 +(持久化,存在 settings 库里,重启也在)
  67 +
  68 +### 正确落点 ④(根治):清微信持久 MMKV 缓存
  69 +即使 ②③ 都做了,`HARDWARE_INFO_MMKV` 重新生成后**仍写 `samsung/Pixel 10 Pro Fold`**,而此时 `getprop`+`settings` 全系统已无任何 "Pixel 10 Pro Fold"。
  70 +→ 说明 Pixel 来自微信**自己的持久缓存种子** `mmkv.default`。
  71 +
  72 +- **只删 `HARDWARE_INFO_MMKV` 没用**:它会从 `mmkv.default` 种子重新长出 Pixel。**必须连 `mmkv.default` 一起删。**
  73 +
  74 +```sh
  75 +am force-stop com.tencent.mm
  76 +# 先备份可回滚
  77 +cp files/mmkv/mmkv.default /data/local/tmp/mmkv.default.bak
  78 +cp files/mmkv/mmkv.default.crc /data/local/tmp/mmkv.default.crc.bak
  79 +# 删种子 + 二级缓存
  80 +rm -f /data/data/com.tencent.mm/files/mmkv/{mmkv.default,mmkv.default.crc,HARDWARE_INFO_MMKV,HARDWARE_INFO_MMKV.crc}
  81 +am start -n com.tencent.mm/.ui.LauncherUI
  82 +# 重新登录 → HARDWARE_INFO_MMKV 重生成 = samsung/SM-X700 ✓
  83 +```
  84 +
  85 +> 删 `mmkv.default` 实测**不登出**(登录态在账号专属存储 / keystore,不在这个默认 MMKV 里)。
  86 +> **不用 `pm clear` / 重装**(那会清掉本地聊天记录 + auto-reply 依赖的 `EnMicroMsg.db`)。删这两个 MMKV 是外科手术版,够用。
  87 +
  88 +---
  89 +
  90 +## 4. 「按账号不同」现象的解释
  91 +
  92 +- **火星人 = SM-X700**:早先某次(本地缓存恰好是 SM-X700 时)注册到微信服务器的旧记录。服务器端设备记录**一旦建立就固定、登录不自动刷新**。
  93 +- **小火子 = Pixel**:删旧记录后重登,新注册读的是**当时的本地缓存 = Pixel**,于是又写成 Pixel。
  94 +
  95 +⇒ 服务器设备名 = 注册当刻的本地缓存值。所以根治必须**先把本地缓存改对(清缓存重采集),每个账号再重新登录一次**让服务器记录刷新。
  96 +
  97 +---
  98 +
  99 +## 5. 最终落地的完整解法(换机 / 重刷照做)
  100 +
  101 +1. **① Java 层**:LSPosed 模块 `WeChatHook.hookDeviceSpoof`(`Build.*` = 三星平板 + `ro.build.characteristics=tablet`)。
  102 +2. **② native 层**:`/data/adb/post-fs-data.d/wechat-model-spoof.sh`(开机 `resetprop ro.product.*.model = SM-X700` 等,全机生效、持久)。
  103 +3. **③ 系统设备名**:`settings put global device_name "Galaxy Tab S8"` + `settings put secure bluetooth_name "Galaxy Tab S8"`(settings 库持久)。
  104 +4. **④ 清微信持久缓存**:删 `files/mmkv/{mmkv.default,HARDWARE_INFO_MMKV}`(+`.crc`)逼重采集。
  105 +5. **每个账号重新登录一次**,让服务器端设备记录刷新成 `samsung SM-X700`。
  106 +
  107 +**验证**:`cat files/mmkv/HARDWARE_INFO_MMKV | strings | grep -iE 'samsung|SM-X|pixel'` 应只见 `samsung / SM-X700`。
  108 +
  109 +---
  110 +
  111 +## 6. 维护注意 / 坑
  112 +
  113 +- **别再 `pm clear` 或重装微信**:否则第 ④ 层缓存会在某个「伪装没就位」的时刻重新焊死 Pixel。真要重装,先确认 ①②③ 都在,再装、再登录。
  114 +- **resetprop 是全机生效**(`getprop` 也变 SM-X700,所有 app 都看到)——放弃了「仅微信进程内」,因为 native prop 读无法 per-app hook(除非上 frida / native inline hook,微信重度混淆 + 反调试,不值)。这台专用 bot 手机可接受。
  115 +- **残留无害**:`WebPrefetchUserAgent` / `AppBrandNetworkConfigUserAgentHelper` / `*_SDKCgiWaitReportMap` 里还有旧 Pixel,是 User-Agent / 网页缓存,与登录设备身份无关,随用自然刷新。
  116 +- **登录态下别改设备身份**:改 hook / resetprop / 清缓存都可能让服务器看到指纹跳变 → 踢下线 + 实名验证(见 `LESSONS.md`)。所有改动务必**在没账号在线时**做,改完再登录。
  117 +
  118 +---
  119 +
  120 +## 7. 一句话总结
  121 +
  122 +> 伪装设备机型别只在 Java 层。App 可能走 **native `__system_property_get`**(要 resetprop)、可能读**系统 settings 设备名**(要改 settings 库)、更可能读**它自己首装时存下的持久缓存**(要删 `mmkv.default` 逼重采集)。四层缺一漏一处。**诊断零命中就是 native / 缓存的信号,别在 Java 层死磕。**
... ...