Commit 6b47a431741f99a79979396fd8997d886698ed9d

Authored by yibo
1 parent c3eb27d3

fix(wechat-spoof): 修复副设备登录设备名泄露 Pixel — resetprop 兜 native 读

微信在 native __system_property_get 读 ro.product.model,绕过所有 Xposed
Java hook,登录记录显示真机 "Pixel 10 Pro Fold" 触发预警。

- hookDeviceSpoof: SystemProperties.get 改模式匹配 ro.product.*.model
  + hook Settings.{Global,Secure,System}.getString(device_name/bluetooth_name)
  + hook BluetoothAdapter.getName() + 诊断日志(定位真实读取源用)
  → 真机验证这些 Java hook 全零命中,证实微信走 native 读
- 真正生效: wechat-model-spoof.sh 用 Magisk resetprop 改系统属性层
  (装到 /data/adb/post-fs-data.d/,开机持久化),native 读也拿伪装值
  → 登录记录变 "samsung SM-X700",Pixel 泄露消失
- 代价:全机生效(getprop 也变),放弃"仅微信进程内",native 读无法 per-app

详见 memory wechat-pad-login-device-spoof。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
xhs_lsposed_a16_fix/wechat-model-spoof.sh 0 → 100644
  1 +#!/system/bin/sh
  2 +# 部署到 /data/adb/post-fs-data.d/wechat-model-spoof.sh (chmod 755)。
  3 +#
  4 +# 微信副设备登录设备名泄露修复:微信在 native 层 __system_property_get 读 ro.product.model,
  5 +# 绕过 LSPosed Java hook(WeChatHook.hookDeviceSpoof 只改 Build.* / Java SystemProperties.get),
  6 +# 导致登录设备记录显示真机 "Pixel 10 Pro Fold",每次登录触发安全预警。
  7 +# 这里在系统属性层把机型改成三星平板 SM-X700,native 读也会拿到伪装值 → 显示 "samsung SM-X700"。
  8 +# 代价:全机生效(getprop 也变),非"仅微信进程内"——native prop 读无法 per-app hook,
  9 +# 只有 resetprop 兜得住。对这台专用 bot 手机可接受。详见 memory wechat-pad-login-device-spoof。
  10 +for p in ro.product.model ro.product.product.model ro.product.vendor.model ro.product.odm.model \
  11 + ro.product.system_ext.model ro.product.system_dlkm.model ro.product.vendor_dlkm.model \
  12 + ro.product.bootimage.model ro.product.model_for_attestation; do
  13 + resetprop "$p" "SM-X700"
  14 +done
  15 +for p in ro.product.brand ro.product.product.brand ro.product.vendor.brand ro.product.odm.brand \
  16 + ro.product.manufacturer ro.product.product.manufacturer; do
  17 + resetprop "$p" "samsung"
  18 +done
  19 +resetprop ro.product.marketname "Galaxy Tab S8"
... ...
xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java
... ... @@ -2727,19 +2727,21 @@ public class WeChatHook implements IXposedHookLoadPackage {
2727 2727 catch (Throwable t) { XposedBridge.log("[WeChatHook] spoof Build." + e.getKey() + " failed: " + t); }
2728 2728 }
2729 2729 // ro.product.* + ro.build.characteristics=tablet (the strong tablet signal).
2730   - final java.util.Map<String, String> P = new java.util.HashMap<>();
2731   - P.put("ro.product.brand", "samsung");
2732   - P.put("ro.product.manufacturer", "samsung");
2733   - P.put("ro.product.model", "SM-X700");
2734   - P.put("ro.product.device", "gts8wifi");
2735   - P.put("ro.product.name", "gts8wifixx");
2736   - P.put("ro.build.characteristics", "tablet");
  2730 + // Pixel 有一大堆分区限定 prop(ro.product.{product,vendor,odm,system_ext,system_dlkm,
  2731 + // vendor_dlkm,bootimage}.model 全 = "Pixel 10 Pro Fold")。写死白名单必漏,改**模式匹配**:
  2732 + // 任何 ro.product.*.model / *marketname / *.brand 等一律盖,未来加分区也不漏。
2737 2733 Class<?> sysProp = XposedHelpers.findClass("android.os.SystemProperties", cl);
2738 2734 XC_MethodHook propHook = new XC_MethodHook() {
2739 2735 @Override protected void afterHookedMethod(MethodHookParam p) {
2740   - if (p.args == null || p.args.length == 0) return;
2741   - Object k = p.args[0];
2742   - if (k != null && P.containsKey(k.toString())) p.setResult(P.get(k.toString()));
  2736 + if (p.args == null || p.args.length == 0 || p.args[0] == null) return;
  2737 + String k = p.args[0].toString();
  2738 + String v = spoofProp(k);
  2739 + if (v != null) {
  2740 + // 诊断:记录微信实际读了哪个 model/name 源(找泄露源用,验证后可删)
  2741 + if (k.contains("model") || k.contains("marketname") || k.contains("name"))
  2742 + XposedBridge.log("[WeChatHook] spoofProp " + k + " → " + v);
  2743 + p.setResult(v);
  2744 + }
2743 2745 }
2744 2746 };
2745 2747 for (java.lang.reflect.Method m : sysProp.getDeclaredMethods()) {
... ... @@ -2749,13 +2751,68 @@ public class WeChatHook implements IXposedHookLoadPackage {
2749 2751 XposedBridge.hookMethod(m, propHook);
2750 2752 }
2751 2753 }
  2754 + // BluetoothAdapter.getName() 是设备名的另一条常见来源(走蓝牙服务 IPC,Settings hook 盖不到)。
  2755 + try {
  2756 + Class<?> bt = XposedHelpers.findClass("android.bluetooth.BluetoothAdapter", cl);
  2757 + for (java.lang.reflect.Method m : bt.getDeclaredMethods()) {
  2758 + if ("getName".equals(m.getName()) && m.getParameterTypes().length == 0) {
  2759 + XposedBridge.hookMethod(m, new XC_MethodHook() {
  2760 + @Override protected void afterHookedMethod(MethodHookParam p) {
  2761 + XposedBridge.log("[WeChatHook] BluetoothAdapter.getName() intercepted");
  2762 + p.setResult("Galaxy Tab S8");
  2763 + }
  2764 + });
  2765 + }
  2766 + }
  2767 + } catch (Throwable t) { XposedBridge.log("[WeChatHook] spoof BluetoothAdapter failed: " + t); }
  2768 + // 🔑 微信副设备「设备名」= Build.MANUFACTURER + Settings.Global "device_name",
  2769 + // 后者在 Pixel 上 = 真机营销名("Pixel 10 Pro Fold"),Build/prop 伪装盖不到它,
  2770 + // 每次登录都以 "samsung-Pixel10 Pro Fold" 触发预警。这里把 device_name/bluetooth_name
  2771 + // 一并伪装成三星平板名,让整串设备身份一致。
  2772 + final java.util.Map<String, String> S = new java.util.HashMap<>();
  2773 + S.put("device_name", "Galaxy Tab S8");
  2774 + S.put("bluetooth_name", "Galaxy Tab S8");
  2775 + XC_MethodHook settingsHook = new XC_MethodHook() {
  2776 + @Override protected void afterHookedMethod(MethodHookParam p) {
  2777 + if (p.args == null || p.args.length < 2) return;
  2778 + Object k = p.args[1]; // getString(ContentResolver, String name)
  2779 + if (k != null && S.containsKey(k.toString())) p.setResult(S.get(k.toString()));
  2780 + }
  2781 + };
  2782 + for (String clsName : new String[]{"android.provider.Settings$Global",
  2783 + "android.provider.Settings$Secure", "android.provider.Settings$System"}) {
  2784 + try {
  2785 + Class<?> sc = XposedHelpers.findClass(clsName, cl);
  2786 + for (java.lang.reflect.Method m : sc.getDeclaredMethods()) {
  2787 + if ("getString".equals(m.getName())
  2788 + && m.getParameterTypes().length == 2
  2789 + && m.getParameterTypes()[1] == String.class) {
  2790 + XposedBridge.hookMethod(m, settingsHook);
  2791 + }
  2792 + }
  2793 + } catch (Throwable t) { XposedBridge.log("[WeChatHook] spoof " + clsName + " failed: " + t); }
  2794 + }
2752 2795 XposedBridge.log("[WeChatHook] hookDeviceSpoof: → Samsung SM-X700 tablet in " + procName
2753   - + " (Build.MODEL now=" + XposedHelpers.getStaticObjectField(build, "MODEL") + ")");
  2796 + + " (Build.MODEL now=" + XposedHelpers.getStaticObjectField(build, "MODEL")
  2797 + + ", device_name spoofed=Galaxy Tab S8)");
2754 2798 } catch (Throwable t) {
2755 2799 XposedBridge.log("[WeChatHook] hookDeviceSpoof failed in " + procName + ": " + t);
2756 2800 }
2757 2801 }
2758 2802  
  2803 + /** 模式匹配伪装 ro.* 设备身份 prop。命中返回三星平板值,未命中返回 null(放行原值)。 */
  2804 + private static String spoofProp(String k) {
  2805 + if (k == null) return null;
  2806 + if (k.equals("ro.build.characteristics")) return "tablet";
  2807 + if (!k.startsWith("ro.product.")) return null;
  2808 + if (k.endsWith("marketname")) return "Galaxy Tab S8";
  2809 + if (k.endsWith(".model")) return "SM-X700";
  2810 + if (k.endsWith(".brand") || k.endsWith(".manufacturer")) return "samsung";
  2811 + if (k.endsWith(".device")) return "gts8wifi";
  2812 + if (k.endsWith(".name")) return "gts8wifixx";
  2813 + return null;
  2814 + }
  2815 +
2759 2816 private void hookInTabletEnv(ClassLoader cl) {
2760 2817 try {
2761 2818 Class<?> bkClass = XposedHelpers.findClass("com.tencent.mm.ui.bk", cl);
... ...