Commit b683af4a48f6545add98f5b1cea4554de8aaa6b2
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>
Showing
1 changed file
with
122 additions
and
0 deletions
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 层死磕。** |