微信副设备登录「设备名泄露 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;memorywechat-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 读也拿到伪装值):
resetprop ro.product.model SM-X700 # + 全分区变体 + marketname
→ 机型(device type)字段变对了,但设备名字段仍 Pixel。
正确落点 ③(有效但不够):改系统 settings
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一起删。
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. 最终落地的完整解法(换机 / 重刷照做)
- ① Java 层:LSPosed 模块
WeChatHook.hookDeviceSpoof(Build.*= 三星平板 +ro.build.characteristics=tablet)。 - ② native 层:
/data/adb/post-fs-data.d/wechat-model-spoof.sh(开机resetprop ro.product.*.model = SM-X700等,全机生效、持久)。 - ③ 系统设备名:
settings put global device_name "Galaxy Tab S8"+settings put secure bluetooth_name "Galaxy Tab S8"(settings 库持久)。 - ④ 清微信持久缓存:删
files/mmkv/{mmkv.default,HARDWARE_INFO_MMKV}(+.crc)逼重采集。 - 每个账号重新登录一次,让服务器端设备记录刷新成
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 层死磕。