From b683af4a48f6545add98f5b1cea4554de8aaa6b2 Mon Sep 17 00:00:00 2001 From: yibo Date: Sat, 11 Jul 2026 23:08:18 +0800 Subject: [PATCH] docs(wechat-spoof): 设备名泄露 Pixel 修复全记录(四层根因+排查弯路+根治步骤) --- wechat_pad_login/DEVICE_NAME_LEAK_FIX.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+), 0 deletions(-) create mode 100644 wechat_pad_login/DEVICE_NAME_LEAK_FIX.md diff --git a/wechat_pad_login/DEVICE_NAME_LEAK_FIX.md b/wechat_pad_login/DEVICE_NAME_LEAK_FIX.md new file mode 100644 index 0000000..a74c12a --- /dev/null +++ b/wechat_pad_login/DEVICE_NAME_LEAK_FIX.md @@ -0,0 +1,122 @@ +# 微信副设备登录「设备名泄露 Pixel」修复全记录(2026-07-11) + +> 一次从「以为一个 hook 就完事」到「挖了四层才根治」的完整复盘。 +> 配套代码:`../xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java`(`hookDeviceSpoof`)、 +> `../xhs_lsposed_a16_fix/wechat-model-spoof.sh`(resetprop 开机脚本)。 +> 背景与平板登录逆向见同目录 `LESSONS.md`;memory `wechat-pad-login-device-spoof`。 + +--- + +## 1. 问题现象 + +微信「登录设备管理 / 账号与安全」里,本机登录记录显示: + +``` +你的微信号于 21:24 在 samsung-Pixel10 Pro Fold 设备上通过微信密码登录 +``` + +- **brand 改到了**(`samsung`,来自 LSPosed 对 `Build.MANUFACTURER` 的伪装) +- **机型没改**(还是真机 `Pixel 10 Pro Fold`) + +→ 伪装只盖住一半,每次登录都以「新设备 / 异常设备」触发安全预警。 + +更诡异的后续现象:**同一台机、同样的系统属性,火星人账号显示 `SM-X700`(对),小火子账号显示 `Pixel`(错)** —— 按账号不同。这条线索最终揭穿了真正的根因(见 §4)。 + +--- + +## 2. 设备身份的「四层」结构(核心认知) + +微信读取「机型」不是一条路,而是四层,**任何一层没盖住都会从那层泄露**: + +| 层 | 读取源 | 谁能盖 | 本次是否泄露过 | +|---|---|---|---| +| ① Java | `android.os.Build.MODEL` / `Build.MANUFACTURER` | LSPosed Java hook | manufacturer 已盖,model 未从这读 | +| ② native | `__system_property_get("ro.product.model")` 及各分区变体 | **只有 Magisk `resetprop`**(Java hook 盖不到 native) | ✅ 泄露过 | +| ③ 系统设备名 | `Settings.Global "device_name"` / `Settings.Secure "bluetooth_name"` | `settings put`(改系统 settings 库) | ✅ 泄露过 | +| ④ **微信自己的持久缓存** | `files/mmkv/mmkv.default`(首装时算一次存下的机型种子)→ `HARDWARE_INFO_MMKV`(二级缓存) | **删缓存逼重采集** | ✅ 泄露过(最坑) | + +关键规律:**微信登录注册读的是第 ④ 层缓存,不是每次实时读系统。** 首次安装时把机型算一次存进 `mmkv.default`,之后一直复用。所以前三层全伪装对了,它照样发缓存里的 Pixel。 + +--- + +## 3. 排查过程:错误的弯路 vs 正确的落点 + +### 弯路 A(无效):以为是 `Settings.Global device_name` +加了 hook `Settings.{Global,Secure,System}.getString` 对 `device_name`/`bluetooth_name` 返回伪装值。 +→ **没用**。微信不走 Java 的 `getString` 包装。 + +### 弯路 B(无效):以为是分区限定 prop 没盖全 +把 `SystemProperties.get` 的 hook 从写死白名单改成模式匹配 `ro.product.*.model`,又加 `BluetoothAdapter.getName()` hook + 诊断日志。 +→ **登录时诊断零命中**。证明微信**根本没调 Java 层的 `SystemProperties.get`**。 + +> 💡 **诊断零命中 = 该立刻想到 native 读**。这是本次最大的时间浪费点:Java 层怎么 hook 都不命中,就该马上转向 native / 缓存,而不是继续加 Java hook。 + +### 正确落点 ②(有效但不够):`resetprop` +`getprop ro.product.model` 全系统改成 `SM-X700`(native 读也拿到伪装值): +```sh +resetprop ro.product.model SM-X700 # + 全分区变体 + marketname +``` +→ 机型(device type)字段变对了,但**设备名字段仍 Pixel**。 + +### 正确落点 ③(有效但不够):改系统 settings +```sh +settings put global device_name "Galaxy Tab S8" +settings put secure bluetooth_name "Galaxy Tab S8" +``` +(持久化,存在 settings 库里,重启也在) + +### 正确落点 ④(根治):清微信持久 MMKV 缓存 +即使 ②③ 都做了,`HARDWARE_INFO_MMKV` 重新生成后**仍写 `samsung/Pixel 10 Pro Fold`**,而此时 `getprop`+`settings` 全系统已无任何 "Pixel 10 Pro Fold"。 +→ 说明 Pixel 来自微信**自己的持久缓存种子** `mmkv.default`。 + +- **只删 `HARDWARE_INFO_MMKV` 没用**:它会从 `mmkv.default` 种子重新长出 Pixel。**必须连 `mmkv.default` 一起删。** + +```sh +am force-stop com.tencent.mm +# 先备份可回滚 +cp files/mmkv/mmkv.default /data/local/tmp/mmkv.default.bak +cp files/mmkv/mmkv.default.crc /data/local/tmp/mmkv.default.crc.bak +# 删种子 + 二级缓存 +rm -f /data/data/com.tencent.mm/files/mmkv/{mmkv.default,mmkv.default.crc,HARDWARE_INFO_MMKV,HARDWARE_INFO_MMKV.crc} +am start -n com.tencent.mm/.ui.LauncherUI +# 重新登录 → HARDWARE_INFO_MMKV 重生成 = samsung/SM-X700 ✓ +``` + +> 删 `mmkv.default` 实测**不登出**(登录态在账号专属存储 / keystore,不在这个默认 MMKV 里)。 +> **不用 `pm clear` / 重装**(那会清掉本地聊天记录 + auto-reply 依赖的 `EnMicroMsg.db`)。删这两个 MMKV 是外科手术版,够用。 + +--- + +## 4. 「按账号不同」现象的解释 + +- **火星人 = SM-X700**:早先某次(本地缓存恰好是 SM-X700 时)注册到微信服务器的旧记录。服务器端设备记录**一旦建立就固定、登录不自动刷新**。 +- **小火子 = Pixel**:删旧记录后重登,新注册读的是**当时的本地缓存 = Pixel**,于是又写成 Pixel。 + +⇒ 服务器设备名 = 注册当刻的本地缓存值。所以根治必须**先把本地缓存改对(清缓存重采集),每个账号再重新登录一次**让服务器记录刷新。 + +--- + +## 5. 最终落地的完整解法(换机 / 重刷照做) + +1. **① Java 层**:LSPosed 模块 `WeChatHook.hookDeviceSpoof`(`Build.*` = 三星平板 + `ro.build.characteristics=tablet`)。 +2. **② native 层**:`/data/adb/post-fs-data.d/wechat-model-spoof.sh`(开机 `resetprop ro.product.*.model = SM-X700` 等,全机生效、持久)。 +3. **③ 系统设备名**:`settings put global device_name "Galaxy Tab S8"` + `settings put secure bluetooth_name "Galaxy Tab S8"`(settings 库持久)。 +4. **④ 清微信持久缓存**:删 `files/mmkv/{mmkv.default,HARDWARE_INFO_MMKV}`(+`.crc`)逼重采集。 +5. **每个账号重新登录一次**,让服务器端设备记录刷新成 `samsung SM-X700`。 + +**验证**:`cat files/mmkv/HARDWARE_INFO_MMKV | strings | grep -iE 'samsung|SM-X|pixel'` 应只见 `samsung / SM-X700`。 + +--- + +## 6. 维护注意 / 坑 + +- **别再 `pm clear` 或重装微信**:否则第 ④ 层缓存会在某个「伪装没就位」的时刻重新焊死 Pixel。真要重装,先确认 ①②③ 都在,再装、再登录。 +- **resetprop 是全机生效**(`getprop` 也变 SM-X700,所有 app 都看到)——放弃了「仅微信进程内」,因为 native prop 读无法 per-app hook(除非上 frida / native inline hook,微信重度混淆 + 反调试,不值)。这台专用 bot 手机可接受。 +- **残留无害**:`WebPrefetchUserAgent` / `AppBrandNetworkConfigUserAgentHelper` / `*_SDKCgiWaitReportMap` 里还有旧 Pixel,是 User-Agent / 网页缓存,与登录设备身份无关,随用自然刷新。 +- **登录态下别改设备身份**:改 hook / resetprop / 清缓存都可能让服务器看到指纹跳变 → 踢下线 + 实名验证(见 `LESSONS.md`)。所有改动务必**在没账号在线时**做,改完再登录。 + +--- + +## 7. 一句话总结 + +> 伪装设备机型别只在 Java 层。App 可能走 **native `__system_property_get`**(要 resetprop)、可能读**系统 settings 设备名**(要改 settings 库)、更可能读**它自己首装时存下的持久缓存**(要删 `mmkv.default` 逼重采集)。四层缺一漏一处。**诊断零命中就是 native / 缓存的信号,别在 Java 层死磕。** -- libgit2 0.21.4